mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-22 23:48:12 +00:00
feat(core/rust/ui): on-the-fly conversion of bytes to hexadecimal representation
[no changelog]
This commit is contained in:
parent
4c7060186a
commit
3b92923caa
@ -168,6 +168,9 @@ fn generate_micropython_bindings() {
|
||||
.allowlist_var("MP_BUFFER_WRITE")
|
||||
.allowlist_var("MP_BUFFER_RW")
|
||||
.allowlist_var("mp_type_str")
|
||||
.allowlist_var("mp_type_bytes")
|
||||
.allowlist_var("mp_type_bytearray")
|
||||
.allowlist_var("mp_type_memoryview")
|
||||
// dict
|
||||
.allowlist_type("mp_obj_dict_t")
|
||||
.allowlist_function("mp_obj_new_dict")
|
||||
|
@ -66,7 +66,7 @@ impl TryFrom<Obj> for StrBuffer {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
|
||||
if obj.is_qstr() || unsafe { ffi::mp_type_str.is_type_of(obj) } {
|
||||
if obj.is_str() {
|
||||
let bufinfo = get_buffer_info(obj, ffi::MP_BUFFER_READ)?;
|
||||
let new = Self {
|
||||
ptr: bufinfo.buf as _,
|
||||
@ -189,3 +189,25 @@ impl crate::trace::Trace for StrBuffer {
|
||||
self.as_ref().trace(t)
|
||||
}
|
||||
}
|
||||
|
||||
/// Version of `get_buffer` for strings that ties the string lifetime with
|
||||
/// another object that is expected to be placed on stack or in micropython heap
|
||||
/// and be the beginning of a chain of references that lead to the `obj`.
|
||||
///
|
||||
/// SAFETY:
|
||||
/// The caller must ensure that:
|
||||
/// (a) the `_owner` is an object visible to the micropython GC,
|
||||
/// (b) the `_owner` contains a reference that leads to `obj`, possibly through
|
||||
/// other objects,
|
||||
/// (c) that path is not broken as long as the returned reference lives.
|
||||
pub unsafe fn get_str_owner<T: ?Sized>(_owner: &T, obj: Obj) -> Result<&str, Error> {
|
||||
if !obj.is_str() {
|
||||
return Err(Error::TypeError);
|
||||
}
|
||||
// SAFETY:
|
||||
// (a) only immutable references are taken.
|
||||
// (b) micropython guarantees immutability and the buffer is not freed/moved as
|
||||
// long as _owner satisfies precondition.
|
||||
let buffer = unsafe { get_buffer(obj)? };
|
||||
str::from_utf8(buffer).map_err(|_| Error::TypeError)
|
||||
}
|
||||
|
@ -410,3 +410,18 @@ impl Obj {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Obj {
|
||||
pub fn is_bytes(self) -> bool {
|
||||
unsafe {
|
||||
ffi::mp_type_bytes.is_type_of(self)
|
||||
|| ffi::mp_type_bytearray.is_type_of(self)
|
||||
|| ffi::mp_type_memoryview.is_type_of(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_str(self) -> bool {
|
||||
let is_type_str = unsafe { ffi::mp_type_str.is_type_of(self) };
|
||||
is_type_str || self.is_qstr()
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,23 @@ pub const PARAGRAPH_BOTTOM_SPACE: i16 = 5;
|
||||
pub type ParagraphVecLong<T> = Vec<Paragraph<T>, 32>;
|
||||
pub type ParagraphVecShort<T> = Vec<Paragraph<T>, 8>;
|
||||
|
||||
/// Maximum number of characters that can be displayed on screen at once. Used
|
||||
/// for on-the-fly conversion of binary data to hexadecimal representation.
|
||||
/// NOTE: can be fine-tuned for particular model screen to decrease memory
|
||||
/// consumption and conversion time.
|
||||
pub const SCRATCH_BUFFER_LEN: usize = 256;
|
||||
|
||||
pub trait ParagraphSource {
|
||||
fn at(&self, i: usize) -> Paragraph<&str>;
|
||||
/// Return text and associated style for given paragraph index and character
|
||||
/// offset within the paragraph.
|
||||
///
|
||||
/// Implementations can use the provided buffer to perform some kind of
|
||||
/// conversion (currently used for displaying binary data in
|
||||
/// hexadecimal) and return a reference to this buffer. Caller needs to
|
||||
/// make sure the buffer is large enough to fit screenfull of characters.
|
||||
fn at<'a>(&'a self, index: usize, offset: usize, buffer: &'a mut [u8]) -> Paragraph<&'a str>;
|
||||
|
||||
/// Number of paragraphs.
|
||||
fn size(&self) -> usize;
|
||||
|
||||
fn into_paragraphs(self) -> Paragraphs<Self>
|
||||
@ -40,8 +55,8 @@ impl<T, const N: usize> ParagraphSource for Vec<Paragraph<T>, N>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
fn at(&self, i: usize) -> Paragraph<&str> {
|
||||
self[i].to_ref()
|
||||
fn at<'a>(&'a self, index: usize, offset: usize, _buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||
self[index].offset_as_ref(offset)
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
@ -53,8 +68,8 @@ impl<T, const N: usize> ParagraphSource for [Paragraph<T>; N]
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
fn at(&self, i: usize) -> Paragraph<&str> {
|
||||
self[i].to_ref()
|
||||
fn at<'a>(&'a self, index: usize, offset: usize, _buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||
self[index].offset_as_ref(offset)
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
@ -66,9 +81,9 @@ impl<T> ParagraphSource for Paragraph<T>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
fn at(&self, i: usize) -> Paragraph<&str> {
|
||||
assert_eq!(i, 0);
|
||||
self.to_ref()
|
||||
fn at<'a>(&'a self, index: usize, offset: usize, _buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||
assert_eq!(index, 0);
|
||||
self.offset_as_ref(offset)
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
@ -157,26 +172,31 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns iterator over visible layouts (bounding box, style) together
|
||||
/// Iterate over visible layouts (bounding box, style) together
|
||||
/// with corresponding string content. Should not get monomorphized.
|
||||
fn visible_content<'a>(
|
||||
content: &'a dyn ParagraphSource,
|
||||
fn foreach_visible<'a, 'b>(
|
||||
source: &'a dyn ParagraphSource,
|
||||
visible: &'a [TextLayout],
|
||||
offset: PageOffset,
|
||||
) -> impl Iterator<Item = (&'a TextLayout, &'a str)> {
|
||||
visible.iter().zip(
|
||||
(offset.par..content.size())
|
||||
.map(|i| content.at(i))
|
||||
.filter(|p| !p.content.is_empty())
|
||||
.enumerate()
|
||||
.map(move |(i, p): (usize, Paragraph<&str>)| {
|
||||
if i == 0 {
|
||||
&p.content[offset.chr..]
|
||||
} else {
|
||||
p.content
|
||||
func: &'b mut dyn FnMut(&TextLayout, &str),
|
||||
) {
|
||||
let mut buffer = [0; SCRATCH_BUFFER_LEN];
|
||||
let mut vis_iter = visible.iter();
|
||||
let mut chr = offset.chr;
|
||||
|
||||
for par in offset.par..source.size() {
|
||||
let s = source.at(par, chr, &mut buffer).content;
|
||||
if s.is_empty() {
|
||||
chr = 0;
|
||||
continue;
|
||||
}
|
||||
if let Some(layout) = vis_iter.next() {
|
||||
func(layout, s);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
chr = 0;
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,9 +217,14 @@ where
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
for (layout, content) in Self::visible_content(&self.source, &self.visible, self.offset) {
|
||||
Self::foreach_visible(
|
||||
&self.source,
|
||||
&self.visible,
|
||||
self.offset,
|
||||
&mut |layout, content| {
|
||||
layout.render_text(content);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||
@ -239,11 +264,15 @@ pub mod trace {
|
||||
impl<T: ParagraphSource> crate::trace::Trace for Paragraphs<T> {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.open("Paragraphs");
|
||||
for (layout, content) in Self::visible_content(&self.source, &self.visible, self.offset)
|
||||
{
|
||||
Self::foreach_visible(
|
||||
&self.source,
|
||||
&self.visible,
|
||||
self.offset,
|
||||
&mut |layout, content| {
|
||||
layout.layout_text(content, &mut layout.initial_cursor(), &mut TraceSink(t));
|
||||
t.string("\n");
|
||||
}
|
||||
},
|
||||
);
|
||||
t.close();
|
||||
}
|
||||
}
|
||||
@ -290,16 +319,18 @@ impl<T> Paragraph<T> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn content(&self) -> &T {
|
||||
&self.content
|
||||
}
|
||||
|
||||
pub fn update(&mut self, content: T) {
|
||||
self.content = content
|
||||
}
|
||||
|
||||
fn to_ref(&self) -> Paragraph<&str>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
/// Copy style and replace content.
|
||||
pub fn with_content<U>(&self, content: U) -> Paragraph<U> {
|
||||
Paragraph {
|
||||
content: self.content.as_ref(),
|
||||
content,
|
||||
style: self.style,
|
||||
align: self.align,
|
||||
break_after: self.break_after,
|
||||
@ -307,6 +338,13 @@ impl<T> Paragraph<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset_as_ref(&self, offset: usize) -> Paragraph<&str>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
self.with_content(&self.content.as_ref()[offset..])
|
||||
}
|
||||
|
||||
fn layout(&self, area: Rect) -> TextLayout {
|
||||
TextLayout {
|
||||
padding_top: PARAGRAPH_TOP_SPACE,
|
||||
@ -344,7 +382,8 @@ impl PageOffset {
|
||||
source: &dyn ParagraphSource,
|
||||
full_height: i16,
|
||||
) -> (PageOffset, Option<Rect>, Option<TextLayout>) {
|
||||
let paragraph = source.at(self.par);
|
||||
let mut buffer = [0; SCRATCH_BUFFER_LEN];
|
||||
let paragraph = source.at(self.par, self.chr, &mut buffer);
|
||||
|
||||
// Skip empty paragraphs.
|
||||
if paragraph.content.is_empty() {
|
||||
@ -355,8 +394,9 @@ impl PageOffset {
|
||||
|
||||
// Handle the `no_break` flag used to keep key-value pair on the same page.
|
||||
if paragraph.no_break && self.chr == 0 {
|
||||
let mut next_buffer = [0; SCRATCH_BUFFER_LEN];
|
||||
if let Some(next_paragraph) =
|
||||
(self.par + 1 < source.size()).then(|| source.at(self.par + 1))
|
||||
(self.par + 1 < source.size()).then(|| source.at(self.par + 1, 0, &mut next_buffer))
|
||||
{
|
||||
if Self::should_place_pair_on_next_page(
|
||||
¶graph,
|
||||
@ -371,7 +411,7 @@ impl PageOffset {
|
||||
|
||||
// Find out the dimensions of the paragraph at given char offset.
|
||||
let mut layout = paragraph.layout(area);
|
||||
let fit = layout.fit_text(¶graph.content[self.chr..]);
|
||||
let fit = layout.fit_text(paragraph.content);
|
||||
let (used, remaining_area) = area.split_top(fit.height());
|
||||
layout.bounds = used;
|
||||
|
||||
|
@ -1,10 +1,18 @@
|
||||
use crate::{
|
||||
error::Error,
|
||||
micropython::{
|
||||
buffer::{get_buffer, get_str_owner, StrBuffer},
|
||||
gc::Gc,
|
||||
iter::{Iter, IterBuf},
|
||||
list::List,
|
||||
obj::Obj,
|
||||
},
|
||||
ui::component::text::{
|
||||
paragraphs::{Paragraph, ParagraphSource},
|
||||
TextStyle,
|
||||
},
|
||||
};
|
||||
use core::str;
|
||||
use cstr_core::cstr;
|
||||
use heapless::Vec;
|
||||
|
||||
@ -32,3 +40,177 @@ where
|
||||
// Returns error if array.len() != N
|
||||
vec.into_array().map_err(|_| err)
|
||||
}
|
||||
|
||||
fn hexlify<'a>(data: &[u8], buffer: &'a mut [u8]) -> &'a str {
|
||||
const HEX_LOWER: [u8; 16] = *b"0123456789abcdef";
|
||||
let mut i: usize = 0;
|
||||
for b in data.iter().take(buffer.len() / 2) {
|
||||
let hi: usize = ((b & 0xf0) >> 4).into();
|
||||
let lo: usize = (b & 0x0f).into();
|
||||
buffer[i] = HEX_LOWER[hi];
|
||||
buffer[i + 1] = HEX_LOWER[lo];
|
||||
i += 2;
|
||||
}
|
||||
// SAFETY: only <0x7f bytes are used to construct the string
|
||||
unsafe { str::from_utf8_unchecked(&buffer[0..i]) }
|
||||
}
|
||||
|
||||
pub enum StrOrBytes {
|
||||
Str(StrBuffer),
|
||||
Bytes(Obj),
|
||||
}
|
||||
|
||||
impl StrOrBytes {
|
||||
pub fn as_str_offset<'a>(&'a self, offset: usize, buffer: &'a mut [u8]) -> &'a str {
|
||||
match self {
|
||||
StrOrBytes::Str(x) => &x.as_ref()[offset..],
|
||||
StrOrBytes::Bytes(x) => Self::hexlify(*x, offset, buffer),
|
||||
}
|
||||
}
|
||||
|
||||
fn hexlify(obj: Obj, offset: usize, buffer: &mut [u8]) -> &str {
|
||||
if !obj.is_bytes() {
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
// Convert offset to byte representation, handle case where it points in the
|
||||
// middle of a byte.
|
||||
let bin_off = offset / 2;
|
||||
let hex_off = offset % 2;
|
||||
|
||||
// SAFETY:
|
||||
// (a) only immutable references are taken
|
||||
// (b) reference is discarded before returning to micropython
|
||||
let bin_slice = if let Ok(buf) = unsafe { get_buffer(obj) } {
|
||||
&buf[bin_off..]
|
||||
} else {
|
||||
return "ERROR";
|
||||
};
|
||||
let hexadecimal = hexlify(bin_slice, buffer);
|
||||
|
||||
&hexadecimal[hex_off..]
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Obj> for StrOrBytes {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(obj: Obj) -> Result<Self, Error> {
|
||||
if obj.is_str() {
|
||||
Ok(StrOrBytes::Str(obj.try_into()?))
|
||||
} else if obj.is_bytes() {
|
||||
Ok(StrOrBytes::Bytes(obj))
|
||||
} else {
|
||||
Err(Error::TypeError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConfirmBlob {
|
||||
pub description: StrBuffer,
|
||||
pub extra: StrBuffer,
|
||||
pub data: StrOrBytes,
|
||||
pub description_font: &'static TextStyle,
|
||||
pub extra_font: &'static TextStyle,
|
||||
pub data_font: &'static TextStyle,
|
||||
}
|
||||
|
||||
impl ParagraphSource for ConfirmBlob {
|
||||
fn at<'a>(&'a self, index: usize, offset: usize, buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||
match index {
|
||||
0 => Paragraph::new(self.description_font, &self.description.as_ref()[offset..]),
|
||||
1 => Paragraph::new(self.extra_font, &self.extra.as_ref()[offset..]),
|
||||
2 => Paragraph::new(self.data_font, self.data.as_str_offset(offset, buffer)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
3
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PropsList {
|
||||
items: Gc<List>,
|
||||
key_font: &'static TextStyle,
|
||||
value_font: &'static TextStyle,
|
||||
value_mono_font: &'static TextStyle,
|
||||
}
|
||||
|
||||
impl PropsList {
|
||||
pub fn new(
|
||||
obj: Obj,
|
||||
key_font: &'static TextStyle,
|
||||
value_font: &'static TextStyle,
|
||||
value_mono_font: &'static TextStyle,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
items: obj.try_into()?,
|
||||
key_font,
|
||||
value_font,
|
||||
value_mono_font,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ParagraphSource for PropsList {
|
||||
fn at<'a>(&'a self, index: usize, offset: usize, buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||
let block = move |buffer| {
|
||||
let entry = self.items.get(index / 2)?;
|
||||
let [key, value, value_is_mono]: [Obj; 3] = iter_into_objs(entry)?;
|
||||
let value_is_mono: bool = bool::try_from(value_is_mono)?;
|
||||
let obj: Obj;
|
||||
let style: &TextStyle;
|
||||
|
||||
if index % 2 == 0 {
|
||||
if !key.is_str() && key != Obj::const_none() {
|
||||
return Err(Error::TypeError);
|
||||
}
|
||||
style = self.key_font;
|
||||
obj = key;
|
||||
} else {
|
||||
if value_is_mono {
|
||||
style = self.value_mono_font;
|
||||
} else {
|
||||
if value.is_bytes() {
|
||||
return Err(Error::TypeError);
|
||||
}
|
||||
style = self.value_font;
|
||||
}
|
||||
obj = value;
|
||||
};
|
||||
|
||||
if obj == Obj::const_none() {
|
||||
return Ok(Paragraph::new(style, ""));
|
||||
}
|
||||
|
||||
let para = if obj.is_str() {
|
||||
// SAFETY:
|
||||
// As long as self is visible to GC, the string will be also. The paragraph
|
||||
// rendering does not keep the returned references for long so we can reasonably
|
||||
// expect for the chain of references from self to the buffer not to be broken.
|
||||
let s = unsafe { get_str_owner(self, obj)? };
|
||||
Paragraph::new(style, &s[offset..])
|
||||
} else if obj.is_bytes() {
|
||||
let s = StrOrBytes::hexlify(obj, offset, buffer);
|
||||
Paragraph::new(style, s)
|
||||
} else {
|
||||
return Err(Error::TypeError);
|
||||
};
|
||||
|
||||
if obj == key && value != Obj::const_none() {
|
||||
Ok(para.no_break())
|
||||
} else {
|
||||
Ok(para)
|
||||
}
|
||||
};
|
||||
match block(buffer) {
|
||||
Ok(p) => p,
|
||||
Err(_) => Paragraph::new(self.value_font, "ERROR"),
|
||||
}
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
2 * self.items.len()
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
use core::{cmp::Ordering, convert::TryInto, ops::Deref};
|
||||
|
||||
use heapless::Vec;
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
micropython::{
|
||||
@ -31,7 +29,7 @@ use crate::{
|
||||
layout::{
|
||||
obj::{ComponentMsgObj, LayoutObj},
|
||||
result::{CANCELLED, CONFIRMED, INFO},
|
||||
util::{iter_into_array, iter_into_objs},
|
||||
util::{iter_into_array, ConfirmBlob, PropsList},
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -331,26 +329,24 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
fn _confirm_blob(
|
||||
fn confirm_blob(
|
||||
title: StrBuffer,
|
||||
data: Option<StrBuffer>,
|
||||
data: Obj,
|
||||
description: Option<StrBuffer>,
|
||||
extra: Option<StrBuffer>,
|
||||
verb: Option<StrBuffer>,
|
||||
verb_cancel: Option<StrBuffer>,
|
||||
hold: bool,
|
||||
) -> Result<Obj, Error> {
|
||||
let mut par_source: Vec<Paragraph<StrBuffer>, 3> = Vec::new();
|
||||
if let Some(description) = description {
|
||||
unwrap!(par_source.push(Paragraph::new(&theme::TEXT_NORMAL, description)));
|
||||
let paragraphs = ConfirmBlob {
|
||||
description: description.unwrap_or_else(StrBuffer::empty),
|
||||
extra: extra.unwrap_or_else(StrBuffer::empty),
|
||||
data: data.try_into()?,
|
||||
description_font: &theme::TEXT_NORMAL,
|
||||
extra_font: &theme::TEXT_BOLD,
|
||||
data_font: &theme::TEXT_MONO,
|
||||
}
|
||||
if let Some(extra) = extra {
|
||||
unwrap!(par_source.push(Paragraph::new(&theme::TEXT_BOLD, extra)));
|
||||
}
|
||||
if let Some(data) = data {
|
||||
unwrap!(par_source.push(Paragraph::new(&theme::TEXT_MONO, data)));
|
||||
}
|
||||
let paragraphs = Paragraphs::new(par_source);
|
||||
.into_paragraphs();
|
||||
|
||||
let obj = if hold {
|
||||
LayoutObj::new(Frame::new(title, SwipeHoldPage::new(paragraphs, theme::BG)))?
|
||||
@ -369,10 +365,10 @@ fn _confirm_blob(
|
||||
extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let data: StrBuffer = kwargs.get(Qstr::MP_QSTR_data)?.try_into()?;
|
||||
let description: StrBuffer =
|
||||
kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?;
|
||||
let extra: StrBuffer = kwargs.get_or(Qstr::MP_QSTR_extra, StrBuffer::empty())?;
|
||||
let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?;
|
||||
let description: Option<StrBuffer> =
|
||||
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
||||
let extra: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?;
|
||||
let verb_cancel: Option<StrBuffer> = kwargs
|
||||
.get(Qstr::MP_QSTR_verb_cancel)
|
||||
.unwrap_or_else(|_| Obj::const_none())
|
||||
@ -382,11 +378,11 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map
|
||||
|
||||
let verb: StrBuffer = "CONFIRM".into();
|
||||
|
||||
_confirm_blob(
|
||||
confirm_blob(
|
||||
title,
|
||||
Some(data),
|
||||
Some(description),
|
||||
Some(extra),
|
||||
data,
|
||||
description,
|
||||
extra,
|
||||
Some(verb),
|
||||
verb_cancel,
|
||||
hold,
|
||||
@ -401,31 +397,12 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
|
||||
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
|
||||
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
|
||||
|
||||
let mut paragraphs = ParagraphVecLong::new();
|
||||
|
||||
let mut iter_buf = IterBuf::new();
|
||||
let iter = Iter::try_from_obj_with_buf(items, &mut iter_buf)?;
|
||||
for para in iter {
|
||||
let [key, value, is_mono]: [Obj; 3] = iter_into_objs(para)?;
|
||||
let key = key.try_into_option::<StrBuffer>()?;
|
||||
let value = value.try_into_option::<StrBuffer>()?;
|
||||
|
||||
if let Some(key) = key {
|
||||
if value.is_some() {
|
||||
paragraphs.add(Paragraph::new(&theme::TEXT_BOLD, key).no_break());
|
||||
} else {
|
||||
paragraphs.add(Paragraph::new(&theme::TEXT_BOLD, key));
|
||||
}
|
||||
}
|
||||
if let Some(value) = value {
|
||||
if is_mono.try_into()? {
|
||||
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value));
|
||||
} else {
|
||||
paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let paragraphs = PropsList::new(
|
||||
items,
|
||||
&theme::TEXT_BOLD,
|
||||
&theme::TEXT_NORMAL,
|
||||
&theme::TEXT_MONO,
|
||||
)?;
|
||||
let obj = if hold {
|
||||
LayoutObj::new(Frame::new(
|
||||
title,
|
||||
@ -497,8 +474,9 @@ extern "C" fn new_show_qr(n_args: usize, args: *const Obj, kwargs: *mut Map) ->
|
||||
extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
||||
let value: StrBuffer = kwargs.get(Qstr::MP_QSTR_value)?.try_into()?;
|
||||
let description: Option<StrBuffer> =
|
||||
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
||||
let value: Obj = kwargs.get(Qstr::MP_QSTR_value)?;
|
||||
|
||||
let verb: Option<StrBuffer> = kwargs
|
||||
.get(Qstr::MP_QSTR_verb)
|
||||
@ -506,15 +484,7 @@ extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Ma
|
||||
.try_into_option()?;
|
||||
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
|
||||
|
||||
_confirm_blob(
|
||||
title,
|
||||
Some(value),
|
||||
Some(description),
|
||||
None,
|
||||
verb,
|
||||
None,
|
||||
hold,
|
||||
)
|
||||
confirm_blob(title, value, description, None, verb, None, hold)
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
@ -1142,9 +1112,9 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// def confirm_blob(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// data: str,
|
||||
/// description: str = "",
|
||||
/// extra: str = "",
|
||||
/// data: str | bytes,
|
||||
/// description: str | None,
|
||||
/// extra: str | None,
|
||||
/// verb_cancel: str | None = None,
|
||||
/// ask_pagination: bool = False,
|
||||
/// hold: bool = False,
|
||||
@ -1155,12 +1125,11 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// def confirm_properties(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// items: Iterable[Tuple[str | None, str | None, bool]],
|
||||
/// items: list[tuple[str | None, str | bytes | None, bool]],
|
||||
/// hold: bool = False,
|
||||
/// ) -> object:
|
||||
/// """Confirm list of key-value pairs. The third component in the tuple should be True if
|
||||
/// the value is to be rendered as binary with monospace font, False otherwise.
|
||||
/// This only concerns the text style, you need to decode the value to UTF-8 in python."""
|
||||
/// the value is to be rendered as binary with monospace font, False otherwise."""
|
||||
Qstr::MP_QSTR_confirm_properties => obj_fn_kw!(0, new_confirm_properties).as_obj(),
|
||||
|
||||
/// def confirm_reset_device(
|
||||
|
@ -75,9 +75,9 @@ def confirm_action(
|
||||
def confirm_blob(
|
||||
*,
|
||||
title: str,
|
||||
data: str,
|
||||
description: str = "",
|
||||
extra: str = "",
|
||||
data: str | bytes,
|
||||
description: str | None,
|
||||
extra: str | None,
|
||||
verb_cancel: str | None = None,
|
||||
ask_pagination: bool = False,
|
||||
hold: bool = False,
|
||||
@ -89,12 +89,11 @@ def confirm_blob(
|
||||
def confirm_properties(
|
||||
*,
|
||||
title: str,
|
||||
items: Iterable[Tuple[str | None, str | None, bool]],
|
||||
items: list[tuple[str | None, str | bytes | None, bool]],
|
||||
hold: bool = False,
|
||||
) -> object:
|
||||
"""Confirm list of key-value pairs. The third component in the tuple should be True if
|
||||
the value is to be rendered as binary with monospace font, False otherwise.
|
||||
This only concerns the text style, you need to decode the value to UTF-8 in python."""
|
||||
the value is to be rendered as binary with monospace font, False otherwise."""
|
||||
|
||||
|
||||
# rust/src/ui/model_tt/layout.rs
|
||||
|
@ -1,5 +1,4 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from ubinascii import hexlify
|
||||
|
||||
from trezor import io, log, loop, ui
|
||||
from trezor.enums import ButtonRequestType
|
||||
@ -317,6 +316,8 @@ def _show_xpub(xpub: str, title: str, cancel: str) -> ui.Layout:
|
||||
title=title,
|
||||
data=xpub,
|
||||
verb_cancel=cancel,
|
||||
extra=None,
|
||||
description=None,
|
||||
)
|
||||
)
|
||||
return content
|
||||
@ -596,9 +597,6 @@ async def confirm_blob(
|
||||
br_code: ButtonRequestType = BR_TYPE_OTHER,
|
||||
ask_pagination: bool = False,
|
||||
) -> None:
|
||||
if isinstance(data, bytes):
|
||||
data = hexlify(data).decode()
|
||||
|
||||
await raise_if_not_confirmed(
|
||||
interact(
|
||||
ctx,
|
||||
@ -607,6 +605,7 @@ async def confirm_blob(
|
||||
title=title.upper(),
|
||||
description=description or "",
|
||||
data=data,
|
||||
extra=None,
|
||||
ask_pagination=ask_pagination,
|
||||
hold=hold,
|
||||
)
|
||||
@ -716,11 +715,8 @@ async def confirm_properties(
|
||||
hold: bool = False,
|
||||
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
||||
) -> None:
|
||||
def handle_bytes(prop: PropertyType) -> tuple[str | None, str | None, bool]:
|
||||
if isinstance(prop[1], bytes):
|
||||
return (prop[0], hexlify(prop[1]).decode(), True)
|
||||
else:
|
||||
return (prop[0], prop[1], False)
|
||||
# Monospace flag for values that are bytes.
|
||||
items = [(prop[0], prop[1], isinstance(prop[1], bytes)) for prop in props]
|
||||
|
||||
await raise_if_not_confirmed(
|
||||
interact(
|
||||
@ -728,7 +724,7 @@ async def confirm_properties(
|
||||
_RustLayout(
|
||||
trezorui2.confirm_properties(
|
||||
title=title.upper(),
|
||||
items=map(handle_bytes, props),
|
||||
items=items,
|
||||
hold=hold,
|
||||
)
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user