gui: add brightness controls to qrcode displays
What changed, and why it matters
This commit adds a brightness button to QR-code screens on the Blockstream Jade hardware wallet. It is a user-interface convenience feature and does not change any security-sensitive logic, cryptography, or data handling.
No security action required; treat as normal feature review.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces a new button event BTN_QR_BRIGHTNESS and wires it into QR display activities. Pressing the button cycles through QR code foreground/background colors via gui_next_qrcode_color() and repaints the screen. It also replaces the previous ‘?’ help button on the xpub QR screen with the new brightness button, and adds brightness control to QR help and back/continue screens. No cryptographic, parsing, network, or memory-safety code is modified.
Changed components
main/button_events.hmain/qrmode.cmain/ui/qrmode.cInspect captured patch +66 / −32
diff --git a/main/button_events.h b/main/button_events.h
index 7a24058..6b6af08 100644
--- a/main/button_events.h
+++ b/main/button_events.h
@@ -7,6 +7,7 @@ typedef enum {
BTN_YES,
BTN_NO,
BTN_QR_HELP_EXIT,
+ BTN_QR_BRIGHTNESS,
BTN_CAMERA_HELP,
BTN_CAMERA_CLICK,
@@ -241,7 +242,6 @@ typedef enum {
BTN_XPUB_OPTIONS_SCRIPTTYPE,
BTN_XPUB_OPTIONS_WALLETTYPE,
BTN_XPUB_OPTIONS_ACCOUNT,
- BTN_XPUB_HELP,
BTN_XPUB_EXIT,
BTN_SCAN_ADDRESS_SKIP_ADDRESSES,
diff --git a/main/qrmode.c b/main/qrmode.c
index 200654f..b53b32a 100644
--- a/main/qrmode.c
+++ b/main/qrmode.c
@@ -408,8 +408,9 @@ void display_xpub_qr(void)
// Options were updated - re-create xpub screen
act = create_display_xpub_qr_activity(qr_flags);
}
- } else if (ev_id == BTN_XPUB_HELP) {
- await_qr_help_activity("blkstrm.com/xpub");
+ } else if (ev_id == BTN_QR_BRIGHTNESS) {
+ gui_next_qrcode_color();
+ gui_repaint(act->root_node);
} else if (ev_id == BTN_XPUB_EXIT) {
// Done
break;
@@ -1511,12 +1512,27 @@ void await_qr_help_activity(const char* url)
gui_activity_t* const act = make_show_qr_help_activity(url_with_crlf, qr_icon);
gui_set_current_activity(act);
+ // Show, and await button click
+ while (true) {
+ int32_t ev_id;
#ifndef CONFIG_DEBUG_UNATTENDED_CI
- gui_activity_wait_event(act, GUI_BUTTON_EVENT, BTN_QR_HELP_EXIT, NULL, NULL, NULL, 0);
+ const bool ret = gui_activity_wait_event(act, GUI_BUTTON_EVENT, ESP_EVENT_ANY_ID, NULL, &ev_id, NULL, 0);
#else
- gui_activity_wait_event(act, GUI_BUTTON_EVENT, BTN_QR_HELP_EXIT, NULL, NULL, NULL,
- CONFIG_DEBUG_UNATTENDED_CI_TIMEOUT_MS / portTICK_PERIOD_MS);
+ gui_activity_wait_event(act, GUI_BUTTON_EVENT, ESP_EVENT_ANY_ID, NULL, NULL, NULL,
+ CONFIG_DEBUG_UNATTENDED_CI_TIMEOUT_MS / portTICK_PERIOD_MS);
+ const bool ret = true;
+ ev_id = BTN_QR_HELP_EXIT;
#endif
+ if (ret) {
+ if (ev_id == BTN_QR_BRIGHTNESS) {
+ gui_next_qrcode_color();
+ gui_repaint(act->root_node);
+ } else if (ev_id == BTN_QR_HELP_EXIT) {
+ // Done
+ break;
+ }
+ }
+ }
}
// Display screen with help url and qr code
@@ -1537,18 +1553,27 @@ bool await_qr_back_continue_activity(
gui_activity_t* const act = make_qr_back_continue_activity(message, message_size, url, qr_icon, default_selection);
gui_set_current_activity(act);
- int32_t ev_id = 0;
+ // Show, and await button click
+ while (true) {
+ int32_t ev_id;
#ifndef CONFIG_DEBUG_UNATTENDED_CI
- const bool ret = gui_activity_wait_event(act, GUI_BUTTON_EVENT, ESP_EVENT_ANY_ID, NULL, &ev_id, NULL, 0);
+ const bool ret = gui_activity_wait_event(act, GUI_BUTTON_EVENT, ESP_EVENT_ANY_ID, NULL, &ev_id, NULL, 0);
#else
- gui_activity_wait_event(act, GUI_BUTTON_EVENT, ESP_EVENT_ANY_ID, NULL, &ev_id, NULL,
- CONFIG_DEBUG_UNATTENDED_CI_TIMEOUT_MS / portTICK_PERIOD_MS);
- const bool ret = true;
- ev_id = BTN_YES;
+ gui_activity_wait_event(act, GUI_BUTTON_EVENT, ESP_EVENT_ANY_ID, NULL, NULL, NULL,
+ CONFIG_DEBUG_UNATTENDED_CI_TIMEOUT_MS / portTICK_PERIOD_MS);
+ const bool ret = true;
+ ev_id = BTN_YES;
#endif
-
- // Return whether 'Continue' was cicked
- return ret && ev_id == BTN_YES;
+ if (ret) {
+ if (ev_id == BTN_QR_BRIGHTNESS) {
+ gui_next_qrcode_color();
+ gui_repaint(act->root_node);
+ } else if (ev_id == BTN_YES || ev_id == BTN_NO) {
+ // Done: return whether 'Continue' was cicked
+ return ev_id == BTN_YES;
+ }
+ }
+ }
}
// QR-Mode PinServer interaction
diff --git a/main/ui/qrmode.c b/main/ui/qrmode.c
index 8ba9925..17de840 100644
--- a/main/ui/qrmode.c
+++ b/main/ui/qrmode.c
@@ -17,6 +17,26 @@ static void make_qrcode(gui_view_node_t* parent, Icon* icons, const size_t num_i
gui_set_icon_to_qr(icon);
}
+static gui_view_node_t* make_back_brightness_row(gui_view_node_t* parent, uint32_t back_ev_id)
+{
+ // Create a row with left back arrow and right brightness button
+ gui_view_node_t* headersplit;
+ gui_make_hsplit(&headersplit, GUI_SPLIT_RELATIVE, 3, 27, 46, 27); // 27 x 56% (hsplit) == 15
+ gui_set_parent(headersplit, parent);
+
+ // back button, space, brightness button
+ btn_data_t hdrbtns[]
+ = { { .txt = "=", .font = JADE_SYMBOLS_16x16_FONT, .ev_id = back_ev_id, .borders = GUI_BORDER_ALL },
+ { .txt = NULL, .font = GUI_DEFAULT_FONT, .ev_id = GUI_BUTTON_EVENT_NONE },
+ { .txt = "P", .font = JADE_SYMBOLS_16x16_FONT, .ev_id = BTN_QR_BRIGHTNESS, .borders = GUI_BORDER_ALL } };
+ // Add individually to avoid creating a new equal 3-way split
+ for (size_t i = 0; i < sizeof(hdrbtns) / sizeof(hdrbtns[0]); ++i) {
+ add_button(headersplit, hdrbtns + i);
+ }
+ // Return the back button that add_button() created for the caller
+ return hdrbtns[0].btn;
+}
+
gui_activity_t* make_show_xpub_qr_activity(
const char* label, const char* pathstr, Icon* icons, const size_t num_icons, const size_t frames_per_qr_icon)
{
@@ -37,11 +57,11 @@ gui_activity_t* make_show_xpub_qr_activity(
gui_make_vsplit(&vsplit, GUI_SPLIT_RELATIVE, 4, 20, 30, 25, 25);
gui_set_parent(vsplit, hsplit);
- // back button
+ // back button, space, brightness button
btn_data_t hdrbtns[]
= { { .txt = "=", .font = JADE_SYMBOLS_16x16_FONT, .ev_id = BTN_XPUB_EXIT, .borders = GUI_BORDER_ALL },
{ .txt = NULL, .font = GUI_DEFAULT_FONT, .ev_id = GUI_BUTTON_EVENT_NONE },
- { .txt = "?", .font = GUI_TITLE_FONT, .ev_id = BTN_XPUB_HELP, .borders = GUI_BORDER_ALL } };
+ { .txt = "P", .font = JADE_SYMBOLS_16x16_FONT, .ev_id = BTN_QR_BRIGHTNESS, .borders = GUI_BORDER_ALL } };
add_buttons(vsplit, UI_ROW, hdrbtns, 3); // 44 (hsplit) / 3 == 14 - almost 15 so ok
// second row, type label
@@ -303,14 +323,8 @@ gui_activity_t* make_show_qr_help_activity(const char* url, Icon* qr_icon)
gui_make_vsplit(&vsplit, GUI_SPLIT_RELATIVE, 3, 20, 25, 55);
gui_set_parent(vsplit, hsplit);
- gui_view_node_t* headersplit;
- gui_make_hsplit(&headersplit, GUI_SPLIT_RELATIVE, 2, 27, 73); // 27 x 56% (hsplit) == 15
- gui_set_parent(headersplit, vsplit);
-
- // first row, header, back button
- btn_data_t hdrbtn
- = { .txt = "=", .font = JADE_SYMBOLS_16x16_FONT, .ev_id = BTN_QR_HELP_EXIT, .borders = GUI_BORDER_ALL };
- add_buttons(headersplit, UI_ROW, &hdrbtn, 1);
+ // first row, header: back button, space, brightness button
+ make_back_brightness_row(vsplit, BTN_QR_HELP_EXIT);
// second row, message
gui_make_text(&node, "Learn more:", TFT_WHITE);
@@ -372,12 +386,7 @@ gui_activity_t* make_qr_back_continue_activity(
gui_make_vsplit(&vsplit, GUI_SPLIT_RELATIVE, 6, 20, 18, 16, 21, 25);
gui_set_parent(vsplit, hsplit);
- gui_view_node_t* headersplit;
- gui_make_hsplit(&headersplit, GUI_SPLIT_RELATIVE, 2, 27, 73); // 27 x 56% (hsplit) == 15
- gui_set_parent(headersplit, vsplit);
-
- btn_data_t hdrbtn = { .txt = "=", .font = JADE_SYMBOLS_16x16_FONT, .ev_id = BTN_NO, .borders = GUI_BORDER_ALL };
- add_buttons(headersplit, UI_ROW, &hdrbtn, 1);
+ gui_view_node_t* back_btn = make_back_brightness_row(vsplit, BTN_NO);
// second/third/fourth row, message
gui_make_text(&node, message[0], TFT_WHITE);
@@ -396,7 +405,7 @@ gui_activity_t* make_qr_back_continue_activity(
add_buttons(vsplit, UI_ROW, &ftrbtn, 1);
// Select default selected button
- gui_set_activity_initial_selection(default_selection ? ftrbtn.btn : hdrbtn.btn);
+ gui_set_activity_initial_selection(default_selection ? ftrbtn.btn : back_btn);
}
// RHS - QR icon
Why this scored 15/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.