mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-23 06:48:16 +00:00
chore(core/rust): flag or delete unused code
This commit is contained in:
parent
0c0d74a501
commit
b63b72ed90
@ -160,13 +160,11 @@ fn generate_micropython_bindings() {
|
||||
.allowlist_function("mp_obj_is_true")
|
||||
.allowlist_function("mp_call_function_n_kw")
|
||||
.allowlist_function("trezor_obj_get_ll_checked")
|
||||
.allowlist_function("trezor_obj_get_ull_checked")
|
||||
.allowlist_function("trezor_obj_str_from_rom_text")
|
||||
// buffer
|
||||
.allowlist_function("mp_get_buffer")
|
||||
.allowlist_var("MP_BUFFER_READ")
|
||||
.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")
|
||||
@ -174,7 +172,6 @@ fn generate_micropython_bindings() {
|
||||
// dict
|
||||
.allowlist_type("mp_obj_dict_t")
|
||||
.allowlist_function("mp_obj_new_dict")
|
||||
.allowlist_function("mp_obj_dict_store")
|
||||
.allowlist_var("mp_type_dict")
|
||||
// fun
|
||||
.allowlist_type("mp_obj_fun_builtin_fixed_t")
|
||||
@ -199,7 +196,6 @@ fn generate_micropython_bindings() {
|
||||
.allowlist_var("mp_type_list")
|
||||
// map
|
||||
.allowlist_type("mp_map_elem_t")
|
||||
.allowlist_type("mp_map_lookup_kind_t")
|
||||
.allowlist_function("mp_map_init")
|
||||
.allowlist_function("mp_map_init_fixed_table")
|
||||
.allowlist_function("mp_map_lookup")
|
||||
@ -259,10 +255,6 @@ fn generate_trezorhal_bindings() {
|
||||
.allowlist_function("flash_init")
|
||||
// storage
|
||||
.allowlist_var("EXTERNAL_SALT_SIZE")
|
||||
.allowlist_var("FLAG_PUBLIC")
|
||||
.allowlist_var("FLAGS_WRITE")
|
||||
.allowlist_var("MAX_APPID")
|
||||
.allowlist_type("PIN_UI_WAIT_CALLBACK")
|
||||
.allowlist_function("storage_init")
|
||||
.allowlist_function("storage_wipe")
|
||||
.allowlist_function("storage_is_unlocked")
|
||||
@ -279,7 +271,6 @@ fn generate_trezorhal_bindings() {
|
||||
.allowlist_function("storage_set_counter")
|
||||
.allowlist_function("storage_next_counter")
|
||||
// display
|
||||
.allowlist_function("display_init")
|
||||
.allowlist_function("display_offset")
|
||||
.allowlist_function("display_refresh")
|
||||
.allowlist_function("display_backlight")
|
||||
@ -293,7 +284,6 @@ fn generate_trezorhal_bindings() {
|
||||
.allowlist_function("display_pixeldata_dirty")
|
||||
.allowlist_function("display_set_window")
|
||||
.allowlist_function("display_sync")
|
||||
.allowlist_var("DISPLAY_CMD_ADDRESS")
|
||||
.allowlist_var("DISPLAY_DATA_ADDRESS")
|
||||
.allowlist_type("toif_format_t")
|
||||
// fonts
|
||||
@ -347,18 +337,12 @@ fn generate_trezorhal_bindings() {
|
||||
.no_copy("buffer_jpeg_t")
|
||||
.no_copy("buffer_jpeg_work_t")
|
||||
.no_copy("buffer_blurring_t")
|
||||
.allowlist_var("text_buffer_height")
|
||||
.allowlist_var("buffer_width")
|
||||
//usb
|
||||
.allowlist_function("usb_configured")
|
||||
// touch
|
||||
.allowlist_function("touch_read")
|
||||
// button
|
||||
.allowlist_function("button_read")
|
||||
.allowlist_var("BTN_EVT_DOWN")
|
||||
.allowlist_var("BTN_EVT_UP")
|
||||
.allowlist_var("BTN_RIGHT")
|
||||
.allowlist_var("BTN_LEFT");
|
||||
.allowlist_function("button_read");
|
||||
|
||||
// Write the bindings to a file in the OUR_DIR.
|
||||
bindings
|
||||
|
@ -21,6 +21,7 @@ impl IterBuf {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // iter_buf is not really used but needs to be there for SAFETY reasons
|
||||
pub struct Iter<'a> {
|
||||
iter: Obj,
|
||||
iter_buf: &'a mut IterBuf,
|
||||
@ -45,14 +46,6 @@ impl<'a> Iter<'a> {
|
||||
caught_exception: Obj::const_null(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn error(&self) -> Option<Error> {
|
||||
if self.caught_exception.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(Error::CaughtException(self.caught_exception))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
|
@ -21,6 +21,7 @@ pub unsafe fn try_or_raise<T>(func: impl FnOnce() -> Result<T, Error>) -> T {
|
||||
/// Extract kwargs from a C call and pass them into Rust. Raise exception if an
|
||||
/// error occurs. Should only called when returning from Rust to C. See
|
||||
/// `raise_exception` for details.
|
||||
#[allow(dead_code)]
|
||||
pub unsafe fn try_with_kwargs(
|
||||
kwargs: *const Map,
|
||||
func: impl FnOnce(&Map) -> Result<Obj, Error>,
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::trezorhal::storage::{get, get_length};
|
||||
|
||||
pub const HOMESCREEN_MAX_SIZE: usize = 16384;
|
||||
|
@ -1,5 +1,6 @@
|
||||
use super::ffi;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn dma2d_setup_4bpp(fg_color: u16, bg_color: u16) {
|
||||
unsafe { ffi::dma2d_setup_4bpp(fg_color, bg_color) }
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
use super::ffi;
|
||||
|
||||
#[cfg(feature = "button")]
|
||||
pub use super::ffi::{BTN_EVT_DOWN, BTN_EVT_UP, BTN_LEFT, BTN_RIGHT};
|
||||
|
||||
#[cfg(feature = "touch")]
|
||||
pub fn io_touch_read() -> u32 {
|
||||
unsafe { ffi::touch_read() }
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use super::ffi;
|
||||
|
||||
pub fn set_color(color: u32) {
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use super::ffi;
|
||||
use crate::error::Error;
|
||||
use core::ptr;
|
||||
|
@ -1,288 +0,0 @@
|
||||
use crate::ui::{component::LineBreaking, display::Font};
|
||||
use core::iter;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
struct LineBreak {
|
||||
/// Index of character **after** the line-break.
|
||||
offset: usize,
|
||||
/// Distance from the last line-break of the sequence, in pixels.
|
||||
width: i16,
|
||||
style: BreakStyle,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
enum BreakStyle {
|
||||
Hard,
|
||||
AtWhitespaceOrWordBoundary,
|
||||
InsideWord,
|
||||
}
|
||||
|
||||
fn limit_line_breaks(
|
||||
breaks: impl Iterator<Item = LineBreak>,
|
||||
line_height: i16,
|
||||
available_height: i16,
|
||||
) -> impl Iterator<Item = LineBreak> {
|
||||
breaks.take(available_height as usize / line_height as usize)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
enum Appendix {
|
||||
None,
|
||||
Hyphen,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
struct Span<'a> {
|
||||
text: &'a str,
|
||||
append: Appendix,
|
||||
}
|
||||
|
||||
fn break_text_to_spans(
|
||||
text: &str,
|
||||
text_font: impl GlyphMetrics,
|
||||
hyphen_font: impl GlyphMetrics,
|
||||
breaking: LineBreaking,
|
||||
available_width: i16,
|
||||
) -> impl Iterator<Item = Span> {
|
||||
let mut finished = false;
|
||||
let mut last_break = LineBreak {
|
||||
offset: 0,
|
||||
width: 0,
|
||||
style: BreakStyle::AtWhitespaceOrWordBoundary,
|
||||
};
|
||||
let mut breaks = select_line_breaks(
|
||||
text.char_indices(),
|
||||
text_font,
|
||||
hyphen_font,
|
||||
breaking,
|
||||
available_width,
|
||||
);
|
||||
iter::from_fn(move || {
|
||||
if finished {
|
||||
None
|
||||
} else if let Some(lb) = breaks.next() {
|
||||
let start_of_line = last_break.offset;
|
||||
let end_of_line = lb.offset; // Not inclusive.
|
||||
last_break = lb;
|
||||
if let BreakStyle::AtWhitespaceOrWordBoundary = lb.style {
|
||||
last_break.offset += 1;
|
||||
}
|
||||
Some(Span {
|
||||
text: &text[start_of_line..end_of_line],
|
||||
append: match lb.style {
|
||||
BreakStyle::Hard | BreakStyle::AtWhitespaceOrWordBoundary => Appendix::None,
|
||||
BreakStyle::InsideWord => Appendix::Hyphen,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
finished = true;
|
||||
Some(Span {
|
||||
text: &text[last_break.offset..],
|
||||
append: Appendix::None,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn select_line_breaks(
|
||||
chars: impl Iterator<Item = (usize, char)>,
|
||||
text_font: impl GlyphMetrics,
|
||||
hyphen_font: impl GlyphMetrics,
|
||||
breaking: LineBreaking,
|
||||
available_width: i16,
|
||||
) -> impl Iterator<Item = LineBreak> {
|
||||
let hyphen_width = hyphen_font.char_width('-');
|
||||
|
||||
let mut proposed = None;
|
||||
let mut line_width = 0;
|
||||
let mut found_any_whitespace = false;
|
||||
|
||||
chars.filter_map(move |(offset, ch)| {
|
||||
let char_width = text_font.char_width(ch);
|
||||
let exceeds_available_width = line_width + char_width > available_width;
|
||||
let have_space_for_break = line_width + char_width + hyphen_width <= available_width;
|
||||
let can_break_word =
|
||||
matches!(breaking, LineBreaking::BreakWordsAndInsertHyphen) || !found_any_whitespace;
|
||||
|
||||
let break_line = match ch {
|
||||
'\n' | '\r' => {
|
||||
// Immediate hard break.
|
||||
Some(LineBreak {
|
||||
offset,
|
||||
width: line_width,
|
||||
style: BreakStyle::Hard,
|
||||
})
|
||||
}
|
||||
' ' | '\t' => {
|
||||
// Whitespace, propose a line-break before this character.
|
||||
proposed = Some(LineBreak {
|
||||
offset,
|
||||
width: line_width,
|
||||
style: BreakStyle::AtWhitespaceOrWordBoundary,
|
||||
});
|
||||
found_any_whitespace = true;
|
||||
None
|
||||
}
|
||||
_ if have_space_for_break && can_break_word => {
|
||||
// Propose a word-break after this character. In case the next character is
|
||||
// whitespace, the proposed word break is replaced by a whitespace break.
|
||||
proposed = Some(LineBreak {
|
||||
offset: offset + 1,
|
||||
width: line_width + char_width + hyphen_width,
|
||||
style: BreakStyle::InsideWord,
|
||||
});
|
||||
None
|
||||
}
|
||||
_ if exceeds_available_width => {
|
||||
// Consume the last proposed line-break. In case we don't have anything
|
||||
// proposed, we hard-break immediately before this character. This only happens
|
||||
// if the first character of the line doesn't fit.
|
||||
Some(proposed.unwrap_or(LineBreak {
|
||||
offset,
|
||||
width: line_width,
|
||||
style: BreakStyle::Hard,
|
||||
}))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if break_line.is_some() {
|
||||
// Reset the state.
|
||||
proposed = None;
|
||||
line_width = 0;
|
||||
found_any_whitespace = false;
|
||||
} else {
|
||||
// Shift cursor.
|
||||
line_width += char_width;
|
||||
}
|
||||
break_line
|
||||
})
|
||||
}
|
||||
|
||||
pub trait GlyphMetrics {
|
||||
fn char_width(&self, ch: char) -> i16;
|
||||
fn text_width(&self, text: &str) -> i16;
|
||||
fn line_height(&self) -> i16;
|
||||
}
|
||||
|
||||
impl GlyphMetrics for Font {
|
||||
fn char_width(&self, ch: char) -> i16 {
|
||||
Font::char_width(*self, ch)
|
||||
}
|
||||
|
||||
fn text_width(&self, text: &str) -> i16 {
|
||||
Font::text_width(*self, text)
|
||||
}
|
||||
|
||||
fn line_height(&self) -> i16 {
|
||||
Font::line_height(*self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_selected_line_breaks() {
|
||||
assert_eq!(line_breaks("abcd ef", 34), vec![inside_word(2, 25)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_break_text() {
|
||||
assert_eq!(
|
||||
break_text("abcd ef", 24),
|
||||
vec![
|
||||
Span {
|
||||
text: "a",
|
||||
append: Appendix::Hyphen
|
||||
},
|
||||
Span {
|
||||
text: "bcd",
|
||||
append: Appendix::None
|
||||
},
|
||||
Span {
|
||||
text: "ef",
|
||||
append: Appendix::None
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct Fixed {
|
||||
width: i16,
|
||||
height: i16,
|
||||
}
|
||||
|
||||
impl GlyphMetrics for Fixed {
|
||||
fn char_width(&self, _ch: char) -> i16 {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn line_height(&self) -> i16 {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn text_width(&self, text: &str) -> i16 {
|
||||
self.width * text.len() as i16
|
||||
}
|
||||
}
|
||||
|
||||
fn break_text(s: &str, w: i16) -> Vec<Span> {
|
||||
break_text_to_spans(
|
||||
s,
|
||||
Fixed {
|
||||
width: 10,
|
||||
height: 10,
|
||||
},
|
||||
Fixed {
|
||||
width: 5,
|
||||
height: 10,
|
||||
},
|
||||
LineBreaking::BreakWordsAndInsertHyphen,
|
||||
w,
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn line_breaks(s: &str, w: i16) -> Vec<LineBreak> {
|
||||
select_line_breaks(
|
||||
s.char_indices(),
|
||||
Fixed {
|
||||
width: 10,
|
||||
height: 10,
|
||||
},
|
||||
Fixed {
|
||||
width: 5,
|
||||
height: 10,
|
||||
},
|
||||
LineBreaking::BreakWordsAndInsertHyphen,
|
||||
w,
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn hard(offset: usize, width: i16) -> LineBreak {
|
||||
LineBreak {
|
||||
offset,
|
||||
width,
|
||||
style: BreakStyle::Hard,
|
||||
}
|
||||
}
|
||||
|
||||
fn whitespace(offset: usize, width: i16) -> LineBreak {
|
||||
LineBreak {
|
||||
offset,
|
||||
width,
|
||||
style: BreakStyle::AtWhitespaceOrWordBoundary,
|
||||
}
|
||||
}
|
||||
|
||||
fn inside_word(offset: usize, width: i16) -> LineBreak {
|
||||
LineBreak {
|
||||
offset,
|
||||
width,
|
||||
style: BreakStyle::InsideWord,
|
||||
}
|
||||
}
|
||||
}
|
@ -632,8 +632,6 @@ pub fn icon_over_icon(
|
||||
bg_buffer_used = &mut *bg2;
|
||||
}
|
||||
|
||||
const BUFFER_BPP: usize = 4;
|
||||
|
||||
let using_fg = process_buffer(
|
||||
y,
|
||||
r_fg,
|
||||
|
@ -52,8 +52,6 @@ pub const HOMESCREEN_TOIF_X_OFFSET: usize = ((WIDTH - HOMESCREEN_TOIF_SIZE) / 2)
|
||||
const HOMESCREEN_MAX_ICON_SIZE: i16 = 20;
|
||||
const NOTIFICATION_HEIGHT: i16 = 36;
|
||||
const NOTIFICATION_BORDER: i16 = 6;
|
||||
const NOTIFICATION_ICON_SPACE: i16 = 8;
|
||||
const NOTIFICATION_TEXT_OFFSET: Offset = Offset::new(1, -2);
|
||||
const TEXT_ICON_SPACE: i16 = 2;
|
||||
|
||||
const HOMESCREEN_DIM_HEIGHT: i16 = 35;
|
||||
|
@ -10,10 +10,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
pub const HEADER_HEIGHT: i16 = 25;
|
||||
pub const HEADER_PADDING_SIDE: i16 = 5;
|
||||
pub const HEADER_PADDING_BOTTOM: i16 = 12;
|
||||
|
||||
/// Contains state commonly used in implementations multi-tap keyboards.
|
||||
pub struct MultiTapKeyboard {
|
||||
/// Configured timeout after which we cancel currently pending key.
|
||||
|
@ -29,7 +29,6 @@ const MAX_VISIBLE_DOTS: usize = 14;
|
||||
const MAX_VISIBLE_DIGITS: usize = 16;
|
||||
const DIGIT_COUNT: usize = 10; // 0..10
|
||||
|
||||
const HEADER_HEIGHT: i16 = 25;
|
||||
const HEADER_PADDING_SIDE: i16 = 5;
|
||||
const HEADER_PADDING_BOTTOM: i16 = 12;
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
#[cfg(not(feature = "bootloader"))]
|
||||
use crate::ui::display;
|
||||
#[cfg(feature = "bootloader")]
|
||||
use crate::ui::model_tt::bootloader::theme::DEVICE_NAME;
|
||||
use crate::ui::{
|
||||
component::{Component, Event, EventCtx, Never},
|
||||
display::{self, Icon},
|
||||
display::Icon,
|
||||
geometry::{self, Offset, Rect},
|
||||
model_tt::theme,
|
||||
};
|
||||
@ -10,6 +12,7 @@ use crate::ui::{
|
||||
const TEXT_BOTTOM_MARGIN: i16 = 24; // matching the homescreen label margin
|
||||
const ICON_TOP_MARGIN: i16 = 48;
|
||||
const MODEL_NAME: &str = "Trezor Model T";
|
||||
#[cfg(not(feature = "bootloader"))]
|
||||
const MODEL_NAME_FONT: display::Font = display::Font::DEMIBOLD;
|
||||
|
||||
pub struct WelcomeScreen {
|
||||
|
@ -1,28 +0,0 @@
|
||||
use core::convert::TryInto;
|
||||
|
||||
use crate::{error, ui::geometry::Point};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum TouchEvent {
|
||||
/// A person has started touching the screen at given absolute coordinates.
|
||||
/// `TouchMove` will usually follow, and `TouchEnd` should finish the
|
||||
/// interaction.
|
||||
TouchStart(Point),
|
||||
/// Touch has moved into a different point on the screen.
|
||||
TouchMove(Point),
|
||||
/// Touch has ended at a point on the screen.
|
||||
TouchEnd(Point),
|
||||
}
|
||||
|
||||
impl TouchEvent {
|
||||
pub fn new(event: u32, x: u32, y: u32) -> Result<Self, error::Error> {
|
||||
let point = Point::new(x.try_into()?, y.try_into()?);
|
||||
let result = match event {
|
||||
1 => Self::TouchStart(point),
|
||||
2 => Self::TouchMove(point),
|
||||
4 => Self::TouchEnd(point),
|
||||
_ => return Err(error::Error::OutOfRange),
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
}
|
@ -685,29 +685,6 @@ extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs:
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_show_qr(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 address: StrBuffer = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?;
|
||||
let verb_cancel: StrBuffer = kwargs.get(Qstr::MP_QSTR_verb_cancel)?.try_into()?;
|
||||
let case_sensitive: bool = kwargs.get(Qstr::MP_QSTR_case_sensitive)?.try_into()?;
|
||||
|
||||
let buttons = Button::cancel_confirm(
|
||||
Button::with_text(verb_cancel),
|
||||
Button::with_text("CONFIRM".into()).styled(theme::button_confirm()),
|
||||
false,
|
||||
);
|
||||
|
||||
let obj = LayoutObj::new(Frame::left_aligned(
|
||||
theme::label_title(),
|
||||
title,
|
||||
Dialog::new(Qr::new(address, case_sensitive)?.with_border(4), buttons),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_show_address_details(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let address: StrBuffer = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?;
|
||||
@ -1671,16 +1648,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// """Confirm TOS before device setup."""
|
||||
Qstr::MP_QSTR_confirm_reset_device => obj_fn_kw!(0, new_confirm_reset_device).as_obj(),
|
||||
|
||||
/// def show_qr(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// address: str,
|
||||
/// verb_cancel: str,
|
||||
/// case_sensitive: bool,
|
||||
/// ) -> object:
|
||||
/// """Show QR code."""
|
||||
Qstr::MP_QSTR_show_qr => obj_fn_kw!(0, new_show_qr).as_obj(),
|
||||
|
||||
/// def show_address_details(
|
||||
/// *,
|
||||
/// address: str,
|
||||
|
@ -2,7 +2,6 @@
|
||||
pub mod bootloader;
|
||||
pub mod component;
|
||||
pub mod constant;
|
||||
pub mod event;
|
||||
pub mod theme;
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
|
Loading…
Reference in New Issue
Block a user