feat(core/rust/ui): return Layout results as singleton objects

[no changelog]
pull/2207/head
Martin Milata 2 years ago
parent 4cf917c7cb
commit 38f4ab0983

@ -23,104 +23,6 @@
#include "librust.h"
/// def layout_new_confirm_action(
/// *,
/// title: str,
/// action: str | None = None,
/// description: str | None = None,
/// verb: str | None = None,
/// verb_cancel: str | None = None,
/// hold: bool | None = None,
/// reverse: bool = False,
/// ) -> object:
/// """Example layout."""
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_confirm_action_obj,
0, ui_layout_new_confirm_action);
#if TREZOR_MODEL == T
/// def layout_new_example(text: str) -> object:
/// """Example layout."""
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui2_layout_new_example_obj,
ui_layout_new_example);
/// def layout_new_pin(
/// *,
/// prompt: str,
/// subprompt: str,
/// allow_cancel: bool,
/// warning: str | None,
/// ) -> object:
/// """PIN keyboard."""
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_pin_obj, 0,
ui_layout_new_pin);
/// def layout_new_passphrase(
/// *,
/// prompt: str,
/// max_len: int,
/// ) -> object:
/// """Passphrase keyboard."""
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_passphrase_obj, 0,
ui_layout_new_passphrase);
/// def layout_new_bip39(
/// *,
/// prompt: str,
/// ) -> object:
/// """BIP39 keyboard."""
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_bip39_obj, 0,
ui_layout_new_bip39);
/// def layout_new_slip39(
/// *,
/// prompt: str,
/// ) -> object:
/// """BIP39 keyboard."""
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_slip39_obj, 0,
ui_layout_new_slip39);
#elif TREZOR_MODEL == 1
/// def layout_new_confirm_text(
/// *,
/// title: str,
/// data: str,
/// description: str | None,
/// ) -> object:
/// """Example layout."""
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_confirm_text_obj, 0,
ui_layout_new_confirm_text);
#endif
STATIC const mp_rom_map_elem_t mp_module_trezorui2_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorui2)},
{MP_ROM_QSTR(MP_QSTR_layout_new_confirm_action),
MP_ROM_PTR(&mod_trezorui2_layout_new_confirm_action_obj)},
#if TREZOR_MODEL == T
{MP_ROM_QSTR(MP_QSTR_layout_new_example),
MP_ROM_PTR(&mod_trezorui2_layout_new_example_obj)},
{MP_ROM_QSTR(MP_QSTR_layout_new_pin),
MP_ROM_PTR(&mod_trezorui2_layout_new_pin_obj)},
{MP_ROM_QSTR(MP_QSTR_layout_new_passphrase),
MP_ROM_PTR(&mod_trezorui2_layout_new_passphrase_obj)},
{MP_ROM_QSTR(MP_QSTR_layout_new_bip39),
MP_ROM_PTR(&mod_trezorui2_layout_new_bip39_obj)},
{MP_ROM_QSTR(MP_QSTR_layout_new_slip39),
MP_ROM_PTR(&mod_trezorui2_layout_new_slip39_obj)},
#elif TREZOR_MODEL == 1
{MP_ROM_QSTR(MP_QSTR_layout_new_confirm_text),
MP_ROM_PTR(&mod_trezorui2_layout_new_confirm_text_obj)},
#endif
};
STATIC MP_DEFINE_CONST_DICT(mp_module_trezorui2_globals,
mp_module_trezorui2_globals_table);
const mp_obj_module_t mp_module_trezorui2 = {
.base = {&mp_type_module},
.globals = (mp_obj_dict_t *)&mp_module_trezorui2_globals,
};
MP_REGISTER_MODULE(MP_QSTR_trezorui2, mp_module_trezorui2,
MICROPY_PY_TREZORUI2);

@ -60,6 +60,7 @@ fn generate_micropython_bindings() {
.allowlist_function("mp_obj_new_int_from_uint")
.allowlist_function("mp_obj_new_bytes")
.allowlist_function("mp_obj_new_str")
.allowlist_function("mp_obj_new_tuple")
.allowlist_function("mp_obj_get_int_maybe")
.allowlist_function("mp_obj_is_true")
.allowlist_function("mp_call_function_n_kw")

@ -12,19 +12,7 @@ mp_obj_t protobuf_debug_msg_type();
mp_obj_t protobuf_debug_msg_def_type();
#endif
mp_obj_t ui_layout_new_example(mp_obj_t);
mp_obj_t ui_layout_new_confirm_action(size_t n_args, const mp_obj_t *args,
mp_map_t *kwargs);
mp_obj_t ui_layout_new_confirm_text(size_t n_args, const mp_obj_t *args,
mp_map_t *kwargs);
mp_obj_t ui_layout_new_pin(size_t n_args, const mp_obj_t *args,
mp_map_t *kwargs);
mp_obj_t ui_layout_new_passphrase(size_t n_args, const mp_obj_t *args,
mp_map_t *kwargs);
mp_obj_t ui_layout_new_bip39(size_t n_args, const mp_obj_t *args,
mp_map_t *kwargs);
mp_obj_t ui_layout_new_slip39(size_t n_args, const mp_obj_t *args,
mp_map_t *kwargs);
extern mp_obj_module_t mp_module_trezorui2;
#ifdef TREZOR_EMULATOR
mp_obj_t ui_debug_layout_type();

@ -10,7 +10,18 @@ static void _librust_qstrs(void) {
MP_QSTR_MESSAGE_NAME;
// layout
MP_QSTR___name__;
MP_QSTR_trezorui2;
MP_QSTR_Layout;
MP_QSTR_CONFIRMED;
MP_QSTR_CANCELLED;
MP_QSTR_confirm_action;
MP_QSTR_confirm_text;
MP_QSTR_request_pin;
MP_QSTR_request_passphrase;
MP_QSTR_request_bip39;
MP_QSTR_request_slip39;
MP_QSTR_set_timer_fn;
MP_QSTR_touch_event;
MP_QSTR_button_event;

@ -175,6 +175,7 @@ macro_rules! obj_type {
macro_rules! obj_module {
($($key:expr => $val:expr),*) => ({
#[allow(unused_unsafe)]
#[allow(unused_doc_comments)]
unsafe {
use $crate::micropython::ffi;

@ -289,6 +289,23 @@ impl TryFrom<&'static CStr> for Obj {
}
}
impl TryFrom<(Obj, Obj)> for Obj {
type Error = Error;
fn try_from(val: (Obj, Obj)) -> Result<Self, Self::Error> {
// SAFETY:
// - Should work with any micropython objects.
// EXCEPTION: Will raise if allocation fails.
let values = [val.0, val.1];
let obj = catch_exception(|| unsafe { ffi::mp_obj_new_tuple(2, values.as_ptr()) })?;
if obj.is_null() {
Err(Error::AllocationFailed)
} else {
Ok(obj)
}
}
}
//
// # Additional conversions based on the methods above.
//

@ -447,7 +447,7 @@ impl Grid {
}
pub fn cells(&self, cells: GridCellSpan) -> Rect {
let from = self.row_col(cells.from.0, cells.to.1);
let from = self.row_col(cells.from.0, cells.from.1);
let to = self.row_col(cells.to.0, cells.to.1);
from.union(to)
}

@ -1 +1,2 @@
pub mod obj;
pub mod result;

@ -14,7 +14,7 @@ use crate::{
},
time::Duration,
ui::{
component::{Child, Component, Event, EventCtx, Never, PageMsg, TimerToken},
component::{Child, Component, Event, EventCtx, Never, TimerToken},
geometry::Rect,
},
util,
@ -38,13 +38,12 @@ pub trait ComponentMsgObj: Component {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error>;
}
impl<T> ComponentMsgObj for T
impl<T> ComponentMsgObj for Child<T>
where
T: Component,
T::Msg: TryInto<Obj, Error = Error>,
T: ComponentMsgObj,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
msg.try_into()
self.inner().msg_try_into_obj(msg)
}
}
@ -333,17 +332,6 @@ impl TryFrom<Duration> for Obj {
}
}
impl<T> TryFrom<PageMsg<T, bool>> for Obj {
type Error = Error;
fn try_from(val: PageMsg<T, bool>) -> Result<Self, Self::Error> {
match val {
PageMsg::Content(_) => Err(Error::TypeError),
PageMsg::Controls(c) => Ok(c.into()),
}
}
}
impl From<Never> for Obj {
fn from(_: Never) -> Self {
unreachable!()

@ -0,0 +1,34 @@
use crate::micropython::{
obj::{Obj, ObjBase},
qstr::Qstr,
typ::Type,
};
#[repr(C)]
pub struct ResultObj {
base: ObjBase,
}
impl ResultObj {
/// Convert ResultObj to a MicroPython object.
pub const fn as_obj(&'static self) -> Obj {
// SAFETY:
// - We are an object struct with a base and a type.
// - 'static lifetime holds us in place.
// - There's nothing to mutate.
unsafe { Obj::from_ptr(self as *const _ as *mut _) }
}
}
// SAFETY: We are in a single-threaded environment.
unsafe impl Sync for ResultObj {}
static CONFIRMED_TYPE: Type = obj_type! { name: Qstr::MP_QSTR_CONFIRMED, };
static CANCELLED_TYPE: Type = obj_type! { name: Qstr::MP_QSTR_CANCELLED, };
pub static CONFIRMED: ResultObj = ResultObj {
base: CONFIRMED_TYPE.as_base(),
};
pub static CANCELLED: ResultObj = ResultObj {
base: CANCELLED_TYPE.as_base(),
};

@ -31,6 +31,10 @@ where
right_btn: right.map(Child::new),
}
}
pub fn inner(&self) -> &T {
self.content.inner()
}
}
impl<T, U> Component for Dialog<T, U>

@ -23,6 +23,10 @@ where
content: Child::new(content),
}
}
pub fn inner(&self) -> &T {
self.content.inner()
}
}
impl<T, U> Component for Frame<T, U>

@ -1,26 +1,52 @@
use core::convert::TryInto;
use crate::{
micropython::{buffer::Buffer, map::Map, obj::Obj, qstr::Qstr},
error::Error,
micropython::{buffer::Buffer, map::Map, module::Module, obj::Obj, qstr::Qstr},
ui::{
component::{text::paragraphs::Paragraphs, FormattedText},
layout::obj::LayoutObj,
model_t1::component::ButtonPos,
component::{
base::Component,
paginated::{PageMsg, Paginate},
text::paragraphs::Paragraphs,
FormattedText,
},
layout::{
obj::{ComponentMsgObj, LayoutObj},
result::{CANCELLED, CONFIRMED},
},
},
util,
};
use super::{
component::{Button, ButtonPage, Dialog, DialogMsg, Frame},
constant, theme,
component::{Button, ButtonPage, ButtonPos, Frame},
theme,
};
#[no_mangle]
extern "C" fn ui_layout_new_confirm_action(
n_args: usize,
args: *const Obj,
kwargs: *const Map,
) -> Obj {
impl<T> ComponentMsgObj for ButtonPage<T>
where
T: Component + Paginate,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
PageMsg::Content(_) => Err(Error::TypeError),
PageMsg::Controls(true) => Ok(CONFIRMED.as_obj()),
PageMsg::Controls(false) => Ok(CANCELLED.as_obj()),
}
}
}
impl<T, U> ComponentMsgObj for Frame<T, U>
where
T: ComponentMsgObj,
U: AsRef<[u8]>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
self.inner().msg_try_into_obj(msg)
}
}
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let title: Buffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let action: Option<Buffer> = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?;
@ -39,9 +65,9 @@ extern "C" fn ui_layout_new_confirm_action(
_ => "",
};
let left = verb_cancel
let _left = verb_cancel
.map(|label| Button::with_text(ButtonPos::Left, label, theme::button_cancel()));
let right =
let _right =
verb.map(|label| Button::with_text(ButtonPos::Right, label, theme::button_default()));
let obj = LayoutObj::new(Frame::new(
@ -58,12 +84,7 @@ extern "C" fn ui_layout_new_confirm_action(
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
#[no_mangle]
extern "C" fn ui_layout_new_confirm_text(
n_args: usize,
args: *const Obj,
kwargs: *const Map,
) -> Obj {
extern "C" fn new_confirm_text(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let title: Buffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let data: Buffer = kwargs.get(Qstr::MP_QSTR_data)?.try_into()?;
@ -87,15 +108,49 @@ extern "C" fn ui_layout_new_confirm_text(
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
#[no_mangle]
pub static mp_module_trezorui2: Module = obj_module! {
Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorui2.to_obj(),
/// CONFIRMED: object
Qstr::MP_QSTR_CONFIRMED => CONFIRMED.as_obj(),
/// CANCELLED: object
Qstr::MP_QSTR_CANCELLED => CANCELLED.as_obj(),
/// def confirm_action(
/// *,
/// title: str,
/// action: str | None = None,
/// description: str | None = None,
/// verb: str | None = None,
/// verb_cancel: str | None = None,
/// hold: bool | None = None,
/// reverse: bool = False,
/// ) -> object:
/// """Confirm action."""
Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(),
/// def confirm_text(
/// *,
/// title: str,
/// data: str,
/// description: str | None,
/// ) -> object:
/// """Confirm text."""
Qstr::MP_QSTR_confirm_text => obj_fn_kw!(0, new_confirm_text).as_obj(),
};
#[cfg(test)]
mod tests {
use crate::{
error::Error,
trace::Trace,
ui::{
component::Component,
display,
model_t1::component::{Dialog, DialogMsg},
model_t1::{
component::{Dialog, DialogMsg},
constant,
},
},
};
@ -107,18 +162,16 @@ mod tests {
String::from_utf8(t).unwrap()
}
impl<T> TryFrom<DialogMsg<T>> for Obj
impl<T, U> ComponentMsgObj for Dialog<T, U>
where
Obj: TryFrom<T>,
Error: From<<T as TryInto<Obj>>::Error>,
T: ComponentMsgObj,
U: AsRef<[u8]>,
{
type Error = Error;
fn try_from(val: DialogMsg<T>) -> Result<Self, Self::Error> {
match val {
DialogMsg::Content(c) => Ok(c.try_into()?),
DialogMsg::LeftClicked => 1.try_into(),
DialogMsg::RightClicked => 2.try_into(),
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
DialogMsg::Content(c) => self.inner().msg_try_into_obj(c),
DialogMsg::LeftClicked => Ok(CANCELLED.as_obj()),
DialogMsg::RightClicked => Ok(CONFIRMED.as_obj()),
}
}
}

@ -11,8 +11,8 @@ use super::{theme, Button, ButtonMsg, Loader, LoaderMsg};
pub enum HoldToConfirmMsg<T> {
Content(T),
Cancelled,
Confirmed,
Cancelled,
}
pub struct HoldToConfirm<T> {
@ -36,6 +36,10 @@ where
pad: Pad::with_background(theme::BG),
}
}
pub fn inner(&self) -> &T {
self.content.inner()
}
}
impl<T> Component for HoldToConfirm<T>

@ -10,9 +10,9 @@ pub enum DialogMsg<T, L, R> {
}
pub struct Dialog<T, L, R> {
pub content: Child<T>,
pub left: Child<L>,
pub right: Child<R>,
content: Child<T>,
left: Child<L>,
right: Child<R>,
}
impl<T, L, R> Dialog<T, L, R>
@ -28,6 +28,10 @@ where
right: Child::new(right),
}
}
pub fn inner(&self) -> &T {
self.content.inner()
}
}
impl<T, L, R> Component for Dialog<T, L, R>

@ -23,6 +23,10 @@ where
content: Child::new(content),
}
}
pub fn inner(&self) -> &T {
&self.content.inner()
}
}
impl<T, U> Component for Frame<T, U>

@ -64,6 +64,10 @@ impl MnemonicInput for Bip39Input {
fn is_empty(&self) -> bool {
self.textbox.is_empty()
}
fn mnemonic(&self) -> Option<&'static str> {
self.suggested_word
}
}
impl Component for Bip39Input {

@ -1,3 +1,5 @@
use core::ops::Deref;
use crate::ui::{
component::{Child, Component, Event, EventCtx, Label, Maybe},
geometry::{Alignment, Grid, Rect},
@ -13,9 +15,9 @@ pub enum MnemonicKeyboardMsg {
Confirmed,
}
pub struct MnemonicKeyboard<T> {
pub struct MnemonicKeyboard<T, U> {
/// Initial prompt, displayed on empty input.
prompt: Child<Maybe<Label<&'static [u8]>>>,
prompt: Child<Maybe<Label<U>>>,
/// Backspace button.
back: Child<Maybe<Button<&'static [u8]>>>,
/// Input area, acting as the auto-complete and confirm button.
@ -24,11 +26,12 @@ pub struct MnemonicKeyboard<T> {
keys: [Child<Button<&'static [u8]>>; MNEMONIC_KEY_COUNT],
}
impl<T> MnemonicKeyboard<T>
impl<T, U> MnemonicKeyboard<T, U>
where
T: MnemonicInput,
U: Deref<Target = [u8]>,
{
pub fn new(input: T, prompt: &'static [u8]) -> Self {
pub fn new(input: T, prompt: U) -> Self {
Self {
prompt: Child::new(Maybe::visible(
theme::BG,
@ -75,11 +78,16 @@ where
self.back
.mutate(ctx, |ctx, b| b.show_if(ctx, !prompt_visible));
}
pub fn mnemonic(&self) -> Option<&'static str> {
self.input.inner().inner().mnemonic()
}
}
impl<T> Component for MnemonicKeyboard<T>
impl<T, U> Component for MnemonicKeyboard<T, U>
where
T: MnemonicInput,
U: Deref<Target = [u8]>,
{
type Msg = MnemonicKeyboardMsg;
@ -158,6 +166,7 @@ pub trait MnemonicInput: Component<Msg = MnemonicInputMsg> {
fn on_key_click(&mut self, ctx: &mut EventCtx, key: usize);
fn on_backspace_click(&mut self, ctx: &mut EventCtx);
fn is_empty(&self) -> bool;
fn mnemonic(&self) -> Option<&'static str>;
}
pub enum MnemonicInputMsg {
@ -167,7 +176,7 @@ pub enum MnemonicInputMsg {
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for MnemonicKeyboard<T> {
impl<T, U> crate::trace::Trace for MnemonicKeyboard<T, U> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("MnemonicKeyboard");
t.close();

@ -100,6 +100,10 @@ impl PassphraseKeyboard {
self.back.mutate(ctx, |ctx, b| b.enable(ctx));
}
}
pub fn passphrase(&self) -> &str {
self.input.inner().textbox.content()
}
}
impl Component for PassphraseKeyboard {

@ -81,6 +81,10 @@ impl MnemonicInput for Slip39Input {
fn is_empty(&self) -> bool {
self.textbox.is_empty()
}
fn mnemonic(&self) -> Option<&'static str> {
self.final_word
}
}
impl Component for Slip39Input {

@ -14,7 +14,7 @@ pub use dialog::{Dialog, DialogLayout, DialogMsg};
pub use frame::Frame;
pub use keyboard::{
bip39::Bip39Input,
mnemonic::{MnemonicKeyboard, MnemonicKeyboardMsg},
mnemonic::{MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg},
passphrase::{PassphraseKeyboard, PassphraseKeyboardMsg},
pin::{PinKeyboard, PinKeyboardMsg},
slip39::Slip39Input,

@ -1,109 +1,124 @@
use core::convert::{TryFrom, TryInto};
use core::{convert::TryInto, ops::Deref};
use crate::{
error::Error,
micropython::{buffer::Buffer, map::Map, obj::Obj, qstr::Qstr},
micropython::{buffer::Buffer, map::Map, module::Module, obj::Obj, qstr::Qstr},
ui::{
component::{base::ComponentExt, text::paragraphs::Paragraphs, FormattedText},
layout::obj::LayoutObj,
component::{
base::ComponentExt,
paginated::{PageMsg, Paginate},
text::paragraphs::Paragraphs,
Component,
},
layout::{
obj::{ComponentMsgObj, LayoutObj},
result::{CANCELLED, CONFIRMED},
},
},
util,
};
use super::{
component::{
Bip39Input, Button, ButtonMsg, DialogMsg, Frame, HoldToConfirm, HoldToConfirmMsg,
MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard, PassphraseKeyboardMsg,
PinKeyboard, PinKeyboardMsg, Slip39Input, SwipePage,
Bip39Input, Button, ButtonMsg, Dialog, DialogMsg, Frame, HoldToConfirm, HoldToConfirmMsg,
MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard,
PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Slip39Input, SwipePage,
},
theme,
};
impl<T> TryFrom<DialogMsg<T, ButtonMsg, ButtonMsg>> for Obj
impl<T, U> ComponentMsgObj for Dialog<T, Button<U>, Button<U>>
where
Obj: TryFrom<T>,
Error: From<<Obj as TryFrom<T>>::Error>,
T: ComponentMsgObj,
U: AsRef<[u8]>,
{
type Error = Error;
fn try_from(val: DialogMsg<T, ButtonMsg, ButtonMsg>) -> Result<Self, Self::Error> {
match val {
DialogMsg::Content(c) => Ok(c.try_into()?),
DialogMsg::Left(ButtonMsg::Clicked) => 1.try_into(),
DialogMsg::Right(ButtonMsg::Clicked) => 2.try_into(),
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
DialogMsg::Content(c) => Ok(self.inner().msg_try_into_obj(c)?),
DialogMsg::Left(ButtonMsg::Clicked) => Ok(CANCELLED.as_obj()),
DialogMsg::Right(ButtonMsg::Clicked) => Ok(CONFIRMED.as_obj()),
_ => Ok(Obj::const_none()),
}
}
}
impl<T> TryFrom<HoldToConfirmMsg<T>> for Obj
impl<T> ComponentMsgObj for HoldToConfirm<T>
where
Obj: TryFrom<T>,
Error: From<<Obj as TryFrom<T>>::Error>,
T: ComponentMsgObj,
{
type Error = Error;
fn try_from(val: HoldToConfirmMsg<T>) -> Result<Self, Self::Error> {
match val {
HoldToConfirmMsg::Content(c) => Ok(c.try_into()?),
HoldToConfirmMsg::Confirmed => 1.try_into(),
HoldToConfirmMsg::Cancelled => 2.try_into(),
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
HoldToConfirmMsg::Content(c) => Ok(self.inner().msg_try_into_obj(c)?),
HoldToConfirmMsg::Confirmed => Ok(CONFIRMED.as_obj()),
HoldToConfirmMsg::Cancelled => Ok(CANCELLED.as_obj()),
}
}
}
impl TryFrom<PinKeyboardMsg> for Obj {
type Error = Error;
fn try_from(val: PinKeyboardMsg) -> Result<Self, Self::Error> {
match val {
PinKeyboardMsg::Confirmed => 1.try_into(),
PinKeyboardMsg::Cancelled => 2.try_into(),
impl<T> ComponentMsgObj for PinKeyboard<T>
where
T: Deref<Target = [u8]>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
PinKeyboardMsg::Confirmed => self.pin().try_into(),
PinKeyboardMsg::Cancelled => Ok(CANCELLED.as_obj()),
}
}
}
impl TryFrom<MnemonicKeyboardMsg> for Obj {
type Error = Error;
fn try_from(val: MnemonicKeyboardMsg) -> Result<Self, Self::Error> {
match val {
MnemonicKeyboardMsg::Confirmed => Ok(Obj::const_true()),
impl ComponentMsgObj for PassphraseKeyboard {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
PassphraseKeyboardMsg::Confirmed => self.passphrase().try_into(),
PassphraseKeyboardMsg::Cancelled => Ok(CANCELLED.as_obj()),
}
}
}
impl TryFrom<PassphraseKeyboardMsg> for Obj {
type Error = Error;
fn try_from(val: PassphraseKeyboardMsg) -> Result<Self, Self::Error> {
match val {
PassphraseKeyboardMsg::Confirmed => Ok(Obj::const_true()),
PassphraseKeyboardMsg::Cancelled => Ok(Obj::const_none()),
impl<T, U> ComponentMsgObj for MnemonicKeyboard<T, U>
where
T: MnemonicInput,
U: Deref<Target = [u8]>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
MnemonicKeyboardMsg::Confirmed => {
if let Some(word) = self.mnemonic() {
word.try_into()
} else {
panic!("invalid mnemonic")
}
}
}
}
}
#[no_mangle]
extern "C" fn ui_layout_new_example(_param: Obj) -> Obj {
let block = move || {
let layout = LayoutObj::new(HoldToConfirm::new(
FormattedText::new::<theme::TTDefaultText>(
"Testing text layout, with some text, and some more text. And {param}",
)
.with(b"param", b"parameters!"),
))?;
Ok(layout.into())
};
unsafe { util::try_or_raise(block) }
impl<T, U> ComponentMsgObj for Frame<T, U>
where
T: ComponentMsgObj,
U: AsRef<[u8]>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
self.inner().msg_try_into_obj(msg)
}
}
#[no_mangle]
extern "C" fn ui_layout_new_confirm_action(
n_args: usize,
args: *const Obj,
kwargs: *const Map,
) -> Obj {
impl<T, U> ComponentMsgObj for SwipePage<T, U>
where
T: Component + Paginate,
U: Component<Msg = bool>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
PageMsg::Content(_) => Err(Error::TypeError),
PageMsg::Controls(true) => Ok(CONFIRMED.as_obj()),
PageMsg::Controls(false) => Ok(CANCELLED.as_obj()),
}
}
}
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: Buffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let action: Option<Buffer> = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?;
@ -144,8 +159,7 @@ extern "C" fn ui_layout_new_confirm_action(
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
#[no_mangle]
extern "C" fn ui_layout_new_pin(n_args: usize, args: *const Obj, kwargs: *const Map) -> Obj {
extern "C" fn new_request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let prompt: Buffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let subprompt: Buffer = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?;
@ -160,8 +174,7 @@ extern "C" fn ui_layout_new_pin(n_args: usize, args: *const Obj, kwargs: *const
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
#[no_mangle]
extern "C" fn ui_layout_new_passphrase(n_args: usize, args: *const Obj, kwargs: *const Map) -> Obj {
extern "C" fn new_request_passphrase(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let _prompt: Buffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let _max_len: u32 = kwargs.get(Qstr::MP_QSTR_max_len)?.try_into()?;
@ -171,41 +184,88 @@ extern "C" fn ui_layout_new_passphrase(n_args: usize, args: *const Obj, kwargs:
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
#[no_mangle]
extern "C" fn ui_layout_new_bip39(n_args: usize, args: *const Obj, kwargs: *const Map) -> Obj {
extern "C" fn new_request_bip39(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let _prompt: Buffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let obj = LayoutObj::new(
MnemonicKeyboard::new(Bip39Input::new(), b"Type word 11 of 12").into_child(),
)?;
let prompt: Buffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let obj = LayoutObj::new(MnemonicKeyboard::new(Bip39Input::new(), prompt).into_child())?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
#[no_mangle]
extern "C" fn ui_layout_new_slip39(n_args: usize, args: *const Obj, kwargs: *const Map) -> Obj {
extern "C" fn new_request_slip39(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let _prompt: Buffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let obj = LayoutObj::new(
MnemonicKeyboard::new(Slip39Input::new(), b"Type word 13 of 20").into_child(),
)?;
let prompt: Buffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let obj = LayoutObj::new(MnemonicKeyboard::new(Slip39Input::new(), prompt).into_child())?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
#[no_mangle]
pub static mp_module_trezorui2: Module = obj_module! {
Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorui2.to_obj(),
/// CONFIRMED: object
Qstr::MP_QSTR_CONFIRMED => CONFIRMED.as_obj(),
/// CANCELLED: object
Qstr::MP_QSTR_CANCELLED => CANCELLED.as_obj(),
/// def confirm_action(
/// *,
/// title: str,
/// action: str | None = None,
/// description: str | None = None,
/// verb: str | None = None,
/// verb_cancel: str | None = None,
/// hold: bool | None = None,
/// reverse: bool = False,
/// ) -> object:
/// """Confirm action."""
Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(),
/// def request_pin(
/// *,
/// prompt: str,
/// subprompt: str | None = None,
/// allow_cancel: bool = True,
/// warning: str | None = None,
/// ) -> str | object:
/// """Request pin on device."""
Qstr::MP_QSTR_request_pin => obj_fn_kw!(0, new_request_pin).as_obj(),
/// def request_passphrase(
/// *,
/// prompt: str,
/// max_len: int,
/// ) -> str | object:
/// """Passphrase input keyboard."""
Qstr::MP_QSTR_request_passphrase => obj_fn_kw!(0, new_request_passphrase).as_obj(),
/// def request_bip39(
/// *,
/// prompt: str,
/// ) -> str:
/// """BIP39 word input keyboard."""
Qstr::MP_QSTR_request_bip39 => obj_fn_kw!(0, new_request_bip39).as_obj(),
/// def request_slip39(
/// *,
/// prompt: str,
/// ) -> str:
/// """SLIP39 word input keyboard."""
Qstr::MP_QSTR_request_slip39 => obj_fn_kw!(0, new_request_slip39).as_obj(),
};
#[cfg(test)]
mod tests {
use crate::{
trace::Trace,
ui::{
component::Component,
component::{Component, FormattedText},
geometry::Rect,
model_tt::{
component::{Button, Dialog},
constant,
},
model_tt::constant,
},
};

@ -1,8 +1,10 @@
from typing import *
CONFIRMED: object
CANCELLED: object
# extmod/rustmods/modtrezorui2.c
def layout_new_confirm_action(
# rust/src/ui/model_t1/layout.rs
def confirm_action(
*,
title: str,
action: str | None = None,
@ -12,55 +14,66 @@ def layout_new_confirm_action(
hold: bool | None = None,
reverse: bool = False,
) -> object:
"""Example layout."""
"""Confirm action."""
# extmod/rustmods/modtrezorui2.c
def layout_new_example(text: str) -> object:
"""Example layout."""
# rust/src/ui/model_t1/layout.rs
def confirm_text(
*,
title: str,
data: str,
description: str | None,
) -> object:
"""Confirm text."""
CONFIRMED: object
CANCELLED: object
# extmod/rustmods/modtrezorui2.c
def layout_new_pin(
# rust/src/ui/model_tt/layout.rs
def confirm_action(
*,
prompt: str,
subprompt: str,
allow_cancel: bool,
warning: str | None,
title: str,
action: str | None = None,
description: str | None = None,
verb: str | None = None,
verb_cancel: str | None = None,
hold: bool | None = None,
reverse: bool = False,
) -> object:
"""PIN keyboard."""
"""Confirm action."""
# extmod/rustmods/modtrezorui2.c
def layout_new_passphrase(
# rust/src/ui/model_tt/layout.rs
def request_pin(
*,
prompt: str,
max_len: int,
) -> object:
"""Passphrase keyboard."""
subprompt: str | None = None,
allow_cancel: bool = True,
warning: str | None = None,
) -> str | object:
"""Request pin on device."""
# extmod/rustmods/modtrezorui2.c
def layout_new_bip39(
# rust/src/ui/model_tt/layout.rs
def request_passphrase(
*,
prompt: str,
) -> object:
"""BIP39 keyboard."""
max_len: int,
) -> str | object:
"""Passphrase input keyboard."""
# extmod/rustmods/modtrezorui2.c
def layout_new_slip39(
# rust/src/ui/model_tt/layout.rs
def request_bip39(
*,
prompt: str,
) -> object:
"""BIP39 keyboard."""
) -> str:
"""BIP39 word input keyboard."""
# extmod/rustmods/modtrezorui2.c
def layout_new_confirm_text(
# rust/src/ui/model_tt/layout.rs
def request_slip39(
*,
title: str,
data: str,
description: str | None,
) -> object:
"""Example layout."""
prompt: str,
) -> str:
"""SLIP39 word input keyboard."""

@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
from trezor import io, log, loop, ui, wire, workflow
from trezor.enums import ButtonRequestType
from trezorui2 import layout_new_confirm_action, layout_new_confirm_text
import trezorui2
from .common import interact
@ -84,7 +84,7 @@ async def confirm_action(
result = await interact(
ctx,
_RustLayout(
layout_new_confirm_action(
trezorui2.confirm_action(
title=title.upper(),
action=action,
description=description,
@ -97,7 +97,7 @@ async def confirm_action(
br_type,
br_code,
)
if result == 1:
if result is not trezorui2.CONFIRMED:
raise exc
@ -114,7 +114,7 @@ async def confirm_text(
result = await interact(
ctx,
_RustLayout(
layout_new_confirm_text(
trezorui2.confirm_text(
title=title.upper(),
data=data,
description=description,
@ -123,7 +123,7 @@ async def confirm_text(
br_type,
br_code,
)
if result == 0:
if result is not trezorui2.CONFIRMED:
raise wire.ActionCancelled

@ -3,15 +3,8 @@ from typing import TYPE_CHECKING
from trezor import io, log, loop, ui, wire, workflow
from trezor.enums import ButtonRequestType
from trezorui2 import (
layout_new_bip39,
layout_new_confirm_action,
layout_new_passphrase,
layout_new_pin,
layout_new_slip39,
)
from ...components.tt import passphrase, pin
import trezorui2
from ...constants.tt import MONO_ADDR_PER_LINE
from ..common import button_request, interact
@ -99,7 +92,7 @@ async def confirm_action(
result = await interact(
ctx,
_RustLayout(
layout_new_confirm_action(
trezorui2.confirm_action(
title=title.upper(),
action=action,
description=description,
@ -111,7 +104,7 @@ async def confirm_action(
br_type,
br_code,
)
if result is not True:
if result is not trezorui2.CONFIRMED:
raise exc
@ -454,10 +447,10 @@ async def request_passphrase_on_device(ctx: wire.GenericContext, max_len: int) -
)
keyboard = _RustLayout(
layout_new_passphrase(prompt="Enter passphrase", max_len=max_len)
trezorui2.request_passphrase(prompt="Enter passphrase", max_len=max_len)
)
result = await ctx.wait(keyboard)
if result is passphrase.CANCELLED:
if result is trezorui2.CANCELLED:
raise wire.ActionCancelled("Passphrase entry cancelled")
assert isinstance(result, str)
@ -480,7 +473,7 @@ async def request_pin_on_device(
subprompt = f"{attempts_remaining} tries left"
dialog = _RustLayout(
layout_new_pin(
trezorui2.request_pin(
prompt=prompt,
subprompt=subprompt,
allow_cancel=allow_cancel,
@ -489,7 +482,7 @@ async def request_pin_on_device(
)
while True:
result = await ctx.wait(dialog)
if result is pin.CANCELLED:
if result is trezorui2.CANCELLED:
raise wire.PinCancelled
assert isinstance(result, str)
return result
@ -500,11 +493,15 @@ async def request_word(
) -> str:
if is_slip39:
keyboard: Any = _RustLayout(
layout_new_bip39(prompt=f"Type word {word_index + 1} of {word_count}:")
trezorui2.request_bip39(
prompt=f"Type word {word_index + 1} of {word_count}:"
)
)
else:
keyboard = _RustLayout(
layout_new_slip39(prompt=f"Type word {word_index + 1} of {word_count}:")
trezorui2.request_slip39(
prompt=f"Type word {word_index + 1} of {word_count}:"
)
)
word: str = await ctx.wait(keyboard)

@ -8,6 +8,7 @@ from pathlib import Path
CORE_DIR = Path(__file__).resolve().parent.parent
EXTMOD_PATH = CORE_DIR / "embed" / "extmod"
RUSTMOD_PATH = CORE_DIR / "embed" / "rust" / "src"
MOCKS_PATH = CORE_DIR / "mocks" / "generated"
COMMENT_PREFIX = "/// "
@ -101,6 +102,37 @@ def build_module(mod_file, dest):
store_to_file(dest, split_to_parts(l, mod_desc))
def build_rsmodule(mod_file, dest):
global current_indent
global current_class
global current_package
assert mod_file.suffix == ".rs"
start_prefix = "pub static mp_module_"
comment_prefix = f" {COMMENT_PREFIX}"
in_module = False
current_indent = 0
current_class = None
mod_desc = str(mod_file.relative_to(CORE_DIR / "embed"))
for l in open(mod_file):
if l.startswith(start_prefix):
in_module = True
current_package = l[len(start_prefix) : ].split(":")[0]
elif l.startswith("}"):
in_module = False
if not in_module:
continue
if not l.startswith(comment_prefix):
continue
l = l[len(comment_prefix) :]
store_to_file(dest, split_to_parts(l, mod_desc))
def place_symlinks(dest):
# make symlinks for the non-generated files
for pyi in MOCKS_PATH.glob("../*.pyi"):
@ -111,6 +143,8 @@ def place_symlinks(dest):
def build_directory(dest):
for modfile in sorted(EXTMOD_PATH.glob("**/mod*.[ch]")):
build_module(modfile, dest)
for modfile in sorted(RUSTMOD_PATH.glob("**/*.rs")):
build_rsmodule(modfile, dest)
place_symlinks(dest)

Loading…
Cancel
Save