diff --git a/core/assets/model_r/coinjoin.png b/core/assets/model_r/coinjoin.png new file mode 100644 index 0000000000..061be793e1 Binary files /dev/null and b/core/assets/model_r/coinjoin.png differ diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index ae99c4fe50..b2aa86b0ba 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -37,6 +37,7 @@ static void _librust_qstrs(void) { MP_QSTR_cancel_arrow; MP_QSTR_case_sensitive; MP_QSTR_chunkify; + MP_QSTR_coinjoin_authorized; MP_QSTR_confirm_action; MP_QSTR_confirm_address; MP_QSTR_confirm_backup; diff --git a/core/embed/rust/src/ui/model_tr/component/homescreen.rs b/core/embed/rust/src/ui/model_tr/component/homescreen.rs index c1ed7469a7..718595bc67 100644 --- a/core/embed/rust/src/ui/model_tr/component/homescreen.rs +++ b/core/embed/rust/src/ui/model_tr/component/homescreen.rs @@ -27,6 +27,7 @@ const NOTIFICATION_HEIGHT: i16 = 12; const LABEL_OUTSET: i16 = 3; const NOTIFICATION_FONT: Font = Font::NORMAL; const NOTIFICATION_ICON: Icon = theme::ICON_WARNING; +const COINJOIN_CORNER: Point = AREA.top_right().ofs(Offset::new(-2, 2)); fn paint_default_image() { theme::ICON_LOGO.draw( @@ -171,13 +172,15 @@ where instruction: Child>, /// Used for unlocking the device from lockscreen invisible_buttons: Child>, + /// Display coinjoin icon? + coinjoin_icon: Option, } impl Lockscreen where T: StringType + Clone, { - pub fn new(label: T, bootscreen: bool) -> Self { + pub fn new(label: T, bootscreen: bool, coinjoin_authorized: bool) -> Self { // Buttons will not be visible, we only need all three of them to be present, // so that even middle-click triggers the event. let invisible_btn_layout = ButtonLayout::arrow_armed_arrow("".into()); @@ -190,6 +193,7 @@ where label: Child::new(Label::centered(label, theme::TEXT_BIG)), instruction: Child::new(Label::centered(instruction_str.into(), theme::TEXT_NORMAL)), invisible_buttons: Child::new(ButtonController::new(invisible_btn_layout)), + coinjoin_icon: coinjoin_authorized.then_some(theme::ICON_COINJOIN), } } } @@ -223,6 +227,14 @@ where ); self.instruction.paint(); self.label.paint(); + if let Some(i) = &self.coinjoin_icon { + i.draw( + COINJOIN_CORNER, + Alignment2D::TOP_RIGHT, + theme::FG, + theme::BG, + ) + } } } diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index 73e20b251a..d64f37bf4f 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -1587,9 +1587,10 @@ extern "C" fn new_show_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut .try_into_option()? .unwrap_or_else(|| model::FULL_NAME.into()); let bootscreen: bool = kwargs.get(Qstr::MP_QSTR_bootscreen)?.try_into()?; + let coinjoin_authorized: bool = kwargs.get_or(Qstr::MP_QSTR_coinjoin_authorized, false)?; let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?; - let obj = LayoutObj::new(Lockscreen::new(label, bootscreen))?; + let obj = LayoutObj::new(Lockscreen::new(label, bootscreen, coinjoin_authorized))?; if skip_first_paint { obj.skip_first_paint(); } @@ -2008,6 +2009,7 @@ pub static mp_module_trezorui2: Module = obj_module! { /// label: str | None, /// bootscreen: bool, /// skip_first_paint: bool, + /// coinjoin_authorized: bool = False, /// ) -> CANCELLED: /// """Homescreen for locked device.""" Qstr::MP_QSTR_show_lockscreen => obj_fn_kw!(0, new_show_lockscreen).as_obj(), diff --git a/core/embed/rust/src/ui/model_tr/res/coinjoin.toif b/core/embed/rust/src/ui/model_tr/res/coinjoin.toif new file mode 100644 index 0000000000..82aec115a4 Binary files /dev/null and b/core/embed/rust/src/ui/model_tr/res/coinjoin.toif differ diff --git a/core/embed/rust/src/ui/model_tr/theme.rs b/core/embed/rust/src/ui/model_tr/theme.rs index 7360164368..917bffb3d3 100644 --- a/core/embed/rust/src/ui/model_tr/theme.rs +++ b/core/embed/rust/src/ui/model_tr/theme.rs @@ -80,6 +80,7 @@ include_icon!( "model_tr/res/cancel.toif", empty_right_col = true ); // 7*7 +include_icon!(ICON_COINJOIN, "model_tr/res/coinjoin.toif"); // 12*12 include_icon!( ICON_DELETE, "model_tr/res/delete.toif", diff --git a/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs b/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs index 88d3ca3ac1..b66d884b7f 100644 --- a/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs +++ b/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs @@ -36,6 +36,7 @@ const LABEL_Y: i16 = HEIGHT - 18; const LOCKED_Y: i16 = HEIGHT / 2 - 13; const TAP_Y: i16 = HEIGHT / 2 + 14; const HOLD_Y: i16 = 35; +const COINJOIN_Y: i16 = 30; const LOADER_OFFSET: Offset = Offset::y(-10); const LOADER_DELAY: Duration = Duration::from_millis(500); const LOADER_DURATION: Duration = Duration::from_millis(2000); @@ -198,7 +199,7 @@ where let text = HomescreenText { text: self.label.as_ref(), style: label_style, - offset: Offset::new(10, LABEL_Y), + offset: Offset::y(LABEL_Y), icon: None, }; @@ -266,11 +267,16 @@ impl> crate::trace::Trace for Homescreen { pub struct Lockscreen { label: T, bootscreen: bool, + coinjoin_authorized: bool, } impl Lockscreen { - pub fn new(label: T, bootscreen: bool) -> Self { - Lockscreen { label, bootscreen } + pub fn new(label: T, bootscreen: bool, coinjoin_authorized: bool) -> Self { + Lockscreen { + label, + bootscreen, + coinjoin_authorized, + } } } @@ -301,27 +307,37 @@ where let mut label_style = theme::TEXT_DEMIBOLD; label_style.text_color = theme::GREY_LIGHT; - let texts: [HomescreenText; 3] = [ + let mut texts: &[HomescreenText] = &[ + HomescreenText { + text: "", + style: theme::TEXT_NORMAL, + offset: Offset::new(2, COINJOIN_Y), + icon: Some(theme::ICON_COINJOIN), + }, HomescreenText { text: locked, style: theme::TEXT_BOLD, - offset: Offset::new(10, LOCKED_Y), + offset: Offset::y(LOCKED_Y), icon: Some(theme::ICON_LOCK), }, HomescreenText { text: tap, style: theme::TEXT_NORMAL, - offset: Offset::new(10, TAP_Y), + offset: Offset::y(TAP_Y), icon: None, }, HomescreenText { text: self.label.as_ref(), style: label_style, - offset: Offset::new(10, LABEL_Y), + offset: Offset::y(LABEL_Y), icon: None, }, ]; + if !self.coinjoin_authorized { + texts = &texts[1..]; + } + let res = get_user_custom_image(); let mut show_default = true; @@ -330,14 +346,14 @@ where let mut input = BufferInput(data.as_ref()); let mut pool = BufferJpegWork::get_cleared(); let mut hs_img = HomescreenJpeg::new(&mut input, pool.buffer.as_mut_slice()); - homescreen_blurred(&mut hs_img, &texts); + homescreen_blurred(&mut hs_img, texts); show_default = false; } else if is_image_toif(data.as_ref()) { let input = unwrap!(Toif::new(data.as_ref())); let mut window = [0; UZLIB_WINDOW_SIZE]; let mut hs_img = HomescreenToif::new(input.decompression_context(Some(&mut window))); - homescreen_blurred(&mut hs_img, &texts); + homescreen_blurred(&mut hs_img, texts); show_default = false; } } @@ -346,7 +362,7 @@ where let mut input = BufferInput(IMAGE_HOMESCREEN); let mut pool = BufferJpegWork::get_cleared(); let mut hs_img = HomescreenJpeg::new(&mut input, pool.buffer.as_mut_slice()); - homescreen_blurred(&mut hs_img, &texts); + homescreen_blurred(&mut hs_img, texts); } } } diff --git a/core/embed/rust/src/ui/model_tt/component/homescreen/render.rs b/core/embed/rust/src/ui/model_tt/component/homescreen/render.rs index 87b0df1bbc..472ae57d14 100644 --- a/core/embed/rust/src/ui/model_tt/component/homescreen/render.rs +++ b/core/embed/rust/src/ui/model_tt/component/homescreen/render.rs @@ -230,9 +230,11 @@ fn homescreen_position_text( let text_top = screen().y0 + text.offset.y - font_max_height + font_baseline; let text_bottom = screen().y0 + text.offset.y + font_baseline; - let icon_left = screen().center().x - (text_width_clamped + icon_size.x + TEXT_ICON_SPACE) / 2; + + let total_width = text_width_clamped + icon_size.x + TEXT_ICON_SPACE; + let icon_left = screen().center().x + text.offset.x - total_width / 2; let text_left = icon_left + icon_size.x + TEXT_ICON_SPACE; - let text_right = screen().center().x + (text_width_clamped + icon_size.x + TEXT_ICON_SPACE) / 2; + let text_right = screen().center().x + text.offset.x + total_width / 2; let text_area = Rect::new( Point::new(text_left, text_top), diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 6252ef922c..a676008ef4 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -1557,9 +1557,10 @@ extern "C" fn new_show_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut .try_into_option()? .unwrap_or_else(|| model::FULL_NAME.into()); let bootscreen: bool = kwargs.get(Qstr::MP_QSTR_bootscreen)?.try_into()?; + let coinjoin_authorized: bool = kwargs.get_or(Qstr::MP_QSTR_coinjoin_authorized, false)?; let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?; - let obj = LayoutObj::new(Lockscreen::new(label, bootscreen))?; + let obj = LayoutObj::new(Lockscreen::new(label, bootscreen, coinjoin_authorized))?; if skip_first_paint { obj.skip_first_paint(); } @@ -1988,6 +1989,7 @@ pub static mp_module_trezorui2: Module = obj_module! { /// label: str | None, /// bootscreen: bool, /// skip_first_paint: bool, + /// coinjoin_authorized: bool = False, /// ) -> CANCELLED: /// """Homescreen for locked device.""" Qstr::MP_QSTR_show_lockscreen => obj_fn_kw!(0, new_show_lockscreen).as_obj(), diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index 75b35d7d02..861b02c9ad 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -433,6 +433,7 @@ def show_lockscreen( label: str | None, bootscreen: bool, skip_first_paint: bool, + coinjoin_authorized: bool = False, ) -> CANCELLED: """Homescreen for locked device.""" @@ -883,6 +884,7 @@ def show_lockscreen( label: str | None, bootscreen: bool, skip_first_paint: bool, + coinjoin_authorized: bool = False, ) -> CANCELLED: """Homescreen for locked device.""" diff --git a/core/src/apps/homescreen/__init__.py b/core/src/apps/homescreen/__init__.py index 3aed7a9207..221682b6a7 100644 --- a/core/src/apps/homescreen/__init__.py +++ b/core/src/apps/homescreen/__init__.py @@ -51,7 +51,10 @@ async def lockscreen() -> None: # Only show the lockscreen UI if the device can in fact be locked. if can_lock_device(): - await Lockscreen(label=storage.device.get_label()) + await Lockscreen( + label=storage.device.get_label(), + coinjoin_authorized=is_set_any_session(MessageType.AuthorizeCoinJoin), + ) # Otherwise proceed directly to unlock() call. If the device is already unlocked, # it should be a no-op storage-wise, but it resets the internal configuration # to an unlocked state. diff --git a/core/src/trezor/ui/layouts/tr/homescreen.py b/core/src/trezor/ui/layouts/tr/homescreen.py index d253dcccba..a58966430b 100644 --- a/core/src/trezor/ui/layouts/tr/homescreen.py +++ b/core/src/trezor/ui/layouts/tr/homescreen.py @@ -80,6 +80,7 @@ class Lockscreen(HomescreenBase): self, label: str | None, bootscreen: bool = False, + coinjoin_authorized: bool = False, ) -> None: self.bootscreen = bootscreen skip = ( @@ -90,6 +91,7 @@ class Lockscreen(HomescreenBase): label=label, bootscreen=bootscreen, skip_first_paint=skip, + coinjoin_authorized=coinjoin_authorized, ), ) diff --git a/core/src/trezor/ui/layouts/tt/homescreen.py b/core/src/trezor/ui/layouts/tt/homescreen.py index 1e4121d5f8..9bcb7ea6d3 100644 --- a/core/src/trezor/ui/layouts/tt/homescreen.py +++ b/core/src/trezor/ui/layouts/tt/homescreen.py @@ -92,6 +92,7 @@ class Lockscreen(HomescreenBase): self, label: str | None, bootscreen: bool = False, + coinjoin_authorized: bool = False, ) -> None: self.bootscreen = bootscreen if bootscreen: @@ -105,6 +106,7 @@ class Lockscreen(HomescreenBase): label=label, bootscreen=bootscreen, skip_first_paint=skip, + coinjoin_authorized=coinjoin_authorized, ), ) diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index 8a6a632976..aa12ac6549 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -737,7 +737,7 @@ }, "TR": { "click_tests": { -"TR_test_autolock.py::test_autolock_does_not_interrupt_preauthorized": "b96ab80af28b6d5e13ef32a8cc19812445c6258a2ad64f15870c7490a37edc06", +"TR_test_autolock.py::test_autolock_does_not_interrupt_preauthorized": "22f65f6d9edc7659e5c54bdda6bd38c68c96637d2eb1f8475a536c9f49423b30", "TR_test_autolock.py::test_autolock_does_not_interrupt_signing": "3d0833002f39256a15ee5008af9d2cd304c61bc2932cb6dc844c027bfed3ed1d", "TR_test_autolock.py::test_autolock_interrupts_passphrase": "1ab8da65492ade3e6301f784b818d355959c665ea4b91e38f6dd2de8c8b21c84", "TR_test_autolock.py::test_autolock_interrupts_signing": "5f26a18a711ec7d17c035cc60478249281edeeac512d4561e55bd7a3c2d2ef94", @@ -795,8 +795,8 @@ "TR_bitcoin-test_authorize_coinjoin.py::test_get_address": "a96bebc82d5aff9c6a8583ddb354e6427689a3be4fddf082c3cd0e8722e54d46", "TR_bitcoin-test_authorize_coinjoin.py::test_get_public_key": "4c2bba305bab30de2fcff0cec5ab1192f2e4d826d86f91f7172dfa624f5f3139", "TR_bitcoin-test_authorize_coinjoin.py::test_multisession_authorization": "5f70b535406a6254113ed2a5f780ba98b8205abf6425eb7038d22395953aa560", -"TR_bitcoin-test_authorize_coinjoin.py::test_sign_tx[False]": "76fe57750f0bfbaa84aaa99f67329dc93e505d51521c2a2490d523996b403aa3", -"TR_bitcoin-test_authorize_coinjoin.py::test_sign_tx[True]": "76fe57750f0bfbaa84aaa99f67329dc93e505d51521c2a2490d523996b403aa3", +"TR_bitcoin-test_authorize_coinjoin.py::test_sign_tx[False]": "ebe4ac22942f10915507491103d1151b2416ffebfd6c3ca6f2b28be8b35d4262", +"TR_bitcoin-test_authorize_coinjoin.py::test_sign_tx[True]": "ebe4ac22942f10915507491103d1151b2416ffebfd6c3ca6f2b28be8b35d4262", "TR_bitcoin-test_authorize_coinjoin.py::test_sign_tx_large": "4f275de439c812363140d3839ebddd9243e2bb34d80d02a487361148b2bbab71", "TR_bitcoin-test_authorize_coinjoin.py::test_sign_tx_migration": "4cf48d6bb48a9efbff9e2949d657fde4dea7ae9e92f47cafdfcd11d7765d76b8", "TR_bitcoin-test_authorize_coinjoin.py::test_sign_tx_spend": "db453154c6d8318befea7230eb2a9639fece5bdfd83c62fbb7a1e9195b77ac1b", @@ -2017,7 +2017,7 @@ }, "TT": { "click_tests": { -"TT_test_autolock.py::test_autolock_does_not_interrupt_preauthorized": "a46dce1eef3df4b242e90af5f6c6a1f2acdb27e7b56a1af0ab0982483bc0d9b7", +"TT_test_autolock.py::test_autolock_does_not_interrupt_preauthorized": "521b6ce07207c262b832fb4e78477f89211dc02a4b6e02a6fe8d389a2be909cf", "TT_test_autolock.py::test_autolock_does_not_interrupt_signing": "e8ff223f44e97a98fbef62c2ed314bdc2d3a2f160d0396d09733847ce103494d", "TT_test_autolock.py::test_autolock_interrupts_passphrase": "15ce8ca8c46be745296ad39b2df4e4503f1b3208c3f5b3e3f48d7bd172779605", "TT_test_autolock.py::test_autolock_interrupts_signing": "a9b983a624b6ca20fd976fc59b570e0bfebc847a448a1db63717932d96cde15c", @@ -2078,8 +2078,8 @@ "TT_bitcoin-test_authorize_coinjoin.py::test_get_address": "f4de0499dac628067619c6081d4ebdba0ad196af7bf9662c5387386eba9e9729", "TT_bitcoin-test_authorize_coinjoin.py::test_get_public_key": "38a7eac0cf0be45770fb28da9c2f74bac6404e85f2a3801acdd4b0107d99f2ff", "TT_bitcoin-test_authorize_coinjoin.py::test_multisession_authorization": "d39b3290ddfecd5a3ceaf249543eaccb2b71c21eb2dbeabef94fe866ad7ce6a8", -"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx[False]": "a5601b1f876c598cfe9361b4203a0b2908e860bef93d3fcc1a4fd2f89eeddfec", -"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx[True]": "a5601b1f876c598cfe9361b4203a0b2908e860bef93d3fcc1a4fd2f89eeddfec", +"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx[False]": "00f8d6cc8ad60877dc4629295e438f29912456f0767997ce3ba9277d1e1bbf22", +"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx[True]": "00f8d6cc8ad60877dc4629295e438f29912456f0767997ce3ba9277d1e1bbf22", "TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_large": "312fca890a6e93da6c3705e3b3c97ba38dd336374634dbed9134362420273380", "TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_migration": "a0757616e39b5883ccfba01af3b386d1fc2d4a00517b2c3d79519ce0f60bc5cd", "TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_spend": "b8ca0b72adfb0cfa1ff653c83a0995517b71f8bfc217753e16aab49dd82a3a65",