Compare commits

...
32 Commits
Author SHA1 Message Date
Kaj Schittecat 475245c90e chore: refresh Mesh America catalog for beta_15 2026-06-24 11:01:48 +02:00
Kaj SchittecatandClaude Opus 4.8 1f43a8ba37 release: beta_15 notes — amber power-save battery, @-mention picker (#42), overlay scroll-keep (#45), app-send sync (#46), kbd-nav off by default
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:58:45 +02:00
Kaj SchittecatandClaude Opus 4.8 d491065719 touch: power-save shows as an amber battery, not a moon icon (T-Deck)
Idle power-save (light-sleep) advertised itself with a separate crescent-moon
glyph in the status bar. Replace it with an iPhone-Low-Power-Mode-style amber
tint on the battery icon (iOS systemYellow): clearer, and frees the bar slot.
The moon stays in the tree but hidden, keeping the layout and its tap handler.

A single applyBattColor() helper owns the battery colour so the map-chrome
restyle (black/white over light tiles) and the per-tick refresh never fight —
the amber overlays whatever base colour the context wants when power-save is on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:54:06 +02:00
Kaj SchittecatandClaude Opus 4.8 d11a5bb6b2 touch: keep list scroll position when closing an overlay (issue #45)
Closing a settings item / modal / chat with keyboard-nav on snapped the
underlying list back to the top. navMaybeRebuild rebuilds the focus group when
the overlay closes, but the previously-focused element lived inside the overlay
and is gone, so focus fell to the first list item and the list scrolled up to
show it. Affected every screen because every overlay closes through this path.

Stash the page item that had focus when leaving for an overlay/chat, and restore
it on return — so focus, and the scroll position, land back where they were.
Falls back to the prior behaviour if that item is genuinely gone. Most visible on
beta_14 (keyboard-nav defaulted on); new installs now default it off.

Reported by ZorkaTheDog (#45).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:31:47 +02:00
Kaj SchittecatandClaude Opus 4.8 c8849916df touch: show companion-app-sent DMs on the device (issue #46, half 1)
A DM sent from the MeshCore companion app went out over the mesh but never
appeared on the T-Deck: the wire protocol deliberately doesn't echo sent
messages back to clients, so the on-device UI (a separate consumer) never saw
app-originated sends. Add an appSentMsgToContact() UI hook (default no-op for
non-touch UIs); the touch UI mirrors the message as a local outgoing bubble in
the recipient's thread. Called from the CMD_SEND_TXT_MSG handler on first
handling only (a client/transport retry was already mirrored).

Still open: the receive direction (T-Deck-received messages reaching the app) —
queueMessage already pushes RESP_CODE_CONTACT_MSG_RECV_V3 to all transports, so
that half needs a frame capture against the real app to pin down.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:54:38 +02:00
Kaj SchittecatandClaude Opus 4.8 278b31cc92 touch: keyboard navigation OFF by default (Tanmatsu stays on)
Reverses the v15 force-on. New installs and existing T-Deck/V4 devices now
default to keyboard-nav OFF (a v18 one-time migration flips existing installs
to off); the user's later explicit on/off then persists. The Tanmatsu is exempt
— it has no touchscreen, so keyboard nav is its only input and stays on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:39:28 +02:00
Kaj SchittecatandClaude Opus 4.8 24f338438e touch: tapping an already-selected field enters edit mode
The touch-to-edit path keyed off navFocusCb (a focus CHANGE), so a field that was
already the selected one — e.g. the Profile node-name field, auto-selected on
entry — couldn't be made typable by tapping it (the tap doesn't re-focus, so
navFocusCb never fires). settingsFieldFocusCb already runs on CLICKED; now a real
tap also sets edit mode, so a selected field becomes typable on tap. Keyboard-nav
onto a field still starts in navigate mode (press Enter).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:29:05 +02:00
Kaj SchittecatandClaude Opus 4.8 3e1b45504f touch: navTreeSig must not COUNT NAV_SKIP children (complete the accent-box fix)
The previous fix returned 0 from the recursion for NAV_SKIP/HIDDEN subtrees, but
the parent loop still iterated over the added/removed child (h = h*prime + 0),
so the child COUNT still moved the signature. The accent box + @-mention picker
are created/deleted on every keystroke, so the count flipped each time → rebuild
→ edit-mode reset → typing broke mid-word (and Esc then read as navigate-back,
exiting the screen). Now skip those children in the loop entirely so they never
affect the signature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:22:18 +02:00
Kaj SchittecatandClaude Opus 4.8 12c34a2b35 touch: accent/mention box no longer breaks typing in non-chat fields
Non-chat text fields (add-channel, Wi-Fi, room login, settings modals) live on
the top UI layer, so keyboard nav is rooted there (useTop). The accent box and
@-mention picker are ALSO on the top layer, and they were counting toward
navTreeSig() — so each keystroke that showed/hid the box flipped the signature,
triggered a navMaybeRebuild(), and that rebuild's remove-all + re-focus churn
reset edit mode, dropping you out of typing mid-word.

- navTreeSig() now skips NAV_SKIP_FLAG subtrees (accent box, mention picker), so
  those passive overlays never trigger a rebuild.
- navFocusCb() ignores the transient f==null a rebuild's remove-all emits, so any
  other rebuild can't reset edit mode either.

(The T-Deck never used the keyboard mirror — kbMirrorActive() is false there; the
mirror is V4-only for the on-screen keyboard.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:05:25 +02:00
Kaj SchittecatandClaude Opus 4.8 0a3f9fe82f touch: @-mention picker (#42), edit-mode cursor cue, robust edit-state
- @-mention picker (issue #42): typing "@" in a chat composer pops a touch list
  of matching contacts (prefix-filtered, up to 6); tap one to insert "@Name ".
  Passive NAV_SKIP_FLAG overlay like the accent box; touch-select for now. Takes
  priority over the accent box while a mention is active (composerSuggestRefresh).
- Cursor now reflects edit mode: shown only while editing, hidden while a focused
  field is just the keyboard-nav target — a reliable "ready to type" cue
  (navSyncCursor() each nav tick).
- Edit mode is decided only on a genuine move to a different field, so a rebuild
  that re-focuses the same field (e.g. while the accent box is up) no longer
  kicks you out of typing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 08:20:40 +02:00
Kaj SchittecatandClaude Opus 4.8 02cff3389d touch: tap-to-edit on touch focus + map-render watchdog yield
- Keyboard nav: tapping a text field by touch now enters edit mode immediately
  (focus came from the pointer indev) instead of requiring Enter. Reaching a
  field with the letter-nav keys still starts in navigate mode (press Enter).
- Map: yield to IDLE after each tile in renderMapTiles() so a slow multi-tile
  render on loopTask can't starve the task watchdog and panic — fixes a beta_14
  TWDT reboot seen during map use (CPU1=loopTask). The download worker already
  does the same vTaskDelay(1) on its side.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 08:08:16 +02:00
Kaj Schittecat 4e3c212a89 chore: refresh Mesh America catalog for beta_14 2026-06-24 00:43:49 +02:00
Kaj SchittecatandClaude Opus 4.8 a804d709cf release: beta_14 notes — hotfix (kbd-nav-off typing regression)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 00:41:55 +02:00
Kaj SchittecatandClaude Opus 4.8 7111789fa7 touch: fix typing dead when keyboard-nav is OFF (beta_13 regression)
The keyboard-nav rework gated the physical-keyboard typing target on
s_nav_ta_editing (edit mode). That flag is only ever set while keyboard
navigation is ON (navFocusCb, driven by the nav focus group). With keyboard
navigation turned OFF the nav group isn't maintained, so the flag stayed false
and `ta` was always null — keys never reached the focused field and typing was
completely dead on the T-Deck (and Tanmatsu).

Fix: when keyboard-nav is OFF there is no navigation to protect, so a focused
field is always the typing target (ta = ta_focused). Edit-mode gating still
applies only when keyboard-nav is ON. Affects handleHwKey (T-Deck) + navPump
(Tanmatsu); the V4 (no keyboard nav) was never affected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 00:39:38 +02:00
Kaj SchittecatandClaude Opus 4.8 fbc015cf5c site: Community Videos section + YouTube/Docs/Map quick-link buttons
- New "Community videos" section under the install/flasher area: a JS-array-driven grid with a click-to-play facade (youtube-nocookie, lazy iframe). First entry is KDHD Stuff's review; add more by appending to the VIDEOS array.
- Float the quick-links into stacked left/right rails: left = Telegram + a YouTube button that jumps to the videos section; right = bug report + a Documentation button (placeholder — toasts "coming soon") + a Map button to t413.com's MapTileDl downloader.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 00:34:44 +02:00
Kaj Schittecat 3ae9582431 chore: refresh Mesh America catalog for beta_13 2026-06-24 00:02:48 +02:00
Kaj SchittecatandClaude Opus 4.8 36e5ed4c36 release: beta_13 notes — community drop (#39 #40 #22 #38 #29 + keyboard/typing/12h/V4 fixes)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 23:59:56 +02:00
Kaj SchittecatandClaude Opus 4.8 f817fca41c touch: disable crash-report UI on Heltec V4 (no way to retrieve/send the dump)
The boot crash-report prompt + the About-page export button are only useful
where the saved coredump file can actually be retrieved and sent to the devs:
the T-Deck (SD card) and Tanmatsu (FFat, via the launcher file manager). On the
Heltec V4 there's no SD and no user-reachable internal storage, so the prompt
was a dead end. Gate crashDumpCheck() to those boards only — on the V4 the dump
is never detected, so the whole crash-report UI stays off (everything downstream
gates on s_crash_dump_size). The panic coredump still sits in its flash
partition for a USB/esptool pull if ever needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 23:49:41 +02:00
SamandClaude Sonnet 4.6 5fcf6ddac3 fix: block commands while transport-off confirmation is pending
Any command arriving while s_meshcomod_pending_action is non-NONE and
the timeout hasn't expired previously fell through the confirmation
block and was processed normally, letting a second 'tcp off' / 'ble off'
silently overwrite the pending action and lose the original confirmation.

Add a final else clause that blocks the new command and reminds the user
to 'ok' or 'cancel' the in-flight request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 23:25:02 +02:00
SamandClaude Sonnet 4.6 5e6aaa8852 fix: restart TCP immediately on enable, guard OTA slot, fix toggleTcp alert race, re-enable warnings
- enableTcp() now calls startTcpServer(false) directly instead of relying
  on the main loop — fixes TCP not restarting when wifi_started is false
  (BLE-only mode, radio toggled off, or cold re-enable from USB).

- Add touchHasOtaUpdateSlot() guard to the OTA button creation block so
  the update affordances stay hidden under Launcher (single app slot),
  where ota url would fail silently.

- Fix toggleTcpCb alert race: snapshot isTcpEnabled() before the toggle
  so the alert text reflects the transition, not a post-action re-read
  that could mismatch during OTA suspension.

- Replace global -w with -Wno-deprecated-declarations -Wno-unused-parameter
  in both touch environments to keep third-party lib noise down while
  surfacing real regressions in OTA, network, and companion paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 23:25:02 +02:00
Kaj Schittecat cd2dc8b3f8 Merge PR #38: touch — optional trackball edge-scroll (tun0) 2026-06-23 23:22:34 +02:00
Kaj Schittecat f4fb7171e5 Merge PR #22: touch — beer emoji, status-bar unread tap, new-contact toast toggle, Czech accents (Vybo)
# Conflicts:
#	src/helpers/esp32/TouchPrefsStore.cpp
2026-06-23 23:22:23 +02:00
Kaj Schittecat 8322b5361d Merge PR #40: ui-touch — room-server posts show author, not room name (Yazutsu, closes #32) 2026-06-23 23:19:14 +02:00
Kaj Schittecat 266f5c8adc Merge PR #39: touch — Direct (0-hop) contact + map filter (tun0) 2026-06-23 23:19:01 +02:00
Kaj SchittecatandClaude Opus 4.8 069aeb8908 touch: keyboard-nav typing fixes, dedicated symbol picker, 12h everywhere
Post-beta_12 touch-UI fixes (T-Deck / Tanmatsu keyboard nav + composer):

Keyboard navigation
- Chat composer auto-edits on open (cursor shows, type immediately); every
  OTHER field stays in navigate mode so the letter-nav keys keep working —
  press select/Enter to edit it. Enter (or Esc/X on Tanmatsu) on an empty
  composer drops back to navigate mode.
- Menu-bar tab-hotkey letters no longer fire while a text field is the focused
  element (fixes a stray tab-jump while typing); directional nav still moves.
- New "Show menu-bar letters" setting (off by default) for the tab hotkeys.

Accent box
- The tap-to-pick accent box no longer steals the keyboard-nav focus group
  (NAV_SKIP_FLAG) — it's a passive hint, so typing continues and a keybind no
  longer navigates. Ready to wire arrow-key selection later.

Composer
- Dedicated "Special characters" picker (% $ @ # & ... currency/math/accents),
  separate from the emoji picker.
- Long-press a sent message -> Resend.

Misc
- 12-hour clock honoured everywhere (top bar, chat bubbles, lock screen), not
  just the chat overview.
- GPS UART RX buffer enlarged only when GPS is enabled (restores V4 RAM).
- Derive time-of-day from received mesh message timestamps when Wi-Fi/GPS off.
- Self-record the task-watchdog context on a TWDT reset for the crash report.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 23:18:34 +02:00
Yazutsu 61a914240f ui-touch: show post author instead of room name for room-server messages
Room posts arrive as TXT_TYPE_SIGNED_PLAIN with the author in the 4-byte sender_prefix and a bare body. The display path passed the room contact's name, so every post showed the room name. Resolve the author from the prefix and pass it as the per-message sender via a new newRoomMsgFromPubWithMeta hook; the room stays the thread. Tested on T-Deck Plus.

Signed-off-by: Yazutsu <andrzej@gruziel.pl>
2026-06-23 22:45:39 +02:00
Ruben Laban 29204dd33f touch: add Direct (0-hop) filter to contact list and map
Contact list gains a "Direct (0-hop)" option in the Filter sheet (value 5)
that shows only nodes heard without any repeater hops (out_path_len == 0).
Header chip shows "Dir" when active. Selecting a filter now auto-dismisses
the sheet (matching sort behaviour).

Map options panel gets a "Direct (0-hop) only" toggle that hides both
contact markers and link lines for any multi-hop or path-unknown node.
2026-06-23 14:36:57 +02:00
Ruben Laban bb86a32b04 touch: add optional edge-scroll for trackball cursor mode
When enabled in Settings → Keyboard, pushing the cursor past any screen
edge routes the overflow into the nearest scrollable widget rather than
absorbing it.  Works on regular tab content (lv_scr_act children) and on
lv_layer_top() overlays such as the app drawer.

Implementation:
- tbFindScrollableAt() hit-tests at the vertical centre of the content
  area (avoids status/tab-bar chrome and per-tab headers), searches
  lv_layer_top() before lv_scr_act(), then walks ancestors to the first
  scrollable with room in the overflow direction.
- scroll_by direction is negated relative to raw overflow (scroll_by(+dy)
  moves children down = reveals earlier content = scroll up).
- Scroll amount is capped to lv_obj_get_scroll_top/bottom/left/right so
  we never push past the widget's content bounds.
- Preference persisted as NVS key "tb_edgesc" (default off).
- Map, Snake, and emoji-picker modes are unaffected (they return before
  cursor mode runs).
2026-06-23 14:07:57 +02:00
Kaj Schittecat f985458363 chore: refresh Mesh America catalog for beta_12 2026-06-23 08:51:17 +02:00
Kaj SchittecatandClaude Opus 4.8 30083efdd7 touch: beta_12 — GPS hotfix (enlarge GPS UART RX buffer)
The GPS NMEA UART used Arduino's default 256-byte RX ring (~66 ms of slack at
38400 baud). On the busy touch UI a single long LVGL/map frame stalls the loop
past that window, dropping bytes and corrupting NMEA ephemeris subframes — each
loss costs the receiver ~30 s, turning a ~1-minute fix into several minutes or,
with frequent stalls, never acquiring. Confirmed on-device: peak UART backlog
hit 448 B at idle, well past the 256 B default. Bump the ring to 4096 B (~1 s of
slack) before the core opens Serial1 in sensors.begin(). App-side (no core
re-tag); gated to ENV_INCLUDE_GPS so both touch boards get it. A -DGPS_BUF_DEBUG
backlog probe is included (compiled out of releases).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 08:49:00 +02:00
Dan Vybiral 4a0d7f5247 touch: beer emoji, status-bar unread tap, new-contact toast toggle, Czech accents
- emoji: bake the beer mug U+1F37A and clinking beers U+1F37B as colour glyphs
  into emoji_data.c (now 252) and add the beer mug to the composer picker; the
  clinking pair was already listed but only rendered monochrome, now colour.
- status bar: tapping the unread badge (envelope + count) on the left now jumps
  to the Chats inbox. The CLICKABLE flag is toggled per-tick so only the badge
  state intercepts the tap — every other left-zone state (Back chevron, profile
  name, OSM credit, blank) keeps the bar's control-center / Back tap.
- contacts: add a "Notify on new contact" toggle to the Auto-add page
  (TouchCfg v8, default ON). When off it suppresses the auto-discovery toast
  chip in UITask::notify(); the diag-log line and tab badge are unaffected.
- keyboard: add the Czech accents c-caron, e-caron, t-caron, z-caron, r-caron
  and u-ring (plus uppercase) to the long-press / tap accent popup. Glyphs are
  already baked in the extras fallback fonts, so no font regeneration needed.

Builds verified on both LilyGo_TDeck_companion_radio_touch and
heltec_v4_tft_companion_radio_usb_tcp_touch.

Signed-off-by: Dan Vybiral <dan.vybiral@greencode.cz>
2026-06-22 20:37:24 +02:00
Kaj SchittecatandClaude Opus 4.8 d22390aeb8 flasher: point manifest at immutable per-tag bin, not latest/
The web-flasher manifest referenced https://firmware.wadamesh.com/latest/<board>-merged.bin
— a stable filename cached 4h (Cache-Control max-age=14400) and overwritten in place each
release. version.json (max-age=300) advertised the new tag within minutes, but the latest/
bin could keep serving the PREVIOUS beta's bytes per-POP for up to 4h, so a user flashing
right after a release could install the old version (reported: flashed beta_11, device showed
beta_10). Point the manifest at the immutable releases/TOUCH/<tag>/ bin instead — that URL is
never overwritten so its cache is always correct, and the 5-min manifest TTL propagates the
new path fast.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 20:10:44 +02:00
18 changed files with 1314 additions and 209 deletions

No files matched your search

+8 -8
View File
@@ -16,19 +16,19 @@
"role": "gui",
"title": "Touch UI",
"version": {
"beta_11": {
"notes": "New keyboard navigation — drive the whole UI from the keyboard, no touchscreen needed: the WASDZ cluster moves the on-screen selection in the actual direction you press (W up, A left, Z down, D right), S selects, Q goes back, and each bottom tab has its own hotkey (E Messages, R Contacts, T Home, U Map, I Settings) shown as a small letter on its icon. On by default; toggle it any time in Settings → Keyboard.\nEvery navigation key is hotkey-bindable — the directions, select, back, the per-tab hotkeys, and the new scroll-up / scroll-down keys (default F / C) — remap them all under Settings → Keyboard.\nThis lays the groundwork for upcoming keyboard-first devices like the Tanmatsu T-Pager that have no touchscreen, and it already gives every T-Deck a fast, reliable way to use wadamesh entirely from the keyboard.\nThe selected item now shows as clean reverse-video (a bright outline for switches) instead of a coloured border, and the highlight only appears while you're actively navigating with the keys.\nThe Home hotkey mirrors tapping the Home tab — press it again on Home to flip between the Commander screen and the app drawer.\nFixed a reboot that could occur when receiving a message, especially during advert/message floods — a use-after-free while a list rebuilt mid-scroll (#27).\nAfter a crash, the device now detects it on the next boot and offers to save the crash report so you can send it in, instead of you having to dig through Settings.\nMap options can now switch the zoom control between the slider and a +/- button pair, like older builds (#26).\nRoute replay now opens on a wide overview so you can see where the hops start and the whole path at a glance — and you can pan and zoom the map freely while it plays.\nNew \"App drawer as home\" option (in the app drawer's cog) makes the launcher your home screen instead of the Commander view, for people who prefer the app grid.\nThe in-progress Tanmatsu (ESP32-P4) port source is now part of the public tree.",
"beta_15": {
"notes": "Power-saving now shows as an amber battery icon in the status bar — like iPhone's Low Power Mode — instead of a separate moon icon (T-Deck).\nNew @-mention picker: type @ in the message box to pick a contact from a list and drop their name in (thanks robinshizuyo-dev, #42).\nClosing a settings item, menu or chat no longer snaps the list back to the top — it stays where you left it (thanks ZorkaTheDog, #45).\nMessages you send from the MeshCore companion app now also show up on the device, in the right conversation (thanks HonzaDandy, #46).\nTyping is more reliable on the T-Deck and Tanmatsu: tapping a text field — even one that's already highlighted — starts typing right away, and the accent pop-up no longer interrupts typing outside of chats.\nThe text cursor now only appears when a field is actually ready for input, so it's a dependable \"you can type now\" cue.\nSmoother map panning: tile rendering no longer trips the watchdog and reboots during heavy scrolling.\nKeyboard navigation is now OFF by default. If you prefer driving the UI with the trackball / arrow keys, switch it back on in Settings -> Keyboard (your choice sticks until you change it). The Tanmatsu keeps it on, since it has no touchscreen.\nIncludes everything from beta_14 and the earlier betas.",
"files": [
{
"type": "flash-wipe",
"name": "wadamesh-heltec-v4-tft-merged.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_11/wadamesh-heltec-v4-tft-merged.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_15/wadamesh-heltec-v4-tft-merged.bin",
"title": "Full install (bootloader + firmware)"
},
{
"type": "flash-update",
"name": "wadamesh-heltec-v4-tft.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_11/wadamesh-heltec-v4-tft.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_15/wadamesh-heltec-v4-tft.bin",
"title": "Update (app only)"
}
]
@@ -48,19 +48,19 @@
"role": "gui",
"title": "Touch UI",
"version": {
"beta_11": {
"notes": "New keyboard navigation — drive the whole UI from the keyboard, no touchscreen needed: the WASDZ cluster moves the on-screen selection in the actual direction you press (W up, A left, Z down, D right), S selects, Q goes back, and each bottom tab has its own hotkey (E Messages, R Contacts, T Home, U Map, I Settings) shown as a small letter on its icon. On by default; toggle it any time in Settings → Keyboard.\nEvery navigation key is hotkey-bindable — the directions, select, back, the per-tab hotkeys, and the new scroll-up / scroll-down keys (default F / C) — remap them all under Settings → Keyboard.\nThis lays the groundwork for upcoming keyboard-first devices like the Tanmatsu T-Pager that have no touchscreen, and it already gives every T-Deck a fast, reliable way to use wadamesh entirely from the keyboard.\nThe selected item now shows as clean reverse-video (a bright outline for switches) instead of a coloured border, and the highlight only appears while you're actively navigating with the keys.\nThe Home hotkey mirrors tapping the Home tab — press it again on Home to flip between the Commander screen and the app drawer.\nFixed a reboot that could occur when receiving a message, especially during advert/message floods — a use-after-free while a list rebuilt mid-scroll (#27).\nAfter a crash, the device now detects it on the next boot and offers to save the crash report so you can send it in, instead of you having to dig through Settings.\nMap options can now switch the zoom control between the slider and a +/- button pair, like older builds (#26).\nRoute replay now opens on a wide overview so you can see where the hops start and the whole path at a glance — and you can pan and zoom the map freely while it plays.\nNew \"App drawer as home\" option (in the app drawer's cog) makes the launcher your home screen instead of the Commander view, for people who prefer the app grid.\nThe in-progress Tanmatsu (ESP32-P4) port source is now part of the public tree.",
"beta_15": {
"notes": "Power-saving now shows as an amber battery icon in the status bar — like iPhone's Low Power Mode — instead of a separate moon icon (T-Deck).\nNew @-mention picker: type @ in the message box to pick a contact from a list and drop their name in (thanks robinshizuyo-dev, #42).\nClosing a settings item, menu or chat no longer snaps the list back to the top — it stays where you left it (thanks ZorkaTheDog, #45).\nMessages you send from the MeshCore companion app now also show up on the device, in the right conversation (thanks HonzaDandy, #46).\nTyping is more reliable on the T-Deck and Tanmatsu: tapping a text field — even one that's already highlighted — starts typing right away, and the accent pop-up no longer interrupts typing outside of chats.\nThe text cursor now only appears when a field is actually ready for input, so it's a dependable \"you can type now\" cue.\nSmoother map panning: tile rendering no longer trips the watchdog and reboots during heavy scrolling.\nKeyboard navigation is now OFF by default. If you prefer driving the UI with the trackball / arrow keys, switch it back on in Settings -> Keyboard (your choice sticks until you change it). The Tanmatsu keeps it on, since it has no touchscreen.\nIncludes everything from beta_14 and the earlier betas.",
"files": [
{
"type": "flash-wipe",
"name": "wadamesh-tdeck-merged.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_11/wadamesh-tdeck-merged.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_15/wadamesh-tdeck-merged.bin",
"title": "Full install (bootloader + firmware)"
},
{
"type": "flash-update",
"name": "wadamesh-tdeck.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_11/wadamesh-tdeck.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_15/wadamesh-tdeck.bin",
"title": "Update (app only)"
}
]
+86 -9
View File
@@ -42,13 +42,13 @@
font-family:'JetBrains Mono',monospace; background:#fff; transition:border-color .15s, color .15s}
.kicker:hover{border-color:var(--teal); color:var(--ink)}
.freeline{margin:0 auto 24px; color:var(--mut); font-size:.92rem}
.fab{position:fixed; top:16px; z-index:45; width:44px; height:44px; border-radius:50%; display:grid; place-items:center;
.fab-rail{position:fixed; top:16px; z-index:45; display:flex; flex-direction:column; gap:12px}
.fab-rail.left{left:16px} .fab-rail.right{right:16px}
.fab{width:44px; height:44px; border-radius:50%; display:grid; place-items:center; cursor:pointer;
background:#fff; border:1px solid var(--line); color:#46525c; box-shadow:0 10px 26px -14px rgba(21,24,30,.45);
transition:border-color .15s, color .15s, transform .15s, box-shadow .15s}
.fab:hover{border-color:var(--teal); color:var(--teal-ink); transform:translateY(-2px); box-shadow:0 14px 30px -14px rgba(21,182,166,.5)}
.fab svg{width:21px; height:21px}
.fab-tg{left:16px}
.fab-bug{right:16px}
.brand{display:flex; align-items:center; justify-content:center; gap:clamp(12px,2.6vw,20px); flex-wrap:wrap; position:relative}
.brand .mark{width:clamp(60px,13vw,88px); height:auto; flex:none}
.brand .word{font-weight:700; font-size:clamp(2rem,7.5vw,3.35rem); letter-spacing:3px; color:var(--ink); line-height:1}
@@ -150,6 +150,27 @@
.noserial{display:none;color:#b4631a;text-align:center;margin-top:14px}
body.no-serial .noserial{display:block}
/* community videos */
.vids{display:grid; gap:18px; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); margin-top:34px}
.vid{border:1px solid var(--line); border-radius:16px; overflow:hidden; background:var(--card); box-shadow:0 12px 30px -22px rgba(21,24,30,.5)}
.vid-thumb{position:relative; aspect-ratio:16/9; width:100%; border:0; padding:0; cursor:pointer; display:block; background-size:cover; background-position:center}
.vid-thumb::after{content:""; position:absolute; inset:0; background:rgba(21,24,30,.12); transition:background .15s}
.vid-thumb:hover::after{background:rgba(21,24,30,0)}
.vid-thumb .play{position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); width:62px; height:44px; border-radius:12px;
background:rgba(229,57,53,.94); display:grid; place-items:center; z-index:1; transition:transform .15s, background .15s}
.vid-thumb:hover .play{transform:translate(-50%,-50%) scale(1.07); background:#e53935}
.vid-thumb .play svg{width:24px; height:24px; color:#fff}
.vid-embed{aspect-ratio:16/9; width:100%; border:0; display:block; background:#000}
.vid-cap{padding:13px 15px; font-size:.92rem; color:var(--ink); font-weight:600}
.vid-cap span{display:block; color:var(--mut); font-size:.82rem; font-weight:400; margin-top:2px}
/* toast (docs placeholder etc.) */
.toast{position:fixed; left:50%; bottom:26px; transform:translateX(-50%) translateY(12px); z-index:70;
background:var(--ink); color:#fff; padding:11px 20px; border-radius:100px; font-size:.92rem; font-weight:600;
box-shadow:0 16px 36px -14px rgba(21,24,30,.55); opacity:0; visibility:hidden; pointer-events:none;
transition:opacity .2s ease, transform .2s ease}
.toast.show{opacity:1; visibility:visible; transform:translateX(-50%) translateY(0)}
footer{border-top:1px solid var(--line); margin-top:48px; padding:30px 0 56px; text-align:center; color:var(--mut); font-size:.86rem}
footer a{color:var(--teal-ink)}
.legal-links{margin-top:12px}
@@ -176,12 +197,26 @@
<div id="vanta-bg"></div>
<!-- floating quick links -->
<a class="fab fab-tg" href="https://t.me/+fHuhG67-RFJlNmFk" target="_blank" rel="noopener" aria-label="Join us on Telegram" title="Join us on Telegram">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9.04 15.47 8.7 19.9c.5 0 .72-.22.98-.47l2.36-2.22 4.89 3.53c.9.5 1.53.24 1.77-.83l3.2-14.5c.3-1.33-.5-1.85-1.36-1.53L1.9 9.46c-1.3.5-1.28 1.22-.22 1.55l5.05 1.57L18.4 5.9c.55-.36 1.05-.16.64.2L9.04 15.47z"/></svg>
</a>
<a class="fab fab-bug" href="https://github.com/ALLFATHER-BV/wadamesh/issues" target="_blank" rel="noopener" aria-label="Report a bug on GitHub" title="Report a bug">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="8" height="14" x="8" y="6" rx="4"/><path d="m19 7-3 2"/><path d="m5 7 3 2"/><path d="m19 19-3-2"/><path d="m5 19 3-2"/><path d="M20 13h-4"/><path d="M4 13h4"/><path d="m10 4 1 2"/><path d="m14 4-1 2"/></svg>
</a>
<div class="fab-rail left">
<a class="fab" href="https://t.me/+fHuhG67-RFJlNmFk" target="_blank" rel="noopener" aria-label="Join us on Telegram" title="Join us on Telegram">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9.04 15.47 8.7 19.9c.5 0 .72-.22.98-.47l2.36-2.22 4.89 3.53c.9.5 1.53.24 1.77-.83l3.2-14.5c.3-1.33-.5-1.85-1.36-1.53L1.9 9.46c-1.3.5-1.28 1.22-.22 1.55l5.05 1.57L18.4 5.9c.55-.36 1.05-.16.64.2L9.04 15.47z"/></svg>
</a>
<a class="fab" href="#videos" aria-label="Community videos" title="Community videos">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
</a>
</div>
<div class="fab-rail right">
<a class="fab" href="https://github.com/ALLFATHER-BV/wadamesh/issues" target="_blank" rel="noopener" aria-label="Report a bug on GitHub" title="Report a bug">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="8" height="14" x="8" y="6" rx="4"/><path d="m19 7-3 2"/><path d="m5 7 3 2"/><path d="m19 19-3-2"/><path d="m5 19 3-2"/><path d="M20 13h-4"/><path d="M4 13h4"/><path d="m10 4 1 2"/><path d="m14 4-1 2"/></svg>
</a>
<button class="fab" id="docsBtn" type="button" aria-label="Documentation" title="Documentation">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v5h5"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/></svg>
</button>
<a class="fab" href="https://t413.com/p/projects/MapTileDl/" target="_blank" rel="noopener" aria-label="Map tile downloader" title="Map tile downloader">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18l-6 3V6l6-3 6 3 6-3v15l-6 3-6-3z"/><path d="M9 3v15"/><path d="M15 6v15"/></svg>
</a>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<header>
<div class="wrap">
@@ -304,6 +339,14 @@
</div>
</section>
<section id="videos">
<div class="wrap">
<h2 class="h2">Community videos</h2>
<p class="p">Walkthroughs, demos and reviews from the community.</p>
<div class="vids" id="vids"></div>
</div>
</section>
<footer>
<div class="wrap">
Built for the mesh · open source under GPL-3.0 ·
@@ -420,6 +463,40 @@ We use no tracking, analytics, or advertising cookies. Our CDN and security prov
if (notes.length) document.querySelectorAll('[data-whatsnew]').forEach(b => b.hidden = false);
}).catch(()=>{});
// ---- community videos — add an entry here to add a video ----
const VIDEOS = [
{ id: 'V19od8jbFRc', title: 'WadaMesh: The Best T-Deck Firmware So Far', by: 'KDHD Stuff' },
// { id: '<youtube-id>', title: '<title>', by: '<channel>' },
];
const vidsEl = document.getElementById('vids');
if (vidsEl) VIDEOS.forEach(v => {
const card = document.createElement('div'); card.className = 'vid';
const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'vid-thumb';
btn.setAttribute('aria-label', 'Play: ' + (v.title || 'community video'));
btn.style.backgroundImage = 'url(https://i.ytimg.com/vi/' + v.id + '/hqdefault.jpg)';
btn.innerHTML = '<span class="play"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg></span>';
btn.addEventListener('click', () => {
const f = document.createElement('iframe'); f.className = 'vid-embed';
f.src = 'https://www.youtube-nocookie.com/embed/' + v.id + '?autoplay=1&rel=0';
f.title = v.title || 'Community video';
f.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
f.allowFullscreen = true; f.loading = 'lazy';
card.replaceChild(f, btn);
});
card.appendChild(btn);
if (v.title) {
const cap = document.createElement('div'); cap.className = 'vid-cap'; cap.textContent = v.title;
if (v.by) { const s = document.createElement('span'); s.textContent = 'by ' + v.by; cap.appendChild(s); }
card.appendChild(cap);
}
vidsEl.appendChild(card);
});
// ---- toast + docs placeholder ----
function toast(msg){ const t = document.getElementById('toast'); if (!t) return; t.textContent = msg; t.classList.add('show'); clearTimeout(t._t); t._t = setTimeout(() => t.classList.remove('show'), 2400); }
const docsBtn = document.getElementById('docsBtn');
if (docsBtn) docsBtn.addEventListener('click', () => toast('Documentation — coming soon!'));
</script>
<!-- animated mesh backdrop — Vanta NET (3D dot-and-line network) in wadamesh teal on white.
+2 -2
View File
@@ -17,7 +17,7 @@ board_build.partitions = partitions_tft_touch.csv
extra_scripts = pre:scripts/inject_wifi_env.py, merge-bin.py
build_flags =
-w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
-Wno-deprecated-declarations -Wno-unused-parameter -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
-D MC_VENDORED_TOUCH_APP
; NOTE: tried -D ARDUINO_USB_MODE=1 (HW-CDC, drops unused TinyUSB MSC/DFU ≈ 12 KB internal
; DRAM) — but on the real V4 the binary companion link regressed: small per-message frames
@@ -221,7 +221,7 @@ board_build.partitions = variants/lilygo_tdeck/partitions_tdeck_touch.csv
extra_scripts = pre:scripts/inject_wifi_env.py, merge-bin.py
build_flags =
-w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
-Wno-deprecated-declarations -Wno-unused-parameter -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
-D MC_VENDORED_TOUCH_APP
-D LORA_FREQ=869.618
-D LORA_BW=62.5
+8
View File
@@ -0,0 +1,8 @@
# beta_12 — GPS hotfix (one user-facing note per non-blank, non-# line; # lines are section comments)
# --- GPS fix (headline) ---
Fixed GPS being slow to get a fix — or sometimes never acquiring at all — on the T-Deck. The GPS receives its data over a small serial buffer, and on the busy touch UI a heavy screen redraw could stall long enough to overflow it and drop the satellite data mid-stream, which forced the receiver to start over. The buffer is now 16x larger, so those redraws no longer cost you the fix — acquisition is back to normal (a fix in about a minute instead of several, or none).
# --- A note from Kaj ---
More and more people are picking up wadamesh lately — which is genuinely awesome — and it's surfacing feature requests and bug reports fast, in big numbers.
I can't get back to every post in every channel, but I'm on it: I'm prioritising the fixes and quality-of-life improvements, and I'll be working through them later today. Thanks for all the reports and for the patience!
+29
View File
@@ -0,0 +1,29 @@
# beta_13 — community drop (one user-facing note per non-blank, non-# line; # lines are section comments)
# --- A note from Kaj (headline) ---
This one's a community release — thank you! A bunch of the features below came from contributor pull requests, and the rest are the keyboard, typing and quality-of-life fixes I promised after the last drop. Huge thanks to everyone sending PRs, opening issues and reporting bugs: Yazutsu, tun0, Vybo and samuelcoustet all have their work in this build. Keep it coming.
# --- Typing & keyboard navigation (T-Deck + Tanmatsu) ---
Open a chat or channel and the cursor is ready — you can type straight away. Every other text field now waits for Enter before it captures the keyboard, so the letter-navigation keys keep working until you actually want to type.
Pressing Enter (or Esc / the back button) on an empty message box drops the cursor and re-selects the field, so you can navigate away with the buttons again.
The menu-bar letter shortcuts no longer jump you to another page while you're typing in a field.
New "Show menu-bar letters" setting under Settings -> Keyboard (off by default) — turn it on to see the tab hotkey letters on the bar.
The accent pop-up no longer grabs the keyboard when it appears, so you can keep typing right through it.
# --- Composer ---
New dedicated "Special characters" picker for symbols that aren't on the keyboard (% $ @ # & =, currency, maths, accents) — separate from the emoji picker.
Long-press one of your sent messages to Resend it.
# --- Clock ---
The 12-hour clock setting now applies everywhere — the top bar, the chat bubbles and the lock screen — not just the chat list.
# --- From contributors ---
Room-server posts now show the actual author's name on each message instead of the room's own name. (Yazutsu, #40)
New "Direct (0-hop)" filter for the contact list and the map — show only nodes you're hearing directly, with no repeater hops in between. (tun0, #39)
Beer emoji in colour, tap the unread badge in the status bar to jump to your Chats, a new "notify on new contact" toggle, and Czech accents in the accent pop-up. (Vybo, #22)
Optional trackball "edge scroll" — push the cursor past a screen edge to scroll the list underneath it (off by default, under Settings -> Keyboard). (tun0, #38)
TCP now restarts immediately after you turn it off and on again, the OTA action is hidden when there's no update slot to use, and a TCP-toggle alert glitch is fixed. (samuelcoustet, #29)
# --- Fixes ---
Heltec V4: removed the after-crash report prompt — that board has no SD card or reachable storage to save the report to, so it was a dead end. (The T-Deck still offers it.)
When both Wi-Fi and GPS are off, the clock now sets itself from the timestamps on messages received over the mesh.
+7
View File
@@ -0,0 +1,7 @@
# beta_14 — hotfix (one user-facing note per non-blank, non-# line; # lines are section comments)
# --- Fix (headline) ---
Fixed a bug introduced in beta_13: if you had turned keyboard navigation OFF (Settings -> Keyboard), the physical keyboard stopped typing into text fields entirely on the T-Deck and Tanmatsu. Typing now works again with keyboard navigation off; with it on, nothing changed. (The Heltec V4 was never affected.)
# --- Also in this release ---
Includes everything from beta_13: room-server posts show the author's name, the Direct (0-hop) contact + map filter, beer emoji with a status-bar unread tap and a new-contact toast toggle and Czech accents, optional trackball edge-scroll, the dedicated special-characters picker, the 12-hour clock applied everywhere, and the keyboard navigation + typing improvements.
+18
View File
@@ -0,0 +1,18 @@
# beta_15 (one user-facing note per non-blank, non-# line; # lines are section comments)
# --- New ---
Power-saving now shows as an amber battery icon in the status bar — like iPhone's Low Power Mode — instead of a separate moon icon (T-Deck).
New @-mention picker: type @ in the message box to pick a contact from a list and drop their name in (thanks robinshizuyo-dev, #42).
# --- Fixed ---
Closing a settings item, menu or chat no longer snaps the list back to the top — it stays where you left it (thanks ZorkaTheDog, #45).
Messages you send from the MeshCore companion app now also show up on the device, in the right conversation (thanks HonzaDandy, #46).
Typing is more reliable on the T-Deck and Tanmatsu: tapping a text field — even one that's already highlighted — starts typing right away, and the accent pop-up no longer interrupts typing outside of chats.
The text cursor now only appears when a field is actually ready for input, so it's a dependable "you can type now" cue.
Smoother map panning: tile rendering no longer trips the watchdog and reboots during heavy scrolling.
# --- Changed ---
Keyboard navigation is now OFF by default. If you prefer driving the UI with the trackball / arrow keys, switch it back on in Settings -> Keyboard (your choice sticks until you change it). The Tanmatsu keeps it on, since it has no touchscreen.
# --- Also in this release ---
Includes everything from beta_14 and the earlier betas.
+10 -2
View File
@@ -5,7 +5,9 @@
# - manifest-heltec-v4-tft.json install dialog, so it must equal the tag)
#
# These live next to the rolling bins at firmware.wadamesh.com/latest/, so the
# flasher always reflects the current release without a site redeploy.
# flasher always reflects the current release without a site redeploy. The
# manifests themselves point at the IMMUTABLE per-tag bins (see below) to dodge
# the latest/*.bin 4h edge cache.
#
# Usage: gen-flasher-meta.py <tag> <outdir> [notes_file]
# notes_file: one note per non-empty line (plain text); optional.
@@ -35,7 +37,13 @@ for fn, (name, binf) in BOARDS.items():
"new_install_prompt_erase": True,
"builds": [{
"chipFamily": "ESP32-S3",
"parts": [{"path": f"https://firmware.wadamesh.com/latest/{binf}", "offset": 0}],
# Point at the IMMUTABLE per-tag bin, NOT latest/. The latest/*.bin URLs
# are stable filenames cached 4h (max-age=14400) and overwritten in place,
# so for up to 4h after a release the flasher could hand out the PREVIOUS
# beta's bytes while version.json already advertised the new tag. The
# per-tag path is never overwritten, so its (24h) cache is always correct.
# The manifest itself is max-age=300, so this new path propagates in <=5min.
"parts": [{"path": f"https://firmware.wadamesh.com/releases/TOUCH/{tag}/{binf}", "offset": 0}],
}],
}
with open(os.path.join(outdir, fn), "w") as f:
+22
View File
@@ -72,6 +72,28 @@ public:
(void)is_flood; (void)snr_q4; (void)rssi;
newMsgFromPub(path_len, from_pub, from_name, text, msgcount);
}
/** Room-server post. Like newMsgFromPubWithMeta, but from_pub/from_name
* identify the ROOM (the chat thread) while `author_name` is the resolved
* display name of the original poster — a room signed message attributes
* the author only by a pubkey prefix, never in the text body. Default
* ignores the author and behaves like a normal message, so non-touch UIs
* are unaffected. */
virtual void newRoomMsgFromPubWithMeta(uint8_t path_len, bool is_flood,
const uint8_t* from_pub, const char* from_name,
const char* author_name,
const char* text, int msgcount,
int8_t snr_q4, int8_t rssi) {
(void)author_name;
newMsgFromPubWithMeta(path_len, is_flood, from_pub, from_name, text, msgcount, snr_q4, rssi);
}
/** A message the COMPANION APP sent on our behalf (CMD_SEND_TXT_MSG). The wire protocol
* deliberately doesn't echo sent messages back to clients, so the on-device UI is otherwise
* the only consumer that never sees app-originated sends — this lets it mirror one as a local
* outgoing bubble so it shows on-device too (issue #46). Default no-op for non-touch UIs. */
virtual void appSentMsgToContact(const uint8_t* to_pub, const char* to_name, const char* text,
uint32_t ack_hash) {
(void)to_pub; (void)to_name; (void)text; (void)ack_hash;
}
virtual void notify(UIEventType t = UIEventType::none) = 0;
virtual void appendDiag(const char* message) { (void)message; }
/** Notify UI of an advert reception. `is_new=true` means the contact is NOT in
+55 -3
View File
@@ -496,6 +496,11 @@ bool MyMesh::handleMeshcomodCommand(const char* text, int text_len) {
s_meshcomod_pending_until_ms = 0;
pushMeshcomodReply("cancelled");
return true;
} else {
// Any other command while a confirmation is pending: block it so the
// new command cannot silently overwrite the pending action.
pushMeshcomodReply("pending confirmation — reply 'ok' to confirm or 'cancel' to abort");
return true;
}
}
@@ -1833,6 +1838,15 @@ ContactInfo* MyMesh::processAck(const uint8_t *data) {
void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packet *pkt,
uint32_t sender_timestamp, const uint8_t *extra, int extra_len, const char *text) {
// Clock bootstrap from a peer's send-time when we have no real clock of our own (Wi-Fi
// and GPS off, or not yet synced). Reliable by construction: adopt ONLY a sane epoch
// (~2023..2033, so the 1902/0 garbage and absurd-future values are ignored), and ONLY
// while our own clock still reads unset — NTP/GPS override it the instant they sync, and
// a clock that already reads real is never moved by the mesh.
if (sender_timestamp > 1700000000UL && sender_timestamp < 2000000000UL &&
getRTCClock()->getCurrentTime() < 1700000000UL) {
getRTCClock()->setCurrentTime(sender_timestamp);
}
int i = 0;
out_frame[i++] = RESP_CODE_CONTACT_MSG_RECV_V3;
out_frame[i++] = (int8_t)(pkt->getSNR() * 4);
@@ -1875,7 +1889,14 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
* serial was disconnected, which meant after a channel message arrived
* over TCP/BLE g_last_event stayed at `channelMessage` and the next DM
* was routed into the channel thread (or vice versa). */
_ui->notify(UIEventType::contactMessage);
// A TXT_TYPE_SIGNED_PLAIN with a 4-byte sender_prefix is a room-server
// post: `from` is the room (the thread) and the author is identified only
// by the prefix — the text is the bare body. Plain DMs/channel msgs are
// unchanged.
const bool is_room_post =
(txt_type == TXT_TYPE_SIGNED_PLAIN) && extra && extra_len >= 4;
_ui->notify(is_room_post ? UIEventType::roomMessage
: UIEventType::contactMessage);
// Pass RX metadata so the touch UI can surface it via the bubble's
// long-press Info sheet. SNR comes off the packet itself (most accurate
// per-message); RSSI is the radio's last-RSSI, which is current since
@@ -1885,8 +1906,29 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
const bool is_flood = pkt->isRouteFlood();
uiStashRxMeta(pkt); // capture route + scope for the per-message Info popup
_last_sender_ts = sender_timestamp; // embedded send-time -> UI bubble ts (room history replay)
_ui->newMsgFromPubWithMeta(path_len, is_flood, from.id.pub_key, from.name,
text, history_count, snr_q4, rssi);
if (is_room_post) {
// Resolve the post's author from the signed message's sender_prefix.
// Prefer a saved contact's name; fall back to our own node name for
// posts we authored (replayed during sync), else a short hex of the
// prefix so the bubble never just shows the room's own name.
char author_buf[16];
const char* author_name;
ContactInfo* author = lookupContactByPubKey(extra, 4);
if (author && author->name[0]) {
author_name = author->name;
} else if (memcmp(extra, self_id.pub_key, 4) == 0) {
author_name = _prefs.node_name;
} else {
mesh::Utils::toHex(author_buf, (uint8_t*)extra, 4);
author_name = author_buf;
}
_ui->newRoomMsgFromPubWithMeta(path_len, is_flood, from.id.pub_key,
from.name, author_name, text,
history_count, snr_q4, rssi);
} else {
_ui->newMsgFromPubWithMeta(path_len, is_flood, from.id.pub_key, from.name,
text, history_count, snr_q4, rssi);
}
}
// CLI command replies don't belong in the chat thread but the touch UI
// *does* want them — they're the response to whatever was typed into the
@@ -1981,6 +2023,12 @@ void MyMesh::onSignedMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uin
void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t timestamp,
const char *text) {
// Clock bootstrap from a channel peer's send-time (same sane-window + unset-only guard
// as queueMessage above) so a Wi-Fi/GPS-off node can still get time off public channels.
if (timestamp > 1700000000UL && timestamp < 2000000000UL &&
getRTCClock()->getCurrentTime() < 1700000000UL) {
getRTCClock()->setCurrentTime(timestamp);
}
int i = 0;
out_frame[i++] = RESP_CODE_CHANNEL_MSG_RECV_V3;
out_frame[i++] = (int8_t)(pkt->getSNR() * 4);
@@ -2802,6 +2850,10 @@ void MyMesh::handleCmdFrame(size_t len) {
memcpy(&out_frame[2], &expected_ack, 4);
memcpy(&out_frame[6], &est_timeout, 4);
_serial->writeFrame(out_frame, 10);
// Mirror the app-sent DM into the on-device touch UI so it shows there too (#46). First
// handling only — a transport/client retry (skip_radio_send) was already mirrored.
if (_ui && !skip_radio_send && recipient)
_ui->appSentMsgToContact(recipient->id.pub_key, recipient->name, text, expected_ack);
// Do not synthesize a private-message "recv" frame from self. That frame has no recipient
// context and can be rendered by clients as a chat with self.
@@ -54,6 +54,9 @@ void MultiTransportCompanionInterface::stopTcpServer() {
void MultiTransportCompanionInterface::enableTcp() {
_tcp_enabled = true;
// Restart immediately: don't wait for the next main-loop tick, and don't
// require wifi_started to be true (TCP itself doesn't need an IP address).
startTcpServer(false);
}
void MultiTransportCompanionInterface::disableTcp() {
+39 -6
View File
@@ -35,7 +35,7 @@ static bool s_begun = false;
// short read (→ treat as absent → defaults); `ver` lets later builds add fields.
static const char* KEY_CFG = "cfg";
static const uint16_t TOUCH_CFG_MAGIC = 0x5743; // 'WC' (WadaCfg)
static const uint8_t TOUCH_CFG_VER = 16; // v2 sig_probe/poll; v3 tz_zone; v4 hide_node_name; v5 map_night/map_zoom; v6 map text/marker visibility; v7 app_grid_large; v8 ui_scale; v9 tb_keypad; v10 sleep_idle; v11 nav_keys; v12 map_zoom_buttons; v13 nav_dir_keys; v14 home_is_drawer; v15 kbd_nav default ON (one-time migrate); v16 nav_scroll_keys
static const uint8_t TOUCH_CFG_VER = 18; // v2 sig_probe/poll; v3 tz_zone; v4 hide_node_name; v5 map_night/map_zoom; v6 map text/marker visibility; v7 app_grid_large; v8 ui_scale; v9 tb_keypad; v10 sleep_idle; v11 nav_keys; v12 map_zoom_buttons; v13 nav_dir_keys; v14 home_is_drawer; v15 kbd_nav default ON (one-time migrate); v16 nav_scroll_keys; v17 notify_new_contact; v18 kbd_nav OFF by default (reverses v15; T-Deck/V4 only, Tanmatsu stays on)
// Defaults (kept identical to the historical per-key defaults).
static const uint16_t DEFAULT_SCREEN_TIMEOUT_S = 20;
@@ -88,6 +88,7 @@ struct __attribute__((packed)) TouchCfg {
uint8_t nav_dir_keys[6]; // keyboard-nav control keys (ASCII): up,down,left,right,select,back — v13 (trailing)
uint8_t home_is_drawer; // Home tab defaults to the app drawer (1) vs the Commander screen (0, default) — v14 (trailing)
uint8_t nav_scroll_keys[2]; // keyboard-nav scroll keys (ASCII): scroll-up, scroll-down — v16 (trailing)
uint8_t notify_new_contact;// toast/chip when a contact is auto-discovered (bool) — v17 (trailing)
};
static TouchCfg s_cfg;
@@ -145,13 +146,26 @@ static void cfgSetDefaults(TouchCfg& c) {
c.map_show_contacts = 1;
c.app_grid_large = 0; // default: compact app grid (T-Deck 4 cols / V4 3 cols)
c.ui_scale = 0; // default: 100% UI scale (native resolution)
c.kbd_nav = 1; // default: keyboard navigation ON (v15)
#if defined(HAS_TANMATSU)
c.kbd_nav = 1; // Tanmatsu: no touchscreen — keyboard nav is the only input, always on
#else
c.kbd_nav = 0; // T-Deck / V4: keyboard navigation OFF by default (opt-in; persists once toggled on)
#endif
c.sleep_idle = 0; // default: idle light-sleep OFF
{ const char* d = "ertui"; for (int i = 0; i < 5; i++) c.nav_keys[i] = (uint8_t)d[i]; } // default tab hotkeys E/R/T/U/I
c.map_zoom_buttons = 0; // default: map zoom = slider
#if defined(HAS_TANMATSU)
{ const char* d = "wxads"; for (int i = 0; i < 6; i++) c.nav_dir_keys[i] = (uint8_t)d[i]; } // Tanmatsu: W up/X down/A left/D right/S select; no Back letter (Esc/F-key), d[5]='\0'
#else
{ const char* d = "wzadsq"; for (int i = 0; i < 6; i++) c.nav_dir_keys[i] = (uint8_t)d[i]; } // default W/Z/A/D/S/Q
#endif
c.home_is_drawer = 0; // default: Home = Commander screen
#if defined(HAS_TANMATSU)
c.nav_scroll_keys[0] = 'f'; c.nav_scroll_keys[1] = 'v'; // Tanmatsu scroll-up F / scroll-down V
#else
c.nav_scroll_keys[0] = 'f'; c.nav_scroll_keys[1] = 'c'; // default scroll-up F / scroll-down C
#endif
c.notify_new_contact = 1; // default: show the new-contact toast (preserve prior behaviour)
}
// Persist the whole blob using the same end()/begin(RW)/put/end()/begin(RO)
@@ -192,10 +206,14 @@ static void cfgLoadOrMigrate() {
// to the real Custom index on the first touchPrefsGetTimezone() call (the
// zone count isn't known here). offset 0 stays zone 0 (Europe) = unchanged.
if (s_cfg.ver < 3 && s_cfg.time_offs != 0) s_cfg.tz_zone = 0xFE;
// v15: keyboard navigation is now ON by default. Force it on ONCE for existing
// users (pre-v15) so they get the feature; their later explicit on/off then
// persists (this only fires for ver < 15, never again).
if (s_cfg.ver < 15) s_cfg.kbd_nav = 1;
// v18: keyboard navigation is now OFF by default (it was force-enabled at v15, but it
// complicated the touch UX more than it helped). Reset existing installs to off ONCE so
// they match the new default; the user's later explicit on/off then persists (fires only
// for ver < 18, never again). The Tanmatsu is exempt — it has no touchscreen, so keyboard
// nav is its only input and must stay on.
#if !defined(HAS_TANMATSU)
if (s_cfg.ver < 18) s_cfg.kbd_nav = 0;
#endif
s_cfg.ver = TOUCH_CFG_VER;
s_cfg.magic = TOUCH_CFG_MAGIC;
cfgFlush(); // rewrite with new fields defaulted-in
@@ -727,6 +745,17 @@ bool touchPrefsSetHideNodeName(bool hide) {
return cfgFlush();
}
bool touchPrefsGetNewContactToast() {
if (!s_begun) touchPrefsBegin();
return s_cfg.notify_new_contact != 0; // default = show the toast
}
bool touchPrefsSetNewContactToast(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.notify_new_contact = on ? 1 : 0;
return cfgFlush();
}
bool touchPrefsGetMapNight() {
if (!s_begun) touchPrefsBegin();
return s_cfg.map_night != 0;
@@ -1217,8 +1246,12 @@ bool touchPrefsGetEnterSends() { if (!s_begun) touchPrefsBegin(); return s_
void touchPrefsSetEnterSends(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("ent_send", on ? 1 : 0); }
bool touchPrefsGetClock12h() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("clk_12h", 0) != 0; }
void touchPrefsSetClock12h(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("clk_12h", on ? 1 : 0); }
bool touchPrefsGetNavMenubarKeys() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("nav_mbk", 0) != 0; }
void touchPrefsSetNavMenubarKeys(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("nav_mbk", on ? 1 : 0); }
bool touchPrefsGetScrollReverse() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("tb_rev", 0) != 0; }
void touchPrefsSetScrollReverse(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("tb_rev", on ? 1 : 0); }
bool touchPrefsGetEdgeScroll() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("tb_edgesc", 0) != 0; }
void touchPrefsSetEdgeScroll(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("tb_edgesc", on ? 1 : 0); }
bool touchPrefsGetLockOnScreenOff() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("lock_off", 0) != 0; }
void touchPrefsSetLockOnScreenOff(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("lock_off", on ? 1 : 0); }
+9
View File
@@ -148,6 +148,11 @@ bool touchPrefsSetHomeIsDrawer(bool on);
bool touchPrefsGetHideNodeName();
bool touchPrefsSetHideNodeName(bool hide);
/** Show the low-key toast/chip when a contact is auto-discovered/added.
* Default true (toast shown). Off keeps the diag-log line + tab badge. */
bool touchPrefsGetNewContactToast();
bool touchPrefsSetNewContactToast(bool on);
/** Store all device data (identity/prefs/contacts/channels) on the SD card under
* /meshcomod instead of internal SPIFFS. T-Deck only; read at boot before data
* loads, so changing it requires a reboot. Default false (SPIFFS). */
@@ -256,8 +261,12 @@ bool touchPrefsGetEnterSends(); // Enter key sends a chat message
void touchPrefsSetEnterSends(bool on);
bool touchPrefsGetClock12h(); // 12-hour clock (default false = 24h)
void touchPrefsSetClock12h(bool on);
bool touchPrefsGetNavMenubarKeys(); // show per-tab nav hotkey letters over the menubar (default false = hidden)
void touchPrefsSetNavMenubarKeys(bool on);
bool touchPrefsGetScrollReverse(); // invert trackball/scrollball direction (default false)
void touchPrefsSetScrollReverse(bool on);
bool touchPrefsGetEdgeScroll(); // push cursor past edge to scroll content (default false)
void touchPrefsSetEdgeScroll(bool on);
bool touchPrefsGetLockOnScreenOff(); // idle screen-off auto-locks; only a deliberate hold wakes (default false)
void touchPrefsSetLockOnScreenOff(bool on);
+37
View File
@@ -473,6 +473,27 @@ void setup() {
#error "need to define filesystem"
#endif
#if defined(ENV_INCLUDE_GPS) && (ENV_INCLUDE_GPS == 1)
// GPS UART resilience (slow / never-acquires TTFF fix): the core opens Serial1 for the
// NMEA GPS with Arduino's default 256-byte RX ring — only ~66 ms of slack at 38400 baud
// (~270 ms at 9600). A single long LVGL/map frame stalls this loop past that, dropping
// UART bytes and corrupting NMEA ephemeris subframes. Each corrupted subframe costs the
// receiver ~30 s of re-acquisition, so a busy UI turns a ~1-minute fix into several
// minutes — or, with frequent stalls, never. A larger ring absorbs the stalls. MUST
// precede the core's Serial1.begin() inside sensors.begin(); setRxBufferSize is a no-op
// once the UART is already running.
//
// Gate on gps_enabled: sensors.begin()'s GPS-detect opens Serial1 on EVERY boot —
// including the many V4s with no GPS module — and never calls Serial1.end(), so an
// unconditional 4 KB ring permanently costs ~3.8 KB of scarce internal DRAM for nothing
// on the GPS-off majority (the "RAM is higher now" reports). GPS-on users (who actually
// hit the overflow) still get the big ring; default GPS-off keeps the stock 256 B. A user
// who enables GPS mid-session picks it up on the next reboot (gps_enabled is persisted).
{
auto* np = the_mesh.getNodePrefs();
if (np && np->gps_enabled) Serial1.setRxBufferSize(4096);
}
#endif
sensors.begin();
#ifdef DISPLAY_CLASS
@@ -621,6 +642,22 @@ void loop() {
}
#endif
the_mesh.loop();
#if defined(GPS_BUF_DEBUG)
// Bench diagnostic (build with -DGPS_BUF_DEBUG only; absent in releases): peak GPS UART
// backlog accumulated between sensors.loop() drains. A peak above the old 256-byte default
// proves loop stalls were overflowing the default ring — i.e. NMEA was being lost, which
// is the slow/never-acquires TTFF mechanism. With the 4096 ring above it can climb past
// 256 without loss, so a >256 reading is direct proof the fix matters on this unit.
{ static size_t s_gps_peak = 0; static uint32_t s_gps_log = 0;
size_t bl = Serial1.available();
if (bl > s_gps_peak) s_gps_peak = bl;
if (millis() - s_gps_log > 5000) {
s_gps_log = millis();
Serial.printf("[GPSBUF] peak=%u B / 5s (old cap 256, now 4096)\n", (unsigned)s_gps_peak);
s_gps_peak = 0;
}
}
#endif
sensors.loop();
#if defined(ESP32)
// GPS time guard (Ricky Leong's "stuck at 1902"): MicroNMEALocationProvider
+961 -174
View File
File diff suppressed because it is too large. Load diff
+12 -3
View File
@@ -196,7 +196,8 @@ private:
uint16_t in_scope = 0);
// Shared core for newMsg / newMsgFromPubWithMeta — see UITask.cpp.
void newMsgImpl(uint8_t path_len, const char* from_name, const char* text, int msgcount,
uint8_t meta_flags, int8_t snr_q4, int8_t rssi);
uint8_t meta_flags, int8_t snr_q4, int8_t rssi,
const char* sender_override = nullptr);
public:
/** Match an arriving ACK (4-byte hash) against the last few outgoing DMs
* and flip their delivery state to DELIV_DELIVERED so the chat detail
@@ -214,7 +215,7 @@ private:
void appendComposerChar(char c);
void appendComposerText(const char* text);
void backspaceComposerChar();
bool sendComposerToActiveThread();
bool sendComposerToActiveThread(const char* override_text);
void markThreadsDirty(unsigned long delay_ms = 200);
void markMsgsDirty(unsigned long delay_ms = 2000);
void flushHistoryIfDue(unsigned long now);
@@ -285,7 +286,8 @@ public:
void composerAppendChar(char c) { appendComposerChar(c); }
void composerAppendText(const char* text) { appendComposerText(text); }
void composerBackspace() { backspaceComposerChar(); }
bool composerSend() { return sendComposerToActiveThread(); }
// override_text != nullptr resends that exact text (msg action menu "Resend"); nullptr = composer draft.
bool composerSend(const char* override_text = nullptr) { return sendComposerToActiveThread(override_text); }
void composerTypingMode() { _composer_action_idx = -1; }
/** Open DM thread for mesh contact index (e.g. Chats thread list / external hooks). */
void openMeshContactDm(uint32_t mesh_contact_index);
@@ -402,6 +404,13 @@ public:
const uint8_t* from_pub, const char* from_name,
const char* text, int msgcount,
int8_t snr_q4, int8_t rssi) override;
void newRoomMsgFromPubWithMeta(uint8_t path_len, bool is_flood,
const uint8_t* from_pub, const char* from_name,
const char* author_name,
const char* text, int msgcount,
int8_t snr_q4, int8_t rssi) override;
void appSentMsgToContact(const uint8_t* to_pub, const char* to_name, const char* text,
uint32_t ack_hash) override;
void notify(UIEventType t = UIEventType::none) override;
void logRxFrame(float snr, float rssi, const uint8_t* raw, int len) override;
void discoveredContact(const ContactInfo& contact, bool is_new, uint8_t path_len) override;
+7 -1
View File
@@ -505,6 +505,10 @@ static const lv_img_dsc_t d_1f40c = { { LV_IMG_CF_TRUE_COLOR_ALPHA, 0, 0, 16, 16
static const uint8_t e_1f41e[] = {0,0,1,0,0,0,31,0,1,0,0,1,0,0,1,44,99,5,0,0,0,110,107,102,110,107,74,110,99,133,0,0,0,170,82,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,78,107,22,0,0,0,0,0,0,0,0,0,0,0,0,239,115,39,110,107,115,142,107,34,110,107,193,77,107,60,0,0,1,239,123,2,0,0,0,0,0,0,0,0,0,77,107,17,142,107,91,110,107,111,115,140,20,146,124,14,239,115,86,203,82,162,45,99,81,0,0,0,175,115,150,110,107,106,170,170,3,239,123,4,0,0,0,0,0,0,0,0,0,110,107,103,110,99,113,110,107,58,106,74,134,203,82,209,110,107,255,167,49,255,17,173,246,146,189,159,73,66,207,255,47,6,0,0,0,0,248,2,0,0,0,0,0,0,0,0,0,78,107,43,110,99,160,0,0,0,80,148,150,138,82,255,69,41,248,69,41,253,7,66,255,40,74,255,227,184,253,195,248,176,138,186,63,0,0,0,231,121,4,239,123,4,0,0,0,109,107,41,110,107,192,174,83,70,20,190,230,7,50,253,69,25,254,134,33,255,69,33,254,227,192,251,227,248,255,4,177,255,199,169,255,77,123,112,0,0,0,0,0,0,0,0,0,0,0,0,207,115,66,105,122,224,36,137,255,37,129,253,69,121,255,101,73,255,69,161,255,227,248,255,227,248,255,101,57,251,36,81,254,8,194,244,207,91,107,110,107,113,0,0,0,0,248,1,175,91,88,199,209,227,195,208,252,227,248,254,195,248,255,228,248,255,37,241,255,4,193,255,101,73,255,69,129,254,227,216,254,37,233,241,142,99,130,110,107,87,0,0,0,0,0,0,142,115,149,8,122,255,101,9,252,36,185,255,69,105,255,69,89,255,69,225,255,69,129,255,134,1,255,69,105,255,195,248,254,4,241,248,44,123,145,0,0,0,0,0,0,0,0,0,175,99,125,199,161,248,4,145,255,36,177,254,134,17,255,134,17,255,69,177,255,37,225,255,36,153,255,36,177,255,195,240,252,37,249,255,45,115,213,239,83,6,0,0,0,44,99,10,175,91,168,166,209,178,163,248,255,227,248,254,36,153,255,36,145,255,227,248,255,37,249,255,4,209,255,134,17,255,36,129,251,101,249,255,77,107,170,0,0,0,0,0,0,110,107,155,142,91,126,171,146,138,4,249,255,227,248,252,228,224,255,101,81,255,4,169,255,36,249,255,36,193,255,101,49,254,4,161,252,102,233,234,143,91,206,77,107,81,0,0,0,77,107,12,0,0,0,174,91,211,167,201,213,162,248,255,36,169,252,134,1,252,69,105,254,4,249,254,36,241,253,195,248,249,4,249,255,195,248,88,207,83,46,77,107,115,0,0,0,0,0,0,170,82,3,110,99,58,110,99,202,4,241,177,228,248,255,36,193,255,4,233,255,227,248,255,37,241,255,4,249,255,227,248,177,10,248,3,0,0,0,0,0,0,0,0,0,239,123,2,0,0,0,110,107,51,110,99,197,243,44,16,163,248,89,227,248,192,227,248,231,227,248,235,36,241,209,4,241,127,69,249,6,0,248,1,170,170,3,181,82,3,0,0,0,239,123,2,170,82,3,110,107,161,78,107,50,0,0,0,0,0,0,0,0,0,195,224,19,4,249,23,0,248,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0};
static const lv_img_dsc_t d_1f41e = { { LV_IMG_CF_TRUE_COLOR_ALPHA, 0, 0, 16, 16 }, sizeof(e_1f41e), e_1f41e };
static const uint8_t e_1f37a[] = {0,0,0,0,0,0,255,255,2,0,0,0,46,254,22,55,255,178,186,255,232,121,255,208,212,254,81,0,0,0,255,255,2,0,248,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,255,147,121,255,223,255,255,255,255,255,255,255,255,255,154,255,251,178,254,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,212,254,98,253,255,255,255,255,255,255,255,253,255,255,255,255,255,253,255,255,255,187,255,255,56,255,199,80,246,19,255,255,1,255,255,1,0,0,0,0,0,0,0,0,0,0,0,0,56,255,241,255,255,252,255,255,252,255,255,255,255,255,255,255,255,255,255,255,253,255,255,255,122,255,255,146,246,34,255,255,3,255,255,3,0,0,0,0,0,0,0,0,0,0,0,0,25,239,167,55,255,255,189,255,253,222,255,255,89,255,255,89,255,255,56,255,253,55,255,254,247,238,216,255,157,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,230,125,89,255,255,56,255,252,23,255,255,81,238,255,82,238,255,49,238,254,172,237,255,84,238,226,255,255,193,223,255,156,28,231,27,0,0,0,0,0,0,0,0,0,0,0,0,176,229,131,89,255,255,43,229,252,166,228,255,134,220,255,69,220,255,68,220,255,192,219,255,111,213,240,25,190,237,218,222,255,93,239,160,0,0,0,0,0,0,0,0,0,0,0,0,241,229,137,122,255,255,101,220,252,160,219,255,2,220,255,164,228,255,226,219,253,34,228,255,110,221,206,206,74,20,23,198,136,60,231,223,0,0,0,0,0,0,0,0,0,0,0,0,17,230,138,154,255,255,134,228,252,2,220,255,100,220,255,100,228,255,2,220,253,97,236,255,206,229,202,0,0,0,92,239,82,60,231,228,0,0,0,0,0,0,0,0,0,0,0,0,49,238,142,153,255,255,197,228,253,98,228,255,196,228,255,33,220,255,98,228,253,193,244,255,238,237,200,0,0,0,92,239,136,219,222,223,0,0,0,0,0,0,0,0,0,0,0,0,146,238,145,219,255,255,36,237,253,35,237,255,69,237,255,161,236,255,194,236,253,33,253,255,13,238,202,255,255,93,125,239,245,150,181,167,0,0,0,0,0,0,0,0,0,0,0,0,113,238,149,50,255,255,131,245,252,33,245,255,99,245,255,132,245,255,33,245,254,96,253,255,79,238,242,94,231,255,150,181,217,239,123,38,0,0,0,0,0,0,0,0,0,0,0,0,218,222,158,197,245,255,128,253,254,161,253,255,228,253,255,161,253,255,128,253,255,162,253,255,86,214,212,212,148,99,140,115,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,239,219,26,231,254,136,229,245,130,245,252,129,245,253,98,245,253,134,229,246,85,214,237,61,231,208,0,0,0,0,0,0,85,173,3,0,0,0,0,0,0,0,0,0,0,0,0,251,222,163,28,223,252,252,222,255,121,214,247,87,214,249,120,214,243,187,214,252,28,223,255,251,222,195,251,222,15,239,255,2,255,255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,231,71,251,230,162,252,222,207,252,222,226,252,222,221,251,222,194,27,231,118,125,239,16,0,0,0,255,255,1,0,0,0,0,0,0,0,0,0};
static const lv_img_dsc_t d_1f37a = { { LV_IMG_CF_TRUE_COLOR_ALPHA, 0, 0, 16, 16 }, sizeof(e_1f37a), e_1f37a };
static const uint8_t e_1f37b[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,2,0,0,0,68,249,65,100,249,52,101,249,94,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,239,251,2,115,254,5,197,249,48,100,249,86,36,249,142,64,248,61,114,255,14,89,255,119,154,255,137,186,255,126,56,255,66,0,0,0,85,253,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,32,2,249,106,102,250,63,220,255,165,221,255,233,255,255,255,255,255,255,255,255,255,155,255,237,192,252,5,0,0,0,255,255,2,255,255,2,255,255,2,89,255,39,187,255,166,252,255,158,253,255,193,187,255,219,254,255,255,255,255,255,255,255,252,255,255,249,221,255,251,123,255,215,254,255,24,255,255,19,0,0,0,255,255,5,0,0,0,88,255,115,255,255,255,255,255,255,255,255,255,255,255,255,189,255,252,156,255,254,189,255,255,121,255,254,212,254,255,216,238,224,187,214,200,186,214,215,125,239,50,0,0,0,255,255,42,91,247,117,188,255,250,154,255,249,122,255,253,255,255,253,255,255,255,255,255,255,121,255,255,49,246,254,232,228,254,76,229,255,30,215,94,121,214,105,60,231,158,158,247,100,251,222,234,252,222,221,240,237,252,101,228,255,199,228,255,122,255,255,23,255,255,122,255,253,174,237,254,100,220,255,2,220,253,165,236,255,26,239,140,255,255,10,60,231,189,60,231,185,85,181,43,191,247,165,106,245,255,192,219,252,7,229,255,214,246,255,2,220,255,142,229,239,35,220,253,67,220,255,131,228,254,224,244,255,113,238,212,127,231,143,121,214,209,93,239,174,0,0,0,156,247,209,70,245,255,2,237,253,112,246,255,48,246,255,224,219,252,115,238,237,80,246,255,225,236,255,165,245,254,96,253,254,73,254,245,187,214,211,51,165,67,218,222,202,61,223,142,90,247,238,98,253,255,131,245,254,199,245,255,68,245,255,6,237,247,213,238,228,51,255,255,195,253,255,226,253,255,160,253,255,199,237,251,220,214,183,255,255,5,84,173,70,123,206,189,142,254,250,96,253,255,193,253,255,96,253,255,64,253,255,201,237,231,95,231,136,176,246,246,229,253,251,130,245,249,138,221,246,121,214,255,28,223,203,90,239,13,0,0,0,158,247,142,181,238,250,112,238,241,78,238,245,9,246,246,226,253,254,78,246,189,255,231,54,184,222,255,53,214,255,120,214,255,188,214,251,61,231,186,93,247,36,0,0,0,121,206,5,154,214,157,57,198,240,58,198,251,90,198,251,123,206,247,185,222,255,251,230,188,255,231,10,61,231,119,62,231,158,61,231,127,124,247,62,0,0,0,0,0,0,255,255,1,0,0,0,170,82,6,181,181,79,182,189,140,214,189,176,214,189,195,248,189,197,154,214,115,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,2,0,0,0};
static const lv_img_dsc_t d_1f37b = { { LV_IMG_CF_TRUE_COLOR_ALPHA, 0, 0, 16, 16 }, sizeof(e_1f37b), e_1f37b };
typedef struct { uint32_t cp; const lv_img_dsc_t* dsc; } EmojiGlyph;
static const EmojiGlyph kGlyphs[] = {
{ 0x200Du, &d_zero },
@@ -554,6 +558,8 @@ static const EmojiGlyph kGlyphs[] = {
{ 0x1F340u, &d_1f340 },
{ 0x1F341u, &d_1f341 },
{ 0x1F342u, &d_1f342 },
{ 0x1F37Au, &d_1f37a },
{ 0x1F37Bu, &d_1f37b },
{ 0x1F381u, &d_1f381 },
{ 0x1F388u, &d_1f388 },
{ 0x1F389u, &d_1f389 },
@@ -761,7 +767,7 @@ static const EmojiGlyph kGlyphs[] = {
{ 0x1F9D0u, &d_1f9d0 },
{ 0x1F9E1u, &d_1f9e1 },
};
const uint16_t kEmojiGlyphCount = 250;
const uint16_t kEmojiGlyphCount = 252;
const lv_img_dsc_t* emojiGlyphLookup(uint32_t cp) {
int lo = 0, hi = (int)(sizeof(kGlyphs)/sizeof(kGlyphs[0])) - 1;
+1 -1
View File
@@ -1,5 +1,5 @@
// AUTO-GENERATED by scripts/build/gen-emoji-font.py — do not edit.
// 250 Noto colour emoji baked to RGB565+alpha at 16px.
// 252 Noto colour emoji baked to RGB565+alpha at 16px.
#pragma once
#include "lvgl.h"
#ifdef __cplusplus