From d0fdd8ad73d7357aab26e548e5a7d6335a2c55d1 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Wed, 15 Nov 2023 16:42:17 +0100 Subject: [PATCH 01/11] Add MultiPageMenu UI gadget --- ledger_device_ui_sdk/Cargo.toml | 1 + ledger_device_ui_sdk/src/bagls/mcu.rs | 9 +- ledger_device_ui_sdk/src/layout.rs | 6 +- ledger_device_ui_sdk/src/lib.rs | 1 + ledger_device_ui_sdk/src/string_se.rs | 2 +- ledger_device_ui_sdk/src/ui.rs | 186 ++++++++++++++++++++++++++ 6 files changed, 201 insertions(+), 4 deletions(-) diff --git a/ledger_device_ui_sdk/Cargo.toml b/ledger_device_ui_sdk/Cargo.toml index 389cb58b..77135122 100644 --- a/ledger_device_ui_sdk/Cargo.toml +++ b/ledger_device_ui_sdk/Cargo.toml @@ -8,6 +8,7 @@ description = "Ledger devices abstractions for displaying text, icons, menus and [dependencies] ledger_secure_sdk_sys = "1.0.0" +ledger_device_sdk = "1.0.0" include_gif = "1.0.0" [features] diff --git a/ledger_device_ui_sdk/src/bagls/mcu.rs b/ledger_device_ui_sdk/src/bagls/mcu.rs index dc27940d..b9ba8c7d 100644 --- a/ledger_device_ui_sdk/src/bagls/mcu.rs +++ b/ledger_device_ui_sdk/src/bagls/mcu.rs @@ -379,6 +379,7 @@ impl<'a> SendToDisplay for Label<'a> { }; let x = match self.layout { Layout::RightAligned => self.layout.get_x(self.text.len() * 7), + Layout::Custom(x) => x as usize, _ => 0, }; let y = self.loc.get_y(self.dims.1 as usize) as i16; @@ -386,6 +387,12 @@ impl<'a> SendToDisplay for Label<'a> { Layout::Centered => crate::SCREEN_WIDTH, _ => self.text.len() * 6, }; + let alignment = match self.layout { + Layout::LeftAligned => 0 as u16, + Layout::Centered => BAGL_FONT_ALIGNMENT_CENTER as u16, + Layout::RightAligned => BAGL_FONT_ALIGNMENT_CENTER as u16, + Layout::Custom(_) => 0 as u16, + }; let baglcomp = BaglComponent { type_: BaglTypes::LabelLine as u8, userid: 0, // FIXME @@ -398,7 +405,7 @@ impl<'a> SendToDisplay for Label<'a> { fill: 0, fgcolor: 0xffffffu32, bgcolor: 0, - font_id: font_id as u16 | BAGL_FONT_ALIGNMENT_CENTER as u16, + font_id: font_id as u16 | alignment, icon_id: 0, }; diff --git a/ledger_device_ui_sdk/src/layout.rs b/ledger_device_ui_sdk/src/layout.rs index dda5769b..6f0ebfb6 100644 --- a/ledger_device_ui_sdk/src/layout.rs +++ b/ledger_device_ui_sdk/src/layout.rs @@ -3,6 +3,7 @@ pub enum Layout { LeftAligned, RightAligned, Centered, + Custom(usize), } impl Layout { @@ -11,6 +12,7 @@ impl Layout { Layout::LeftAligned => crate::PADDING, Layout::Centered => (crate::SCREEN_WIDTH - width) / 2, Layout::RightAligned => crate::SCREEN_WIDTH - crate::PADDING - width, + Layout::Custom(x) => *x, } } } @@ -26,9 +28,9 @@ pub enum Location { impl Location { pub fn get_y(&self, height: usize) -> usize { match self { - Location::Top => 0, + Location::Top => crate::Y_PADDING, Location::Middle => (crate::SCREEN_HEIGHT - height) / 2, - Location::Bottom => crate::SCREEN_HEIGHT - height, + Location::Bottom => crate::SCREEN_HEIGHT - height - crate::Y_PADDING, Location::Custom(y) => *y, } } diff --git a/ledger_device_ui_sdk/src/lib.rs b/ledger_device_ui_sdk/src/lib.rs index a9db0771..d4b13eaf 100644 --- a/ledger_device_ui_sdk/src/lib.rs +++ b/ledger_device_ui_sdk/src/lib.rs @@ -18,6 +18,7 @@ pub mod screen_util; pub mod ui; pub const PADDING: usize = 2; +pub const Y_PADDING: usize = 3; pub const SCREEN_WIDTH: usize = 128; #[cfg(target_os = "nanos")] diff --git a/ledger_device_ui_sdk/src/string_se.rs b/ledger_device_ui_sdk/src/string_se.rs index 23a7711a..87c50938 100644 --- a/ledger_device_ui_sdk/src/string_se.rs +++ b/ledger_device_ui_sdk/src/string_se.rs @@ -52,7 +52,7 @@ impl StringPlace for [&str] { fn place(&self, loc: Location, layout: Layout, bold: bool) { let c_height = OPEN_SANS[bold as usize].height as usize; - let padding = if self.len() > 4 { 0 } else { 2 }; + let padding = if self.len() > 4 { 0 } else { 1 }; let total_height = self.len() * (c_height + padding); let mut cur_y = loc.get_y(total_height); for string in self.iter() { diff --git a/ledger_device_ui_sdk/src/ui.rs b/ledger_device_ui_sdk/src/ui.rs index 26878cca..8dec25cd 100644 --- a/ledger_device_ui_sdk/src/ui.rs +++ b/ledger_device_ui_sdk/src/ui.rs @@ -1,6 +1,9 @@ #![allow(dead_code)] use ledger_secure_sdk_sys::{seph, buttons::{get_button_event, ButtonEvent, ButtonsState}}; +use ledger_device_sdk::{io, buttons::ButtonEvent::*}; + +use crate::bitmaps::Glyph; use crate::bagls::*; @@ -304,6 +307,189 @@ impl<'a> Menu<'a> { } } +pub enum PageStyle { + PictureNormal, // Picture (should be 16x16) with two lines of text (page layout depends on device). + PictureBold, // Icon on top with one line of text on the bottom. + BoldNormal, // One line of bold text and one line of normal text. + Normal, // 2 lines of centered text. +} + +pub struct Page<'a> { + style: PageStyle, + label: [&'a str; 2], + glyph: Option<&'a Glyph<'a>>, +} + +// new_picture_normal +impl<'a> From<([&'a str; 2], &'a Glyph<'a>)> for Page<'a> { + fn from((label, glyph): ([&'a str; 2], &'a Glyph<'a>)) -> Page<'a> { + Page::new(PageStyle::PictureNormal, label, Some(glyph)) + } +} + +// new bold normal or new normal +impl<'a> From<([&'a str; 2], bool)> for Page<'a> { + fn from((label, bold): ([&'a str; 2], bool)) -> Page<'a> { + if bold { + Page::new(PageStyle::BoldNormal, label, None) + } + else + { + Page::new(PageStyle::Normal, label, None) + } + } +} + +// new picture bold +impl<'a> From<(&'a str, &'a Glyph<'a>)> for Page<'a> { + fn from((label, glyph): (&'a str, &'a Glyph<'a>)) -> Page<'a> { + let label = [label, ""]; + Page::new(PageStyle::PictureBold, label, Some(glyph)) + } +} + +impl<'a> Page<'a> { + pub fn new(style: PageStyle, label: [&'a str; 2], glyph: Option<&'a Glyph<'a>>) -> Self { + Page { + style, + label, + glyph, + } + } + + } + + pub fn place(&self) { + clear_screen(); + match self.style { + PageStyle::PictureNormal => { + let mut icon_x = 16; + let mut icon_y = 8; + if cfg!(target_os = "nanos") { + self.label + .place(Location::Middle, Layout::Custom(41), false); + } else { + icon_x = 57; + icon_y = 10; + self.label + .place(Location::Custom(28), Layout::Centered, false); + } + match self.glyph { + Some(glyph) => { + let icon = Icon::from(glyph); + icon.set_x(icon_x).set_y(icon_y).display(); + } + None => {} + } + } + PageStyle::PictureBold => { + let mut icon_x = 56; + let mut icon_y = 2; + if cfg!(target_os = "nanos") { + self.label[0].place(Location::Bottom, Layout::Centered, true); + } else { + icon_x = 57; + icon_y = 17; + self.label[0].place(Location::Custom(35), Layout::Centered, true); + } + match self.glyph { + Some(glyph) => { + let icon = Icon::from(glyph); + icon.set_x(icon_x).set_y(icon_y).display(); + } + None => {} + } + } + PageStyle::BoldNormal => { + let padding = 1; + let total_height = OPEN_SANS[0].height as usize + + OPEN_SANS[1].height as usize + + 2 * padding as usize; + let mut cur_y = Location::Middle.get_y(total_height); + self.label[0].place(Location::Custom(cur_y), Layout::Centered, true); + cur_y += OPEN_SANS[0].height as usize + 2 * padding as usize; + self.label[1].place(Location::Custom(cur_y), Layout::Centered, false); + } + PageStyle::Normal => { + self.label.place(Location::Middle, Layout::Centered, false); + } + } + } +} + +pub enum EventOrPageIndex { + Event(io::Event), + Index(usize) +} + +pub struct MultiPageMenu<'a> { + comm : &'a mut io::Comm, + pages: &'a [&'a Page<'a>], +} + +impl<'a> MultiPageMenu<'a> { + pub fn new(comm: &'a mut io::Comm, pages: &'a [&'a Page]) -> Self { + MultiPageMenu { comm, pages } + } + + pub fn show(&mut self) -> EventOrPageIndex { + clear_screen(); + + self.pages[0].place(); + + LEFT_ARROW.display(); + RIGHT_ARROW.display(); + + crate::screen_util::screen_update(); + + let mut index = 0; + + loop { + match self.comm.next_event() { + io::Event::Button(button) => { + match button { + LeftButtonPress => { + LEFT_S_ARROW.instant_display(); + } + RightButtonPress => { + RIGHT_S_ARROW.instant_display(); + } + BothButtonsRelease => return EventOrPageIndex::Index(index), + b => { + match b { + LeftButtonRelease => { + LEFT_S_ARROW.erase(); + if index as i16 - 1 < 0 { + index = self.pages.len() - 1; + } else { + index = index.saturating_sub(1); + } + } + RightButtonRelease => { + RIGHT_S_ARROW.erase(); + if index < self.pages.len() - 1 { + index += 1; + } else { + index = 0; + } + } + _ => (), + } + clear_screen(); + self.pages[index].place(); + LEFT_ARROW.display(); + RIGHT_ARROW.display(); + crate::screen_util::screen_update(); + } + } + }, + io::Event::Command(ins) => return EventOrPageIndex::Event(io::Event::Command(ins)), + _ => (), + }; + } + } +} + /// A gadget that displays /// a short message in the /// middle of the screen and From 0f0b84cba470cd77828d4a69ffc40d26d7b7ccbd Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Wed, 15 Nov 2023 16:46:16 +0100 Subject: [PATCH 02/11] Add new MultiFieldReview gadget --- ledger_device_ui_sdk/Cargo.toml | 1 + ledger_device_ui_sdk/src/ui.rs | 217 ++++++++++++++++++++++++++++++-- 2 files changed, 209 insertions(+), 9 deletions(-) diff --git a/ledger_device_ui_sdk/Cargo.toml b/ledger_device_ui_sdk/Cargo.toml index 77135122..28d59806 100644 --- a/ledger_device_ui_sdk/Cargo.toml +++ b/ledger_device_ui_sdk/Cargo.toml @@ -10,6 +10,7 @@ description = "Ledger devices abstractions for displaying text, icons, menus and ledger_secure_sdk_sys = "1.0.0" ledger_device_sdk = "1.0.0" include_gif = "1.0.0" +numtoa = "0.2.4" [features] speculos = [] diff --git a/ledger_device_ui_sdk/src/ui.rs b/ledger_device_ui_sdk/src/ui.rs index 8dec25cd..f09b866c 100644 --- a/ledger_device_ui_sdk/src/ui.rs +++ b/ledger_device_ui_sdk/src/ui.rs @@ -10,6 +10,10 @@ use crate::bagls::*; use crate::layout; use crate::layout::{Draw, Location, StringPlace}; +use numtoa::NumToA; + +const MAX_CHAR_PER_LINE: usize = 18; + /// Handles communication to filter /// out actual events, and converts key /// events into presses/releases @@ -307,6 +311,7 @@ impl<'a> Menu<'a> { } } +#[derive(Copy, Clone)] pub enum PageStyle { PictureNormal, // Picture (should be 16x16) with two lines of text (page layout depends on device). PictureBold, // Icon on top with one line of text on the bottom. @@ -314,10 +319,13 @@ pub enum PageStyle { Normal, // 2 lines of centered text. } +#[derive(Copy, Clone)] pub struct Page<'a> { style: PageStyle, label: [&'a str; 2], glyph: Option<&'a Glyph<'a>>, + chunk_count: u8, + chunk_idx: u8, } // new_picture_normal @@ -350,17 +358,37 @@ impl<'a> From<(&'a str, &'a Glyph<'a>)> for Page<'a> { impl<'a> Page<'a> { pub fn new(style: PageStyle, label: [&'a str; 2], glyph: Option<&'a Glyph<'a>>) -> Self { + let chunk_count = 0; + let chunk_idx = 0; Page { style, label, glyph, + chunk_count, + chunk_idx, } } - } - pub fn place(&self) { - clear_screen(); + let mut label_bytes = [0u8; MAX_CHAR_PER_LINE]; + let mut label_str: &str = ""; + // Notes: + // * chunk_count and chunk_idx are only used for multi-page fields + // and are set to 0 by default. MultiFieldReview is the only place where + // these values are set. + // * Only pages of style BoldNormal will display the chunk count and index. + if self.chunk_count > 1 { + // Convert the chunk count to a string + let mut chunk_count_buf = [0u8;3]; + let chunk_count_str = self.chunk_count.numtoa_str(10,&mut chunk_count_buf); + // Convert the chunk index to a string + let mut chunk_idx_buf = [0u8;3]; + let chunk_idx_str = self.chunk_idx.numtoa_str(10,&mut chunk_idx_buf); + // Add the chunk count and index to the label + concatenate(&[self.label[0], " (", chunk_idx_str, "/", chunk_count_str, ")"], &mut label_bytes); + label_str = core::str::from_utf8(&mut label_bytes).unwrap(); + } + match self.style { PageStyle::PictureNormal => { let mut icon_x = 16; @@ -406,7 +434,12 @@ impl<'a> Page<'a> { + OPEN_SANS[1].height as usize + 2 * padding as usize; let mut cur_y = Location::Middle.get_y(total_height); - self.label[0].place(Location::Custom(cur_y), Layout::Centered, true); + if self.chunk_count > 1 { + label_str.place(Location::Custom(cur_y), Layout::Centered, true); + } + else { + self.label[0].place(Location::Custom(cur_y), Layout::Centered, true); + } cur_y += OPEN_SANS[0].height as usize + 2 * padding as usize; self.label[1].place(Location::Custom(cur_y), Layout::Centered, false); } @@ -415,6 +448,22 @@ impl<'a> Page<'a> { } } } + + pub fn place_and_wait(&self) { + let mut buttons = ButtonsState::new(); + + self.place(); + + loop { + match get_event(&mut buttons) { + Some(ButtonEvent::LeftButtonRelease) + | Some(ButtonEvent::RightButtonRelease) + | Some(ButtonEvent::BothButtonsRelease) => return, + _ => (), + } + } + } + } pub enum EventOrPageIndex { @@ -529,7 +578,7 @@ impl<'a> SingleMessage<'a> { /// A horizontal scroller that /// splits any given message /// over several panes in chunks -/// of CHAR_N characters. +/// of MAX_CHAR_PER_LINE characters. /// Press both buttons to exit. pub struct MessageScroller<'a> { message: &'a str, @@ -543,8 +592,7 @@ impl<'a> MessageScroller<'a> { pub fn event_loop(&self) { clear_screen(); let mut buttons = ButtonsState::new(); - const CHAR_N: usize = 16; - let page_count = (self.message.len() - 1) / CHAR_N + 1; + let page_count = (self.message.len() - 1) / MAX_CHAR_PER_LINE + 1; if page_count == 0 { return; } @@ -554,8 +602,8 @@ impl<'a> MessageScroller<'a> { // A closure to draw common elements of the screen // cur_page passed as parameter to prevent borrowing let mut draw = |page: usize| { - let start = page * CHAR_N; - let end = (start + CHAR_N).min(self.message.len()); + let start = page * MAX_CHAR_PER_LINE; + let end = (start + MAX_CHAR_PER_LINE).min(self.message.len()); let chunk = &self.message[start..end]; label.erase(); label.text = &chunk; @@ -602,3 +650,154 @@ impl<'a> MessageScroller<'a> { } } } + +pub struct Field<'a> { + pub name: &'a str, + pub value: &'a str, +} +pub struct MultiFieldReview<'a> { + fields: &'a [Field<'a>], + review_message: &'a[&'a str], + review_glyph: Option<&'a Glyph<'a>>, + validation_message: &'a str, + validation_glyph: Option<&'a Glyph<'a>>, + cancel_message: &'a str, + cancel_glyph: Option<&'a Glyph<'a>>, +} + +// Function to concatenate multiple strings into a fixed-size array +fn concatenate(strings: &[&str], output: &mut [u8]) { + let mut offset = 0; + + for s in strings { + let s_len = s.len(); + let copy_len = core::cmp::min(s_len, output.len() - offset); + + if copy_len > 0 { + output[offset..offset + copy_len].copy_from_slice(&s.as_bytes()[..copy_len]); + offset += copy_len; + } else { + // If the output buffer is full, stop concatenating. + break; + } + } +} + +impl<'a> MultiFieldReview<'a> { + pub fn new(fields: &'a [Field<'a>], + review_message: &'a [&'a str], + review_glyph: Option<&'a Glyph<'a>>, + validation_message: &'a str, + validation_glyph: Option<&'a Glyph<'a>>, + cancel_message: &'a str, + cancel_glyph: Option<&'a Glyph<'a>>) -> Self { + MultiFieldReview { + fields, + review_message, + review_glyph, + validation_message, + validation_glyph, + cancel_message, + cancel_glyph, + } + } + + pub fn show(&self) -> bool { + let mut buttons = ButtonsState::new(); + + let first_page = match self.review_message.len() { + 0 => { + Page::new(PageStyle::PictureNormal, ["", ""], self.review_glyph) + } + 1 => { + Page::new(PageStyle::PictureBold, [self.review_message[0], ""], self.review_glyph) + } + _ => { + Page::new(PageStyle::PictureNormal, [self.review_message[0], self.review_message[1]], self.review_glyph) + } + }; + let validation_page = Page::new(PageStyle::PictureBold, [self.validation_message, ""], self.validation_glyph); + let cancel_page = Page::new(PageStyle::PictureBold, [self.cancel_message, ""], self.cancel_glyph); + let mut review_pages: [Page; 32] = [Page::new(PageStyle::Normal, ["", ""], None); 32]; + let mut total_page_count = 0; + + // Determine each field page count + for field in self.fields { + let field_page_count = (field.value.len() - 1) / MAX_CHAR_PER_LINE + 1; + // Create pages for each chunk of the field + for i in 0..field_page_count { + let start = i * MAX_CHAR_PER_LINE; + let end = (start + MAX_CHAR_PER_LINE).min(field.value.len()); + let chunk = &field.value[start..end]; + review_pages[total_page_count] = Page::new(PageStyle::BoldNormal, [field.name, chunk], None); + review_pages[total_page_count].chunk_count = field_page_count as u8; + review_pages[total_page_count].chunk_idx = (i + 1) as u8; + total_page_count += 1; + } + } + + review_pages[total_page_count] = validation_page; + total_page_count += 1; + review_pages[total_page_count] = cancel_page; + + clear_screen(); + first_page.place_and_wait(); + crate::screen_util::screen_update(); + clear_screen(); + RIGHT_ARROW.display(); + + let mut cur_page = 0; + review_pages[cur_page].place(); + + loop { + match get_event(&mut buttons) { + Some(ButtonEvent::LeftButtonPress) => { + if cur_page > 0 { + LEFT_S_ARROW.instant_display(); + } + } + Some(ButtonEvent::RightButtonPress) => { + if cur_page < total_page_count { + RIGHT_S_ARROW.instant_display(); + } + } + Some(b) => { + match b { + ButtonEvent::LeftButtonRelease => { + if cur_page > 0 { + cur_page -= 1; + } + } + ButtonEvent::RightButtonRelease => { + if cur_page < total_page_count { + cur_page += 1; + } + } + ButtonEvent::BothButtonsRelease => + { + if cur_page == total_page_count { + // Cancel + return false; + } + else if cur_page == total_page_count - 1 { + // Validate + return true; + } + } + _ => (), + } + clear_screen(); + review_pages[cur_page].place(); + if cur_page > 0 { + LEFT_ARROW.display(); + } + if cur_page < total_page_count { + RIGHT_ARROW.display(); + } + crate::screen_util::screen_update(); + } + _ => (), + } + } + } +} From 2fae98aadbfe97a7573fdba52cf4453063656251 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Wed, 15 Nov 2023 16:48:11 +0100 Subject: [PATCH 03/11] Remove usage of "double arrows" in new gadgets for simpler Ragger tests integration. --- ledger_device_ui_sdk/src/ui.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/ledger_device_ui_sdk/src/ui.rs b/ledger_device_ui_sdk/src/ui.rs index f09b866c..3e6377da 100644 --- a/ledger_device_ui_sdk/src/ui.rs +++ b/ledger_device_ui_sdk/src/ui.rs @@ -497,17 +497,10 @@ impl<'a> MultiPageMenu<'a> { match self.comm.next_event() { io::Event::Button(button) => { match button { - LeftButtonPress => { - LEFT_S_ARROW.instant_display(); - } - RightButtonPress => { - RIGHT_S_ARROW.instant_display(); - } BothButtonsRelease => return EventOrPageIndex::Index(index), b => { match b { LeftButtonRelease => { - LEFT_S_ARROW.erase(); if index as i16 - 1 < 0 { index = self.pages.len() - 1; } else { @@ -515,7 +508,6 @@ impl<'a> MultiPageMenu<'a> { } } RightButtonRelease => { - RIGHT_S_ARROW.erase(); if index < self.pages.len() - 1 { index += 1; } else { @@ -751,16 +743,6 @@ impl<'a> MultiFieldReview<'a> { loop { match get_event(&mut buttons) { - Some(ButtonEvent::LeftButtonPress) => { - if cur_page > 0 { - LEFT_S_ARROW.instant_display(); - } - } - Some(ButtonEvent::RightButtonPress) => { - if cur_page < total_page_count { - RIGHT_S_ARROW.instant_display(); - } - } Some(b) => { match b { ButtonEvent::LeftButtonRelease => { From 5d54a2d144ce44bd8d9275055bcf3d2775f5ec4e Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Wed, 15 Nov 2023 16:51:39 +0100 Subject: [PATCH 04/11] Update BoldNormal page style for LNSP/LNX (allow up to 3 lines of text) --- ledger_device_ui_sdk/src/ui.rs | 262 ++++++++++++++++++++------------- 1 file changed, 159 insertions(+), 103 deletions(-) diff --git a/ledger_device_ui_sdk/src/ui.rs b/ledger_device_ui_sdk/src/ui.rs index 3e6377da..4abef82f 100644 --- a/ledger_device_ui_sdk/src/ui.rs +++ b/ledger_device_ui_sdk/src/ui.rs @@ -1,5 +1,7 @@ #![allow(dead_code)] +use core::str::from_utf8; + use ledger_secure_sdk_sys::{seph, buttons::{get_button_event, ButtonEvent, ButtonsState}}; use ledger_device_sdk::{io, buttons::ButtonEvent::*}; @@ -12,7 +14,7 @@ use crate::layout::{Draw, Location, StringPlace}; use numtoa::NumToA; -const MAX_CHAR_PER_LINE: usize = 18; +const MAX_CHAR_PER_LINE: usize = 17; /// Handles communication to filter /// out actual events, and converts key @@ -311,7 +313,7 @@ impl<'a> Menu<'a> { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq)] pub enum PageStyle { PictureNormal, // Picture (should be 16x16) with two lines of text (page layout depends on device). PictureBold, // Icon on top with one line of text on the bottom. @@ -331,7 +333,7 @@ pub struct Page<'a> { // new_picture_normal impl<'a> From<([&'a str; 2], &'a Glyph<'a>)> for Page<'a> { fn from((label, glyph): ([&'a str; 2], &'a Glyph<'a>)) -> Page<'a> { - Page::new(PageStyle::PictureNormal, label, Some(glyph)) + Page::new(PageStyle::PictureNormal, [label[0], label[1]], Some(glyph)) } } @@ -339,11 +341,9 @@ impl<'a> From<([&'a str; 2], &'a Glyph<'a>)> for Page<'a> { impl<'a> From<([&'a str; 2], bool)> for Page<'a> { fn from((label, bold): ([&'a str; 2], bool)) -> Page<'a> { if bold { - Page::new(PageStyle::BoldNormal, label, None) - } - else - { - Page::new(PageStyle::Normal, label, None) + Page::new(PageStyle::BoldNormal, [label[0], label[1]], None) + } else { + Page::new(PageStyle::Normal, [label[0], label[1]], None) } } } @@ -370,26 +370,10 @@ impl<'a> Page<'a> { } pub fn place(&self) { - let mut label_bytes = [0u8; MAX_CHAR_PER_LINE]; - let mut label_str: &str = ""; - // Notes: - // * chunk_count and chunk_idx are only used for multi-page fields - // and are set to 0 by default. MultiFieldReview is the only place where - // these values are set. - // * Only pages of style BoldNormal will display the chunk count and index. - if self.chunk_count > 1 { - // Convert the chunk count to a string - let mut chunk_count_buf = [0u8;3]; - let chunk_count_str = self.chunk_count.numtoa_str(10,&mut chunk_count_buf); - // Convert the chunk index to a string - let mut chunk_idx_buf = [0u8;3]; - let chunk_idx_str = self.chunk_idx.numtoa_str(10,&mut chunk_idx_buf); - // Add the chunk count and index to the label - concatenate(&[self.label[0], " (", chunk_idx_str, "/", chunk_count_str, ")"], &mut label_bytes); - label_str = core::str::from_utf8(&mut label_bytes).unwrap(); - } - match self.style { + PageStyle::Normal => { + self.label.place(Location::Middle, Layout::Centered, false); + } PageStyle::PictureNormal => { let mut icon_x = 16; let mut icon_y = 8; @@ -430,21 +414,65 @@ impl<'a> Page<'a> { } PageStyle::BoldNormal => { let padding = 1; - let total_height = OPEN_SANS[0].height as usize + let mut max_text_lines = 3; + if cfg!(target_os = "nanos") + { + max_text_lines = 1; + } + let total_height = (OPEN_SANS[0].height * max_text_lines) as usize + OPEN_SANS[1].height as usize + 2 * padding as usize; let mut cur_y = Location::Middle.get_y(total_height); + + // Display the chunk count and index if needed if self.chunk_count > 1 { - label_str.place(Location::Custom(cur_y), Layout::Centered, true); - } - else { + let mut label_bytes = [0u8; MAX_CHAR_PER_LINE]; + // Convert the chunk count to a string + let mut chunk_count_buf = [0u8; 3]; + let chunk_count_str = self.chunk_count.numtoa_str(10, &mut chunk_count_buf); + // Convert the chunk index to a string + let mut chunk_idx_buf = [0u8; 3]; + let chunk_idx_str = self.chunk_idx.numtoa_str(10, &mut chunk_idx_buf); + // Add the chunk count and index to the label + concatenate( + &[ + self.label[0], + " (", + chunk_idx_str, + "/", + chunk_count_str, + ")", + ], + &mut label_bytes, + ); + from_utf8(&mut label_bytes).unwrap().trim_matches(char::from(0)).place(Location::Custom(cur_y), Layout::Centered, true); + } else { self.label[0].place(Location::Custom(cur_y), Layout::Centered, true); } cur_y += OPEN_SANS[0].height as usize + 2 * padding as usize; - self.label[1].place(Location::Custom(cur_y), Layout::Centered, false); - } - PageStyle::Normal => { - self.label.place(Location::Middle, Layout::Centered, false); + + // If the device is a Nano S, display the second label as + // a single line of text + if cfg!(target_os = "nanos") + { + self.label[1].place(Location::Custom(cur_y), Layout::Centered, false); + } + // Otherwise, display the second label as up to 3 lines of text + else { + let mut indices = [(0, 0); 3]; + let len = self.label[1].len(); + for i in 0..3 { + let start = (i * MAX_CHAR_PER_LINE).min(len); + if start >= len { + break; // Break if we reach the end of the string + } + let end = (start + MAX_CHAR_PER_LINE).min(len); + indices[i] = (start, end); + (&self.label[1][start..end]).place(Location::Custom(cur_y), Layout::Centered, false); + cur_y += OPEN_SANS[0].height as usize + 2 * padding as usize; + + } + } } } } @@ -463,27 +491,26 @@ impl<'a> Page<'a> { } } } - } pub enum EventOrPageIndex { Event(io::Event), - Index(usize) + Index(usize), } pub struct MultiPageMenu<'a> { - comm : &'a mut io::Comm, + comm: &'a mut io::Comm, pages: &'a [&'a Page<'a>], } impl<'a> MultiPageMenu<'a> { - pub fn new(comm: &'a mut io::Comm, pages: &'a [&'a Page]) -> Self { + pub fn new(comm: &'a mut io::Comm, pages: &'a [&'a Page]) -> Self { MultiPageMenu { comm, pages } } pub fn show(&mut self) -> EventOrPageIndex { clear_screen(); - + self.pages[0].place(); LEFT_ARROW.display(); @@ -495,33 +522,31 @@ impl<'a> MultiPageMenu<'a> { loop { match self.comm.next_event() { - io::Event::Button(button) => { - match button { - BothButtonsRelease => return EventOrPageIndex::Index(index), - b => { - match b { - LeftButtonRelease => { - if index as i16 - 1 < 0 { - index = self.pages.len() - 1; - } else { - index = index.saturating_sub(1); - } + io::Event::Button(button) => match button { + BothButtonsRelease => return EventOrPageIndex::Index(index), + b => { + match b { + LeftButtonRelease => { + if index as i16 - 1 < 0 { + index = self.pages.len() - 1; + } else { + index = index.saturating_sub(1); } - RightButtonRelease => { - if index < self.pages.len() - 1 { - index += 1; - } else { - index = 0; - } + } + RightButtonRelease => { + if index < self.pages.len() - 1 { + index += 1; + } else { + index = 0; } - _ => (), } - clear_screen(); - self.pages[index].place(); - LEFT_ARROW.display(); - RIGHT_ARROW.display(); - crate::screen_util::screen_update(); + _ => (), } + clear_screen(); + self.pages[index].place(); + LEFT_ARROW.display(); + RIGHT_ARROW.display(); + crate::screen_util::screen_update(); } }, io::Event::Command(ins) => return EventOrPageIndex::Event(io::Event::Command(ins)), @@ -649,7 +674,7 @@ pub struct Field<'a> { } pub struct MultiFieldReview<'a> { fields: &'a [Field<'a>], - review_message: &'a[&'a str], + review_message: &'a [&'a str], review_glyph: Option<&'a Glyph<'a>>, validation_message: &'a str, validation_glyph: Option<&'a Glyph<'a>>, @@ -675,14 +700,18 @@ fn concatenate(strings: &[&str], output: &mut [u8]) { } } +const MAX_REVIEW_PAGES: usize = 48; + impl<'a> MultiFieldReview<'a> { - pub fn new(fields: &'a [Field<'a>], - review_message: &'a [&'a str], - review_glyph: Option<&'a Glyph<'a>>, - validation_message: &'a str, - validation_glyph: Option<&'a Glyph<'a>>, - cancel_message: &'a str, - cancel_glyph: Option<&'a Glyph<'a>>) -> Self { + pub fn new( + fields: &'a [Field<'a>], + review_message: &'a [&'a str], + review_glyph: Option<&'a Glyph<'a>>, + validation_message: &'a str, + validation_glyph: Option<&'a Glyph<'a>>, + cancel_message: &'a str, + cancel_glyph: Option<&'a Glyph<'a>>, + ) -> Self { MultiFieldReview { fields, review_message, @@ -694,37 +723,60 @@ impl<'a> MultiFieldReview<'a> { } } - pub fn show(&self) -> bool { + pub fn show(&self) -> bool { let mut buttons = ButtonsState::new(); let first_page = match self.review_message.len() { - 0 => { - Page::new(PageStyle::PictureNormal, ["", ""], self.review_glyph) - } - 1 => { - Page::new(PageStyle::PictureBold, [self.review_message[0], ""], self.review_glyph) - } - _ => { - Page::new(PageStyle::PictureNormal, [self.review_message[0], self.review_message[1]], self.review_glyph) - } + 0 => Page::new(PageStyle::PictureNormal, ["", ""], self.review_glyph), + 1 => Page::new( + PageStyle::PictureBold, + [self.review_message[0], ""], + self.review_glyph, + ), + _ => Page::new( + PageStyle::PictureNormal, + [self.review_message[0], self.review_message[1]], + self.review_glyph, + ), }; - let validation_page = Page::new(PageStyle::PictureBold, [self.validation_message, ""], self.validation_glyph); - let cancel_page = Page::new(PageStyle::PictureBold, [self.cancel_message, ""], self.cancel_glyph); - let mut review_pages: [Page; 32] = [Page::new(PageStyle::Normal, ["", ""], None); 32]; + + let validation_page = Page::new( + PageStyle::PictureBold, + [self.validation_message, ""], + self.validation_glyph, + ); + let cancel_page = Page::new( + PageStyle::PictureBold, + [self.cancel_message, ""], + self.cancel_glyph, + ); + let mut review_pages: [Page; MAX_REVIEW_PAGES] = [Page::new(PageStyle::Normal, ["", ""], None); MAX_REVIEW_PAGES]; let mut total_page_count = 0; - + + let mut max_chars_per_page = MAX_CHAR_PER_LINE * 3; + if cfg!(target_os = "nanos") { + max_chars_per_page = MAX_CHAR_PER_LINE; + } + // Determine each field page count for field in self.fields { - let field_page_count = (field.value.len() - 1) / MAX_CHAR_PER_LINE + 1; - // Create pages for each chunk of the field + let field_page_count = (field.value.len() - 1) / max_chars_per_page + 1; + // Create pages for each chunk of the field for i in 0..field_page_count { - let start = i * MAX_CHAR_PER_LINE; - let end = (start + MAX_CHAR_PER_LINE).min(field.value.len()); + let start = i * max_chars_per_page; + let end = (start + max_chars_per_page).min(field.value.len()); let chunk = &field.value[start..end]; + review_pages[total_page_count] = Page::new(PageStyle::BoldNormal, [field.name, chunk], None); review_pages[total_page_count].chunk_count = field_page_count as u8; review_pages[total_page_count].chunk_idx = (i + 1) as u8; - total_page_count += 1; + // Check if we have reached the maximum number of pages + // We need to keep 2 pages for the validation and cancel pages + total_page_count = if total_page_count < MAX_REVIEW_PAGES - 2 { + total_page_count + 1 + } else { + break; + }; } } @@ -739,6 +791,7 @@ impl<'a> MultiFieldReview<'a> { RIGHT_ARROW.display(); let mut cur_page = 0; + let mut refresh : bool = true; review_pages[cur_page].place(); loop { @@ -749,34 +802,37 @@ impl<'a> MultiFieldReview<'a> { if cur_page > 0 { cur_page -= 1; } + refresh = true; } ButtonEvent::RightButtonRelease => { if cur_page < total_page_count { cur_page += 1; } + refresh = true; } - ButtonEvent::BothButtonsRelease => - { + ButtonEvent::BothButtonsRelease => { if cur_page == total_page_count { // Cancel return false; - } - else if cur_page == total_page_count - 1 { + } else if cur_page == total_page_count - 1 { // Validate return true; } } - _ => (), - } - clear_screen(); - review_pages[cur_page].place(); - if cur_page > 0 { - LEFT_ARROW.display(); + _ => refresh = false, } - if cur_page < total_page_count { - RIGHT_ARROW.display(); + if refresh + { + clear_screen(); + review_pages[cur_page].place(); + if cur_page > 0 { + LEFT_ARROW.display(); + } + if cur_page < total_page_count { + RIGHT_ARROW.display(); + } + crate::screen_util::screen_update(); } - crate::screen_util::screen_update(); } _ => (), } From 16b82c56ba0adb3579a7b9e2c35532e88ab7ec9e Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Wed, 15 Nov 2023 16:52:25 +0100 Subject: [PATCH 05/11] Format --- ledger_device_ui_sdk/src/ui.rs | 49 +++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/ledger_device_ui_sdk/src/ui.rs b/ledger_device_ui_sdk/src/ui.rs index 4abef82f..8f8b8c5f 100644 --- a/ledger_device_ui_sdk/src/ui.rs +++ b/ledger_device_ui_sdk/src/ui.rs @@ -2,12 +2,15 @@ use core::str::from_utf8; -use ledger_secure_sdk_sys::{seph, buttons::{get_button_event, ButtonEvent, ButtonsState}}; -use ledger_device_sdk::{io, buttons::ButtonEvent::*}; +use ledger_device_sdk::{buttons::ButtonEvent::*, io}; +use ledger_secure_sdk_sys::{ + buttons::{get_button_event, ButtonEvent, ButtonsState}, + seph, +}; use crate::bitmaps::Glyph; -use crate::bagls::*; +use crate::{bagls::*, fonts::OPEN_SANS}; use crate::layout; use crate::layout::{Draw, Location, StringPlace}; @@ -41,7 +44,6 @@ pub fn get_event(buttons: &mut ButtonsState) -> Option { pub fn clear_screen() { #[cfg(not(target_os = "nanos"))] { - #[cfg(not(feature = "speculos"))] unsafe { ledger_secure_sdk_sys::screen_clear(); @@ -415,15 +417,14 @@ impl<'a> Page<'a> { PageStyle::BoldNormal => { let padding = 1; let mut max_text_lines = 3; - if cfg!(target_os = "nanos") - { + if cfg!(target_os = "nanos") { max_text_lines = 1; } - let total_height = (OPEN_SANS[0].height * max_text_lines) as usize + let total_height = (OPEN_SANS[0].height * max_text_lines) as usize + OPEN_SANS[1].height as usize + 2 * padding as usize; let mut cur_y = Location::Middle.get_y(total_height); - + // Display the chunk count and index if needed if self.chunk_count > 1 { let mut label_bytes = [0u8; MAX_CHAR_PER_LINE]; @@ -445,16 +446,18 @@ impl<'a> Page<'a> { ], &mut label_bytes, ); - from_utf8(&mut label_bytes).unwrap().trim_matches(char::from(0)).place(Location::Custom(cur_y), Layout::Centered, true); + from_utf8(&mut label_bytes) + .unwrap() + .trim_matches(char::from(0)) + .place(Location::Custom(cur_y), Layout::Centered, true); } else { self.label[0].place(Location::Custom(cur_y), Layout::Centered, true); } cur_y += OPEN_SANS[0].height as usize + 2 * padding as usize; - + // If the device is a Nano S, display the second label as // a single line of text - if cfg!(target_os = "nanos") - { + if cfg!(target_os = "nanos") { self.label[1].place(Location::Custom(cur_y), Layout::Centered, false); } // Otherwise, display the second label as up to 3 lines of text @@ -468,9 +471,12 @@ impl<'a> Page<'a> { } let end = (start + MAX_CHAR_PER_LINE).min(len); indices[i] = (start, end); - (&self.label[1][start..end]).place(Location::Custom(cur_y), Layout::Centered, false); + (&self.label[1][start..end]).place( + Location::Custom(cur_y), + Layout::Centered, + false, + ); cur_y += OPEN_SANS[0].height as usize + 2 * padding as usize; - } } } @@ -723,7 +729,7 @@ impl<'a> MultiFieldReview<'a> { } } - pub fn show(&self) -> bool { + pub fn show(&self) -> bool { let mut buttons = ButtonsState::new(); let first_page = match self.review_message.len() { @@ -750,7 +756,8 @@ impl<'a> MultiFieldReview<'a> { [self.cancel_message, ""], self.cancel_glyph, ); - let mut review_pages: [Page; MAX_REVIEW_PAGES] = [Page::new(PageStyle::Normal, ["", ""], None); MAX_REVIEW_PAGES]; + let mut review_pages: [Page; MAX_REVIEW_PAGES] = + [Page::new(PageStyle::Normal, ["", ""], None); MAX_REVIEW_PAGES]; let mut total_page_count = 0; let mut max_chars_per_page = MAX_CHAR_PER_LINE * 3; @@ -766,8 +773,9 @@ impl<'a> MultiFieldReview<'a> { let start = i * max_chars_per_page; let end = (start + max_chars_per_page).min(field.value.len()); let chunk = &field.value[start..end]; - - review_pages[total_page_count] = Page::new(PageStyle::BoldNormal, [field.name, chunk], None); + + review_pages[total_page_count] = + Page::new(PageStyle::BoldNormal, [field.name, chunk], None); review_pages[total_page_count].chunk_count = field_page_count as u8; review_pages[total_page_count].chunk_idx = (i + 1) as u8; // Check if we have reached the maximum number of pages @@ -791,7 +799,7 @@ impl<'a> MultiFieldReview<'a> { RIGHT_ARROW.display(); let mut cur_page = 0; - let mut refresh : bool = true; + let mut refresh: bool = true; review_pages[cur_page].place(); loop { @@ -821,8 +829,7 @@ impl<'a> MultiFieldReview<'a> { } _ => refresh = false, } - if refresh - { + if refresh { clear_screen(); review_pages[cur_page].place(); if cur_page > 0 { From 53194e4958816673ac2c91fe61ed4f526d93c46f Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Thu, 16 Nov 2023 09:49:44 +0100 Subject: [PATCH 06/11] Use dependencies on "add-ui-gadgets" branch (like the boilerplate app) --- ledger_device_ui_sdk/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ledger_device_ui_sdk/Cargo.toml b/ledger_device_ui_sdk/Cargo.toml index 28d59806..153efa06 100644 --- a/ledger_device_ui_sdk/Cargo.toml +++ b/ledger_device_ui_sdk/Cargo.toml @@ -7,9 +7,9 @@ license.workspace = true description = "Ledger devices abstractions for displaying text, icons, menus and other common gadgets to the screen" [dependencies] -ledger_secure_sdk_sys = "1.0.0" -ledger_device_sdk = "1.0.0" -include_gif = "1.0.0" +ledger_device_sdk = { git = "https://github.com/LedgerHQ/ledger-device-rust-sdk.git", branch = "add-ui-gadgets" } +ledger_secure_sdk_sys = { git = "https://github.com/LedgerHQ/ledger-device-rust-sdk.git", branch = "add-ui-gadgets" } +include_gif = { git = "https://github.com/LedgerHQ/ledger-device-rust-sdk.git", branch = "add-ui-gadgets" } numtoa = "0.2.4" [features] From 9879ff4ff8233f803609421cf93d0d4b9a6860e3 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Thu, 16 Nov 2023 10:06:41 +0100 Subject: [PATCH 07/11] Update CI --- .github/workflows/rust.yml | 84 ++++++++++++-------------------------- 1 file changed, 27 insertions(+), 57 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2432582f..9ada5bde 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -15,96 +15,66 @@ env: jobs: clippy: + name: Run static analysis runs-on: ubuntu-latest - + container: + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest strategy: matrix: target: ["nanos", "nanox", "nanosplus"] - steps: - - name: arm-none-eabi-gcc - uses: fiam/arm-none-eabi-gcc@v1.0.3 - with: - release: '9-2019-q4' - - name: Install toolchains - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - components: rust-src, clippy - - uses: actions/checkout@v2 + - name: Clone + uses: actions/checkout@v2 - name: Cargo clippy uses: actions-rs/cargo@v1 with: command: clippy - args: -p ledger_device_rust_sdk --target ledger_device_rust_sdk/${{ matrix.target }}.json - fmt: + args: -p ledger_device_sdk --target ledger_device_sdk/${{ matrix.target }}.json + + format: + name: Check code formatting runs-on: ubuntu-latest + container: + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest steps: - - name: Install toolchains - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - components: rust-src, rustfmt - - uses: actions/checkout@v2 - - name: Cargo fmt + - name: Clone + uses: actions/checkout@v2 + - name: Run cargo fmt uses: actions-rs/cargo@v1 with: command: fmt - args: -p ledger_device_rust_sdk --all -- --check + args: -p ledger_device_sdk --all -- --check build: + name: Build SDK runs-on: ubuntu-latest + container: + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest strategy: matrix: target: ["nanos", "nanox", "nanosplus"] - steps: - - name: arm-none-eabi-gcc - uses: fiam/arm-none-eabi-gcc@v1.0.3 - with: - release: '9-2019-q4' - - name: Install clang - run: sudo apt-get update && sudo apt install -y clang - - name: Install toolchains - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - components: rust-src - - uses: actions/checkout@v2 + - name: Clone + uses: actions/checkout@v2 - name: Cargo build uses: actions-rs/cargo@v1 with: command: build - args: -p ledger_device_rust_sdk --target ledger_device_rust_sdk/${{ matrix.target }}.json + args: -p ledger_device_sdk --target ledger_device_sdk/${{ matrix.target }}.json test: + name: Run unit tests runs-on: ubuntu-latest + container: + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest strategy: matrix: target: ["nanos", "nanox", "nanosplus"] - steps: - - name: arm-none-eabi-gcc - uses: fiam/arm-none-eabi-gcc@v1.0.3 - with: - release: '9-2019-q4' - - name: Install clang - run: sudo apt-get update && sudo apt install -y clang - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - components: rust-src - - name: Install dependencies - run: | - sudo apt-get update && sudo apt-get install -y qemu-user-static - pip install speculos --extra-index-url https://test.pypi.org/simple/ - - uses: actions/checkout@v2 + - name: Clone + uses: actions/checkout@v2 - name: Unit tests uses: actions-rs/cargo@v1 with: command: test - args: -p ledger_device_rust_sdk --target ledger_device_rust_sdk/${{ matrix.target }}.json --features speculos + args: -p ledger_device_sdk --target ledger_device_sdk/${{ matrix.target }}.json --features speculos From 8fbf680c9b6163cb5d78c32af003df8aed0c60cf Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Thu, 16 Nov 2023 10:55:00 +0100 Subject: [PATCH 08/11] Format --- include_gif/src/lib.rs | 12 ++++++------ ledger_device_sdk/src/lib.rs | 3 ++- ledger_device_ui_sdk/examples/gadgets.rs | 9 +++++++-- ledger_device_ui_sdk/src/bagls/se.rs | 3 +-- ledger_device_ui_sdk/src/bitmaps.rs | 18 +++++++++--------- ledger_device_ui_sdk/src/screen_util.rs | 19 ++++++++++--------- ledger_device_ui_sdk/src/string_se.rs | 9 ++++++--- 7 files changed, 41 insertions(+), 32 deletions(-) diff --git a/include_gif/src/lib.rs b/include_gif/src/lib.rs index a756743e..c7826cd9 100644 --- a/include_gif/src/lib.rs +++ b/include_gif/src/lib.rs @@ -1,10 +1,10 @@ extern crate proc_macro; use proc_macro::TokenStream; -use syn; use std; use std::fs::File; use std::io::Write; +use syn; #[proc_macro] pub fn include_gif(input: TokenStream) -> TokenStream { @@ -18,19 +18,19 @@ pub fn include_gif(input: TokenStream) -> TokenStream { let palette = decoder.palette().unwrap(); let dimensions = frame.width * frame.height; let (size, remainder) = ((dimensions / 8) as usize, (dimensions % 8) as usize); - + let mut packed = Vec::new(); for i in 0..size { let mut byte = 0; for j in 0..8 { - let color = (palette[frame.buffer[8*i + j] as usize * 3] != 0) as u8; + let color = (palette[frame.buffer[8 * i + j] as usize * 3] != 0) as u8; byte |= color << j; } packed.push(byte); } - let mut byte = 0; + let mut byte = 0; for j in 0..remainder { - let color = (palette[frame.buffer[8*size + j] as usize * 3] != 0) as u8; + let color = (palette[frame.buffer[8 * size + j] as usize * 3] != 0) as u8; byte |= color << j; } packed.push(byte); @@ -40,4 +40,4 @@ pub fn include_gif(input: TokenStream) -> TokenStream { let a = std::str::from_utf8(&b).unwrap(); a.parse().unwrap() -} \ No newline at end of file +} diff --git a/ledger_device_sdk/src/lib.rs b/ledger_device_sdk/src/lib.rs index 9b32e602..6771c936 100644 --- a/ledger_device_sdk/src/lib.rs +++ b/ledger_device_sdk/src/lib.rs @@ -137,7 +137,8 @@ impl NVMData { asm!( "mov {}, r9", out(reg) static_base); let offset = (addr - static_base) as isize; let data_addr = (_nvram_data as *const u8).offset(offset); - let pic_addr = ledger_secure_sdk_sys::pic(data_addr as *mut core::ffi::c_void) as *mut T; + let pic_addr = + ledger_secure_sdk_sys::pic(data_addr as *mut core::ffi::c_void) as *mut T; &mut *pic_addr.cast() } } diff --git a/ledger_device_ui_sdk/examples/gadgets.rs b/ledger_device_ui_sdk/examples/gadgets.rs index 185bfc93..4cd22f83 100644 --- a/ledger_device_ui_sdk/examples/gadgets.rs +++ b/ledger_device_ui_sdk/examples/gadgets.rs @@ -118,13 +118,18 @@ extern "C" fn sample_main() { ui::clear_screen(); - let checkmark = ledger_device_ui_sdk::bagls::CHECKMARK_ICON.set_x(0).set_y(4); + let checkmark = ledger_device_ui_sdk::bagls::CHECKMARK_ICON + .set_x(0) + .set_y(4); checkmark.instant_display(); ledger_device_ui_sdk::bagls::CROSS_ICON .set_x(20) .set_y(4) .instant_display(); - ledger_device_ui_sdk::bagls::COGGLE.set_x(40).set_y(4).instant_display(); + ledger_device_ui_sdk::bagls::COGGLE + .set_x(40) + .set_y(4) + .instant_display(); wait_any(); checkmark.instant_erase(); wait_any(); diff --git a/ledger_device_ui_sdk/src/bagls/se.rs b/ledger_device_ui_sdk/src/bagls/se.rs index 982e1eda..e4b916ed 100644 --- a/ledger_device_ui_sdk/src/bagls/se.rs +++ b/ledger_device_ui_sdk/src/bagls/se.rs @@ -73,7 +73,7 @@ use crate::bagls::RectFull; impl Draw for RectFull { fn display(&self) { - unsafe { + unsafe { ledger_secure_sdk_sys::bagl_hal_draw_rect( 1, self.pos.0, @@ -97,7 +97,6 @@ impl Draw for RectFull { } } - use core::ffi::c_void; #[inline(never)] diff --git a/ledger_device_ui_sdk/src/bitmaps.rs b/ledger_device_ui_sdk/src/bitmaps.rs index c79e4bf7..bb865566 100644 --- a/ledger_device_ui_sdk/src/bitmaps.rs +++ b/ledger_device_ui_sdk/src/bitmaps.rs @@ -40,15 +40,15 @@ pub fn manual_screen_clear() { let inverted = [0u32, 1u32]; unsafe { ledger_secure_sdk_sys::bagl_hal_draw_bitmap_within_rect( - 0, - 0, - 128, - 64, - 2, - inverted.as_ptr(), - 1, - BLANK.as_ptr(), - 128 * 64 + 0, + 0, + 128, + 64, + 2, + inverted.as_ptr(), + 1, + BLANK.as_ptr(), + 128 * 64, ); } } diff --git a/ledger_device_ui_sdk/src/screen_util.rs b/ledger_device_ui_sdk/src/screen_util.rs index c6816295..ce70a544 100644 --- a/ledger_device_ui_sdk/src/screen_util.rs +++ b/ledger_device_ui_sdk/src/screen_util.rs @@ -6,15 +6,16 @@ pub fn draw(x_pos: i32, y_pos: i32, w: u32, h: u32, inv: bool, bmp: &[u8]) { let inverted = [inv as u32, !inv as u32]; unsafe { ledger_secure_sdk_sys::bagl_hal_draw_bitmap_within_rect( - x_pos, - y_pos, - w, - h, - 2, - inverted.as_ptr(), - 1, - bmp.as_ptr(), - w * h); + x_pos, + y_pos, + w, + h, + 2, + inverted.as_ptr(), + 1, + bmp.as_ptr(), + w * h, + ); } } diff --git a/ledger_device_ui_sdk/src/string_se.rs b/ledger_device_ui_sdk/src/string_se.rs index 87c50938..af892ac0 100644 --- a/ledger_device_ui_sdk/src/string_se.rs +++ b/ledger_device_ui_sdk/src/string_se.rs @@ -11,9 +11,12 @@ extern "C" { impl StringPlace for &str { fn compute_width(&self, bold: bool) -> usize { let font_choice = bold as usize; - self.as_bytes().iter().map(ledger_secure_sdk_sys::pic_rs).fold(0, |acc, c| { - acc + OPEN_SANS[font_choice].dims[*c as usize - 0x20] as usize - }) + self.as_bytes() + .iter() + .map(ledger_secure_sdk_sys::pic_rs) + .fold(0, |acc, c| { + acc + OPEN_SANS[font_choice].dims[*c as usize - 0x20] as usize + }) } fn place(&self, loc: Location, layout: Layout, bold: bool) { From b947a157a478017d4fa0b041e2eb95d2c58425b7 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Thu, 16 Nov 2023 11:13:13 +0100 Subject: [PATCH 09/11] Remove remaining mentions of "ledger_device_rust_sdk" --- README.md | 2 +- ledger_device_sdk/examples/signature.rs | 2 +- ledger_device_sdk/src/nvm.rs | 4 ++-- ledger_device_ui_sdk/examples/gadgets.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2a46a3c8..c0716393 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Ledger Device Rust SDK This workspace contains the 4 crates members of Ledger Device Rust SDK -* [ledger_device_rust_sdk](./ledger_device_rust_sdk): main Rust SDK crate used to build an application that runs on BOLOS OS, +* [ledger_device_sdk](./ledger_device_sdk): main Rust SDK crate used to build an application that runs on BOLOS OS, * [ledger_device_ui_sdk](./ledger_device_ui_sdk): UI SDK used by application to get access to UI gadgets, * [ledger_secure_sdk_sys](./ledger_secure_sdk_sys): bindings to [ledger_secure_sdk](https://github.com/LedgerHQ/ledger-secure-sdk) * [include_gif](./include_gif): procedural macro used to manage GIF. diff --git a/ledger_device_sdk/examples/signature.rs b/ledger_device_sdk/examples/signature.rs index eb6092d0..b08953b9 100644 --- a/ledger_device_sdk/examples/signature.rs +++ b/ledger_device_sdk/examples/signature.rs @@ -8,7 +8,7 @@ fn panic(_info: &PanicInfo) -> ! { loop {} } -use ledger_device_rust_sdk::ecc::{make_bip32_path, Secp256r1, SeedDerive}; +use ledger_device_sdk::ecc::{make_bip32_path, Secp256r1, SeedDerive}; const PATH: [u32; 5] = make_bip32_path(b"m/44'/123'/0'/0/0"); diff --git a/ledger_device_sdk/src/nvm.rs b/ledger_device_sdk/src/nvm.rs index 74c12d44..ac55a223 100644 --- a/ledger_device_sdk/src/nvm.rs +++ b/ledger_device_sdk/src/nvm.rs @@ -13,8 +13,8 @@ //! update: //! //! ``` -//! use ledger_device_rust_sdk::PIC; -//! use ledger_device_rust_sdk::nvm::AtomicStorage; +//! use ledger_device_sdk::PIC; +//! use ledger_device_sdk::nvm::AtomicStorage; //! //! // This is necessary to store the object in NVM and not in RAM //! #[link_section=".nvm_data"] diff --git a/ledger_device_ui_sdk/examples/gadgets.rs b/ledger_device_ui_sdk/examples/gadgets.rs index 4cd22f83..459bd873 100644 --- a/ledger_device_ui_sdk/examples/gadgets.rs +++ b/ledger_device_ui_sdk/examples/gadgets.rs @@ -7,7 +7,7 @@ fn panic(_: &PanicInfo) -> ! { loop {} } -use ledger_device_rust_sdk::buttons::*; +use ledger_device_sdk::buttons::*; use ledger_device_ui_sdk::layout::{Layout, Location, StringPlace}; use ledger_device_ui_sdk::ui; @@ -134,5 +134,5 @@ extern "C" fn sample_main() { checkmark.instant_erase(); wait_any(); - ledger_device_rust_sdk::exit_app(0); + ledger_device_sdk::exit_app(0); } From 41bf18af6d40296ec32726040ed039b97a0b3546 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Thu, 16 Nov 2023 11:45:23 +0100 Subject: [PATCH 10/11] Add runner config in root level config.toml for unit tests --- .cargo/config.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.cargo/config.toml b/.cargo/config.toml index cfbadfd2..3fdbcc87 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,12 @@ +[target.nanos] +runner = "speculos -m nanos --display=headless" + +[target.nanox] +runner = "speculos -m nanox -a 5 --display=headless" + +[target.nanosplus] +runner = "speculos -m nanosp -a 1 --display=headless" + [unstable] build-std = ["core"] build-std-features = ["compiler-builtins-mem"] From 05355eb2b917fd37585139788c5bef9847369de9 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Thu, 16 Nov 2023 12:49:36 +0100 Subject: [PATCH 11/11] Update UI crate version and put back 1.0.0 versions for its SDK dependencies in Cargo.toml --- ledger_device_ui_sdk/Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ledger_device_ui_sdk/Cargo.toml b/ledger_device_ui_sdk/Cargo.toml index 153efa06..fc7ce248 100644 --- a/ledger_device_ui_sdk/Cargo.toml +++ b/ledger_device_ui_sdk/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "ledger_device_ui_sdk" -version = "1.0.0" +version = "1.1.0" authors = ["yhql"] edition = "2021" license.workspace = true description = "Ledger devices abstractions for displaying text, icons, menus and other common gadgets to the screen" [dependencies] -ledger_device_sdk = { git = "https://github.com/LedgerHQ/ledger-device-rust-sdk.git", branch = "add-ui-gadgets" } -ledger_secure_sdk_sys = { git = "https://github.com/LedgerHQ/ledger-device-rust-sdk.git", branch = "add-ui-gadgets" } -include_gif = { git = "https://github.com/LedgerHQ/ledger-device-rust-sdk.git", branch = "add-ui-gadgets" } +ledger_device_sdk = "1.0.0" +ledger_secure_sdk_sys = "1.0.0" +include_gif = "1.0.0" numtoa = "0.2.4" [features]