v0.0.34 - Add qrexec bridge subcommand and --bridge-source-trusted flag for persistent-signer qrexec transport
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
|
||||
extern "C" {
|
||||
#include "src/nostr_core/nostr_common.h"
|
||||
#include "src/nostr_core/utils.h"
|
||||
#include "src/nostr_core/nip006.h"
|
||||
#include "src/nostr_core/nip001.h"
|
||||
#include "src/cjson/cJSON.h"
|
||||
}
|
||||
|
||||
#include "display.h"
|
||||
#include "mnemonic_kb.h"
|
||||
|
||||
@@ -81,10 +89,22 @@ static uint16_t s_enter_selected_indices[12] = {0};
|
||||
static bool s_enter_committed[12] = {false};
|
||||
|
||||
static void signer_enter_ready_screen();
|
||||
static void set_device_mode(device_mode_t mode, bool blink_led);
|
||||
|
||||
// -------------------- Transport framing ----------------------
|
||||
static uint8_t s_rx_buf[2048];
|
||||
static size_t s_rx_len = 0;
|
||||
enum rpc_transport_t {
|
||||
RPC_TRANSPORT_WEBUSB = 0,
|
||||
RPC_TRANSPORT_SERIAL = 1,
|
||||
};
|
||||
|
||||
static rpc_transport_t s_rpc_reply_transport = RPC_TRANSPORT_WEBUSB;
|
||||
|
||||
static uint8_t s_rx_web_buf[2048];
|
||||
static size_t s_rx_web_len = 0;
|
||||
|
||||
static uint8_t s_rx_serial_buf[2048];
|
||||
static size_t s_rx_serial_len = 0;
|
||||
|
||||
static uint8_t s_tx_buf[2048];
|
||||
|
||||
// ---------------------- Encoder state ------------------------
|
||||
@@ -234,21 +254,107 @@ static bool json_extract_string(const char *json, const char *key, char *out, si
|
||||
return true;
|
||||
}
|
||||
|
||||
// Deterministic placeholder key derivation for transport integration testing.
|
||||
static void pseudo_pubkey_hex(char out_hex_64[65]) {
|
||||
uint32_t h = 2166136261u;
|
||||
for (size_t i = 0; i < strlen(s_mnemonic); i++) {
|
||||
h ^= (uint8_t)s_mnemonic[i];
|
||||
h *= 16777619u;
|
||||
static bool json_extract_int(const char *json, const char *key, int *out) {
|
||||
if (!json || !key || !out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
uint8_t b = (uint8_t)((h >> ((i % 4) * 8)) ^ (i * 37));
|
||||
static const char *hex = "0123456789abcdef";
|
||||
out_hex_64[i * 2] = hex[(b >> 4) & 0x0F];
|
||||
out_hex_64[i * 2 + 1] = hex[b & 0x0F];
|
||||
char needle[64];
|
||||
snprintf(needle, sizeof(needle), "\"%s\"", key);
|
||||
const char *k = strstr(json, needle);
|
||||
if (!k) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *colon = strchr(k, ':');
|
||||
if (!colon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
colon++;
|
||||
while (*colon == ' ' || *colon == '\t' || *colon == '\r' || *colon == '\n') {
|
||||
colon++;
|
||||
}
|
||||
|
||||
char *endptr = nullptr;
|
||||
long v = strtol(colon, &endptr, 10);
|
||||
if (endptr == colon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = (int)v;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool json_extract_first_param_object(const char *json, char *out, size_t out_sz) {
|
||||
if (!json || !out || out_sz < 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *params = strstr(json, "\"params\"");
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *arr = strchr(params, '[');
|
||||
if (!arr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *start = strchr(arr, '{');
|
||||
if (!start) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int depth = 0;
|
||||
bool in_string = false;
|
||||
bool escape = false;
|
||||
const char *p = start;
|
||||
|
||||
for (; *p; ++p) {
|
||||
const char c = *p;
|
||||
|
||||
if (in_string) {
|
||||
if (escape) {
|
||||
escape = false;
|
||||
} else if (c == '\\') {
|
||||
escape = true;
|
||||
} else if (c == '"') {
|
||||
in_string = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '"') {
|
||||
in_string = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '{') {
|
||||
depth++;
|
||||
} else if (c == '}') {
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
const size_t n = (size_t)(p - start + 1);
|
||||
if (n >= out_sz) {
|
||||
return false;
|
||||
}
|
||||
memcpy(out, start, n);
|
||||
out[n] = '\0';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void secure_zero(void *p, size_t n) {
|
||||
if (!p || n == 0) return;
|
||||
volatile uint8_t *vp = reinterpret_cast<volatile uint8_t *>(p);
|
||||
while (n--) {
|
||||
*vp++ = 0;
|
||||
}
|
||||
out_hex_64[64] = '\0';
|
||||
}
|
||||
|
||||
static void signer_apply_seed(const char *mnemonic) {
|
||||
@@ -289,35 +395,68 @@ static bool wait_for_user_approval(uint32_t timeout_ms) {
|
||||
static void send_json_response(const char *json) {
|
||||
size_t n = strlen(json);
|
||||
if (n > (sizeof(s_tx_buf) - 4)) return;
|
||||
|
||||
write_u32_be(s_tx_buf, (uint32_t)n);
|
||||
memcpy(s_tx_buf + 4, json, n);
|
||||
usb_web.write(s_tx_buf, n + 4);
|
||||
usb_web.flush();
|
||||
|
||||
if (s_rpc_reply_transport == RPC_TRANSPORT_SERIAL) {
|
||||
Serial.write(s_tx_buf, n + 4);
|
||||
Serial.flush();
|
||||
} else {
|
||||
usb_web.write(s_tx_buf, n + 4);
|
||||
usb_web.flush();
|
||||
}
|
||||
|
||||
s_diag_responses_tx++;
|
||||
}
|
||||
|
||||
static void send_rpc_result(const char *id, const char *result_json) {
|
||||
char out[2048];
|
||||
const char *rpc_id = (id && id[0]) ? id : "0";
|
||||
const char *result = (result_json && result_json[0]) ? result_json : "null";
|
||||
snprintf(out, sizeof(out), "{\"jsonrpc\":\"2.0\",\"id\":\"%s\",\"result\":%s}", rpc_id, result);
|
||||
send_json_response(out);
|
||||
}
|
||||
|
||||
static void send_rpc_error(const char *id, int code, const char *message) {
|
||||
char out[1024];
|
||||
const char *rpc_id = (id && id[0]) ? id : "0";
|
||||
const char *msg = (message && message[0]) ? message : "error";
|
||||
snprintf(out, sizeof(out),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":\"%s\",\"error\":{\"code\":%d,\"message\":\"%s\"}}",
|
||||
rpc_id, code, msg);
|
||||
send_json_response(out);
|
||||
}
|
||||
|
||||
static void handle_rpc(const char *req_json) {
|
||||
char rpc_id[96] = {0};
|
||||
(void)json_extract_string(req_json, "id", rpc_id, sizeof(rpc_id));
|
||||
|
||||
int nostr_index = 0;
|
||||
(void)json_extract_int(req_json, "nostr_index", &nostr_index);
|
||||
|
||||
char method[64] = {0};
|
||||
if (!json_extract_string(req_json, "method", method, sizeof(method))) {
|
||||
send_json_response("{\"error\":{\"code\":-32600,\"message\":\"invalid request\"}}");
|
||||
send_rpc_error(rpc_id, -32600, "invalid request");
|
||||
// display_set_status("RPC invalid request");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(method, "ping") == 0) {
|
||||
send_json_response("{\"result\":\"pong\"}");
|
||||
send_rpc_result(rpc_id, "\"pong\"");
|
||||
// display_set_status("RPC ping");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(method, "get_status") == 0) {
|
||||
char resp[256];
|
||||
snprintf(resp, sizeof(resp),
|
||||
"{\"result\":{\"mode\":\"%s\",\"seed_loaded\":%s,\"auto_approve\":%s}}",
|
||||
char result[256];
|
||||
snprintf(result, sizeof(result),
|
||||
"{\"mode\":\"%s\",\"seed_loaded\":%s,\"auto_approve\":%s,\"nostr_index\":%d}",
|
||||
s_mode == MODE_MEDIA ? "media" : "signer",
|
||||
s_seed_loaded ? "true" : "false",
|
||||
s_auto_approve ? "true" : "false");
|
||||
send_json_response(resp);
|
||||
s_auto_approve ? "true" : "false",
|
||||
nostr_index);
|
||||
send_rpc_result(rpc_id, result);
|
||||
// display_set_status("RPC get_status");
|
||||
return;
|
||||
}
|
||||
@@ -325,7 +464,7 @@ static void handle_rpc(const char *req_json) {
|
||||
if (strcmp(method, "set_mnemonic") == 0) {
|
||||
char phrase[256] = {0};
|
||||
if (!json_extract_string(req_json, "mnemonic", phrase, sizeof(phrase))) {
|
||||
send_json_response("{\"error\":{\"code\":-32602,\"message\":\"missing mnemonic\"}}");
|
||||
send_rpc_error(rpc_id, -32602, "missing mnemonic");
|
||||
// display_set_status("Mnemonic missing");
|
||||
return;
|
||||
}
|
||||
@@ -334,21 +473,21 @@ static void handle_rpc(const char *req_json) {
|
||||
if (s_mode == MODE_SIGNER && s_seed_loaded) {
|
||||
signer_enter_ready_screen();
|
||||
}
|
||||
send_json_response("{\"result\":\"ok\"}");
|
||||
send_rpc_result(rpc_id, "\"ok\"");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(method, "set_auto_approve") == 0) {
|
||||
if (strstr(req_json, "\"value\":true") != nullptr) {
|
||||
s_auto_approve = true;
|
||||
send_json_response("{\"result\":true}");
|
||||
send_rpc_result(rpc_id, "true");
|
||||
// display_set_status("Auto-approve ON");
|
||||
} else if (strstr(req_json, "\"value\":false") != nullptr) {
|
||||
s_auto_approve = false;
|
||||
send_json_response("{\"result\":false}");
|
||||
send_rpc_result(rpc_id, "false");
|
||||
// display_set_status("Auto-approve OFF");
|
||||
} else {
|
||||
send_json_response("{\"error\":{\"code\":-32602,\"message\":\"missing value\"}}");
|
||||
send_rpc_error(rpc_id, -32602, "missing value");
|
||||
// display_set_status("Auto-approve invalid");
|
||||
}
|
||||
return;
|
||||
@@ -356,38 +495,45 @@ static void handle_rpc(const char *req_json) {
|
||||
|
||||
if (strcmp(method, "get_public_key") == 0) {
|
||||
if (!s_seed_loaded) {
|
||||
send_json_response("{\"error\":{\"code\":2014,\"message\":\"seed not loaded\"}}");
|
||||
// display_set_status("No seed");
|
||||
send_rpc_error(rpc_id, 2014, "seed not loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
char pubhex[65];
|
||||
pseudo_pubkey_hex(pubhex);
|
||||
unsigned char private_key[32] = {0};
|
||||
unsigned char public_key[32] = {0};
|
||||
if (nostr_derive_keys_from_mnemonic(s_mnemonic, nostr_index, private_key, public_key) != NOSTR_SUCCESS) {
|
||||
secure_zero(private_key, sizeof(private_key));
|
||||
secure_zero(public_key, sizeof(public_key));
|
||||
send_rpc_error(rpc_id, -32603, "key derivation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
char resp[192];
|
||||
snprintf(resp, sizeof(resp), "{\"result\":{\"pubkey\":\"%s\"}}", pubhex);
|
||||
send_json_response(resp);
|
||||
// display_set_status("Pubkey served");
|
||||
char pubhex[65] = {0};
|
||||
nostr_bytes_to_hex(public_key, 32, pubhex);
|
||||
|
||||
secure_zero(private_key, sizeof(private_key));
|
||||
secure_zero(public_key, sizeof(public_key));
|
||||
|
||||
char result[96];
|
||||
snprintf(result, sizeof(result), "\"%s\"", pubhex);
|
||||
send_rpc_result(rpc_id, result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(method, "sign_event") == 0) {
|
||||
if (!s_seed_loaded) {
|
||||
send_json_response("{\"error\":{\"code\":2014,\"message\":\"seed not loaded\"}}");
|
||||
// display_set_status("Sign blocked:no seed");
|
||||
send_rpc_error(rpc_id, 2014, "seed not loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_mode != MODE_SIGNER) {
|
||||
send_json_response("{\"error\":{\"code\":2015,\"message\":\"not in signer mode\"}}");
|
||||
// display_set_status("Sign blocked:mode");
|
||||
send_rpc_error(rpc_id, 2015, "not in signer mode");
|
||||
return;
|
||||
}
|
||||
|
||||
bool approved = s_auto_approve ? true : wait_for_user_approval(30000);
|
||||
if (!approved) {
|
||||
send_json_response("{\"error\":{\"code\":2001,\"message\":\"user denied or timeout\"}}");
|
||||
// Return signer UI screen after approval prompt
|
||||
send_rpc_error(rpc_id, 2001, "user denied or timeout");
|
||||
if (s_signer_ui_state == SIGNER_UI_MENU) {
|
||||
display_show_signer_menu(s_signer_menu_selected);
|
||||
} else if (s_signer_ui_state == SIGNER_UI_READY) {
|
||||
@@ -396,8 +542,81 @@ static void handle_rpc(const char *req_json) {
|
||||
return;
|
||||
}
|
||||
|
||||
send_json_response("{\"result\":{\"sig\":\"dev_signature_placeholder\"}}");
|
||||
// display_set_status("Event signed");
|
||||
char event_obj[1400] = {0};
|
||||
if (!json_extract_first_param_object(req_json, event_obj, sizeof(event_obj))) {
|
||||
send_rpc_error(rpc_id, -32602, "missing event object");
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON *event_in = cJSON_Parse(event_obj);
|
||||
if (!event_in || !cJSON_IsObject(event_in)) {
|
||||
if (event_in) cJSON_Delete(event_in);
|
||||
send_rpc_error(rpc_id, -32602, "invalid event object");
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON *kind_item = cJSON_GetObjectItemCaseSensitive(event_in, "kind");
|
||||
cJSON *content_item = cJSON_GetObjectItemCaseSensitive(event_in, "content");
|
||||
cJSON *tags_item = cJSON_GetObjectItemCaseSensitive(event_in, "tags");
|
||||
cJSON *created_at_item = cJSON_GetObjectItemCaseSensitive(event_in, "created_at");
|
||||
|
||||
if (!cJSON_IsNumber(kind_item) || !cJSON_IsString(content_item)) {
|
||||
cJSON_Delete(event_in);
|
||||
send_rpc_error(rpc_id, -32602, "event missing kind/content");
|
||||
return;
|
||||
}
|
||||
|
||||
int kind = kind_item->valueint;
|
||||
const char *content = content_item->valuestring ? content_item->valuestring : "";
|
||||
time_t created_at = (time_t)time(nullptr);
|
||||
if (cJSON_IsNumber(created_at_item)) {
|
||||
created_at = (time_t)created_at_item->valuedouble;
|
||||
}
|
||||
|
||||
cJSON *tags_dup = nullptr;
|
||||
if (cJSON_IsArray(tags_item)) {
|
||||
tags_dup = cJSON_Duplicate(tags_item, 1);
|
||||
if (!tags_dup) {
|
||||
cJSON_Delete(event_in);
|
||||
send_rpc_error(rpc_id, -32603, "tags allocation failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char private_key[32] = {0};
|
||||
unsigned char public_key[32] = {0};
|
||||
if (nostr_derive_keys_from_mnemonic(s_mnemonic, nostr_index, private_key, public_key) != NOSTR_SUCCESS) {
|
||||
if (tags_dup) cJSON_Delete(tags_dup);
|
||||
cJSON_Delete(event_in);
|
||||
secure_zero(private_key, sizeof(private_key));
|
||||
secure_zero(public_key, sizeof(public_key));
|
||||
send_rpc_error(rpc_id, -32603, "key derivation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON *signed_event = nostr_create_and_sign_event(kind, content, tags_dup, private_key, created_at);
|
||||
|
||||
secure_zero(private_key, sizeof(private_key));
|
||||
secure_zero(public_key, sizeof(public_key));
|
||||
if (tags_dup) cJSON_Delete(tags_dup);
|
||||
cJSON_Delete(event_in);
|
||||
|
||||
if (!signed_event) {
|
||||
send_rpc_error(rpc_id, -32603, "event signing failed");
|
||||
return;
|
||||
}
|
||||
|
||||
char *signed_event_json = cJSON_PrintUnformatted(signed_event);
|
||||
cJSON_Delete(signed_event);
|
||||
|
||||
if (!signed_event_json) {
|
||||
send_rpc_error(rpc_id, -32603, "event serialization failed");
|
||||
return;
|
||||
}
|
||||
|
||||
send_rpc_result(rpc_id, signed_event_json);
|
||||
free(signed_event_json);
|
||||
|
||||
if (s_signer_ui_state == SIGNER_UI_MENU) {
|
||||
display_show_signer_menu(s_signer_menu_selected);
|
||||
} else if (s_signer_ui_state == SIGNER_UI_READY) {
|
||||
@@ -406,20 +625,52 @@ static void handle_rpc(const char *req_json) {
|
||||
return;
|
||||
}
|
||||
|
||||
send_json_response("{\"error\":{\"code\":-32601,\"message\":\"method not found\"}}");
|
||||
send_rpc_error(rpc_id, -32601, "method not found");
|
||||
// display_set_status("RPC method missing");
|
||||
}
|
||||
|
||||
static void pump_framed_requests(uint8_t *rx_buf, size_t *rx_len, rpc_transport_t transport) {
|
||||
while (*rx_len >= 4) {
|
||||
const uint32_t body_len = read_u32_be(rx_buf);
|
||||
if (body_len > (sizeof(s_rx_web_buf) - 4)) {
|
||||
*rx_len = 0;
|
||||
s_rpc_reply_transport = transport;
|
||||
send_json_response("{\"error\":{\"code\":-32000,\"message\":\"frame too large\"}}");
|
||||
// display_set_status("Frame too large");
|
||||
return;
|
||||
}
|
||||
|
||||
if (*rx_len < (size_t)(4 + body_len)) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_diag_frames_rx++;
|
||||
|
||||
char req[1536];
|
||||
size_t n = body_len;
|
||||
if (n >= sizeof(req)) n = sizeof(req) - 1;
|
||||
memcpy(req, rx_buf + 4, n);
|
||||
req[n] = '\0';
|
||||
|
||||
const size_t remain = *rx_len - (4 + body_len);
|
||||
memmove(rx_buf, rx_buf + 4 + body_len, remain);
|
||||
*rx_len = remain;
|
||||
|
||||
s_rpc_reply_transport = transport;
|
||||
handle_rpc(req);
|
||||
}
|
||||
}
|
||||
|
||||
static void webusb_pump_frames() {
|
||||
while (usb_web.available()) {
|
||||
if (s_rx_len >= sizeof(s_rx_buf)) {
|
||||
s_rx_len = 0;
|
||||
if (s_rx_web_len >= sizeof(s_rx_web_buf)) {
|
||||
s_rx_web_len = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
int c = usb_web.read();
|
||||
const int c = usb_web.read();
|
||||
if (c < 0) break;
|
||||
s_rx_buf[s_rx_len++] = (uint8_t)c;
|
||||
s_rx_web_buf[s_rx_web_len++] = (uint8_t)c;
|
||||
|
||||
// DIAG: a byte arrived from the host. Pulse LED + count it so we can
|
||||
// confirm, with no host tooling, that request bytes reach the device.
|
||||
@@ -427,32 +678,25 @@ static void webusb_pump_frames() {
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
}
|
||||
|
||||
while (s_rx_len >= 4) {
|
||||
s_diag_frames_rx++;
|
||||
uint32_t body_len = read_u32_be(s_rx_buf);
|
||||
if (body_len > (sizeof(s_rx_buf) - 4)) {
|
||||
s_rx_len = 0;
|
||||
send_json_response("{\"error\":{\"code\":-32000,\"message\":\"frame too large\"}}");
|
||||
// display_set_status("Frame too large");
|
||||
return;
|
||||
pump_framed_requests(s_rx_web_buf, &s_rx_web_len, RPC_TRANSPORT_WEBUSB);
|
||||
}
|
||||
|
||||
static void serial_pump_frames() {
|
||||
while (Serial.available() > 0) {
|
||||
if (s_rx_serial_len >= sizeof(s_rx_serial_buf)) {
|
||||
s_rx_serial_len = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (s_rx_len < (size_t)(4 + body_len)) {
|
||||
return;
|
||||
}
|
||||
const int c = Serial.read();
|
||||
if (c < 0) break;
|
||||
s_rx_serial_buf[s_rx_serial_len++] = (uint8_t)c;
|
||||
|
||||
char req[1536];
|
||||
size_t n = body_len;
|
||||
if (n >= sizeof(req)) n = sizeof(req) - 1;
|
||||
memcpy(req, s_rx_buf + 4, n);
|
||||
req[n] = '\0';
|
||||
|
||||
size_t remain = s_rx_len - (4 + body_len);
|
||||
memmove(s_rx_buf, s_rx_buf + 4 + body_len, remain);
|
||||
s_rx_len = remain;
|
||||
|
||||
handle_rpc(req);
|
||||
s_diag_bytes_rx++;
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
}
|
||||
|
||||
pump_framed_requests(s_rx_serial_buf, &s_rx_serial_len, RPC_TRANSPORT_SERIAL);
|
||||
}
|
||||
|
||||
static void signer_enter_menu_screen() {
|
||||
@@ -579,6 +823,24 @@ static void build_entered_mnemonic(char *out, size_t out_sz) {
|
||||
}
|
||||
}
|
||||
|
||||
static void set_device_mode(device_mode_t mode, bool blink_led) {
|
||||
s_mode = mode;
|
||||
|
||||
display_set_mode(s_mode == MODE_SIGNER);
|
||||
if (s_mode == MODE_SIGNER) {
|
||||
display_set_view(DISPLAY_VIEW_SIGNER);
|
||||
signer_enter_menu_screen();
|
||||
// display_set_status("Signer mode");
|
||||
} else {
|
||||
display_set_view(DISPLAY_VIEW_VOLUME);
|
||||
// display_set_status("Media mode");
|
||||
}
|
||||
|
||||
if (blink_led) {
|
||||
led_blink(s_mode == MODE_SIGNER ? 3 : 1, 80, 80);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_mode_switch_chord() {
|
||||
const bool play = btn_pressed(PIN_BTN_PLAY);
|
||||
const bool prev = btn_pressed(PIN_BTN_PREV);
|
||||
@@ -589,19 +851,7 @@ static void handle_mode_switch_chord() {
|
||||
}
|
||||
|
||||
s_mode_switch_latched = true;
|
||||
s_mode = (s_mode == MODE_MEDIA) ? MODE_SIGNER : MODE_MEDIA;
|
||||
|
||||
display_set_mode(s_mode == MODE_SIGNER);
|
||||
if (s_mode == MODE_SIGNER) {
|
||||
display_set_view(DISPLAY_VIEW_SIGNER);
|
||||
signer_enter_menu_screen();
|
||||
// display_set_status("Signer mode");
|
||||
} else {
|
||||
display_set_view(DISPLAY_VIEW_VOLUME);
|
||||
// display_set_status("Media mode");
|
||||
}
|
||||
|
||||
led_blink(s_mode == MODE_SIGNER ? 3 : 1, 80, 80);
|
||||
set_device_mode((s_mode == MODE_MEDIA) ? MODE_SIGNER : MODE_MEDIA, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -826,6 +1076,12 @@ void setup() {
|
||||
pinMode(PIN_BTN_NEXT, INPUT_PULLUP);
|
||||
pinMode(PIN_BTN_PREV, INPUT_PULLUP);
|
||||
|
||||
Serial.begin(115200);
|
||||
|
||||
if (nostr_init() != NOSTR_SUCCESS) {
|
||||
// Keep running transport/UI so host can still diagnose, but signing will fail.
|
||||
}
|
||||
|
||||
usb_web.setLandingPage(&landing_url);
|
||||
usb_web.setStringDescriptor("n_signer WebUSB");
|
||||
usb_web.begin();
|
||||
@@ -881,6 +1137,7 @@ void loop() {
|
||||
signer_mode_tick();
|
||||
}
|
||||
|
||||
serial_pump_frames();
|
||||
webusb_pump_frames();
|
||||
|
||||
// DIAG: periodically surface transport activity on the OLED status line so
|
||||
|
||||
Reference in New Issue
Block a user