Commit Graph
1093 Commits
Author SHA1 Message Date
Michael A. Cojocari ea8583ead7 Merge remote-tracking branch 'refs/remotes/pr/498' into integration/all-open-prs-20260913 2026-09-13 18:58:22 -04:00
Michael A. Cojocari fdde0dd0cc Merge remote-tracking branch 'refs/remotes/pr/497' into integration/all-open-prs-20260913 2026-09-13 18:58:22 -04:00
Michael A. Cojocari 478a0030e3 Merge remote-tracking branch 'refs/remotes/pr/496' into integration/all-open-prs-20260913 2026-09-13 18:58:22 -04:00
Michael A. Cojocari b77b236d42 Merge remote-tracking branch 'refs/remotes/pr/495' into integration/all-open-prs-20260913 2026-09-13 18:58:22 -04:00
Michael A. Cojocari 8e5775dc34 Merge remote-tracking branch 'refs/remotes/pr/494' into integration/all-open-prs-20260913
# Conflicts:
#	src/ui-touch/UITask.cpp
2026-09-13 18:58:12 -04:00
Michael A. Cojocari 31394f8f1e Merge remote-tracking branch 'refs/remotes/pr/493' into integration/all-open-prs-20260913 2026-09-13 18:57:49 -04:00
Michael A. Cojocari f060bbee09 Merge remote-tracking branch 'refs/remotes/pr/492' into integration/all-open-prs-20260913
# Conflicts:
#	src/ui-touch/UITask.cpp
2026-09-13 18:57:44 -04:00
Michael A. Cojocari 7639363c78 Merge remote-tracking branch 'refs/remotes/pr/489' into integration/all-open-prs-20260913 2026-09-13 18:56:58 -04:00
Michael A. Cojocari 2dd4dc24d5 Merge remote-tracking branch 'refs/remotes/pr/488' into integration/all-open-prs-20260913 2026-09-13 18:56:58 -04:00
Michael A. Cojocari 85e5a28be2 Merge remote-tracking branch 'refs/remotes/pr/487' into integration/all-open-prs-20260913 2026-09-13 18:56:58 -04:00
Michael A. Cojocari 240d455f9c Merge remote-tracking branch 'refs/remotes/pr/486' into integration/all-open-prs-20260913 2026-09-13 18:56:57 -04:00
Michael A. Cojocari 07c09c7b33 Merge remote-tracking branch 'refs/remotes/pr/479' into integration/all-open-prs-20260913 2026-09-13 18:56:57 -04:00
Michael A. Cojocari e95f6ef9c7 Merge remote-tracking branch 'refs/remotes/pr/478' into integration/all-open-prs-20260913 2026-09-13 18:56:57 -04:00
Michael A. Cojocari d3cd12c257 Merge remote-tracking branch 'refs/remotes/pr/477' into integration/all-open-prs-20260913 2026-09-13 18:56:57 -04:00
Michael A. Cojocari e5eb3f9f2d Merge remote-tracking branch 'refs/remotes/pr/476' into integration/all-open-prs-20260913 2026-09-13 18:56:57 -04:00
Michael A. Cojocari a6fdbfcf36 Merge remote-tracking branch 'refs/remotes/pr/469' into integration/all-open-prs-20260913 2026-09-13 18:56:57 -04:00
Michael A. Cojocari 3f59a6f9d9 Fix battery estimate after clock changes 2026-09-10 20:21:42 -04:00
Michael A. Cojocari 2a481ef64c Add M9 lock-home-to-drawer option 2026-09-10 20:04:57 -04:00
aigarslv 141e7b0bdf Add Latvian (LV) keyboard layout
Same QWERTY deck as English (no letters removed, so loanwords/names still type normally) with the three most common extra letters - s-caron, c-caron, z-caron - promoted directly onto the on-screen deck, matching how Romanian's a-breve/s-cedilla/t-cedilla were added. The remaining eight - a-macron, e-macron, g-cedilla, i-macron, k-cedilla, l-cedilla, n-cedilla, u-macron - reach the physical T-Deck keyboard via the number row (same trick as Romanian's digit row) and the touch keyboard's existing accent popups on their base letter.

Also fixes kbLayoutCode()'s k_codes[] table, which was missing an "RO" entry (so the on-screen language-cycle button mislabeled Romanian as "EN"); LV is appended alongside the fix.

All 22 upper/lower Latvian glyphs sit in Latin Extended-A (U+0100-017F), already covered by the bundled extras_* fallback fonts, so no font changes are needed.
2026-09-10 22:42:10 +03:00
Michael A. Cojocari 1d24205341 Show M9 file timestamps directly 2026-09-10 10:47:57 -04:00
Michael A. Cojocari 294abcb99a Fix M9 admin log scrolling 2026-09-10 10:36:29 -04:00
Michael A. Cojocari bafe905773 Make M9 navigation shortcuts global 2026-09-10 10:30:49 -04:00
Michael A. Cojocari bd46b798eb Improve M9 chat navigation 2026-09-10 09:18:40 -04:00
Michael A. Cojocari 1e79ad044f Fix At-a-glance zero-hop count 2026-09-09 17:47:22 -04:00
Michael A. Cojocari cfb6cc7feb Fix M9 message info scrolling 2026-09-09 17:44:16 -04:00
Michael A. Cojocari 80b9f13da3 Fix M9 discovered contacts scrolling 2026-09-09 17:36:30 -04:00
Michael A. Cojocari 68dea386d3 Wio Tracker L2 login and keyboard tweaks 2026-09-09 17:17:33 -04:00
Michael A. Cojocari fdaa3d22b7 Tweaks and suggested changes. 2026-09-09 14:23:25 -04:00
Michael A. Cojocari 8062d7a077 M9: keep caret in composer through text end 2026-09-09 13:46:05 -04:00
Michael A. Cojocari 994c155149 Work 2026-09-09 11:58:51 -04:00
Michael A. Cojocari 1c36025932 M9: refine navigation cursor highlights (#434) 2026-09-09 11:29:22 -04:00
Michael A. Cojocari 229ef986e6 M9: limit purge confirmation focus (#432) 2026-09-09 10:44:08 -04:00
Michael A. Cojocari bc3229d27e M9: fix settings detail navigation (#427) 2026-09-09 10:37:26 -04:00
Kaj SchittecatandClaude Opus 5 16d09696ca touch: beta_79 release notes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
beta_79
2026-09-09 13:06:36 +02:00
Kaj SchittecatandClaude Opus 5 b6440d01cd touch: charging icon on every board, not just two (#473)
Two Discord reports a day apart on different boards: museifu696 (M9, beta_78)
had no charging symbol at all, and thesupergeek (V4-R8) found charge state not
being detected after comparing against a T-Deck.

For the M9 this was structural. batteryIsCharging lived inside

  #if defined(HAS_TDECK_GT911) || defined(HELTEC_LORA_V4_R8)

whose #else returned a flat false, so on the M9 -- and the Pager, RAK Tap, Wio
L2, Attaky, P4 and Tanmatsu -- no voltage could ever produce a charging icon.

That split was a real, evidence-based decision, but it was about the Heltec V4
alone: its noisier ADC path made the EMA drag the average down so the threshold
never tripped (the note above the block explains it). The #else then answered
the question for every OTHER board too, purely by falling through, and nobody
had evaluated those.

So share only the verdict, not the sampler. The EMA stays gated exactly as
before, which means battery PERCENTAGE on the direct-read boards is untouched
and the V4's original complaint cannot recur: the direct read is what it keeps,
and its plugged-in reading already sat high.

Sharing is sound rather than assumed: the same mv feeds batteryPercentFromMv(),
so a board reporting the USB rail instead of the battery would already show a
permanent 100% and someone would have said so.

Kept deliberately PURE. batteryMvSmoothed()'s charge_flip calls this twice with
different arguments to compare the old and new verdicts, so a latching Schmitt
trigger would corrupt itself; flap is bounded by the 20 s publish hold and the
50 mV margin instead. The threshold against each board's uncalibrated ADC still
wants on-device confirmation, as the V4-R8 note already says.

Two follow-ons: kBattChargingMv (4250) is removed, dead and misleading since the
test actually uses batteryFullMv() + 50; and the idle-power-save comment that
reasoned from "the M9 is in batteryIsCharging's #else branch, so the USB-powered
gate can never block" is corrected, because it now can, which is the point of
that gate.

Reported-by: museifu696, thesupergeek
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 11:59:21 +02:00
Kaj SchittecatandClaude Opus 5 565fd883ae touch: a Lua app error must not reset the device (crash dump, T-Deck)
Kaj's T-Deck coredump: abort() on core 1, decoding to luaG_callerror ->
abort() reached from luaAppPress(). luaG_callerror is "attempt to call a ...
value", and the abort is Lua's DEFAULT panic function, because lua_atpanic was
never installed anywhere in this file. So any error reaching Lua with no
protected frame resets the device.

The callback itself was never the problem: guardedCall wraps it in pcall with a
traceback handler and turns a fault into a toast plus a clean close, which is
the host's whole contract. The hole is everything that runs BEFORE that pcall
is armed:

  - pushCallback() used lua_getfield, which honours __index. An app table
    carrying a metatable whose __index is not callable raises exactly
    luaG_callerror, unprotected. It is now a raw lookup, which is also the
    correct semantics: callbacks are plain fields on the table the chunk
    returned, and no shipped app uses setmetatable.
  - the argument marshalling in sendInput/sendKey pushes a table and several
    values before the pcall exists, and those raise on stack growth or OOM.
    lua_checkstack appeared ZERO times in this file; pushCallback now reserves
    the room up front, and checkstack reports failure rather than raising.

lua_atpanic is installed as well. It cannot prevent the abort -- Lua aborts once
a panic function returns and there is no safe frame to unwind to -- but the
error text now reaches the serial log and a toast instead of vanishing, which is
the difference between a reproducible report and a mystery reset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 11:47:12 +02:00
Kaj SchittecatandClaude Opus 5 503b2fe1f2 touch: sound previews obey the master switch (#464), exact telemetry position (v59)
#464 (PD0RCM, T-Deck Plus): "I can hear the sample sound and the volume change,
but not incoming messages." That board has the hardware, and previews and
arrivals go through the same playback function on it, so the audio path was
never the problem.

Every preview on the Sound page played UNCONDITIONALLY: the sample, the volume
steps, and each per-type toggle. An arriving message checks the master Sound
switch and Do Not Disturb. So with the master off the page chimes at you the
entire time you configure it while nothing ever sounds on arrival, which reads
exactly like a broken notification path and is almost certainly what he hit.
A preview is now silent precisely when a real notification would be, and says
which of the two silenced it rather than just going quiet. The master switch
keeps its own confirmation chime, because that fires exactly when you turn it
ON, which is the one case where a sound is the right answer.

Telemetry position (v59, honza_87628, via Discord): the advert displacement of
#399 was extended to telemetry answers because a privacy setting a telemetry
request walks straight around is a hole rather than a feature. He then made the
fair counter-argument that an answer is not a broadcast: it is encrypted to one
contact who already holds the permission, and it only goes out because they
asked. Losing the ability to give trusted contacts a real fix is a real loss.

So make it a choice instead of an assumption. New tail field telem_loc_exact,
OFF by default, so the private behaviour is what you get unless you say
otherwise, and the broadcast advert stays displaced either way. The switch sits
directly under "Share my location when asked", where the decision belongs.

Schema 58 -> 59. The host test enumerates every appended field and caught the
change, as designed; all three of its migration assertions are updated and it
passes.

Reported-by: PD0RCM, honza_87628
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 11:37:54 +02:00
Kaj Schittecat 459698df5a Merge pr468 2026-09-09 11:16:52 +02:00
Michael A. Cojocari cfb09edcf1 docs: record T-Deck Pro test status 2026-09-08 16:57:00 -04:00
Michael A. Cojocari 971ac93496 Merge remote-tracking branch 'upstream/main' into feat/tdeck-pro-target 2026-09-08 16:56:02 -04:00
Michael A. Cojocari ff499bf938 M9: fix conversation navigation (#467) 2026-09-08 16:53:17 -04:00
Michael A. Cojocari 9715cac742 Work 2026-09-08 16:31:52 -04:00
Michael A. Cojocari f3136ea951 Merge branch 'main' into feat/tdeck-pro-target
# Conflicts:
#	deploy/apps/lang/bg.lang
#	deploy/apps/lang/de.lang
#	deploy/apps/lang/el.lang
#	deploy/apps/lang/es.lang
#	deploy/apps/lang/fr.lang
#	deploy/apps/lang/hu.lang
#	deploy/apps/lang/it.lang
#	deploy/apps/lang/nl.lang
#	deploy/apps/lang/pt-br.lang
#	deploy/apps/lang/ro.lang
#	deploy/apps/lang/ru.lang
#	deploy/apps/lang/sr.lang
#	deploy/apps/lang/uk.lang
#	src/ui-touch/UITask.cpp
2026-09-08 15:41:45 -04:00
Kaj SchittecatandClaude Opus 5 57b8adca8a touch: leading type icon on Discover rows (#459)
jesshampshire asked for the Discover pane to present like the Chats and
Contacts panes. It was the only one of the three without a leading icon, so a
repeater and an ordinary peer looked identical until you read the meta line
under the name.

Same glyph set and the same 22 px column the Contacts rows use: antenna for a
repeater, loop for a room, person for everything else, rendered in g_font_16 so
the FontAwesome PUA codepoints resolve through its fallback chain. The name and
meta labels shift right by that column and lose the width to it.

The type stays spelled out in the meta line as well: the icon cannot tell a
sensor or chat node from a plain peer, so dropping the word to buy the width
back would lose information on exactly the rows where it matters. On the narrow
boards that line ellipsises as it already did.

Requested-by: jesshampshire
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 20:38:50 +02:00
Kaj SchittecatandClaude Opus 5 5fc4608c37 touch: contacts cursor across a list rebuild (#467), idle-saver blocker breakdown (#465)
#467 (Buko84): leaving a page stashes the focused row so the cursor can go back
where it was, and it stashed it as a raw pointer. Reading a channel's unread
messages clears its badge, which rebuilds every thread row -- so on the way out
that pointer is either dead or, worse, recycled by the allocator onto a
DIFFERENT row, which is how the cursor lands on an unrelated channel. With no
unread messages nothing rebuilds and it works, which is exactly the split he
identified.

Identify the row by the slot it occupied instead. A list rebuilt in place keeps
its order, so the slot is reliable where the address is not; the pointer is only
consulted when the number of targets actually changed. The restore also scrolls
that row back into view now: the rest of the rebuild deliberately runs with
focus-scroll suppressed, which is right for the transient focus during
recollect, but it left the restored cursor off-screen whenever the rebuild had
also reset the list's scroll position.

#465 (jrote1): his follow-up screenshot shows the gate passing (178406 cycles,
last wake timer) but only 15% asleep. 178406 parks at the 50 ms throttle is
~2.5 h, which against 15% puts uptime near 16.5 h -- so every park runs its full
length and the parks are not the problem. The device simply was not ELIGIBLE
for the other 85%, and nothing reported which of the six gate conditions was
holding it shut.

So charge the wall time between checks to whichever condition was blocking at
that moment, and surface the biggest one ("held by mesh busy 62%") on the
Battery page and the live Lock-settings diag. gatePasses() becomes gateBlocker()
returning the condition rather than a bool. Gaps over 5 s are dropped rather
than blamed on a condition, and the clock is stamped AFTER a park so a completed
park is never mis-attributed to the next blocker.

Also: device_caps.h computed CAP_SOUND from HAS_UI_SOUND, which UITask.cpp does
not define until ~1400 lines AFTER that header is included -- so CAP_SOUND was 0
on every board. Nothing consumed it, so it was silent rather than broken, but
any future `#if CAP_SOUND` would have compiled notification sound out
everywhere. The board list moves into device_caps.h (every macro involved is a
-D on the command line, so all of them are genuinely visible there) and
HAS_UI_SOUND now derives from CAP_SOUND. Verified per board: on for T-Deck,
V4-TFT, V4-R8, M9, Pager and P4; off for RAK Tap, Wio L2 and Attaky, which is
the pre-existing behaviour, unchanged.

Reported-by: Buko84, jrote1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 20:28:34 +02:00
Kaj Schittecat 5b5ce2adef Merge pr466 2026-09-08 20:14:09 +02:00
Michael A. Cojocari 796504445d Wio Tracker L2 tweaks 2026-09-08 13:56:39 -04:00
Kaj SchittecatandClaude Opus 5 04f0a1043a touch: stop the T-Display P4 advertising a lock screen it never had (#451)
Piguyraspberry reported the P4 lock screen never appearing and its settings
page rendering blank. Both are the same thing: the board declares
CAP_LOCK_SCREEN but nothing behind it was ever wired for it.

  - nothing can REACH the lock. The control-center Lock button is compiled
    only for HAS_TDECK_GT911; the other triggers are the Tanmatsu volume key
    and the Pager's own hold handler.
  - nothing can LEAVE it. Every unlock path is a physical-control branch
    (T-Deck trackball hold, Tanmatsu Vol-, M9 d-pad, Pager Backspace, Wio
    wake button) and the P4 matches none. The on-screen hint even falls
    through to "hold the trackball to unlock" on a board with no trackball.
  - the DSEC_LOCK settings body is gated on HAS_TDECK_GT911 || HAS_THINKNODE_M9
    rather than on the capability, which is why the page came up empty.

The Heltec V4-R8 sits in exactly this position and is deliberately 0, with a
comment saying enabling it is a feature rather than a cap flip. Same call
here. The settings card is already skipped under !CAP_LOCK_SCREEN, so this
removes the empty page rather than papering over it.

#451 stays open as the tracking issue for actually building it: a touch
reveal + hold-to-unlock path, a P4 hint string, and capability gates on the
settings section.

Reported-by: Piguyraspberry
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:14:22 +02:00
Kaj SchittecatandClaude Opus 5 f931fdfeda touch: Known regions back-out (#449), idle power-save gate (#450), review follow-ups
Known regions (#449, DenRalle on Tanmatsu): s_regions_modal was in no popup
registry at all. Without a row the Back ladder never peels it, so the page
stays on screen, and the open-popup COUNT stays zero, so whatever you open
next opens underneath it. Both halves of the report ("screen remains
visible", "pages open only in the background"), one missing line.

Idle power-save (#450, jrote1): the Battery page in the report shows the
gate blocked on "Wi-Fi on" with Wi-Fi disabled and cycles stuck at 0.
tsWifiOff() ANDed the wifiConfigGetRadioEnabled() pref with the real radio
state, and that pref DEFAULTS TO ENABLED when its NVS key was never written
-- so anyone who had not explicitly toggled Wi-Fi in this firmware read as
"Wi-Fi on" permanently. The gate is asking whether the modem draws power,
and WiFi.getMode() == WIFI_OFF answers exactly that on its own.

The other half of the same bug is in main.cpp: the power-down ran only if
wifi_started, but a scan or the version check can bring STA up without
going through that state machine, and then disabling Wi-Fi skipped
mode(WIFI_OFF) and left the modem running. getMode() != WIFI_OFF proves the
driver is inited, which is what the guard actually wanted to test, so it
closes the leak without reintroducing the low-heap esp_wifi_init crash the
original comment guards against. The block is one-shot behind
wifiConfigConsumeApplyRequest(), not per-loop.

Follow-ups from reviewing the merged PRs:

- #457 moved the Lua app page to the slim bar, which exposed that bar_h was
  measured BEFORE appPageBegin* installs the chrome -- so the body was sized
  against the OUTGOING page's bar. Install first, then measure (#236 again,
  one step earlier).

- #453 makes /meshcore-*.json downloadable, and those carry "private_key"
  plus every channel secret. The transfer is opt-in, LAN-only and rate
  limited (3 tries, 30 s lockout), so the surface is sound -- but it is
  plain HTTP, so say so on the page and flag backup rows in the list.

Reported-by: DenRalle, jrote1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:06:59 +02:00
Kaj Schittecat 26597a40cc Merge pr457 2026-09-08 14:01:08 +02:00