feat(core/ui): sending button requests from rust

[no changelog]
Martin Milata 3 weeks ago
parent 34f6964241
commit 45eaab0ce0

@ -93,6 +93,7 @@ static void _librust_qstrs(void) {
MP_QSTR_bounds; MP_QSTR_bounds;
MP_QSTR_button; MP_QSTR_button;
MP_QSTR_button_event; MP_QSTR_button_event;
MP_QSTR_button_request;
MP_QSTR_buttons__abort; MP_QSTR_buttons__abort;
MP_QSTR_buttons__access; MP_QSTR_buttons__access;
MP_QSTR_buttons__again; MP_QSTR_buttons__again;

@ -0,0 +1,44 @@
// ButtonRequestType from messages-common.proto
// Eventually this should be generated
#[derive(Clone, Copy)]
#[repr(u16)]
pub enum ButtonRequestCode {
Other = 1,
FeeOverThreshold = 2,
ConfirmOutput = 3,
ResetDevice = 4,
ConfirmWord = 5,
WipeDevice = 6,
ProtectCall = 7,
SignTx = 8,
FirmwareCheck = 9,
Address = 10,
PublicKey = 11,
MnemonicWordCount = 12,
MnemonicInput = 13,
UnknownDerivationPath = 15,
RecoveryHomepage = 16,
Success = 17,
Warning = 18,
PassphraseEntry = 19,
PinEntry = 20,
}
impl ButtonRequestCode {
pub fn num(&self) -> u16 {
*self as u16
}
pub fn with_type(self, br_type: &'static str) -> Option<ButtonRequest> {
Some(ButtonRequest {
code: self,
br_type,
})
}
}
#[derive(Clone, Copy)]
pub struct ButtonRequest {
pub code: ButtonRequestCode,
pub br_type: &'static str,
}

@ -6,6 +6,7 @@ use crate::{
strutil::TString, strutil::TString,
time::Duration, time::Duration,
ui::{ ui::{
button_request::{ButtonRequest, ButtonRequestCode},
component::{maybe::PaintOverlapping, MsgMap}, component::{maybe::PaintOverlapping, MsgMap},
display::{self, Color}, display::{self, Color},
geometry::{Offset, Rect}, geometry::{Offset, Rect},
@ -487,6 +488,7 @@ pub struct EventCtx {
paint_requested: bool, paint_requested: bool,
anim_frame_scheduled: bool, anim_frame_scheduled: bool,
page_count: Option<usize>, page_count: Option<usize>,
button_request: Option<ButtonRequest>,
root_repaint_requested: bool, root_repaint_requested: bool,
} }
@ -513,6 +515,7 @@ impl EventCtx {
* `Child::marked_for_paint` being true. */ * `Child::marked_for_paint` being true. */
anim_frame_scheduled: false, anim_frame_scheduled: false,
page_count: None, page_count: None,
button_request: None,
root_repaint_requested: false, root_repaint_requested: false,
} }
} }
@ -570,6 +573,16 @@ impl EventCtx {
self.page_count self.page_count
} }
pub fn send_button_request(&mut self, code: ButtonRequestCode, br_type: &'static str) {
#[cfg(feature = "ui_debug")]
assert!(self.button_request.is_none());
self.button_request = Some(ButtonRequest { code, br_type });
}
pub fn button_request(&mut self) -> Option<ButtonRequest> {
self.button_request.take()
}
pub fn pop_timer(&mut self) -> Option<(TimerToken, Duration)> { pub fn pop_timer(&mut self) -> Option<(TimerToken, Duration)> {
self.timers.pop() self.timers.pop()
} }
@ -579,6 +592,9 @@ impl EventCtx {
self.paint_requested = false; self.paint_requested = false;
self.anim_frame_scheduled = false; self.anim_frame_scheduled = false;
self.page_count = None; self.page_count = None;
#[cfg(feature = "ui_debug")]
assert!(self.button_request.is_none());
self.button_request = None;
self.root_repaint_requested = false; self.root_repaint_requested = false;
} }

@ -0,0 +1,84 @@
use crate::ui::{
button_request::{ButtonRequest, ButtonRequestCode},
component::{Component, Event, EventCtx},
geometry::Rect,
shape::Renderer,
};
/// Component that sends a ButtonRequest after receiving Event::Attach. The
/// request is only sent once.
#[derive(Clone, Copy)]
pub struct OneButtonRequest<T> {
button_request: Option<ButtonRequest>,
pub inner: T,
}
impl<T> OneButtonRequest<T> {
pub fn new(button_request: ButtonRequest, inner: T) -> Self {
Self {
button_request: Some(button_request),
inner,
}
}
}
impl<T: Component> Component for OneButtonRequest<T> {
type Msg = T::Msg;
fn place(&mut self, bounds: Rect) -> Rect {
self.inner.place(bounds)
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if matches!(event, Event::Attach) {
if let Some(button_request) = self.button_request.take() {
ctx.send_button_request(button_request.code, button_request.br_type)
}
}
self.inner.event(ctx, event)
}
fn paint(&mut self) {
self.inner.paint()
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.inner.render(target)
}
}
#[cfg(feature = "ui_debug")]
impl<T: crate::trace::Trace> crate::trace::Trace for OneButtonRequest<T> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
self.inner.trace(t)
}
}
pub trait ButtonRequestExt {
fn one_button_request(
self,
code: ButtonRequestCode,
br_type: &'static str,
) -> OneButtonRequest<Self>
where
Self: Sized,
{
OneButtonRequest::new(ButtonRequest { code, br_type }, self)
}
}
impl<T: Component> ButtonRequestExt for T {}
#[cfg(all(feature = "micropython", feature = "touch"))]
impl<T> crate::ui::flow::Swipable for OneButtonRequest<T>
where
T: Component + crate::ui::flow::Swipable,
{
fn can_swipe(&self, direction: crate::ui::component::SwipeDirection) -> bool {
self.inner.can_swipe(direction)
}
fn swiped(&mut self, ctx: &mut EventCtx, direction: crate::ui::component::SwipeDirection) {
self.inner.swiped(ctx, direction)
}
}

@ -3,6 +3,7 @@
pub mod bar; pub mod bar;
pub mod base; pub mod base;
pub mod border; pub mod border;
pub mod button_request;
pub mod connect; pub mod connect;
pub mod empty; pub mod empty;
pub mod image; pub mod image;
@ -24,6 +25,7 @@ pub mod timeout;
pub use bar::Bar; pub use bar::Bar;
pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, Root, TimerToken}; pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, Root, TimerToken};
pub use border::Border; pub use border::Border;
pub use button_request::{ButtonRequestExt, OneButtonRequest};
pub use empty::Empty; pub use empty::Empty;
#[cfg(all(feature = "jpeg", feature = "micropython"))] #[cfg(all(feature = "jpeg", feature = "micropython"))]
pub use jpeg::Jpeg; pub use jpeg::Jpeg;

@ -17,6 +17,7 @@ use crate::{
}, },
time::Duration, time::Duration,
ui::{ ui::{
button_request::ButtonRequest,
component::{Component, Event, EventCtx, Never, Root, TimerToken}, component::{Component, Event, EventCtx, Never, Root, TimerToken},
constant, constant,
display::{sync, Color}, display::{sync, Color},
@ -248,6 +249,17 @@ impl LayoutObj {
self.inner.borrow().page_count.into() self.inner.borrow().page_count.into()
} }
fn obj_button_request(&self) -> Result<Obj, Error> {
let inner = &mut *self.inner.borrow_mut();
match inner.event_ctx.button_request() {
None => Ok(Obj::const_none()),
Some(ButtonRequest { code, br_type }) => {
(code.num().into(), br_type.try_into()?).try_into()
}
}
}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
fn obj_bounds(&self) { fn obj_bounds(&self) {
use crate::ui::display; use crate::ui::display;
@ -280,6 +292,7 @@ impl LayoutObj {
Qstr::MP_QSTR_trace => obj_fn_2!(ui_layout_trace).as_obj(), Qstr::MP_QSTR_trace => obj_fn_2!(ui_layout_trace).as_obj(),
Qstr::MP_QSTR_bounds => obj_fn_1!(ui_layout_bounds).as_obj(), Qstr::MP_QSTR_bounds => obj_fn_1!(ui_layout_bounds).as_obj(),
Qstr::MP_QSTR_page_count => obj_fn_1!(ui_layout_page_count).as_obj(), Qstr::MP_QSTR_page_count => obj_fn_1!(ui_layout_page_count).as_obj(),
Qstr::MP_QSTR_button_request => obj_fn_1!(ui_layout_button_request).as_obj(),
}), }),
}; };
&TYPE &TYPE
@ -470,6 +483,14 @@ extern "C" fn ui_layout_page_count(this: Obj) -> Obj {
unsafe { util::try_or_raise(block) } unsafe { util::try_or_raise(block) }
} }
extern "C" fn ui_layout_button_request(this: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
Ok(this.obj_button_request()?)
};
unsafe { util::try_or_raise(block) }
}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
#[no_mangle] #[no_mangle]
pub extern "C" fn ui_debug_layout_type() -> &'static Type { pub extern "C" fn ui_debug_layout_type() -> &'static Type {

@ -2,6 +2,7 @@
pub mod macros; pub mod macros;
pub mod animation; pub mod animation;
pub mod button_request;
pub mod component; pub mod component;
pub mod constant; pub mod constant;
pub mod display; pub mod display;

@ -1,6 +1,7 @@
use crate::{ use crate::{
error, error,
ui::{ ui::{
button_request::ButtonRequestCode,
component::{ component::{
image::BlendedImage, image::BlendedImage,
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
@ -116,7 +117,8 @@ impl GetAddress {
) )
.with_subtitle("address".into()) .with_subtitle("address".into())
.with_menu_button() .with_menu_button()
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info)), .map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info))
.one_button_request(ButtonRequestCode::Address, "show_address"),
)? )?
.add( .add(
Frame::left_aligned( Frame::left_aligned(

@ -240,9 +240,10 @@ async def handle_EndSession(msg: EndSession) -> Success:
async def handle_Ping(msg: Ping) -> Success: async def handle_Ping(msg: Ping) -> Success:
if msg.button_protection: if msg.button_protection:
from trezor.enums import ButtonRequestType as B from trezor.enums import ButtonRequestType as B
from trezor.ui.layouts import confirm_action from trezor.ui.layouts import confirm_action, flow_demo
await confirm_action("ping", TR.words__confirm, "ping", br_code=B.ProtectCall) await flow_demo()
# await confirm_action("ping", TR.words__confirm, "ping", br_code=B.ProtectCall)
return Success(message=msg.message) return Success(message=msg.message)

@ -1,9 +1,10 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import trezorui2 import trezorui2
from trezor import TR, io, loop, ui from trezor import TR, io, log, loop, ui
from trezor.enums import ButtonRequestType from trezor.enums import ButtonRequestType
from trezor.wire import ActionCancelled from trezor.messages import ButtonAck, ButtonRequest
from trezor.wire import ActionCancelled, context
from trezor.wire.context import wait as ctx_wait from trezor.wire.context import wait as ctx_wait
from ..common import button_request, interact from ..common import button_request, interact
@ -36,7 +37,10 @@ class RustLayout(ui.Layout):
def __init__(self, layout: Any): def __init__(self, layout: Any):
self.layout = layout self.layout = layout
self.timer = loop.Timer() self.timer = loop.Timer()
self.br_chan = loop.chan()
self.layout.attach_timer_fn(self.set_timer) self.layout.attach_timer_fn(self.set_timer)
self._send_button_request()
def set_timer(self, token: int, deadline: int) -> None: def set_timer(self, token: int, deadline: int) -> None:
self.timer.schedule(deadline, token) self.timer.schedule(deadline, token)
@ -63,6 +67,9 @@ class RustLayout(ui.Layout):
self.handle_swipe(), self.handle_swipe(),
self.handle_click_signal(), self.handle_click_signal(),
self.handle_result_signal(), self.handle_result_signal(),
context.with_context(
context.get_context(), self.handle_button_request()
),
) )
async def handle_result_signal(self) -> None: async def handle_result_signal(self) -> None:
@ -110,6 +117,7 @@ class RustLayout(ui.Layout):
(io.TOUCH_END, orig_x + 2 * off_x, orig_y + 2 * off_y), (io.TOUCH_END, orig_x + 2 * off_x, orig_y + 2 * off_y),
): ):
msg = self.layout.touch_event(event, x, y) msg = self.layout.touch_event(event, x, y)
self._send_button_request()
self._paint() self._paint()
if msg is not None: if msg is not None:
raise ui.Result(msg) raise ui.Result(msg)
@ -129,10 +137,12 @@ class RustLayout(ui.Layout):
from apps.debug import notify_layout_change from apps.debug import notify_layout_change
self.layout.touch_event(io.TOUCH_START, x, y) self.layout.touch_event(io.TOUCH_START, x, y)
self._send_button_request()
self._paint() self._paint()
if hold_ms is not None: if hold_ms is not None:
await loop.sleep(hold_ms) await loop.sleep(hold_ms)
msg = self.layout.touch_event(io.TOUCH_END, x, y) msg = self.layout.touch_event(io.TOUCH_END, x, y)
self._send_button_request()
if msg is not None: if msg is not None:
debug_storage.new_layout_event_id = event_id debug_storage.new_layout_event_id = event_id
@ -159,7 +169,13 @@ class RustLayout(ui.Layout):
else: else:
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
return self.handle_timers(), self.handle_input_and_rendering() return (
self.handle_timers(),
self.handle_input_and_rendering(),
context.with_context(
context.get_context(), self.handle_button_request()
),
)
def _first_paint(self) -> None: def _first_paint(self) -> None:
ui.backlight_fade(ui.style.BACKLIGHT_NONE) ui.backlight_fade(ui.style.BACKLIGHT_NONE)
@ -199,6 +215,7 @@ class RustLayout(ui.Layout):
msg = None msg = None
if event in (io.TOUCH_START, io.TOUCH_MOVE, io.TOUCH_END): if event in (io.TOUCH_START, io.TOUCH_MOVE, io.TOUCH_END):
msg = self.layout.touch_event(event, x, y) msg = self.layout.touch_event(event, x, y)
self._send_button_request()
if msg is not None: if msg is not None:
raise ui.Result(msg) raise ui.Result(msg)
self._paint() self._paint()
@ -208,10 +225,25 @@ class RustLayout(ui.Layout):
# Using `yield` instead of `await` to avoid allocations. # Using `yield` instead of `await` to avoid allocations.
token = yield self.timer token = yield self.timer
msg = self.layout.timer(token) msg = self.layout.timer(token)
self._send_button_request()
if msg is not None: if msg is not None:
raise ui.Result(msg) raise ui.Result(msg)
self._paint() self._paint()
def handle_button_request(self) -> loop.Task:
while True:
br_code, br_type, page_count = yield from self.br_chan.take()
log.debug(__name__, "ButtonRequest.type=%s", br_type)
yield from context.maybe_call(
ButtonRequest(code=br_code, pages=page_count), ButtonAck
)
def _send_button_request(self):
res = self.layout.button_request()
if res is not None:
br_code, br_type = res
self.br_chan.publish((br_code, br_type, self.layout.page_count()))
def page_count(self) -> int: def page_count(self) -> int:
return self.layout.page_count() return self.layout.page_count()
@ -279,14 +311,10 @@ async def confirm_action(
async def flow_demo() -> None: async def flow_demo() -> None:
await raise_if_not_confirmed( from trezor import workflow
interact(
RustLayout(trezorui2.flow_get_address()), workflow.close_others()
"get_address", await raise_if_not_confirmed(RustLayout(trezorui2.flow_get_address()))
BR_TYPE_OTHER,
),
ActionCancelled,
)
async def confirm_single( async def confirm_single(

Loading…
Cancel
Save