feat(core): added user adjustable brightness setting

pull/3483/head
tychovrahe 5 months ago
parent 6b31b8eec3
commit 6e6638773b

@ -181,6 +181,7 @@ message ApplySettings {
optional SafetyCheckLevel safety_checks = 9; // Safety check level, set to Prompt to limit path namespace enforcement
optional bool experimental_features = 10; // enable experimental message types
optional bool hide_passphrase_from_host = 11; // do not show passphrase coming from host
optional uint32 brightness = 12; // display brightness, 0 = select on device
}
/**

@ -0,0 +1 @@
[T2T1] Added user adjustable brightness setting.

@ -148,10 +148,14 @@ int main(void) {
dma2d_init();
#endif
#if defined TREZOR_MODEL_T
set_core_clock(CLOCK_180_MHZ);
#endif
display_reinit();
#ifdef STM32U5
check_oem_keys();
//check_oem_keys();
#endif
screen_boot_stage_2();

@ -131,6 +131,7 @@ static void _librust_qstrs(void) {
MP_QSTR_buttons__try_again;
MP_QSTR_buttons__turn_off;
MP_QSTR_buttons__turn_on;
MP_QSTR_callback;
MP_QSTR_can_go_back;
MP_QSTR_cancel_arrow;
MP_QSTR_cancel_cross;
@ -382,6 +383,7 @@ static void _librust_qstrs(void) {
MP_QSTR_request_bip39;
MP_QSTR_request_complete_repaint;
MP_QSTR_request_number;
MP_QSTR_request_number_slider;
MP_QSTR_request_passphrase;
MP_QSTR_request_pin;
MP_QSTR_request_slip39;

@ -36,6 +36,7 @@ const INITIALIZED: u16 = FLAG_PUBLIC | APP_DEVICE | 0x0013;
const SAFETY_CHECK_LEVEL: u16 = APP_DEVICE | 0x0014;
const EXPERIMENTAL_FEATURES: u16 = APP_DEVICE | 0x0015;
const HIDE_PASSPHRASE_FROM_HOST: u16 = APP_DEVICE | 0x0016;
const BRIGHTNESS: u16 = FLAG_PUBLIC | APP_DEVICE | 0x0017;
pub fn get_avatar_len() -> StorageResult<usize> {
get_length(HOMESCREEN)
@ -47,3 +48,12 @@ pub fn load_avatar(dest: &mut [u8]) -> StorageResult<()> {
ensure!(dest_len == result.len(), "Internal error in load_avatar");
Ok(())
}
pub fn get_brightness() -> StorageResult<u8> {
let mut dest: [u8; 1] = [0; 1];
let res = get(BRIGHTNESS, &mut dest);
match res {
Ok(_) => Ok(dest[0]),
Err(e) => Err(e),
}
}

@ -0,0 +1,41 @@
#[cfg(not(feature = "bootloader"))]
use crate::storage;
// Typical backlight values.
#[cfg(feature = "bootloader")]
pub fn get_backlight_normal() -> u16 {
150
}
#[cfg(not(feature = "bootloader"))]
pub fn get_backlight_normal() -> u16 {
storage::get_brightness().unwrap_or(150).into()
}
#[cfg(feature = "bootloader")]
pub fn get_backlight_low() -> u16 {
150
}
#[cfg(not(feature = "bootloader"))]
pub fn get_backlight_low() -> u16 {
let val = storage::get_brightness();
return if val.is_ok() && val.unwrap() < 45 {
val.unwrap().into()
} else {
45
};
}
pub fn get_backlight_dim() -> u16 {
5
}
pub fn get_backlight_none() -> u16 {
2
}
pub fn get_backlight_max() -> u16 {
255
}

@ -1,5 +1,7 @@
pub mod bootloader;
pub mod backlight;
use crate::ui::{
component::text::{LineBreaking, PageBreaking, TextStyle},
display::{Color, Font, Icon},
@ -8,10 +10,6 @@ use crate::ui::{
use super::component::{ButtonStyle, ButtonStyleSheet, LoaderStyle, LoaderStyleSheet};
// Typical backlight values.
pub const BACKLIGHT_NORMAL: u16 = 150;
pub const BACKLIGHT_DIM: u16 = 5;
// Color palette.
pub const WHITE: Color = Color::rgb(0xFF, 0xFF, 0xFF);
pub const BLACK: Color = Color::rgb(0, 0, 0);

@ -16,6 +16,7 @@ use super::{
bl_confirm::{Confirm, ConfirmTitle},
Button, ResultScreen, WelcomeScreen,
},
theme,
theme::{
bootloader::{
button_bld, button_bld_menu, button_confirm, button_wipe_cancel, button_wipe_confirm,
@ -23,7 +24,7 @@ use super::{
FIRE40, RESULT_FW_INSTALL, RESULT_INITIAL, RESULT_WIPE, TEXT_BOLD, TEXT_NORMAL,
TEXT_WIPE_BOLD, TEXT_WIPE_NORMAL, WARNING40, WELCOME_COLOR, X24,
},
BACKLIGHT_NORMAL, BLACK, FG, WHITE,
BLACK, FG, WHITE,
},
ModelTTFeatures,
};
@ -258,7 +259,7 @@ impl UIFeaturesBootloader for ModelTTFeatures {
if fading {
Self::fadein();
} else {
display::set_backlight(BACKLIGHT_NORMAL);
display::set_backlight(theme::backlight::get_backlight_normal());
}
display::refresh();
}

@ -207,7 +207,7 @@ where
if self.fade.take() {
// Note that this is blocking and takes some time.
display::fade_backlight(theme::BACKLIGHT_NORMAL);
display::fade_backlight(theme::backlight::get_backlight_normal());
}
}

@ -295,7 +295,7 @@ impl Component for PassphraseKeyboard {
}
if self.fade.take() {
// Note that this is blocking and takes some time.
display::fade_backlight(theme::BACKLIGHT_NORMAL);
display::fade_backlight(theme::backlight::get_backlight_normal());
}
}

@ -16,6 +16,7 @@ mod keyboard;
mod loader;
#[cfg(feature = "translations")]
mod number_input;
mod number_input_slider;
#[cfg(feature = "translations")]
mod page;
mod progress;
@ -50,6 +51,7 @@ pub use keyboard::{
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
#[cfg(feature = "translations")]
pub use number_input::{NumberInputDialog, NumberInputDialogMsg};
pub use number_input_slider::{NumberInputSliderDialog, NumberInputSliderDialogMsg};
#[cfg(feature = "translations")]
pub use page::ButtonPage;
pub use progress::Progress;

@ -0,0 +1,202 @@
use crate::ui::{
component::{base::ComponentExt, Child, Component, Event, EventCtx},
constant::screen,
display,
event::TouchEvent,
geometry::{Grid, Insets, Point, Rect},
};
use super::{theme, Button, ButtonMsg};
pub enum NumberInputSliderDialogMsg {
Confirmed,
Cancelled,
}
pub struct NumberInputSliderDialog<F>
where
F: Fn(u32),
{
area: Rect,
callback: F,
input: Child<NumberInputSlider>,
cancel_button: Child<Button>,
confirm_button: Child<Button>,
}
impl<F> NumberInputSliderDialog<F>
where
F: Fn(u32),
{
pub fn new(min: u32, max: u32, init_value: u32, callback: F) -> Self {
Self {
area: Rect::zero(),
callback,
input: NumberInputSlider::new(min, max, init_value).into_child(),
cancel_button: Button::with_text("CANCEL".into())
.styled(theme::button_cancel())
.into_child(),
confirm_button: Button::with_text("CONFIRM".into())
.styled(theme::button_confirm())
.into_child(),
}
}
pub fn value(&self) -> u32 {
self.input.inner().value
}
}
impl<F> Component for NumberInputSliderDialog<F>
where
F: Fn(u32),
{
type Msg = NumberInputSliderDialogMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.area = bounds;
let button_height = theme::BUTTON_HEIGHT;
let content_area = self.area.inset(Insets::top(2 * theme::BUTTON_SPACING));
let (_, content_area) = content_area.split_top(30);
let (input_area, _) = content_area.split_top(15);
let (_, button_area) = content_area.split_bottom(button_height);
// let (content_area, button_area) = content_area.split_bottom(button_height);
// let content_area = content_area.inset(Insets::new(
// theme::BUTTON_SPACING,
// 0,
// theme::BUTTON_SPACING,
// theme::CONTENT_BORDER,
// ));
let grid = Grid::new(button_area, 1, 2).with_spacing(theme::KEYBOARD_SPACING);
self.input.place(input_area.inset(Insets::sides(20)));
self.cancel_button.place(grid.row_col(0, 0));
self.confirm_button.place(grid.row_col(0, 1));
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(NumberInputSliderMsg::Changed(i)) = self.input.event(ctx, event) {
(self.callback)(i);
}
if let Some(ButtonMsg::Clicked) = self.cancel_button.event(ctx, event) {
return Some(Self::Msg::Cancelled);
}
if let Some(ButtonMsg::Clicked) = self.confirm_button.event(ctx, event) {
return Some(Self::Msg::Confirmed);
};
None
}
fn paint(&mut self) {
self.input.paint();
// self.paragraphs_pad.paint();
// self.paragraphs.paint();
self.cancel_button.paint();
self.confirm_button.paint();
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.area);
self.input.bounds(sink);
// self.paragraphs.bounds(sink);
self.cancel_button.bounds(sink);
self.confirm_button.bounds(sink);
}
}
#[cfg(feature = "ui_debug")]
impl<F> crate::trace::Trace for NumberInputSliderDialog<F>
where
F: Fn(u32),
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("NumberInputSliderDialog");
t.child("input", &self.input);
t.child("cancel_button", &self.cancel_button);
t.child("confirm_button", &self.confirm_button);
}
}
pub enum NumberInputSliderMsg {
Changed(u32),
}
pub struct NumberInputSlider {
area: Rect,
touch_area: Rect,
min: u32,
max: u32,
value: u32,
}
impl NumberInputSlider {
pub fn new(min: u32, max: u32, value: u32) -> Self {
let value = value.clamp(min, max);
Self {
area: Rect::zero(),
touch_area: Rect::zero(),
min,
max,
value,
}
}
pub fn slider_eval(&mut self, pos: Point, ctx: &mut EventCtx) -> Option<NumberInputSliderMsg> {
if self.touch_area.contains(pos) {
let filled = pos.x - self.area.x0;
let filled = filled.clamp(0, self.area.width());
let val_pct = (filled as u32 * 100) / self.area.width() as u32;
let val = (val_pct * (self.max - self.min)) / 100 + self.min;
if val != self.value {
self.value = val;
ctx.request_paint();
return Some(NumberInputSliderMsg::Changed(self.value));
}
}
None
}
}
impl Component for NumberInputSlider {
type Msg = NumberInputSliderMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.area = bounds;
self.touch_area = bounds.outset(Insets::new(40, 20, 40, 20)).clamp(screen());
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Event::Touch(touch_event) = event {
return match touch_event {
TouchEvent::TouchStart(pos) => self.slider_eval(pos, ctx),
TouchEvent::TouchMove(pos) => self.slider_eval(pos, ctx),
TouchEvent::TouchEnd(pos) => self.slider_eval(pos, ctx),
};
}
None
}
fn paint(&mut self) {
let val_pct = (100 * (self.value - self.min)) / (self.max - self.min);
let fill_to = (val_pct as i16 * self.area.width()) / 100;
display::bar_with_text_and_fill(self.area, None, theme::FG, theme::BG, 0, fill_to as _);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.area)
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for NumberInputSlider {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("NumberInput");
t.int("value", self.value as i64);
}
}

@ -159,7 +159,7 @@ where
// Swipe has dimmed the screen, so fade back to normal backlight after the next
// paint.
self.fade = Some(theme::BACKLIGHT_NORMAL);
self.fade = Some(theme::backlight::get_backlight_normal());
}
fn is_cancel_visible(&self) -> bool {

@ -80,7 +80,8 @@ where
// Swipe has dimmed the screen, so fade back to normal backlight after the next
// paint.
self.fade.set(Some(theme::BACKLIGHT_NORMAL));
self.fade
.set(Some(theme::backlight::get_backlight_normal()));
}
fn is_horizontal(&self) -> bool {

@ -3,10 +3,9 @@ use crate::ui::{
display,
event::TouchEvent,
geometry::{Point, Rect},
model_tt::theme::backlight,
};
use super::theme;
pub enum SwipeDirection {
Up,
Down,
@ -36,8 +35,8 @@ impl Swipe {
allow_down: false,
allow_left: false,
allow_right: false,
backlight_start: theme::BACKLIGHT_NORMAL,
backlight_end: theme::BACKLIGHT_NONE,
backlight_start: backlight::get_backlight_normal(),
backlight_end: backlight::get_backlight_none(),
origin: None,
}
}

@ -44,8 +44,9 @@ use super::{
CancelConfirmMsg, CancelInfoConfirmMsg, CoinJoinProgress, Dialog, DialogMsg, FidoConfirm,
FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg, IconDialog, Lockscreen, MnemonicInput,
MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputDialog, NumberInputDialogMsg,
PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress,
SelectWordCount, SelectWordCountMsg, SelectWordMsg, SimplePage, Slip39Input,
NumberInputSliderDialog, NumberInputSliderDialogMsg, PassphraseKeyboard,
PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress, SelectWordCount,
SelectWordCountMsg, SelectWordMsg, SimplePage, Slip39Input,
},
theme,
};
@ -245,6 +246,19 @@ where
}
}
impl<F> ComponentMsgObj for NumberInputSliderDialog<F>
where
F: Fn(u32),
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
let value = self.value().try_into()?;
match msg {
NumberInputSliderDialogMsg::Confirmed => Ok((CONFIRMED.as_obj(), value).try_into()?),
NumberInputSliderDialogMsg::Cancelled => Ok((CANCELLED.as_obj(), value).try_into()?),
}
}
}
impl<T> ComponentMsgObj for Border<T>
where
T: ComponentMsgObj,
@ -1341,6 +1355,31 @@ extern "C" fn new_request_number(n_args: usize, args: *const Obj, kwargs: *mut M
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_request_number_slider(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let min_count: u32 = kwargs.get(Qstr::MP_QSTR_min_count)?.try_into()?;
let max_count: u32 = kwargs.get(Qstr::MP_QSTR_max_count)?.try_into()?;
let count: u32 = kwargs.get(Qstr::MP_QSTR_count)?.try_into()?;
let value_callback: Obj = kwargs.get(Qstr::MP_QSTR_callback)?;
assert!(value_callback != Obj::const_none());
let callback = move |i: u32| {
value_callback
.call_with_n_args(&[i.try_into().unwrap()])
.unwrap();
};
let obj = LayoutObj::new(Frame::centered(
theme::label_title(),
title,
NumberInputSliderDialog::new(min_count, max_count, count, callback),
))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
@ -2036,6 +2075,18 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Number input with + and - buttons, description, and info button."""
Qstr::MP_QSTR_request_number => obj_fn_kw!(0, new_request_number).as_obj(),
/// def request_number_slider(
/// *,
/// title: str,
/// count: int,
/// min_count: int,
/// max_count: int,
/// callback: Callable[[int], None] | None = None,
/// ) -> object:
/// """Number input with slider."""
Qstr::MP_QSTR_request_number_slider => obj_fn_kw!(0, new_request_number_slider).as_obj(),
/// def show_checklist(
/// *,
/// title: str,

@ -6,6 +6,9 @@ pub mod component;
pub mod constant;
pub mod theme;
#[cfg(feature = "backlight")]
use crate::ui::model_tt::theme::backlight;
#[cfg(feature = "micropython")]
pub mod layout;
mod screens;
@ -15,17 +18,17 @@ pub struct ModelTTFeatures;
impl UIFeaturesCommon for ModelTTFeatures {
fn fadein() {
#[cfg(feature = "backlight")]
crate::ui::display::fade_backlight_duration(theme::BACKLIGHT_NORMAL, 150);
crate::ui::display::fade_backlight_duration(backlight::get_backlight_normal(), 150);
}
fn fadeout() {
#[cfg(feature = "backlight")]
crate::ui::display::fade_backlight_duration(theme::BACKLIGHT_DIM, 150);
crate::ui::display::fade_backlight_duration(backlight::get_backlight_normal(), 150);
}
fn backlight_on() {
#[cfg(feature = "backlight")]
crate::ui::display::set_backlight(theme::BACKLIGHT_NORMAL);
crate::ui::display::set_backlight(backlight::get_backlight_normal());
}
const SCREEN: Rect = constant::SCREEN;

@ -0,0 +1,30 @@
#[cfg(not(feature = "bootloader"))]
use crate::storage;
// Typical backlight values.
#[cfg(feature = "bootloader")]
pub fn get_backlight_normal() -> u16 {
150
}
#[cfg(not(feature = "bootloader"))]
pub fn get_backlight_normal() -> u16 {
storage::get_brightness().unwrap_or(150).into()
}
pub fn get_backlight_low() -> u16 {
45
}
pub fn get_backlight_dim() -> u16 {
5
}
pub fn get_backlight_none() -> u16 {
2
}
pub fn get_backlight_max() -> u16 {
255
}

@ -1,3 +1,4 @@
pub mod backlight;
pub mod bootloader;
use crate::{
@ -18,13 +19,6 @@ use num_traits::FromPrimitive;
pub const ERASE_HOLD_DURATION: Duration = Duration::from_millis(1500);
// Typical backlight values.
pub const BACKLIGHT_NORMAL: u16 = 150;
pub const BACKLIGHT_LOW: u16 = 45;
pub const BACKLIGHT_DIM: u16 = 5;
pub const BACKLIGHT_NONE: u16 = 2;
pub const BACKLIGHT_MAX: u16 = 255;
// Color palette.
pub const WHITE: Color = Color::rgb(0xFF, 0xFF, 0xFF);
pub const BLACK: Color = Color::rgb(0, 0, 0);

@ -4,9 +4,11 @@
#include STM32_HAL_H
#include TREZOR_BOARD
#define TIM_FREQ 1000000
#define TIM_FREQ 10000000
#define LED_PWM_PRESCALER (SystemCoreClock / TIM_FREQ - 1) // 1 MHz
#define LED_PWM_PRESCALER (SystemCoreClock / TIM_FREQ - 1)
#define LED_PWM_PRESCALER_SLOW (SystemCoreClock / 1000000 - 1) // 1 MHz
#define LED_PWM_TIM_PERIOD (TIM_FREQ / BACKLIGHT_PWM_FREQ)
@ -16,6 +18,35 @@ static int pwm_period = 0;
int backlight_pwm_set(int val) {
if (BACKLIGHT != val && val >= 0 && val <= 255) {
// TPS61043: min 1% duty cycle
if (val < BACKLIGHT_PWM_TIM->ARR / 100) {
val = 0;
}
// TPS61043 goes to shutdown when duty cycle is 0 (after 32ms),
// so we need to set GPIO to high for at least 500us
// to wake it up.
if (BACKLIGHT_PWM_TIM->BACKLIGHT_PWM_TIM_CCR == 0) {
GPIO_InitTypeDef GPIO_InitStructure = {0};
HAL_GPIO_WritePin(BACKLIGHT_PWM_PORT, BACKLIGHT_PWM_PIN, GPIO_PIN_SET);
// LCD_PWM/PA7 (backlight control)
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStructure.Pin = BACKLIGHT_PWM_PIN;
HAL_GPIO_Init(BACKLIGHT_PWM_PORT, &GPIO_InitStructure);
hal_delay_us(500);
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStructure.Alternate = BACKLIGHT_PWM_TIM_AF;
GPIO_InitStructure.Pin = BACKLIGHT_PWM_PIN;
HAL_GPIO_Init(BACKLIGHT_PWM_PORT, &GPIO_InitStructure);
}
BACKLIGHT = val;
BACKLIGHT_PWM_TIM->CCR1 = (pwm_period * val) / 255;
}
@ -157,6 +188,7 @@ void backlight_pwm_reinit(void) {
BACKLIGHT = prev_val;
pwm_period = LED_PWM_TIM_PERIOD;
BACKLIGHT_PWM_TIM->PSC = LED_PWM_PRESCALER;
BACKLIGHT_PWM_TIM->CR1 |= TIM_CR1_ARPE;
BACKLIGHT_PWM_TIM->CR2 |= TIM_CR2_CCPC;
BACKLIGHT_PWM_TIM->BACKLIGHT_PWM_TIM_CCR = (pwm_period * prev_val) / 255;
@ -176,6 +208,7 @@ void backlight_pwm_set_slow(void) {
prev_val = prev_val > 255 ? 255 : prev_val;
pwm_period = LED_PWM_SLOW_TIM_PERIOD;
BACKLIGHT_PWM_TIM->PSC = LED_PWM_PRESCALER_SLOW;
BACKLIGHT_PWM_TIM->CR1 |= TIM_CR1_ARPE;
BACKLIGHT_PWM_TIM->CR2 |= TIM_CR2_CCPC;
BACKLIGHT_PWM_TIM->ARR = LED_PWM_SLOW_TIM_PERIOD - 1;

@ -866,6 +866,18 @@ def request_number(
"""Number input with + and - buttons, description, and info button."""
# rust/src/ui/model_tt/layout.rs
def request_number_slider(
*,
title: str,
count: int,
min_count: int,
max_count: int,
callback: Callable[[int], None] | None = None,
) -> object:
"""Number input with slider."""
# rust/src/ui/model_tt/layout.rs
def show_checklist(
*,

@ -179,6 +179,8 @@ trezor.ui.layouts.tt.fido
import trezor.ui.layouts.tt.fido
trezor.ui.layouts.tt.homescreen
import trezor.ui.layouts.tt.homescreen
trezor.ui.layouts.tt.progress
import trezor.ui.layouts.tt.progress
trezor.ui.layouts.tt.recovery
import trezor.ui.layouts.tt.recovery
trezor.ui.layouts.tt.reset

@ -434,7 +434,7 @@ def reload_settings_from_storage() -> None:
)
wire.EXPERIMENTAL_ENABLED = storage_device.get_experimental_features()
if ui.display.orientation() != storage_device.get_rotation():
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
ui.backlight_fade(ui.style.get_backlight_dim())
ui.display.orientation(storage_device.get_rotation())

@ -2,9 +2,10 @@ from typing import TYPE_CHECKING
import storage.device as storage_device
import trezorui2
from trezor import TR
from trezor import TR, utils
from trezor.enums import ButtonRequestType
from trezor.ui.layouts import confirm_action
from trezor.ui import display, style
from trezor.ui.layouts import confirm_action, request_number_slider
from trezor.wire import DataError
if TYPE_CHECKING:
@ -50,6 +51,7 @@ async def apply_settings(msg: ApplySettings) -> Success:
msg_safety_checks = msg.safety_checks # local_cache_attribute
experimental_features = msg.experimental_features # local_cache_attribute
hide_passphrase_from_host = msg.hide_passphrase_from_host # local_cache_attribute
brightness = msg.brightness
if (
homescreen is None
@ -61,6 +63,7 @@ async def apply_settings(msg: ApplySettings) -> Success:
and msg_safety_checks is None
and experimental_features is None
and hide_passphrase_from_host is None
and (brightness is None or not utils.USE_BACKLIGHT)
):
raise ProcessError("No setting provided")
@ -114,6 +117,10 @@ async def apply_settings(msg: ApplySettings) -> Success:
await _require_confirm_hide_passphrase_from_host(hide_passphrase_from_host)
storage_device.set_hide_passphrase_from_host(hide_passphrase_from_host)
if brightness is not None and utils.USE_BACKLIGHT:
new_brightness = await _require_set_brightness()
storage_device.set_brightness(new_brightness)
reload_settings_from_storage()
return Success(message="Settings applied")
@ -255,3 +262,19 @@ async def _require_confirm_hide_passphrase_from_host(enable: bool) -> None:
description=TR.passphrase__hide,
br_code=BRT_PROTECT_CALL,
)
if utils.USE_BACKLIGHT:
async def _require_set_brightness() -> int:
def callback(val: int) -> None:
display.backlight(val)
return await request_number_slider(
"Set brightness",
callback,
min_count=style.get_backlight_min(),
max_count=style.get_backlight_max(),
count=style.get_backlight_normal(),
br_name="set_brightness",
)

@ -49,7 +49,7 @@ async def bootscreen() -> None:
if can_lock_device():
enforce_welcome_screen_duration()
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
ui.backlight_fade(ui.style.get_backlight_dim())
ui.display.orientation(storage.device.get_rotation())
await lockscreen
await verify_user_pin()
@ -64,7 +64,7 @@ async def bootscreen() -> None:
if rotation != ui.display.orientation():
# there is a slight delay before next screen is shown,
# so we don't fade unless there is a change of orientation
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
ui.backlight_fade(ui.style.get_backlight_dim())
ui.display.orientation(rotation)
allow_all_loader_messages()
return

@ -49,12 +49,12 @@ def get_bool(app: int, key: int, public: bool = False) -> bool:
return get(app, key, public) == _TRUE_BYTE
def set_uint8(app: int, key: int, val: int) -> None:
set(app, key, val.to_bytes(1, "big"))
def set_uint8(app: int, key: int, val: int, public: bool = False) -> None:
set(app, key, val.to_bytes(1, "big"), public)
def get_uint8(app: int, key: int) -> int | None:
val = get(app, key)
def get_uint8(app: int, key: int, public: bool = False) -> int | None:
val = get(app, key, public)
if not val:
return None
return int.from_bytes(val, "big")

@ -35,6 +35,7 @@ INITIALIZED = const(0x13) # bool (0x01 or empty)
_SAFETY_CHECK_LEVEL = const(0x14) # int
_EXPERIMENTAL_FEATURES = const(0x15) # bool (0x01 or empty)
_HIDE_PASSPHRASE_FROM_HOST = const(0x16) # bool (0x01 or empty)
_BRIGHTNESS = const(0x17) # int
SAFETY_CHECK_LEVEL_STRICT : Literal[0] = const(0)
SAFETY_CHECK_LEVEL_PROMPT : Literal[1] = const(1)
@ -344,3 +345,17 @@ def get_hide_passphrase_from_host() -> bool:
Whether we should hide the passphrase from the host.
"""
return common.get_bool(_NAMESPACE, _HIDE_PASSPHRASE_FROM_HOST)
def set_brightness(brightness: int) -> None:
"""
Set the display brightness setting.
"""
common.set_uint8(_NAMESPACE, _BRIGHTNESS, brightness, True)
def get_brightness() -> int | None:
"""
Get the display brightness setting.
"""
return common.get_uint8(_NAMESPACE, _BRIGHTNESS, True)

@ -2233,6 +2233,7 @@ if TYPE_CHECKING:
safety_checks: "SafetyCheckLevel | None"
experimental_features: "bool | None"
hide_passphrase_from_host: "bool | None"
brightness: "int | None"
def __init__(
self,
@ -2246,6 +2247,7 @@ if TYPE_CHECKING:
safety_checks: "SafetyCheckLevel | None" = None,
experimental_features: "bool | None" = None,
hide_passphrase_from_host: "bool | None" = None,
brightness: "int | None" = None,
) -> None:
pass

@ -62,12 +62,12 @@ async def _alert(count: int) -> None:
long_sleep = loop.sleep(80)
for i in range(count * 2):
if i % 2 == 0:
display.backlight(style.BACKLIGHT_MAX)
display.backlight(style.get_backlight_max())
await short_sleep
else:
display.backlight(style.BACKLIGHT_DIM)
display.backlight(style.get_backlight_dim())
await long_sleep
display.backlight(style.BACKLIGHT_NORMAL)
display.backlight(style.get_backlight_normal())
global _alert_in_progress
_alert_in_progress = False

@ -34,11 +34,11 @@ class RustProgress:
layout: Any,
):
self.layout = layout
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
ui.backlight_fade(ui.style.get_backlight_dim())
self.layout.attach_timer_fn(self.set_timer)
self.layout.paint()
ui.refresh()
ui.backlight_fade(ui.style.BACKLIGHT_NORMAL)
ui.backlight_fade(ui.style.get_backlight_normal())
def set_timer(self, token: int, deadline: int) -> None:
raise RuntimeError # progress layouts should not set timers

@ -9,7 +9,7 @@ from trezor.wire.context import wait as ctx_wait
from ..common import button_request, interact
if TYPE_CHECKING:
from typing import Any, Awaitable, Iterable, NoReturn, Sequence, TypeVar
from typing import Any, Awaitable, Callable, Iterable, NoReturn, Sequence, TypeVar
from ..common import ExceptionType, PropertyType
@ -36,13 +36,13 @@ if __debug__:
class RustLayout(LayoutParentType[T]):
BACKLIGHT_LEVEL = ui.style.BACKLIGHT_NORMAL
# pylint: disable=super-init-not-called
def __init__(self, layout: trezorui2.LayoutObj[T]):
self.layout = layout
self.timer = loop.Timer()
self.layout.attach_timer_fn(self.set_timer)
self.backlight_level = ui.style.get_backlight_normal()
def set_timer(self, token: int, deadline: int) -> None:
self.timer.schedule(deadline, token)
@ -168,7 +168,7 @@ class RustLayout(LayoutParentType[T]):
return self.handle_timers(), self.handle_input_and_rendering()
def _first_paint(self) -> None:
ui.backlight_fade(ui.style.BACKLIGHT_NONE)
ui.backlight_fade(ui.style.get_backlight_none())
self._paint()
if __debug__ and self.should_notify_layout_change:
@ -191,7 +191,7 @@ class RustLayout(LayoutParentType[T]):
notify_layout_change(self, event_id)
# Turn the brightness on again.
ui.backlight_fade(self.BACKLIGHT_LEVEL)
ui.backlight_fade(self.backlight_level)
def handle_input_and_rendering(self) -> loop.Task: # type: ignore [awaitable-is-generator]
from trezor import workflow
@ -228,10 +228,10 @@ def draw_simple(layout: trezorui2.LayoutObj[Any]) -> None:
raise RuntimeError
layout.attach_timer_fn(dummy_set_timer)
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
ui.backlight_fade(ui.style.get_backlight_dim())
layout.paint()
ui.refresh()
ui.backlight_fade(ui.style.BACKLIGHT_NORMAL)
ui.backlight_fade(ui.style.get_backlight_normal())
async def raise_if_not_confirmed(
@ -1525,3 +1525,40 @@ def confirm_firmware_update(description: str, fingerprint: str) -> Awaitable[Non
BR_TYPE_OTHER,
)
)
async def request_number_slider(
title: str,
callback: Callable[[int], None],
count: int,
min_count: int,
max_count: int,
br_name: str,
) -> int:
num_input = RustLayout(
trezorui2.request_number_slider(
title=title.upper(),
callback=callback,
count=count,
min_count=min_count,
max_count=max_count,
)
)
while True:
result = await interact(
num_input,
br_name,
BR_TYPE_OTHER,
)
# if __debug__:
# if not isinstance(result, tuple):
# # DebugLink currently can't send number of shares and it doesn't
# # change the counter either so just use the initial value.
# result = (result, count)
status, value = result
if status == CONFIRMED:
assert isinstance(value, int)
return value
raise ActionCancelled

@ -85,7 +85,6 @@ class Homescreen(HomescreenBase):
class Lockscreen(HomescreenBase):
RENDER_INDICATOR = storage_cache.LOCKSCREEN_ON
BACKLIGHT_LEVEL = ui.style.BACKLIGHT_LOW
def __init__(
self,
@ -94,8 +93,9 @@ class Lockscreen(HomescreenBase):
coinjoin_authorized: bool = False,
) -> None:
self.bootscreen = bootscreen
self.backlight_level = ui.style.get_backlight_low()
if bootscreen:
self.BACKLIGHT_LEVEL = ui.style.BACKLIGHT_NORMAL
self.backlight_level = ui.style.get_backlight_normal()
skip = (
not bootscreen and storage_cache.homescreen_shown is self.RENDER_INDICATOR

@ -1,8 +1,41 @@
from micropython import const
import storage.device
# backlight brightness
BACKLIGHT_NORMAL = const(150)
BACKLIGHT_LOW = const(45)
BACKLIGHT_DIM = const(5)
BACKLIGHT_NONE = const(0)
BACKLIGHT_MAX = const(255)
_BACKLIGHT_NORMAL = const(150)
_BACKLIGHT_LOW = const(45)
_BACKLIGHT_DIM = const(5)
_BACKLIGHT_NONE = const(0)
_BACKLIGHT_MIN = const(10)
_BACKLIGHT_MAX = const(255)
def get_backlight_normal() -> int:
val = storage.device.get_brightness()
if val is None:
return _BACKLIGHT_NORMAL
return val
def get_backlight_low() -> int:
val = storage.device.get_brightness()
if val is None or val > _BACKLIGHT_LOW:
return _BACKLIGHT_LOW
return val
def get_backlight_dim() -> int:
return _BACKLIGHT_DIM
def get_backlight_none() -> int:
return _BACKLIGHT_NONE
def get_backlight_min() -> int:
return _BACKLIGHT_MIN
def get_backlight_max() -> int:
return _BACKLIGHT_MAX

@ -0,0 +1 @@
Added user adjustable brightness setting.

@ -206,6 +206,13 @@ def label(client: "TrezorClient", label: str) -> str:
return device.apply_settings(client, label=label)
@cli.command()
@with_client
def brightness(client: "TrezorClient") -> str:
"""Set display brightness."""
return device.apply_settings(client, brightness=0)
@cli.command()
@click.argument("path_or_url", required=False)
@click.option(

@ -47,6 +47,7 @@ def apply_settings(
safety_checks: Optional[messages.SafetyCheckLevel] = None,
experimental_features: Optional[bool] = None,
hide_passphrase_from_host: Optional[bool] = None,
brightness: Optional[int] = None,
) -> "MessageType":
if language is not None:
warnings.warn(
@ -63,6 +64,7 @@ def apply_settings(
safety_checks=safety_checks,
experimental_features=experimental_features,
hide_passphrase_from_host=hide_passphrase_from_host,
brightness=brightness,
)
out = client.call(settings)

@ -3361,6 +3361,7 @@ class ApplySettings(protobuf.MessageType):
9: protobuf.Field("safety_checks", "SafetyCheckLevel", repeated=False, required=False, default=None),
10: protobuf.Field("experimental_features", "bool", repeated=False, required=False, default=None),
11: protobuf.Field("hide_passphrase_from_host", "bool", repeated=False, required=False, default=None),
12: protobuf.Field("brightness", "uint32", repeated=False, required=False, default=None),
}
def __init__(
@ -3377,6 +3378,7 @@ class ApplySettings(protobuf.MessageType):
safety_checks: Optional["SafetyCheckLevel"] = None,
experimental_features: Optional["bool"] = None,
hide_passphrase_from_host: Optional["bool"] = None,
brightness: Optional["int"] = None,
) -> None:
self.language = language
self.label = label
@ -3389,6 +3391,7 @@ class ApplySettings(protobuf.MessageType):
self.safety_checks = safety_checks
self.experimental_features = experimental_features
self.hide_passphrase_from_host = hide_passphrase_from_host
self.brightness = brightness
class ChangeLanguage(protobuf.MessageType):

@ -2993,6 +2993,8 @@ pub struct ApplySettings {
pub experimental_features: ::std::option::Option<bool>,
// @@protoc_insertion_point(field:hw.trezor.messages.management.ApplySettings.hide_passphrase_from_host)
pub hide_passphrase_from_host: ::std::option::Option<bool>,
// @@protoc_insertion_point(field:hw.trezor.messages.management.ApplySettings.brightness)
pub brightness: ::std::option::Option<u32>,
// special fields
// @@protoc_insertion_point(special_field:hw.trezor.messages.management.ApplySettings.special_fields)
pub special_fields: ::protobuf::SpecialFields,
@ -3272,8 +3274,27 @@ impl ApplySettings {
self.hide_passphrase_from_host = ::std::option::Option::Some(v);
}
// optional uint32 brightness = 12;
pub fn brightness(&self) -> u32 {
self.brightness.unwrap_or(0)
}
pub fn clear_brightness(&mut self) {
self.brightness = ::std::option::Option::None;
}
pub fn has_brightness(&self) -> bool {
self.brightness.is_some()
}
// Param is passed by value, moved
pub fn set_brightness(&mut self, v: u32) {
self.brightness = ::std::option::Option::Some(v);
}
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
let mut fields = ::std::vec::Vec::with_capacity(11);
let mut fields = ::std::vec::Vec::with_capacity(12);
let mut oneofs = ::std::vec::Vec::with_capacity(0);
fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
"language",
@ -3330,6 +3351,11 @@ impl ApplySettings {
|m: &ApplySettings| { &m.hide_passphrase_from_host },
|m: &mut ApplySettings| { &mut m.hide_passphrase_from_host },
));
fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
"brightness",
|m: &ApplySettings| { &m.brightness },
|m: &mut ApplySettings| { &mut m.brightness },
));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<ApplySettings>(
"ApplySettings",
fields,
@ -3381,6 +3407,9 @@ impl ::protobuf::Message for ApplySettings {
88 => {
self.hide_passphrase_from_host = ::std::option::Option::Some(is.read_bool()?);
},
96 => {
self.brightness = ::std::option::Option::Some(is.read_uint32()?);
},
tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
},
@ -3426,6 +3455,9 @@ impl ::protobuf::Message for ApplySettings {
if let Some(v) = self.hide_passphrase_from_host {
my_size += 1 + 1;
}
if let Some(v) = self.brightness {
my_size += ::protobuf::rt::uint32_size(12, v);
}
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
self.special_fields.cached_size().set(my_size as u32);
my_size
@ -3465,6 +3497,9 @@ impl ::protobuf::Message for ApplySettings {
if let Some(v) = self.hide_passphrase_from_host {
os.write_bool(11, v)?;
}
if let Some(v) = self.brightness {
os.write_uint32(12, v)?;
}
os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(())
}
@ -3493,6 +3528,7 @@ impl ::protobuf::Message for ApplySettings {
self.safety_checks = ::std::option::Option::None;
self.experimental_features = ::std::option::Option::None;
self.hide_passphrase_from_host = ::std::option::Option::None;
self.brightness = ::std::option::Option::None;
self.special_fields.clear();
}
@ -3509,6 +3545,7 @@ impl ::protobuf::Message for ApplySettings {
safety_checks: ::std::option::Option::None,
experimental_features: ::std::option::Option::None,
hide_passphrase_from_host: ::std::option::Option::None,
brightness: ::std::option::Option::None,
special_fields: ::protobuf::SpecialFields::new(),
};
&instance

Loading…
Cancel
Save