M9 and Tanmatsu feedback from #609

- M9: double-press Home opens the launcher settings (what Home opens, icon size)
- M9: "Mark all as read" is the first item in the chats menu
- M9: MIC key opens the chat options in a chat, else the focused item's hold action
- M9: stronger focus on the map overlay buttons
- M9: the at-a-glance card shows even when the message-flash wake lit the screen first
- Tanmatsu: screensaver preview ignores input for its first 1.2 s so it no longer ends at once

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
(cherry picked from commit f479382c9b12070cffae530e3b4789d689c441c5)
This commit is contained in:
Michael A. Cojocari committed 2026-10-13 09:31:38 -04:00
1 parent 1700366f3f
commit b63b4f3904
3 files changed
+71 -13

No files matched your search

+2 -2
View File
@@ -50,13 +50,13 @@ under a running operation.
| Key | Press | Hold |
|---|---|---|
| **MSG** | Jump to the Chats tab; inside a conversation, return to the chat list (closes an open app first) | None |
| **HOME** | Peel one layer off an open app; on the Home tab, toggle the app drawer; otherwise jump Home (and clear the Back trail). **Double-press** to open Settings | None |
| **HOME** | Peel one layer off an open app; on the Home tab, toggle the app drawer; otherwise jump Home (and clear the Back trail). **Double-press** to open the launcher settings (what Home opens, icon size) | None |
| **@ (Mentions)** | Open the Mentions screen | None |
| **ADV** | Open the Send Advert page | **Toggle GPS on/off** |
| **MAP** | Jump to the Map tab; press again *on* the map to toggle pan mode | None |
| **BACK** | Close the top layer (see above) | None |
| **CTRL** | Open the Control Center (quick toggles, incl. the keyboard light: off / on / auto) | None |
| **MIC** | Nothing yet (deliberately reserved) | None |
| **MIC** | Options: in an open chat, its menu (same as the header ⋯); elsewhere, the focused item's hold action, e.g. a chat row's menu | None |
| **OK / Enter** | Activate the focused item; send a message; run a terminal command; newline in the editor | **Long-press the focused item / unlock the lock screen** |
The **MSG**, **HOME**, **@**, **ADV**, **MAP**, and **CTRL** shortcuts remain
+66 -8
View File
@@ -30746,8 +30746,14 @@ static void chatsAddCb(lv_event_t* e) {
static void chatsMoreCb(lv_event_t* e) {
if (lv_event_get_code(e) != LV_EVENT_CLICKED) return;
const ActItem items[] = {
#if defined(HAS_THINKNODE_M9)
// M9: first, so the menu opens on it and the confirm opens on "Mark read" (#609).
{ LV_SYMBOL_OK, TR("Mark all as read"), chatsMarkAllAsk, false },
{ LV_SYMBOL_PLUS, TR("Add a channel"), []{ openAddChannelSheet(); }, false },
#else
{ LV_SYMBOL_PLUS, TR("Add a channel"), []{ openAddChannelSheet(); }, false },
{ LV_SYMBOL_OK, TR("Mark all as read"), chatsMarkAllAsk, false },
#endif
{ UI_ICON_QR_CODE, TR("Share my contact"), []{ openShareMyContactPopup(); }, false },
};
openActionList(TR("Chats"), items, (int)(sizeof items / sizeof items[0]));
@@ -37498,6 +37504,19 @@ static void makeMapTab(lv_obj_t* tab) {
lv_obj_set_pos(b, k_map_canvas_w - 32 - 4, y);
styleButton(b);
lv_obj_set_style_bg_opa(b, LV_OPA_70, LV_PART_MAIN);
#if defined(HAS_THINKNODE_M9)
// #609: the keypad cursor was too faint over the tiles. Focused, the button fills
// solid accent with a thick white ring, so it can't be lost against the map.
for (lv_style_selector_t sel : { (lv_style_selector_t)(LV_PART_MAIN | LV_STATE_FOCUS_KEY),
(lv_style_selector_t)(LV_PART_MAIN | LV_STATE_FOCUSED) }) {
lv_obj_set_style_bg_color(b, lv_color_hex(COLOR_ACCENT), sel);
lv_obj_set_style_bg_opa(b, LV_OPA_COVER, sel);
lv_obj_set_style_outline_color(b, lv_color_white(), sel);
lv_obj_set_style_outline_width(b, 3, sel);
lv_obj_set_style_outline_pad(b, 1, sel);
lv_obj_set_style_outline_opa(b, LV_OPA_COVER, sel);
}
#endif
lv_obj_add_event_cb(b, cb, LV_EVENT_CLICKED, nullptr);
lv_obj_t* l = lv_label_create(b);
lv_label_set_text(l, sym);
@@ -45065,6 +45084,7 @@ static bool m9IsGlobalShortcutKey(int key) {
case M9_KEY_MAP:
case M9_KEY_CTRL:
case M9_KEY_GPS_LONG:
case M9_KEY_MIC:
return true;
default:
return false;
@@ -47607,7 +47627,8 @@ static bool bleKbdTabHotkey(int cp) {
return true;
}
static uint32_t s_m9_home_last_ms = 0; // last HOME press, for double-press → Settings
static uint32_t s_m9_home_last_ms = 0; // last HOME press, for double-press → launcher settings
static bool s_m9_home_prev_drawer = false; // drawer state before that press
static bool m9LockedHomeDrawerFrontmost() {
#if defined(HAS_THINKNODE_M9)
lv_obj_t* top = lv_layer_top();
@@ -47830,6 +47851,20 @@ static bool m9HandleNavKey(int key) {
navPushTap(LV_KEY_ENTER);
}
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return true;
case M9_KEY_MIC: {
// Options key (#609): in an open chat, its thread menu (same as the header ⋯);
// elsewhere, the focused item's hold action (a chat row's menu, etc.). Holding
// Enter does the same, but its long-press code can't be relied on.
if (s_setup_root || s_apppage_close || anyPopupOpen()) return true;
if (navOpenChatPanel()) {
s_nav_ta_editing = false;
openActiveChatSettings();
} else {
lv_obj_t* foc = s_nav_group ? lv_group_get_focused(s_nav_group) : nullptr;
if (foc && lv_obj_is_valid(foc)) lv_event_send(foc, LV_EVENT_LONG_PRESSED, nullptr);
}
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return true;
}
case M9_KEY_ENTER_LONG: {
lv_obj_t* foc = s_nav_group ? lv_group_get_focused(s_nav_group) : nullptr;
if (foc && lv_obj_is_valid(foc)) lv_event_send(foc, LV_EVENT_LONG_PRESSED, nullptr);
@@ -47949,18 +47984,25 @@ static bool m9HandleNavKey(int key) {
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return true;
}
{
// Double-press HOME opens Settings. The controller sends one byte per press and
// no key-up, so a hold can't be seen; the first press still acts as a normal HOME.
// Double-press HOME opens the launcher settings (what touch boards get by holding
// the Home tab). The controller sends one byte per press and no key-up, so a hold
// can't be seen; the first press still acts as a normal HOME, and the second puts
// the drawer back the way it was before the first.
const uint32_t now = millis();
const bool dbl = s_m9_home_last_ms && (uint32_t)(now - s_m9_home_last_ms) < 450;
s_m9_home_last_ms = dbl ? 0 : now;
if (dbl) {
s_m9_map_pan = false;
for (int i = 0; i < 8 && anyPopupOpen(); i++) { if (!hwKeyDismissTopPopup()) break; }
if (!anyPopupOpen() && getActiveTab() == HOME_TAB_INDEX && s_home_drawer_mode) setHomeDrawer(false);
if (!anyPopupOpen()) { navGoToMainTab(SETTINGS_TAB_INDEX); m9NavClear(); }
if (!anyPopupOpen() && (getActiveTab() == HOME_TAB_INDEX || navGoToMainTab(HOME_TAB_INDEX))) {
if (s_home_drawer_mode != s_m9_home_prev_drawer && !m9LockedHomeDrawerFrontmost())
setHomeDrawer(s_m9_home_prev_drawer);
m9NavClear();
openAppGridSheet();
}
s_nav_show = true; if (g_lv.task) g_lv.task->noteUserInput(); return true;
}
s_m9_home_prev_drawer = (getActiveTab() == HOME_TAB_INDEX) && s_home_drawer_mode;
}
if (getActiveTab() == HOME_TAB_INDEX) {
const bool was_open = s_home_drawer_mode; // read BEFORE dismissing anything
@@ -72650,6 +72692,9 @@ void UITask::wakeScreen() {
if (_manual_lock && s_pin_on) { unlockScreen(); return; }
#if CAP_SCREENSAVER
if (s_ssaver_on) {
// A preview ignores input for its first moment: the key or tap that asked for it
// (its release, a repeat) used to end it after half a second (#609, Tanmatsu).
if (s_ssaver_is_preview && (uint32_t)(millis() - s_ssaver_start_ms) < 1200) return;
// The screensaver is already lit and the CPU at full speed: nothing to power
// up. Clearing _screen_off is what ends it; loop() tears it down and puts the
// user's brightness back.
@@ -74242,10 +74287,18 @@ void UITask::newMsgImpl(uint8_t path_len, const char* from_name, const char* tex
: (glance_cmute & TOUCH_CHMUTE_MSG) != 0);
#else
const bool glance_muted = false;
#endif
#if defined(HAS_M9_KEYBOARD)
// M9 (#609): the message-flash wake can light the screen before this runs, which
// used to skip the glance and leave the app (Commander) showing for the notify
// window. A screen lit by a message, not by the user, still gets the glance.
const bool lit_by_msg = s_notify_wake_ms && !s_glance_lit_ms && _last_input_ms <= s_notify_wake_ms;
#else
const bool lit_by_msg = false;
#endif
if (glance_enabled_ok && !dndActive() && glance_locked_ok && !glance_muted &&
(_screen_off || s_glance_lit_ms)) {
const bool was_off = _screen_off;
(_screen_off || s_glance_lit_ms || lit_by_msg)) {
const bool was_off = _screen_off || lit_by_msg;
// Title carries WHO and HOW FAR, not just where. Reading "3 unread" off a
// dark screen tells you nothing you can act on; knowing it is a direct
// message from a named contact one hop away, versus channel chatter from
@@ -74263,7 +74316,12 @@ void UITask::newMsgImpl(uint8_t path_len, const char* from_name, const char* tex
atGlanceShow(gm, was_off); // fade in only on the initial reveal of a burst
// Over the screensaver the panel is lit already: the glance just covers it,
// and when the glance times out the screensaver is what remains (loop()).
if (was_off && !s_ssaver_on) {
if (lit_by_msg) {
#if defined(HAS_M9_KEYBOARD)
s_notify_wake_ms = 0; // the glance's own 5 s window re-dims the screen now
#endif
lv_refr_now(nullptr);
} else if (was_off && !s_ssaver_on) {
lv_refr_now(nullptr); // paint before the backlight comes on -- no stale-frame flash
if (_manual_lock) {
// Locked: light the panel WITHOUT clearing the lock -- wakeScreen()
+3 -3
View File
@@ -45,9 +45,9 @@
// (labeled "GPS toggle" — bound to toggleGPS()), Enter long-press emits 0xA3
// (bound: LV_EVENT_LONG_PRESSED on the focused widget / lock-screen unlock).
// Shift/Sym/Alt never reach the wire — the controller consumes them as layer
// modifiers. DELIBERATELY UNBOUND: MIC 0x88 (no audio-capture feature exists)
// and 0x89 (matrix col3/row4, physical key cap unidentified — binding an
// unknown cap invites accidental triggers).
// modifiers. MIC 0x88 is bound as an Options key (a chat's menu, or the focused
// item's hold action). DELIBERATELY UNBOUND: 0x89 (matrix col3/row4, physical
// key cap unidentified — binding an unknown cap invites accidental triggers).
#define M9_KEY_GPS_LONG 0x87
#define M9_KEY_ENTER_LONG 0xA3