fix(core/eckhart): attempts width in PIN keyboard
What changed, and why it matters
This commit fixes a layout bug in the PIN entry screen on Trezor's Eckhart hardware model. Previously, the area showing how many PIN attempts remain was given a fixed width of 85 pixels, which could be too narrow for some languages and cause the text to be cut off or poorly displayed. The patch now dynamically measures the actual text width and reserves space accordingly, with a small gap between the prompt and attempts text. There is no direct security vulnerability here; it is a UI correctness and localization improvement.
No security action required. Treat as a normal UI/layout fix. Reviewers may optionally verify that the dynamic width clamp prevents zero-width prompt areas in edge cases.
Security signals we found
No security-relevant code paths modified
No input validation, cryptography, authentication, or memory-safety changes
UI layout only: text width measurement and area splitting
Commit message and diff contain no security keywords or references
Evidence from the diff
The change replaces a hard-coded ATTEMPTS_WIDTH constant with a dynamic attempts_width() method that computes the maximum line width of the attempts prompt text (split on ‘\n’) using the configured font. It also introduces PROMPT_ATTEMPTS_GAP (8 px) and clamps the attempts width so the prompt area never disappears. The prompt area is inset on the right by the gap. This is purely a layout/rendering fix for the PIN keyboard component in the Eckhart UI layout.
Changed components
core/embed/rust/src/ui/layout_eckhart/firmware/keyboard/pin.rsTrezor Eckhart PIN keyboard UIInspect captured patch +709 / −693
### core/embed/rust/src/ui/layout_eckhart/firmware/keyboard/pin.rs
@@ -37,9 +37,8 @@ pub struct PinKeyboard<'a> {
impl<'a> PinKeyboard<'a> {
const LAST_DIGIT_TIMEOUT: Duration = Duration::from_secs(1);
const MAJOR_WARNING_TIMEOUT: Duration = Duration::from_secs(2);
- // Ad hoc number that so that all languages can reasonably show the attempts
- // prompt
- const ATTEMPTS_WIDTH: i16 = 85;
+ /// Spacing between the prompt and the attempts areas [px]
+ const PROMPT_ATTEMPTS_GAP: i16 = 8;
pub fn new(
prompt: TString<'a>,
@@ -121,6 +120,19 @@ impl<'a> PinKeyboard<'a> {
self.keypad.set_state(keypad_state, ctx);
}
+ /// Width needed by the attempts prompt. The text consists of lines
+ /// separated by a hard newline (e.g. "10\ntries left"), so the widest
+ /// line determines the needed area width.
+ fn attempts_width(&self) -> i16 {
+ let font = self.attempts.font();
+ self.attempts.text().map(|t| {
+ t.split('\n')
+ .map(|line| font.text_width(line))
+ .max()
+ .unwrap_or(0)
+ })
+ }
+
pub fn pin(&self) -> &str {
self.input.pin()
}
@@ -139,7 +151,11 @@ impl Component for PinKeyboard<'_> {
let (input_touch_area, _) = bounds.split_top(INPUT_TOUCH_HEIGHT);
let prompts_area = input_touch_area.inset(KEYBOARD_PROMPT_INSETS);
- let (prompt_area, attempts_area) = prompts_area.split_right(Self::ATTEMPTS_WIDTH);
+ let attempts_width = self
+ .attempts_width()
+ .min(prompts_area.width() - Self::PROMPT_ATTEMPTS_GAP);
+ let (prompt_area, attempts_area) = prompts_area.split_right(attempts_width);
+ let prompt_area = prompt_area.inset(Insets::right(Self::PROMPT_ATTEMPTS_GAP));
// Prompts and PIN dots placement.
self.input.place(input_touch_area);
### tests/ui_tests/fixtures.json
[binary or diff unavailable]Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.