mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-05-29 04:08:46 +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_WRITE")
|
||||||
.allowlist_var("MP_BUFFER_RW")
|
.allowlist_var("MP_BUFFER_RW")
|
||||||
.allowlist_var("mp_type_str")
|
.allowlist_var("mp_type_str")
|
||||||
|
.allowlist_var("mp_type_bytes")
|
||||||
|
.allowlist_var("mp_type_bytearray")
|
||||||
|
.allowlist_var("mp_type_memoryview")
|
||||||
// dict
|
// dict
|
||||||
.allowlist_type("mp_obj_dict_t")
|
.allowlist_type("mp_obj_dict_t")
|
||||||
.allowlist_function("mp_obj_new_dict")
|
.allowlist_function("mp_obj_new_dict")
|
||||||
|
@ -66,7 +66,7 @@ impl TryFrom<Obj> for StrBuffer {
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn try_from(obj: Obj) -> Result<Self, Self::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 bufinfo = get_buffer_info(obj, ffi::MP_BUFFER_READ)?;
|
||||||
let new = Self {
|
let new = Self {
|
||||||
ptr: bufinfo.buf as _,
|
ptr: bufinfo.buf as _,
|
||||||
@ -189,3 +189,25 @@ impl crate::trace::Trace for StrBuffer {
|
|||||||
self.as_ref().trace(t)
|
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 ParagraphVecLong<T> = Vec<Paragraph<T>, 32>;
|
||||||
pub type ParagraphVecShort<T> = Vec<Paragraph<T>, 8>;
|
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 {
|
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 size(&self) -> usize;
|
||||||
|
|
||||||
fn into_paragraphs(self) -> Paragraphs<Self>
|
fn into_paragraphs(self) -> Paragraphs<Self>
|
||||||
@ -40,8 +55,8 @@ impl<T, const N: usize> ParagraphSource for Vec<Paragraph<T>, N>
|
|||||||
where
|
where
|
||||||
T: AsRef<str>,
|
T: AsRef<str>,
|
||||||
{
|
{
|
||||||
fn at(&self, i: usize) -> Paragraph<&str> {
|
fn at<'a>(&'a self, index: usize, offset: usize, _buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||||
self[i].to_ref()
|
self[index].offset_as_ref(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(&self) -> usize {
|
fn size(&self) -> usize {
|
||||||
@ -53,8 +68,8 @@ impl<T, const N: usize> ParagraphSource for [Paragraph<T>; N]
|
|||||||
where
|
where
|
||||||
T: AsRef<str>,
|
T: AsRef<str>,
|
||||||
{
|
{
|
||||||
fn at(&self, i: usize) -> Paragraph<&str> {
|
fn at<'a>(&'a self, index: usize, offset: usize, _buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||||
self[i].to_ref()
|
self[index].offset_as_ref(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(&self) -> usize {
|
fn size(&self) -> usize {
|
||||||
@ -66,9 +81,9 @@ impl<T> ParagraphSource for Paragraph<T>
|
|||||||
where
|
where
|
||||||
T: AsRef<str>,
|
T: AsRef<str>,
|
||||||
{
|
{
|
||||||
fn at(&self, i: usize) -> Paragraph<&str> {
|
fn at<'a>(&'a self, index: usize, offset: usize, _buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||||
assert_eq!(i, 0);
|
assert_eq!(index, 0);
|
||||||
self.to_ref()
|
self.offset_as_ref(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(&self) -> usize {
|
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.
|
/// with corresponding string content. Should not get monomorphized.
|
||||||
fn visible_content<'a>(
|
fn foreach_visible<'a, 'b>(
|
||||||
content: &'a dyn ParagraphSource,
|
source: &'a dyn ParagraphSource,
|
||||||
visible: &'a [TextLayout],
|
visible: &'a [TextLayout],
|
||||||
offset: PageOffset,
|
offset: PageOffset,
|
||||||
) -> impl Iterator<Item = (&'a TextLayout, &'a str)> {
|
func: &'b mut dyn FnMut(&TextLayout, &str),
|
||||||
visible.iter().zip(
|
) {
|
||||||
(offset.par..content.size())
|
let mut buffer = [0; SCRATCH_BUFFER_LEN];
|
||||||
.map(|i| content.at(i))
|
let mut vis_iter = visible.iter();
|
||||||
.filter(|p| !p.content.is_empty())
|
let mut chr = offset.chr;
|
||||||
.enumerate()
|
|
||||||
.map(move |(i, p): (usize, Paragraph<&str>)| {
|
for par in offset.par..source.size() {
|
||||||
if i == 0 {
|
let s = source.at(par, chr, &mut buffer).content;
|
||||||
&p.content[offset.chr..]
|
if s.is_empty() {
|
||||||
} else {
|
chr = 0;
|
||||||
p.content
|
continue;
|
||||||
}
|
}
|
||||||
}),
|
if let Some(layout) = vis_iter.next() {
|
||||||
)
|
func(layout, s);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
chr = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,9 +217,14 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self) {
|
fn paint(&mut self) {
|
||||||
for (layout, content) in Self::visible_content(&self.source, &self.visible, self.offset) {
|
Self::foreach_visible(
|
||||||
layout.render_text(content);
|
&self.source,
|
||||||
}
|
&self.visible,
|
||||||
|
self.offset,
|
||||||
|
&mut |layout, content| {
|
||||||
|
layout.render_text(content);
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
@ -239,11 +264,15 @@ pub mod trace {
|
|||||||
impl<T: ParagraphSource> crate::trace::Trace for Paragraphs<T> {
|
impl<T: ParagraphSource> crate::trace::Trace for Paragraphs<T> {
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.open("Paragraphs");
|
t.open("Paragraphs");
|
||||||
for (layout, content) in Self::visible_content(&self.source, &self.visible, self.offset)
|
Self::foreach_visible(
|
||||||
{
|
&self.source,
|
||||||
layout.layout_text(content, &mut layout.initial_cursor(), &mut TraceSink(t));
|
&self.visible,
|
||||||
t.string("\n");
|
self.offset,
|
||||||
}
|
&mut |layout, content| {
|
||||||
|
layout.layout_text(content, &mut layout.initial_cursor(), &mut TraceSink(t));
|
||||||
|
t.string("\n");
|
||||||
|
},
|
||||||
|
);
|
||||||
t.close();
|
t.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,16 +319,18 @@ impl<T> Paragraph<T> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn content(&self) -> &T {
|
||||||
|
&self.content
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, content: T) {
|
pub fn update(&mut self, content: T) {
|
||||||
self.content = content
|
self.content = content
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_ref(&self) -> Paragraph<&str>
|
/// Copy style and replace content.
|
||||||
where
|
pub fn with_content<U>(&self, content: U) -> Paragraph<U> {
|
||||||
T: AsRef<str>,
|
|
||||||
{
|
|
||||||
Paragraph {
|
Paragraph {
|
||||||
content: self.content.as_ref(),
|
content,
|
||||||
style: self.style,
|
style: self.style,
|
||||||
align: self.align,
|
align: self.align,
|
||||||
break_after: self.break_after,
|
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 {
|
fn layout(&self, area: Rect) -> TextLayout {
|
||||||
TextLayout {
|
TextLayout {
|
||||||
padding_top: PARAGRAPH_TOP_SPACE,
|
padding_top: PARAGRAPH_TOP_SPACE,
|
||||||
@ -344,7 +382,8 @@ impl PageOffset {
|
|||||||
source: &dyn ParagraphSource,
|
source: &dyn ParagraphSource,
|
||||||
full_height: i16,
|
full_height: i16,
|
||||||
) -> (PageOffset, Option<Rect>, Option<TextLayout>) {
|
) -> (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.
|
// Skip empty paragraphs.
|
||||||
if paragraph.content.is_empty() {
|
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.
|
// Handle the `no_break` flag used to keep key-value pair on the same page.
|
||||||
if paragraph.no_break && self.chr == 0 {
|
if paragraph.no_break && self.chr == 0 {
|
||||||
|
let mut next_buffer = [0; SCRATCH_BUFFER_LEN];
|
||||||
if let Some(next_paragraph) =
|
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(
|
if Self::should_place_pair_on_next_page(
|
||||||
¶graph,
|
¶graph,
|
||||||
@ -371,7 +411,7 @@ impl PageOffset {
|
|||||||
|
|
||||||
// Find out the dimensions of the paragraph at given char offset.
|
// Find out the dimensions of the paragraph at given char offset.
|
||||||
let mut layout = paragraph.layout(area);
|
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());
|
let (used, remaining_area) = area.split_top(fit.height());
|
||||||
layout.bounds = used;
|
layout.bounds = used;
|
||||||
|
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
micropython::{
|
micropython::{
|
||||||
|
buffer::{get_buffer, get_str_owner, StrBuffer},
|
||||||
|
gc::Gc,
|
||||||
iter::{Iter, IterBuf},
|
iter::{Iter, IterBuf},
|
||||||
|
list::List,
|
||||||
obj::Obj,
|
obj::Obj,
|
||||||
},
|
},
|
||||||
|
ui::component::text::{
|
||||||
|
paragraphs::{Paragraph, ParagraphSource},
|
||||||
|
TextStyle,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
use core::str;
|
||||||
use cstr_core::cstr;
|
use cstr_core::cstr;
|
||||||
use heapless::Vec;
|
use heapless::Vec;
|
||||||
|
|
||||||
@ -32,3 +40,177 @@ where
|
|||||||
// Returns error if array.len() != N
|
// Returns error if array.len() != N
|
||||||
vec.into_array().map_err(|_| err)
|
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 core::{cmp::Ordering, convert::TryInto, ops::Deref};
|
||||||
|
|
||||||
use heapless::Vec;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
micropython::{
|
micropython::{
|
||||||
@ -31,7 +29,7 @@ use crate::{
|
|||||||
layout::{
|
layout::{
|
||||||
obj::{ComponentMsgObj, LayoutObj},
|
obj::{ComponentMsgObj, LayoutObj},
|
||||||
result::{CANCELLED, CONFIRMED, INFO},
|
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) }
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _confirm_blob(
|
fn confirm_blob(
|
||||||
title: StrBuffer,
|
title: StrBuffer,
|
||||||
data: Option<StrBuffer>,
|
data: Obj,
|
||||||
description: Option<StrBuffer>,
|
description: Option<StrBuffer>,
|
||||||
extra: Option<StrBuffer>,
|
extra: Option<StrBuffer>,
|
||||||
verb: Option<StrBuffer>,
|
verb: Option<StrBuffer>,
|
||||||
verb_cancel: Option<StrBuffer>,
|
verb_cancel: Option<StrBuffer>,
|
||||||
hold: bool,
|
hold: bool,
|
||||||
) -> Result<Obj, Error> {
|
) -> Result<Obj, Error> {
|
||||||
let mut par_source: Vec<Paragraph<StrBuffer>, 3> = Vec::new();
|
let paragraphs = ConfirmBlob {
|
||||||
if let Some(description) = description {
|
description: description.unwrap_or_else(StrBuffer::empty),
|
||||||
unwrap!(par_source.push(Paragraph::new(&theme::TEXT_NORMAL, description)));
|
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 {
|
.into_paragraphs();
|
||||||
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);
|
|
||||||
|
|
||||||
let obj = if hold {
|
let obj = if hold {
|
||||||
LayoutObj::new(Frame::new(title, SwipeHoldPage::new(paragraphs, theme::BG)))?
|
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 {
|
extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
let data: StrBuffer = kwargs.get(Qstr::MP_QSTR_data)?.try_into()?;
|
let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?;
|
||||||
let description: StrBuffer =
|
let description: Option<StrBuffer> =
|
||||||
kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?;
|
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
||||||
let extra: StrBuffer = kwargs.get_or(Qstr::MP_QSTR_extra, StrBuffer::empty())?;
|
let extra: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?;
|
||||||
let verb_cancel: Option<StrBuffer> = kwargs
|
let verb_cancel: Option<StrBuffer> = kwargs
|
||||||
.get(Qstr::MP_QSTR_verb_cancel)
|
.get(Qstr::MP_QSTR_verb_cancel)
|
||||||
.unwrap_or_else(|_| Obj::const_none())
|
.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();
|
let verb: StrBuffer = "CONFIRM".into();
|
||||||
|
|
||||||
_confirm_blob(
|
confirm_blob(
|
||||||
title,
|
title,
|
||||||
Some(data),
|
data,
|
||||||
Some(description),
|
description,
|
||||||
Some(extra),
|
extra,
|
||||||
Some(verb),
|
Some(verb),
|
||||||
verb_cancel,
|
verb_cancel,
|
||||||
hold,
|
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 hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
|
||||||
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
|
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
|
||||||
|
|
||||||
let mut paragraphs = ParagraphVecLong::new();
|
let paragraphs = PropsList::new(
|
||||||
|
items,
|
||||||
let mut iter_buf = IterBuf::new();
|
&theme::TEXT_BOLD,
|
||||||
let iter = Iter::try_from_obj_with_buf(items, &mut iter_buf)?;
|
&theme::TEXT_NORMAL,
|
||||||
for para in iter {
|
&theme::TEXT_MONO,
|
||||||
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 obj = if hold {
|
let obj = if hold {
|
||||||
LayoutObj::new(Frame::new(
|
LayoutObj::new(Frame::new(
|
||||||
title,
|
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 {
|
extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
let description: Option<StrBuffer> =
|
||||||
let value: StrBuffer = kwargs.get(Qstr::MP_QSTR_value)?.try_into()?;
|
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
||||||
|
let value: Obj = kwargs.get(Qstr::MP_QSTR_value)?;
|
||||||
|
|
||||||
let verb: Option<StrBuffer> = kwargs
|
let verb: Option<StrBuffer> = kwargs
|
||||||
.get(Qstr::MP_QSTR_verb)
|
.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()?;
|
.try_into_option()?;
|
||||||
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
|
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
|
||||||
|
|
||||||
_confirm_blob(
|
confirm_blob(title, value, description, None, verb, None, hold)
|
||||||
title,
|
|
||||||
Some(value),
|
|
||||||
Some(description),
|
|
||||||
None,
|
|
||||||
verb,
|
|
||||||
None,
|
|
||||||
hold,
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
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(
|
/// def confirm_blob(
|
||||||
/// *,
|
/// *,
|
||||||
/// title: str,
|
/// title: str,
|
||||||
/// data: str,
|
/// data: str | bytes,
|
||||||
/// description: str = "",
|
/// description: str | None,
|
||||||
/// extra: str = "",
|
/// extra: str | None,
|
||||||
/// verb_cancel: str | None = None,
|
/// verb_cancel: str | None = None,
|
||||||
/// ask_pagination: bool = False,
|
/// ask_pagination: bool = False,
|
||||||
/// hold: bool = False,
|
/// hold: bool = False,
|
||||||
@ -1155,12 +1125,11 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// def confirm_properties(
|
/// def confirm_properties(
|
||||||
/// *,
|
/// *,
|
||||||
/// title: str,
|
/// title: str,
|
||||||
/// items: Iterable[Tuple[str | None, str | None, bool]],
|
/// items: list[tuple[str | None, str | bytes | None, bool]],
|
||||||
/// hold: bool = False,
|
/// hold: bool = False,
|
||||||
/// ) -> object:
|
/// ) -> object:
|
||||||
/// """Confirm list of key-value pairs. The third component in the tuple should be True if
|
/// """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.
|
/// 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."""
|
|
||||||
Qstr::MP_QSTR_confirm_properties => obj_fn_kw!(0, new_confirm_properties).as_obj(),
|
Qstr::MP_QSTR_confirm_properties => obj_fn_kw!(0, new_confirm_properties).as_obj(),
|
||||||
|
|
||||||
/// def confirm_reset_device(
|
/// def confirm_reset_device(
|
||||||
|
@ -75,9 +75,9 @@ def confirm_action(
|
|||||||
def confirm_blob(
|
def confirm_blob(
|
||||||
*,
|
*,
|
||||||
title: str,
|
title: str,
|
||||||
data: str,
|
data: str | bytes,
|
||||||
description: str = "",
|
description: str | None,
|
||||||
extra: str = "",
|
extra: str | None,
|
||||||
verb_cancel: str | None = None,
|
verb_cancel: str | None = None,
|
||||||
ask_pagination: bool = False,
|
ask_pagination: bool = False,
|
||||||
hold: bool = False,
|
hold: bool = False,
|
||||||
@ -89,12 +89,11 @@ def confirm_blob(
|
|||||||
def confirm_properties(
|
def confirm_properties(
|
||||||
*,
|
*,
|
||||||
title: str,
|
title: str,
|
||||||
items: Iterable[Tuple[str | None, str | None, bool]],
|
items: list[tuple[str | None, str | bytes | None, bool]],
|
||||||
hold: bool = False,
|
hold: bool = False,
|
||||||
) -> object:
|
) -> object:
|
||||||
"""Confirm list of key-value pairs. The third component in the tuple should be True if
|
"""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.
|
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."""
|
|
||||||
|
|
||||||
|
|
||||||
# rust/src/ui/model_tt/layout.rs
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from ubinascii import hexlify
|
|
||||||
|
|
||||||
from trezor import io, log, loop, ui
|
from trezor import io, log, loop, ui
|
||||||
from trezor.enums import ButtonRequestType
|
from trezor.enums import ButtonRequestType
|
||||||
@ -317,6 +316,8 @@ def _show_xpub(xpub: str, title: str, cancel: str) -> ui.Layout:
|
|||||||
title=title,
|
title=title,
|
||||||
data=xpub,
|
data=xpub,
|
||||||
verb_cancel=cancel,
|
verb_cancel=cancel,
|
||||||
|
extra=None,
|
||||||
|
description=None,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return content
|
return content
|
||||||
@ -596,9 +597,6 @@ async def confirm_blob(
|
|||||||
br_code: ButtonRequestType = BR_TYPE_OTHER,
|
br_code: ButtonRequestType = BR_TYPE_OTHER,
|
||||||
ask_pagination: bool = False,
|
ask_pagination: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if isinstance(data, bytes):
|
|
||||||
data = hexlify(data).decode()
|
|
||||||
|
|
||||||
await raise_if_not_confirmed(
|
await raise_if_not_confirmed(
|
||||||
interact(
|
interact(
|
||||||
ctx,
|
ctx,
|
||||||
@ -607,6 +605,7 @@ async def confirm_blob(
|
|||||||
title=title.upper(),
|
title=title.upper(),
|
||||||
description=description or "",
|
description=description or "",
|
||||||
data=data,
|
data=data,
|
||||||
|
extra=None,
|
||||||
ask_pagination=ask_pagination,
|
ask_pagination=ask_pagination,
|
||||||
hold=hold,
|
hold=hold,
|
||||||
)
|
)
|
||||||
@ -716,11 +715,8 @@ async def confirm_properties(
|
|||||||
hold: bool = False,
|
hold: bool = False,
|
||||||
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
||||||
) -> None:
|
) -> None:
|
||||||
def handle_bytes(prop: PropertyType) -> tuple[str | None, str | None, bool]:
|
# Monospace flag for values that are bytes.
|
||||||
if isinstance(prop[1], bytes):
|
items = [(prop[0], prop[1], isinstance(prop[1], bytes)) for prop in props]
|
||||||
return (prop[0], hexlify(prop[1]).decode(), True)
|
|
||||||
else:
|
|
||||||
return (prop[0], prop[1], False)
|
|
||||||
|
|
||||||
await raise_if_not_confirmed(
|
await raise_if_not_confirmed(
|
||||||
interact(
|
interact(
|
||||||
@ -728,7 +724,7 @@ async def confirm_properties(
|
|||||||
_RustLayout(
|
_RustLayout(
|
||||||
trezorui2.confirm_properties(
|
trezorui2.confirm_properties(
|
||||||
title=title.upper(),
|
title=title.upper(),
|
||||||
items=map(handle_bytes, props),
|
items=items,
|
||||||
hold=hold,
|
hold=hold,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
Loading…
Reference in New Issue
Block a user