Adds an lxma://<hash>:<pubkey> QR overlay reachable from Settings and
from a new header row at the top of the Contacts tab. Including the
public key lets Columba/Sideband skip the PENDING_IDENTITY round-trip.
Contacts list stays visible even with no saved contacts so the header
row is always reachable; the empty-state floats above it.
Input events otherwise wait up to a full 33ms frame interval before
LVGL renders, which is noticeable when the loop body is loaded.
When the input poll reports activity, run lv_timer_handler()
immediately and let the next idle frame fall back to the 30 FPS cadence.
037af2a left the backlight at 0 in Display::begin() to avoid exposing an
unpainted framebuffer, but powerMgr.setBrightness() at step 24 only runs
at the end of boot, so the whole boot sequence rendered onto a dark panel.
Set PWM 128 right after the first lvBootScreen.setProgress(), once LVGL
has flushed the boot screen. powerMgr at step 24 still overrides with the
user's configured brightness.
Both the LXMF Addr and Identity readonly entries truncated to the
first 16 hex chars, which made it impossible to type or read the
real address into another client (Sideband, Columba, etc) for adding
ratdeck as a contact. Drop the substring(0, 16) calls and show the
full 32-char hex. Wraps inside the row at the existing font; legible
and copyable.
bmorcelli/Launcher labels its user-app data partition "spiffs"; ours
is "littlefs". Subtype is identical in both layouts, so try littlefs
first and fall back to spiffs to coexist with Launcher installs.
Previously a single ASCII glyph collapsed SENT and DELIVERED. Wire
PacketReceipt delivery callbacks through to sideband-style single
and double checks.
LXMFManager tracks outstanding receipts (opportunistic + link
single-packet) by hash. On delivery: persist DELIVERED and fire
_statusCb. On 60s timeout: drop the entry, leave at SENT — lack
of proof over LoRa isn't proof of failure. Resource transfers stay
at SENT; microReticulum's Transport skips receipts for resource
packets and Link::start_resource_transfer exposes no concluded
callback to hook.
LvMessageView renders the glyph via applyStatusGlyph (Montserrat 12):
REFRESH for in-flight, single OK for SENT, double OK for DELIVERED,
WARNING for FAILED. Status callback matches by timestamp (~1s
tolerance) so DELIVERED lands on the right bubble after later
messages have already moved past QUEUED.
The timestamp label was positioned via lv_obj_align_to below the
message box, but the bubble container's LV_SIZE_CONTENT height was
driven entirely by the box, so the time rendered below the bubble's
allocated row in the flex scroll — visibly hanging into the gap.
Add pad_bottom=14 to the bubble so its outer height includes the
timestamp row, and anchor the box at TOP_LEFT/TOP_RIGHT (was
LEFT_MID/RIGHT_MID) so the layout stacks cleanly: box on top,
timestamp tucked under it inside the bubble's bbox.
Contacts/Messages/Nodes screens consumed long-press as soon as they
loaded, which surfaced delete/menu dialogs instead of blanking the
display. Gate per-screen long-press consumption on _focusActive so the
delete affordance only fires after the user has navigated into the
list with up/down/enter. From the resting tab view, long-press falls
through to main.cpp and powers the screen off — same behaviour as the
home screen.
Trackball click already wakes the screen and is the documented way to
revive the device. Firing it as an enter event after release means the
wake also confirms whatever was focused at the time. Gate short-click
emit on _clickFromScreenOn (already captured at click-down for the
long-press case) so the press only does its wake job when the screen
was off.
Trackball deltas crossing the nav threshold previously fired
up/down/left/right key events AND set _strongActivity=true, which
woke the screen from SCREEN_OFF on any pocket/backpack jostle and
also accumulated phantom navigation in the UI behind the dark screen.
Gate the nav-event block on isScreenOn(), mirroring the touch
suppression. Click/long-press detection stays unconditional so an
intentional trackball press still wakes the device.
The handler had no aspect filter, so it captured every announce from
every aspect of a peer (lxmf.delivery, lxmf.propagation, NomadNet
node, etc.). Each aspect has a different destination hash, so the
same Sideband/Columba peer appeared multiple times in the nodes list
— once with the display name (lxmf.delivery's app_data carries it)
and once or more as raw hex (other aspects had no/different name).
Restrict to lxmf.delivery: that's the only destination we ever route
LXMF messages to. Propagation/NomadNet visibility can come back later
behind an opt-in toggle if needed.
Note: existing saved contacts captured under other aspects will still
appear after this change since they're loaded from disk. They can be
removed manually via the contacts UI.
PATH_CONTACTS / PATH_MESSAGES / SD_PATH_CONTACTS / SD_PATH_MESSAGES
all ended in '/'. The FATFS layer used by the SD library and LittleFS
won't reliably enumerate when given a path ending in slash — opendir
returns a valid handle but readdir yields nothing. Direct file reads
(SD.open with FILE_READ for a known path) still work, which is why
settings/identity persisted but contacts and conversations vanished
on reboot.
Drop the trailing slash from the constants and add explicit "/" at
all concat sites: saveContact, removeContact, conversationDir,
sdConversationDir, and the migrateTruncatedDirs path builders.
Two pocket-carry quality-of-life additions:
1. Long-press (1.2s) trackball click blanks the screen if no LVGL
screen consumes the long-press. InputManager captures the power
state at click DOWN (_clickFromScreenOn) and only emits the
long-press if the screen was already on, so a long-press from
SCREEN_OFF wakes without immediately re-blanking.
2. Touch events are ignored entirely while screen is off, preventing
accidental wakes from pressure on the panel in a pocket/bag.
Trackball click and keyboard keys still wake normally.
InputManager now holds a Power* injected via setPowerMgr() in setup.
Sideband/Columba and other LXMF clients commonly include "/" in display
names (e.g. handle/path conventions). The allowlist in sanitizeName
stripped it, mangling "alice/columba" into "alicecolumba". Saved
contacts get re-sanitized on each load (line 393), so existing names
also lose the slash on next boot.
New public method that transitions to SCREEN_OFF on demand. A
_justWokeFromOff flag is set in activity() and cleared at the end
of loop() so a single keypress that woke the screen can't immediately
re-blank it via a hotkey/long-press handler running in the same tick.
Pure infrastructure — no UI binding included; consumers can wire it
to whatever input event they want (long-press, hotkey, etc.).
Python LXMF was sending bz2-compressed Resource transfers that
microReticulum cannot decode on ESP32 (bz2's working memory exceeds
available SRAM). The companion microReticulum commit (290a133)
removes bz2 entirely; this release advertises no-compression-support
in our LXMF announces so Python LXMF disables auto_compress when
sending to Ratdeck destinations.
Also pins lvgl and LovyanGFX minor versions in lib_deps to prevent
future fresh-clone builds from pulling incompatible 1.2.x / 8.4.x.
Thanks to @RFNexus for surfacing the ESP32 bz2 constraint.
encodeAnnounceName now emits msgpack fixarray(3):
[display_name(bin), stamp_cost=0, supported_functionality=[]]
Empty supported_functionality list = SF_COMPRESSION (=0x00) absent,
so Python LXMF disables auto_compress for our destinations and stops
shipping bz2-compressed Resources we can't decode. Always emit the
3-element form even when the name is empty — Python defaults to
auto_compress=True for legacy <3-element app_data.
Pairs with microReticulum 290a133 which removed bz2 entirely.
LovyanGFX 1.2.x bundles its own lvgl-compat headers that collide
with lvgl >=8.4 on shared types (lv_area_t, lv_font_t, etc.).
Tilde restricts fresh clones to the 1.1.x and 8.3.x ranges this
build was originally cached against, so `pio run` from a fresh
checkout doesn't randomly break.
CORE_DEBUG_LEVEL=0 drops Arduino core noise on expected-missing files, LittleFSFileSystem::open_file now actually works so path_table persists across reboots.