Compare commits

...
11 Commits
Author SHA1 Message Date
Kaj SchittecatandClaude Opus 5 b877e43d5e touch: beta_74 release notes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 18:49:27 +02:00
Kaj SchittecatandClaude Opus 5 41c7b94a1d keyboard: a way out of a wrong protocol guess that does not need the keyboard (#351)
Vybo reports no change in beta_73. The runtime demotion I added is not firing on
his device, and I still do not have the one serial line that would say why. So
rather than guess a third time, give him a switch.

Settings > Keyboard > "Older keyboard protocol" skips raw detection entirely and
uses the protocol every T-Deck controller understands. The point is that it is
reachable BY TOUCH: when detection guesses wrong the keyboard types the wrong
letters, so any remedy that needs the keyboard is not a remedy. It applies
immediately, without a reboot, because the driver re-reads the flag every poll
and will drop out of raw mode on the spot.

The cost of turning it on is modifier latching, which a controller that needs
this switch cannot do anyway.

Schema goes to v53, appended at the tail with the existing static_assert
updated to point at the new last field.

This does not replace finding the real cause; it stops people being stuck while
I do. Automatic detection is unchanged and still the default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 18:36:42 +02:00
Kaj SchittecatandClaude Opus 5 5c66a5b9cd keyboard: latch shift like the other modifiers (#344)
jrote1: symbol and alt latch, shift does not, so a capital letter still means
holding shift down while reaching for the key.

Shift was a bare "is it held" test while the other two ran through
LatchedModifier. It has its own now, so it behaves identically: tap once to
capitalise the next letter, tap twice to lock until you tap again.

The board has two shift keys and they share one latch. Whichever you press arms
it and either releases it, which is what someone alternating hands would expect.

The one-shot is consumed by the key it capitalises, in the same place symbol and
alt are consumed, so it cannot leak onto the following letter.

Still to do from that issue: an on-screen indication of which modifier is
currently latched. That is a UI change rather than a driver one and wants its
own look at where it belongs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 17:49:20 +02:00
Kaj Schittecat c59a951449 Merge PR #346: Attaky front D-pad + SELECT for UI navigation
oumike. The board has nine onboard buttons and only POWER was wired; the front
D-pad and SELECT did nothing. They hang off an AW9523 expander rather than
direct GPIO, so the driver drains an expander queue in attakyNavPump().

Adds ATTAKY_MESH_SERIES to CAP_KEYPAD_NAV, which makes it the first board
carrying that flag with no physical keyboard. He flagged that himself; the
matrix build is what proves nothing behind the flag assumes a keyboard.
2026-08-28 17:37:35 +02:00
Kaj SchittecatandClaude Opus 5 fff611692f touch: show the hop hash beside the repeater name in a route (#348, #347)
jrote1: a route lists resolved repeater names, and when a name is wrong or two
repeaters share one there is nothing left to identify the hop by. The hash is
what the node is actually keyed by on the wire, so it is the part that stays
true exactly when you are reading a route to work out where a message went.

Each hop now reads "3A Repeater Name" rather than just the name. The unresolved
case is unchanged, since it was already showing the hash.

Also pisti87's two corrections (#347): "Known regions" had never been
translated, and the Bluetooth pairing hint gets his shorter wording. Languages
to v21.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 17:37:24 +02:00
Kaj SchittecatandClaude Opus 5 dbf2c43f7d web: stop the remote UI rounding the bandwidth to a whole number (#345)
bradhawkins85: a T-Deck on BW 62.5 shows 62 in the remote web settings, and
applying anything from that form sends 62 back to the radio. So opening the page
and pressing Apply silently reconfigures the radio onto a bandwidth the mesh is
not using.

Two independent halves, both needed:

  - The settings JSON emitted "bw":%.0f, so 62.5 was already 62 before the
    browser saw it. Now %.1f.
  - The form's bandwidth input had no step attribute, so it defaults to step=1
    and a decimal is invalid. The frequency field beside it has step=0.001; this
    one was simply missed. Now step=0.1.

The apply path was never wrong: it parses with %f and passes a float through.
Only the outbound direction lost the fraction, which is why it looked like a
display quirk while actually changing the radio.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 16:31:11 +02:00
Kaj SchittecatandClaude Opus 5 708f4434f5 touch: beta_73 release notes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 10:32:19 +02:00
Kaj SchittecatandClaude Opus 5 875ac92a48 keyboard: let raw mode admit it guessed wrong (#341)
Vybo's older T-Deck types garbage on beta_71 and beta_72: "I get some keyboard
codes when typing". His controller has the pre-backlight firmware and does not
speak the raw column protocol, but the probe concluded it did.

A legacy controller answers a five-byte read with the pressed character in byte
0 and padding after it, and every lowercase letter has its high bit clear, so
"five bytes came back and no high bit is set" passes. Read as a column bitmap,
0x61 ('a') sets bits 0, 5 and 6 of column 0 and emits "q "; 0x6d ('m') emits
"qa ". That is exactly what he sees. Worse, the retry window I added in beta_72
gave a legacy controller more chances to look plausible, which is why it did not
help him.

The probe cannot fix this alone. Idle, both controllers answer with zeros, and
requiring proof of a real matrix before committing means an idle boot never
finds any, falls back to legacy, and everyone silently loses modifier latching.
Tried that first; it cost latching on a device where raw mode was working.

So the decision stays optimistic and becomes reversible. Raw mode now tests each
frame: three or more bits in byte 0, a printable value, and every other column
empty is not something a key matrix can report, but it is precisely an ASCII
character. On that, the driver returns the controller to key mode, switches to
legacy, and delivers the character.

Checked against the real tables rather than assumed: all 26 lowercase letters
set three or more bits, so any letter demotes on the FIRST press. Only '0', ' '
and '!' do not, and space renders correctly anyway since column 0 bit 5 IS
space. Going the other way needs three or more of {q, w, a, space} held at once
and nothing else; the printable-range test drops q+w+a (0x0B), and what remains
is a fumble that costs a reboot rather than a wrong character.

Worst case for a misdetected board is now one odd keystroke instead of a
keyboard that types nonsense until it is reflashed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 10:20:26 +02:00
Kaj SchittecatandClaude Opus 5 afecfc0224 i18n: two Hungarian strings pisti87 asked for (#340)
"Mark read" was an untranslated placeholder. "Join the public channel" had the
long form, which he wants shortened to fit the button.

His text for the second reads "nyílvános"; the file already uses "nyilvános"
everywhere else, so this follows the file rather than introducing a second
spelling. Easy to change back if the long i was deliberate.

The other line numbers in that issue are from his own build and no longer point
at what he means, so they need his source rather than mine to resolve.

Languages go to v20.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 09:38:22 +02:00
Michael A. Cojocari 107367bf82 Merge remote-tracking branch 'upstream/main' into attaky 2026-08-27 20:41:39 -04:00
Michael A. Cojocari fe6741b26d Work 2026-08-27 20:37:59 -04:00
42 changed files with 708 additions and 108 deletions

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ USB) and the [GitHub releases](https://github.com/ALLFATHER-BV/wadamesh/releases
| LilyGo T-Lora Pager | ESP32-S3, LR1121 or SX1262 | 2.33" IPS LCD, 480x222 (no touch), QWERTY + rotary encoder | Web flasher (pick the build matching your radio) | Beta (new in beta_45) | Fully supported; keyboard-first navigation, microSD, map tile packs |
| Heltec V4-R8 + Expansion Kit V2 | ESP32-S3 (8 MB octal PSRAM), SX1262 | 2.4" touch (CHSC6x) | Web flasher | Beta (new in beta_45) | As the V4 plus microSD and the Expansion Kit sensors; buzzer supported |
| LilyGo T-Display P4 | ESP32-P4 + ESP32-C6, SX1262 | AMOLED or TFT-LCD, touch | Web flasher (AMOLED) or the .bin for the LCD SKU | Beta (new in beta_45) | Two screen SKUs; fuel-gauge battery reporting, full TX power |
| Attaky Mesh Series | ESP32-S3, SX1262 | Touch display | Web flasher | Beta (new in beta_47) | Community port by attakygit (#158/#169); detachable keyboard supported |
| Attaky Mesh Series | ESP32-S3, SX1262 | Touch display + front D-pad | Web flasher | Beta (new in beta_47) | Community port by attakygit (#158/#169); detachable keyboard supported; the front D-pad + SELECT navigate the UI |
## Feature notes per board
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: bg
# ver: 19
# ver: 21
# name: Български
# base: bg
(device behind) (устройството изостава)
@@ -184,6 +184,7 @@ Contact added Контактът е добавен
Contact deleted Контактът е изтрит
Contact gone Контактът изчезна
Contact missing Контактът липсва
Contact shared Контактът е споделен
Contacts Контакти
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. Контактите са във вътрешната памет - SD картата не се монтира при старт. Постави картата отново и рестартирай.
Contacts are on internal flash. Контактите са във вътрешната памет.
@@ -778,6 +779,8 @@ Settings Настройки
Setup complete Настройката е готова
Share QR Сподели QR
Share channel Сподели канала
Share contact Сподели контакт
Share failed Споделянето неуспешно
Share location in advert Споделяй местоположението в advert
Share secret Сподели ключа
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Споделете 32-символната тайна, за да се присъединят други. Празно = случайна.
@@ -1103,3 +1106,10 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: de
# ver: 19
# ver: 21
# name: Deutsch
# base: de
(device behind) (Gerät geht nach)
@@ -184,6 +184,7 @@ Contact added Kontakt hinzugefügt
Contact deleted Kontakt gelöscht
Contact gone Kontakt weg
Contact missing Kontakt fehlt
Contact shared Kontakt geteilt
Contacts Kontakte
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. Kontakte liegen im internen Flash - die SD-Karte wurde beim Start nicht eingebunden. Karte neu einsetzen und neu starten.
Contacts are on internal flash. Kontakte liegen im internen Flash.
@@ -777,6 +778,8 @@ Settings Einstellungen
Setup complete Einrichtung abgeschlossen
Share QR QR teilen
Share channel Kanal teilen
Share contact Kontakt teilen
Share failed Teilen fehlgeschlagen
Share location in advert Standort im Advert teilen
Share secret Geheimnis teilen
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Teilen Sie das 32-Zeichen-Geheimnis, damit andere beitreten. Leer lassen für ein zufälliges.
@@ -1099,3 +1102,10 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: el
# ver: 19
# ver: 21
# name: Ελληνικά
# base: el
(device behind) (συσκευή πίσω)
@@ -184,6 +184,7 @@ Contact added Η επαφή προστέθηκε
Contact deleted Η επαφή διαγράφηκε
Contact gone Η επαφή χάθηκε
Contact missing Η επαφή λείπει
Contact shared Η επαφή κοινοποιήθηκε
Contacts Επαφές
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. Οι επαφές είναι στην εσωτερική μνήμη - η κάρτα SD δεν προσαρτήθηκε στην εκκίνηση. Επανατοποθετήστε την και επανεκκινήστε.
Contacts are on internal flash. Οι επαφές είναι στην εσωτερική μνήμη.
@@ -778,6 +779,8 @@ Settings Ρυθμίσεις
Setup complete Η ρύθμιση ολοκληρώθηκε
Share QR Κοινοποίηση QR
Share channel Κοινή χρήση καναλιού
Share contact Κοινοποίηση επαφής
Share failed Αποτυχία κοινοποίησης
Share location in advert Κοινή χρήση θέσης στο advert
Share secret Κοινοποίηση μυστικού
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Μοιραστείτε το μυστικό 32 χαρακτήρων για να συμμετάσχουν άλλοι. Κενό = τυχαίο.
@@ -1103,3 +1106,10 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: es
# ver: 19
# ver: 21
# name: Español
# base: es
(device behind) (dispositivo atrasado)
@@ -184,6 +184,7 @@ Contact added Contacto añadido
Contact deleted Contacto eliminado
Contact gone Contacto perdido
Contact missing Falta el contacto
Contact shared Contacto compartido
Contacts Contactos
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. Los contactos están en memoria interna - la tarjeta SD no se montó al arrancar. Reinserta la tarjeta y reinicia.
Contacts are on internal flash. Los contactos están en la memoria interna.
@@ -778,6 +779,8 @@ Settings Ajustes
Setup complete Configuración completa
Share QR Compartir QR
Share channel Compartir canal
Share contact Compartir contacto
Share failed Error al compartir
Share location in advert Compartir ubicación en anuncio
Share secret Compartir secreto
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Comparte el secreto de 32 caracteres para que otros se unan. Déjalo vacío para uno aleatorio.
@@ -1103,3 +1106,10 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: fr
# ver: 19
# ver: 21
# name: Français
# base: fr
(device behind) (appareil en retard)
@@ -184,6 +184,7 @@ Contact added Contact ajouté
Contact deleted Contact supprimé
Contact gone Contact perdu
Contact missing Contact manquant
Contact shared Contact partagé
Contacts Contacts
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. Les contacts sont en mémoire interne - la carte SD ne s'est pas montée au démarrage. Réinsérez la carte et redémarrez.
Contacts are on internal flash. Les contacts sont sur la mémoire interne.
@@ -777,6 +778,8 @@ Settings Paramètres
Setup complete Configuration terminée
Share QR Partager le QR
Share channel Partager le canal
Share contact Partager le contact
Share failed Échec du partage
Share location in advert Partager la position dans l'annonce
Share secret Partager le secret
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Partagez le secret de 32 caractères pour que d'autres rejoignent. Laissez vide pour un aléatoire.
@@ -1102,3 +1105,10 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: hu
# ver: 19
# ver: 21
# name: Magyar
# base: hu
(device behind) (készülék hátulján)
@@ -186,6 +186,7 @@ Contact added Névjegy hozzáadva
Contact deleted Névjegy törölve
Contact gone Eltűnt névjegy
Contact missing Hiányzó névjegy
Contact shared Névjegy megosztva
Contacts Névjegyek
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. A névjegyek a belső tárolón vannak - az SD-kártya nem csatolódott induláskor. Helyezd be újra a kártyát, és indítsd újra.
Contacts are on internal flash. A névjegyek a belső tárolón vannak.
@@ -400,7 +401,7 @@ Join a private channel Belépés priváthoz
Join hashtag channel Csatlakozz # csatornához
Join network Csatlakozás a hálózathoz
Join private channel Csatlakozz privát csatornához
Join the public channel Csatlakozás a nyilvános csatornához
Join the public channel Belépés a nyilvánoshoz
Joined %.40s Csatlakozott %.40s alatt
Keep per chat (messages) Megőrzés csevegésenként (üzenet)
Key already in use Kulcs már hozzárendelve
@@ -413,7 +414,7 @@ Keyboard nav: WASDZ on Billentyűzet navigáció: WASDZ bekapcsolva
Keyboard nav: off Billentyűzetnavigáció: kikapcsolva
Keyboard navigation Billentyűzetnavigáció
Keys Kulcsok
Known regions Known regions
Known regions Ismert régiók
LOS → %s Rálátás → %s
Landscape (desktop) Fekvő (asztali)
Landscape set - applies when remote mode starts. Fekvő beállítva - a távoli mód indulásakor lép életbe.
@@ -471,7 +472,7 @@ Map — no tile pack on SPIFFS yet.\n\nUpload a tile pack to /tiles/<z>/<x>/<y>.
Map — set your location\nin Settings → Profile to\nshow the map here. Térkép – a Beállítások → Profil menüpontban állítsd be a tartózkodási helyedet, hogy itt jelenjen meg a térkép.
Mark all chats and channels as read? Az összes csevegést és csatornát megjelölöd olvasottként?
Mark as read Olvasottnak jelöl
Mark read Mark read
Mark read Olvasott
Marked read Olvasottként megjelölve
Max hops (0..64) Max ugrások (0-64)
Max zoom for this pack Maximális zoom ehhez a csomaghoz
@@ -791,6 +792,8 @@ Settings Beállítások
Setup complete Beállítás kész
Share QR QR megosztása
Share channel Csatorna megosztása
Share contact Névjegy megosztása
Share failed Megosztás sikertelen
Share location in advert Helyszín megosztása a hirdetésben
Share secret Titok megosztása
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Oszd meg a 32 karakteres titkot, hogy mások is csatlakozhassanak. Hagyd üresen a titkot, hogy véletlenszerű titkos kódot generálj.
@@ -896,7 +899,7 @@ Turn this on, then open the address below in any web browser on the same Wi-Fi -
Type Tipus
Type a URL, then Go Írj be egy URL-t, majd Mehet
Type a message... Írj be egy üzenetet...
Type a new 6-digit code · applies after a reboot Írjon be egy új 6 jegyű kódot · újraindítás után érvényes
Type a new 6-digit code · applies after a reboot Írjon be egy 6 jegyű kódot · újraindítás után él
Type your network name — or switch to Wi-Fi in Settings to scan Írd be a hálózatod nevét – vagy válts Wi-Fi-re a beállításokban a kereséshez
UI size (restart to apply) UI mérete (újraindítás az alkalmazáshoz)
UI size saved — restart to apply UI mérete mentve – indítsa újra a programot az alkalmazáshoz
@@ -1121,3 +1124,9 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal (stale) Jel (elavult)
nothing heard yet Még semmi hír.
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: it
# ver: 19
# ver: 21
# name: Italiano
# base: it
(device behind) (dispositivo indietro)
@@ -184,6 +184,7 @@ Contact added Contatto aggiunto
Contact deleted Contatto eliminato
Contact gone Contatto perso
Contact missing Contatto mancante
Contact shared Contatto condiviso
Contacts Contatti
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. I contatti sono sulla memoria interna - la scheda SD non si è montata all'avvio. Reinserisci la scheda e riavvia.
Contacts are on internal flash. I contatti sono sulla memoria interna.
@@ -777,6 +778,8 @@ Settings Impostazioni
Setup complete Configurazione completata
Share QR Condividi QR
Share channel Condividi canale
Share contact Condividi contatto
Share failed Condivisione non riuscita
Share location in advert Condividi posizione in advert
Share secret Condividi segreto
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Condividi il segreto di 32 caratteri per far unire altri. Lascia vuoto per uno casuale.
@@ -1102,3 +1105,10 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: nl
# ver: 19
# ver: 21
# name: Nederlands
# base: nl
(device behind) (apparaat loopt achter)
@@ -184,6 +184,7 @@ Contact added Contact toegevoegd
Contact deleted Contact verwijderd
Contact gone Contact weg
Contact missing Contact ontbreekt
Contact shared Contact gedeeld
Contacts Contacten
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. Contacten staan op intern flash - de SD-kaart is bij het opstarten niet gekoppeld. Plaats de kaart opnieuw en herstart.
Contacts are on internal flash. Contacten staan op intern flashgeheugen.
@@ -777,6 +778,8 @@ Settings Instellingen
Setup complete Installatie voltooid
Share QR QR delen
Share channel Kanaal delen
Share contact Contact delen
Share failed Delen mislukt
Share location in advert Locatie delen in advert
Share secret Geheim delen
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Deel het 32-teken geheim zodat anderen kunnen deelnemen. Leeg laten voor een willekeurig geheim.
@@ -1100,3 +1103,10 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: pt-br
# ver: 19
# ver: 21
# name: Português (BR)
# base: pt-br
(device behind) (dispositivo atrasado)
@@ -184,6 +184,7 @@ Contact added Contato adicionado
Contact deleted Contato excluído
Contact gone Contato sumiu
Contact missing Contato ausente
Contact shared Contato compartilhado
Contacts Contatos
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. Os contatos estão na memória interna - o cartão SD não montou na inicialização. Recoloque o cartão e reinicie.
Contacts are on internal flash. Os contatos estão na memória interna.
@@ -778,6 +779,8 @@ Settings Ajustes
Setup complete Configuração concluída
Share QR Compartilhar QR
Share channel Compartilhar canal
Share contact Compartilhar contato
Share failed Falha ao compartilhar
Share location in advert Compartilhar local no advert
Share secret Compartilhar segredo
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Compartilhe o segredo de 32 caracteres para outros entrarem. Deixe vazio para gerar um aleatório.
@@ -1102,3 +1105,10 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: ro
# ver: 19
# ver: 21
# name: Română
# base: ro
(device behind) (dispozitiv în urmă)
@@ -184,6 +184,7 @@ Contact added Contact adăugat
Contact deleted Contact şters
Contact gone Contact dispărut
Contact missing Contact lipsă
Contact shared Contact partajat
Contacts Contacte
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. Contactele sunt pe memoria internă - cardul SD nu s-a montat la pornire. Reaşază cardul şi reporneşte.
Contacts are on internal flash. Contactele sunt pe memoria internă.
@@ -778,6 +779,8 @@ Settings Setări
Setup complete Configurare finalizată
Share QR Partajează QR
Share channel Partajează canalul
Share contact Partajează contactul
Share failed Partajare eşuată
Share location in advert Partajează locaţia în advert
Share secret Partajează secretul
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Partajează secretul de 32 caractere ca să poată intra şi alţii. Lasă secretul gol pentru a genera unul aleatoriu.
@@ -1104,3 +1107,10 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: ru
# ver: 19
# ver: 21
# name: Русский
# base: ru
(device behind) (устройство отстаёт)
@@ -184,6 +184,7 @@ Contact added Контакт добавлен
Contact deleted Контакт удалён
Contact gone Контакт пропал
Contact missing Контакт отсутствует
Contact shared Контакт отправлен
Contacts Контакты
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. Контакты во внутренней памяти - SD-карта не смонтировалась при загрузке. Переустановите карту и перезагрузитесь.
Contacts are on internal flash. Контакты во внутренней памяти.
@@ -778,6 +779,8 @@ Settings Настройки
Setup complete Настройка завершена
Share QR Поделиться QR
Share channel Поделиться каналом
Share contact Поделиться контактом
Share failed Не удалось отправить контакт
Share location in advert Делиться местоположением в advert
Share secret Поделиться ключом
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Поделитесь секретом из 32 символов, чтобы другие присоединились. Пусто = случайный.
@@ -1103,3 +1106,10 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: sr
# ver: 19
# ver: 21
# name: Српски
# base: sr
(device behind) (уређај касни)
@@ -184,6 +184,7 @@ Contact added Контакт додат
Contact deleted Контакт обрисан
Contact gone Контакт нестао
Contact missing Контакт недостаје
Contact shared Контакт подељен
Contacts Контакти
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. Контакти су у унутрашњој меморији - SD картица се није монтирала при покретању. Поново убаци картицу и рестартуј.
Contacts are on internal flash. Контакти су у унутрашњој меморији.
@@ -778,6 +779,8 @@ Settings Подешавања
Setup complete Подешавање завршено
Share QR Подели QR
Share channel Подели канал
Share contact Подели контакт
Share failed Дељење није успело
Share location in advert Дели локацију у advert-у
Share secret Подели тајну
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Поделите тајну од 32 знака да се други придруже. Празно = насумична.
@@ -1103,3 +1106,10 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: uk
# ver: 19
# ver: 21
# name: Українська
# base: uk
(device behind) (пристрій відстає)
@@ -184,6 +184,7 @@ Contact added Контакт додано
Contact deleted Контакт видалено
Contact gone Контакт зник
Contact missing Контакт відсутній
Contact shared Контакт надіслано
Contacts Контакти
Contacts are on internal flash - the SD card did not mount at boot. Re-seat the card and reboot. Контакти у внутрішній пам'яті - SD-карта не змонтувалася під час завантаження. Переставте картку і перезавантажте.
Contacts are on internal flash. Контакти у внутрішній пам'яті.
@@ -778,6 +779,8 @@ Settings Налаштування
Setup complete Налаштування завершено
Share QR Поділитися QR
Share channel Поділитися каналом
Share contact Поділитися контактом
Share failed Не вдалося надіслати контакт
Share location in advert Ділитися місцезнаходженням в advert
Share secret Поділитися ключем
Share the 32-char secret so others can join. Leave the secret empty to generate a random one. Поділіться секретом із 32 символів, щоб інші приєдналися. Порожньо = випадковий.
@@ -1103,3 +1106,10 @@ Glance shows message previews\nwhile the screen is locked Glance shows message p
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
N/A N/A
This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+5 -1
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: bg
# ver: 19
# ver: 21
# name: Български
# base: bg
(device behind) (устройството изостава)
@@ -1109,3 +1109,7 @@ This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+5 -1
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: de
# ver: 19
# ver: 21
# name: Deutsch
# base: de
(device behind) (Gerät geht nach)
@@ -1105,3 +1105,7 @@ This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+5 -1
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: el
# ver: 19
# ver: 21
# name: Ελληνικά
# base: el
(device behind) (συσκευή πίσω)
@@ -1109,3 +1109,7 @@ This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+5 -1
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: es
# ver: 19
# ver: 21
# name: Español
# base: es
(device behind) (dispositivo atrasado)
@@ -1109,3 +1109,7 @@ This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+5 -1
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: fr
# ver: 19
# ver: 21
# name: Français
# base: fr
(device behind) (appareil en retard)
@@ -1108,3 +1108,7 @@ This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+9 -5
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: hu
# ver: 19
# ver: 21
# name: Magyar
# base: hu
(device behind) (készülék hátulján)
@@ -401,7 +401,7 @@ Join a private channel Belépés priváthoz
Join hashtag channel Csatlakozz # csatornához
Join network Csatlakozás a hálózathoz
Join private channel Csatlakozz privát csatornához
Join the public channel Csatlakozás a nyilvános csatornához
Join the public channel Belépés a nyilvánoshoz
Joined %.40s Csatlakozott %.40s alatt
Keep per chat (messages) Megőrzés csevegésenként (üzenet)
Key already in use Kulcs már hozzárendelve
@@ -414,7 +414,7 @@ Keyboard nav: WASDZ on Billentyűzet navigáció: WASDZ bekapcsolva
Keyboard nav: off Billentyűzetnavigáció: kikapcsolva
Keyboard navigation Billentyűzetnavigáció
Keys Kulcsok
Known regions Known regions
Known regions Ismert régiók
LOS → %s Rálátás → %s
Landscape (desktop) Fekvő (asztali)
Landscape set - applies when remote mode starts. Fekvő beállítva - a távoli mód indulásakor lép életbe.
@@ -472,7 +472,7 @@ Map — no tile pack on SPIFFS yet.\n\nUpload a tile pack to /tiles/<z>/<x>/<y>.
Map — set your location\nin Settings → Profile to\nshow the map here. Térkép – a Beállítások → Profil menüpontban állítsd be a tartózkodási helyedet, hogy itt jelenjen meg a térkép.
Mark all chats and channels as read? Az összes csevegést és csatornát megjelölöd olvasottként?
Mark as read Olvasottnak jelöl
Mark read Mark read
Mark read Olvasott
Marked read Olvasottként megjelölve
Max hops (0..64) Max ugrások (0-64)
Max zoom for this pack Maximális zoom ehhez a csomaghoz
@@ -899,7 +899,7 @@ Turn this on, then open the address below in any web browser on the same Wi-Fi -
Type Tipus
Type a URL, then Go Írj be egy URL-t, majd Mehet
Type a message... Írj be egy üzenetet...
Type a new 6-digit code · applies after a reboot Írjon be egy új 6 jegyű kódot · újraindítás után érvényes
Type a new 6-digit code · applies after a reboot Írjon be egy 6 jegyű kódot · újraindítás után él
Type your network name — or switch to Wi-Fi in Settings to scan Írd be a hálózatod nevét – vagy válts Wi-Fi-re a beállításokban a kereséshez
UI size (restart to apply) UI mérete (újraindítás az alkalmazáshoz)
UI size saved — restart to apply UI mérete mentve – indítsa újra a programot az alkalmazáshoz
@@ -1126,3 +1126,7 @@ N/A N/A
This board cannot run this app. This board cannot run this app.
Signal (stale) Jel (elavult)
nothing heard yet Még semmi hír.
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+5 -1
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: it
# ver: 19
# ver: 21
# name: Italiano
# base: it
(device behind) (dispositivo indietro)
@@ -1108,3 +1108,7 @@ This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+5 -1
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: nl
# ver: 19
# ver: 21
# name: Nederlands
# base: nl
(device behind) (apparaat loopt achter)
@@ -1106,3 +1106,7 @@ This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+5 -1
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: pt-br
# ver: 19
# ver: 21
# name: Português (BR)
# base: pt-br
(device behind) (dispositivo atrasado)
@@ -1108,3 +1108,7 @@ This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+5 -1
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: ro
# ver: 19
# ver: 21
# name: Română
# base: ro
(device behind) (dispozitiv în urmă)
@@ -1110,3 +1110,7 @@ This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+5 -1
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: ru
# ver: 19
# ver: 21
# name: Русский
# base: ru
(device behind) (устройство отстаёт)
@@ -1109,3 +1109,7 @@ This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+5 -1
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: sr
# ver: 19
# ver: 21
# name: Српски
# base: sr
(device behind) (уређај касни)
@@ -1109,3 +1109,7 @@ This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+5 -1
View File
@@ -1,7 +1,7 @@
# wadamesh language file
# canonical source - translators: edit this file and PR it
# code: uk
# ver: 19
# ver: 21
# name: Українська
# base: uk
(device behind) (пристрій відстає)
@@ -1109,3 +1109,7 @@ This board cannot run this app. This board cannot run this app.
Signal Signal
Signal (stale) Signal (stale)
nothing heard yet nothing heard yet
Detecting the keyboard protocol Detecting the keyboard protocol
Older keyboard protocol Older keyboard protocol
Using the older keyboard protocol Using the older keyboard protocol
turn on if your keyboard types the wrong letters; disables modifier latching turn on if your keyboard types the wrong letters; disables modifier latching
+13 -13
View File
@@ -3,67 +3,67 @@
{
"code": "bg",
"name": "Български",
"ver": "19"
"ver": "21"
},
{
"code": "de",
"name": "Deutsch",
"ver": "19"
"ver": "21"
},
{
"code": "el",
"name": "Ελληνικά",
"ver": "19"
"ver": "21"
},
{
"code": "es",
"name": "Español",
"ver": "19"
"ver": "21"
},
{
"code": "fr",
"name": "Français",
"ver": "19"
"ver": "21"
},
{
"code": "hu",
"name": "Magyar",
"ver": "19"
"ver": "21"
},
{
"code": "it",
"name": "Italiano",
"ver": "19"
"ver": "21"
},
{
"code": "nl",
"name": "Nederlands",
"ver": "19"
"ver": "21"
},
{
"code": "pt-br",
"name": "Português (BR)",
"ver": "19"
"ver": "21"
},
{
"code": "ro",
"name": "Română",
"ver": "19"
"ver": "21"
},
{
"code": "ru",
"name": "Русский",
"ver": "19"
"ver": "21"
},
{
"code": "sr",
"name": "Српски",
"ver": "19"
"ver": "21"
},
{
"code": "uk",
"name": "Українська",
"ver": "19"
"ver": "21"
}
]
}
+17
View File
@@ -0,0 +1,17 @@
# beta_73 - test build. Stable stays beta_65.
# One user-facing note per non-blank, non-# line; # lines are section comments.
# --- Older T-Decks could not be typed on ------------------------------------------------------
If your T-Deck types the wrong letters, this release fixes it. Pressing "a" produced "q " and "m" produced "qa ", which made the keyboard unusable for writing, navigating or locking the screen.
It affects the older T-Deck keyboards, the ones without the controllable backlight, and it started in beta_70. The firmware asks the keyboard at startup whether it speaks the newer protocol that modifier latching needs, and an older keyboard's answer looked enough like a yes to be taken as one. Everything typed after that was read the wrong way round.
Nothing can tell the two keyboards apart while nobody is typing, so the firmware now simply corrects itself: the first letter you press is unmistakably a character rather than a key-position report, and it switches to the older protocol on the spot and delivers that letter. You lose modifier latching, which your keyboard cannot do anyway, and you get a working keyboard.
Newer keyboards are unaffected and keep latching.
The beta_72 change that was meant to make this detection more reliable made it worse, because it gave an older keyboard more chances to look like a newer one.
# --- Translations ---------------------------------------------------------------------------------
Two Hungarian strings: "Mark read", which had never been translated, and a shorter wording for joining the public channel so it fits its button. These are already on devices, since language files publish separately from the firmware.
# --- For testers -------------------------------------------------------------------------------
If your keyboard was typing nonsense, you should be able to install this normally rather than needing a cable.
A reminder that two settings control whether an arriving message lights a dark or locked screen, and they are separate: Message flash wakes the screen, and At a glance draws the preview text on it. Both are in Options, Display. Turning off At a glance alone will not stop the screen lighting up.
Still open and not fixed here: a map that renders nothing after beta_71, a screen that will not stay dark after a manual lock, and a web flasher that hangs at "wrapping up" on the T-Display P4.
+25
View File
@@ -0,0 +1,25 @@
# beta_74 - test build. Stable stays beta_65.
# One user-facing note per non-blank, non-# line; # lines are section comments.
# --- If your T-Deck types the wrong letters -----------------------------------------------
There is now a switch that fixes it, and you can reach it without typing: Settings, Keyboard, "Older keyboard protocol". It skips the automatic detection and uses the protocol every T-Deck keyboard understands.
This exists because the detection cannot always be certain, and when it gets it wrong the keyboard types the wrong letters, so any fix that needs you to type is not a fix. The switch is operated by touch and applies immediately, with no reboot.
Turning it on costs modifier latching, which a keyboard that needs the switch cannot do anyway.
The automatic detection is unchanged and still the default. If beta_73 fixed your keyboard, nothing here changes for you.
# --- Web settings could quietly change your radio ------------------------------------------
The remote web page showed a bandwidth of 62.5 as 62, and pressing Apply sent 62 back to the device. Opening the page and applying anything reconfigured the radio onto a bandwidth the mesh was not using. Both halves of that are fixed: the device now sends the fraction, and the form accepts it.
# --- Keyboard ---------------------------------------------------------------------------------
Shift latches like the symbol and alt keys do: tap once for the next letter, tap twice to lock until you tap again. No more holding shift down while reaching for a key.
The Attaky Mesh Deck's front D-pad and SELECT button work now. The board has nine buttons and only the power button did anything.
# --- Reading a route ------------------------------------------------------------------------
Each hop in a message route shows its path prefix next to the repeater name. When a repeater is named misleadingly, or two share a name, the prefix is the part that still identifies it, which is exactly when you are looking at a route.
# --- Translations ---------------------------------------------------------------------------------
"Known regions" and the Bluetooth pairing hint are translatable and translated into Hungarian.
# --- For testers ------------------------------------------------------------------------------
Still open and not fixed here: a map that renders nothing since beta_71, a screen that will not stay dark after a manual lock, the web flasher hanging at "wrapping up" on the T-Display P4, and an M9 that reports no SD card when formatting.
A reminder that two separate settings control whether an arriving message lights a dark screen. "Flash on new message" is in Settings, Keyboard, and it is the one that wakes the screen. "At a glance" is in Settings, Display, and it draws the preview text. Turning off the second one alone will not stop the screen lighting up.
+8 -2
View File
@@ -9,7 +9,7 @@
namespace TouchPrefsSchema {
static constexpr uint16_t MAGIC = 0x5743; // 'WC' (WadaCfg)
static constexpr uint8_t CURRENT_VERSION = 52; // v49: fem_lna default flip on the V4-R8; v50: map tile z/x/y line off by default (no new fields); v51: console_mode (boot into the text console; new trailing field, default OFF); v52: console_monitor (show incoming messages in the console, default ON)
static constexpr uint8_t CURRENT_VERSION = 53; // v49: fem_lna default flip on the V4-R8; v50: map tile z/x/y line off by default (no new fields); v51: console_mode (boot into the text console; new trailing field, default OFF); v52: console_monitor (show incoming messages in the console, default ON)
static constexpr uint8_t BROKEN_MID_INSERT_VERSION = 44;
// Persisted byte layout. New fields must be appended at the end: older blobs
@@ -83,6 +83,12 @@ struct __attribute__((packed)) Config {
// every field after it, which is what v45 had to undo. Append, never insert.
uint8_t console_mode; // v51: boot into the LVGL-free text console (CONSOLE_MODE.md)
uint8_t console_monitor; // v52: console mode shows incoming messages as they arrive
// v53: never probe for the raw keyboard protocol, always use the older one.
// The detection cannot be certain while nobody is typing, and when it guesses
// wrong the keyboard types nonsense -- which also makes it very hard to reach
// a setting to fix it. This is reachable by touch, so it is a way out that
// does not need the keyboard that is broken (#341, #351).
uint8_t kb_force_legacy;
};
static constexpr size_t HEADER_SIZE = offsetof(Config, bright);
@@ -90,7 +96,7 @@ static constexpr size_t STABLE_V44_PREFIX_SIZE = offsetof(Config, web_mirror);
static_assert(offsetof(Config, web_mirror) == offsetof(Config, rx_queue) + sizeof(Config::rx_queue),
"the pre-v44 suffix moved");
static_assert(offsetof(Config, console_monitor) + sizeof(Config::console_monitor) == sizeof(Config),
static_assert(offsetof(Config, kb_force_legacy) + sizeof(Config::kb_force_legacy) == sizeof(Config),
"new preference fields must remain trailing");
// lang_file must stay immediately before the tail, or a v48-era blob overlays
// onto the wrong bytes. Checking both ends means the next person to append is
+12
View File
@@ -135,6 +135,7 @@ static void cfgSetDefaults(TouchCfg& c) {
c.boot_advert = 0; // OFF: no automatic advert on boot — opt-in (#76)
c.console_mode = 0; // OFF: boot into the graphical UI (CONSOLE_MODE.md)
c.console_monitor = 1; // ON: the console shows messages as they arrive
c.kb_force_legacy = 0; // OFF: detect the keyboard protocol automatically
c.compact_chat = 0; // OFF: bubble chat layout (opt-in IRC-style dense rows)
c.clock_floor = 0; // no persisted send-timestamp floor yet
c.rx_queue = 1; // ON: buffered receive (test-channel default; opt-out toggle in Radio & Mesh)
@@ -242,6 +243,7 @@ static void cfgLoadOrMigrate() {
// a garbage 1 would boot a user into a console they did not ask for.
if (stored_version < 51) s_cfg.console_mode = 0;
if (stored_version < 52) s_cfg.console_monitor = 1; // new trailing field: on by default
if (stored_version < 53) s_cfg.kb_force_legacy = 0; // new trailing field: auto-detect
if (stored_version < 31) s_cfg.compact_chat = 0; // new trailing field: compact chat rows off by default
if (stored_version < 32) s_cfg.clock_floor = 0; // new trailing field: no send-timestamp floor persisted yet (#89)
if (stored_version < 33) s_cfg.rx_queue = 1; // buffered LoRa receive ON for the test channel (opt-out toggle in Radio & Mesh)
@@ -1114,6 +1116,16 @@ bool touchPrefsSetConsoleMode(bool on) {
return cfgFlush();
}
bool touchPrefsGetKbForceLegacy() {
if (!s_begun) touchPrefsBegin();
return s_cfg.kb_force_legacy != 0;
}
bool touchPrefsSetKbForceLegacy(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.kb_force_legacy = on ? 1 : 0;
return cfgFlush();
}
bool touchPrefsGetConsoleMonitor() {
if (!s_begun) touchPrefsBegin();
return s_cfg.console_monitor != 0;
+7
View File
@@ -193,6 +193,13 @@ bool touchPrefsSetMsgFlash(bool on);
* refresh our pubkey (useful after a reflash wiped storage). Opt-in, default off. All boards. */
bool touchPrefsGetConsoleMode(); // boot into the LVGL-free console (CONSOLE_MODE.md)
bool touchPrefsSetConsoleMode(bool on);
// Skip raw-protocol detection on the T-Deck keyboard and always use the older
// one. Reachable by touch, so it is a way out when a misdetection has left the
// keyboard typing nonsense (#341, #351).
bool touchPrefsGetKbForceLegacy();
bool touchPrefsSetKbForceLegacy(bool on);
bool touchPrefsGetKbForceLegacy();
bool touchPrefsSetKbForceLegacy(bool on);
bool touchPrefsGetConsoleMonitor(); // console: show incoming messages live
bool touchPrefsSetConsoleMonitor(bool on);
bool touchPrefsGetBootAdvert();
@@ -374,7 +374,7 @@ static const char WS_HTML_TERMINAL_PAGE[] =
"function showSettings(d){var g=E;\n"
" var h='<div class=sset><label>NODE NAME</label><div class=srow><input type=text id=sfname><button class=sbtn id=sbname>Save</button></div>';\n"
" h+='<label>RADIO (applies live)</label><div class=sgrid>';\n"
" h+='<div class=f><label>Freq MHz</label><input type=number step=0.001 id=sffreq></div><div class=f><label>BW kHz</label><input type=number id=sfbw></div>';\n"
" h+='<div class=f><label>Freq MHz</label><input type=number step=0.001 id=sffreq></div><div class=f><label>BW kHz</label><input type=number step=0.1 id=sfbw></div>';\n"
" h+='<div class=f><label>SF</label><input type=number id=sfsf></div><div class=f><label>CR</label><input type=number id=sfcr></div>';\n"
" h+='<div class=f><label>TX dBm</label><input type=number id=sftx></div><div class=f><label>&nbsp;</label><button class=sbtn id=sbradio style=width:100%>Apply</button></div></div>';\n"
" h+='<label>CONNECTIVITY</label><div class=stog><b>Wi-Fi</b><div class=sw id=swifi><i></i></div></div>';\n"
+104 -27
View File
@@ -75,6 +75,27 @@ static void processRawFrame(const uint8_t frame[TDeckKeyboardState::COLS], uint3
portEXIT_CRITICAL(&s_keyboard_mux);
}
// True when a five-byte answer can only be a legacy ASCII reply: the character
// in byte 0 with padding after it. A real matrix frame puts one bit per pressed
// key in that key's own column, so three or more bits in byte 0 with every other
// column empty is not something the hardware can report.
static bool looksLikeAscii(const uint8_t frame[TDeckKeyboardState::COLS]) {
for (size_t col = 1; col < TDeckKeyboardState::COLS; ++col) if (frame[col]) return false;
if (frame[0] < 0x20 || frame[0] > 0x7E) return false; // must be a printable character
int bits = 0;
for (int b = 0; b < 8; ++b) if (frame[0] & (1u << b)) ++bits;
return bits > 2;
}
// Verified against the real tables rather than assumed:
// - all 26 lowercase letters set 3 or more bits, so ANY letter demotes on the
// first press. Only '0', ' ' and '!' do not, and space happens to render
// correctly anyway (column 0 bit 5 IS space).
// - going the other way, a genuine frame reaching this needs three or more of
// {q, w, a, space} held at once and nothing else. The printable-range test
// above removes q+w+a (0x0B); what remains are combinations including space,
// which is a fumble rather than typing, and costs a reboot to undo.
static void processLegacyKey(uint8_t key) {
portENTER_CRITICAL(&s_keyboard_mux);
const uint32_t generation = s_modifier_mode_generation;
@@ -90,6 +111,12 @@ static void processLegacyKey(uint8_t key) {
portEXIT_CRITICAL(&s_keyboard_mux);
}
// Set from the UI at boot (Settings > Keyboard). When true the probe is skipped
// entirely and the older protocol is used, which is the escape hatch for a
// controller that detection gets wrong (#351).
static bool s_force_legacy = false;
void tdeckKeyboardForceLegacy(bool on) { s_force_legacy = on; }
void tdeckKeyboardBegin() {
s_mode = KeyboardMode::Probe;
s_raw_state = TDeckKeyboardState{};
@@ -127,17 +154,34 @@ void tdeckKeyboardFlushBacklight() {
void tdeckKeyboardPoll() {
if (!s_inited) return;
tdeckKeyboardFlushBacklight();
if (s_force_legacy && s_mode != KeyboardMode::Legacy) {
keyboardCommand(0x04);
s_mode = KeyboardMode::Legacy;
Serial.println("[keyboard] T-Deck legacy mode: forced in settings");
}
if (s_mode == KeyboardMode::Probe) {
// LilyGO keyboard firmware from June 2025 onward accepts 0x03 and returns
// five column-state bytes. Older controllers ignore it and keep returning
// one resolved ASCII byte; restore 0x04 key mode when that is what we have.
// Deciding whether this controller speaks the raw column protocol.
//
// KEEP RETRYING before concluding "old firmware". This used to decide on a
// SINGLE read, and the first I2C read after boot is the least trustworthy
// one there is: the C3 is still coming up, so a short or garbled frame is
// ordinary. One such frame committed the device to legacy mode for the whole
// session, with no recovery short of a reboot -- reported as latching simply
// not working on a T-Deck carrying the June 2025 firmware (#332).
// The old test was "five bytes came back and no high bit is set", which a
// LEGACY controller passes trivially: it answers a five-byte read with the
// pressed character followed by padding, and every lowercase letter has its
// high bit clear. Read as a column bitmap, 'a' (0x61) emits "q " and 'm'
// (0x6d) emits "qa ". That is the garbage Vybo reported on an older T-Deck
// (#341), and the retry window added in beta_72 made it MORE likely to
// latch, not less.
//
// The positive test: a real matrix frame from a key press has ONE bit set,
// in whichever column that key lives. A legacy reply only ever has byte 0
// non-zero, and an ASCII letter sets three or four bits at once. So:
//
// any non-zero byte in columns 1..4 -> genuinely raw (padding is never
// non-zero there)
// byte 0 with >2 bits set, rest zero -> an ASCII character, so legacy
//
// An all-zero frame is idle and proves nothing either way, so keep probing.
// If the window expires having seen only column-0 activity, fall back to
// LEGACY. That is the safe default: the cost of being wrong is losing
// modifier latching, never losing the keyboard.
static uint32_t s_probe_start_ms = 0;
const uint32_t now = millis();
if (!s_probe_start_ms) s_probe_start_ms = now;
@@ -145,37 +189,70 @@ void tdeckKeyboardPoll() {
keyboardCommand(0x03);
uint8_t frame[TDeckKeyboardState::COLS] = {};
const int count = keyboardRead(frame, sizeof frame);
bool valid_raw = count == (int)sizeof frame;
size_t bad_col = 0;
for (size_t col = 0; col < sizeof frame && valid_raw; ++col) {
if (frame[col] & 0x80) { valid_raw = false; bad_col = col; }
}
if (valid_raw) {
bool shape_ok = (count == (int)sizeof frame);
for (size_t col = 0; col < sizeof frame && shape_ok; ++col)
if (frame[col] & 0x80) shape_ok = false;
if (shape_ok) {
bool cols1plus = false;
for (size_t col = 1; col < sizeof frame; ++col) if (frame[col]) cols1plus = true;
int b0bits = 0;
for (int b = 0; b < 8; ++b) if (frame[0] & (1u << b)) ++b0bits;
if (cols1plus) { // only a real matrix reaches here
s_mode = KeyboardMode::Raw;
Serial.println("[keyboard] T-Deck raw mode: modifier latching enabled");
processRawFrame(frame, now);
return;
}
if (frame[0] && b0bits > 2) { // an ASCII byte, not a column
keyboardCommand(0x04);
s_mode = KeyboardMode::Legacy;
Serial.printf("[keyboard] T-Deck legacy mode: controller answered with a character "
"(0x%02X), not a column frame\n", frame[0]);
processLegacyKey(frame[0]);
return;
}
if ((uint32_t)(now - s_probe_start_ms) < kProbeWindowMs) return; // idle: keep looking
// Window expired with a well-formed, idle frame every time. That is what a
// raw controller looks like when nobody is typing, so take it -- a legacy
// one would have shown us a character by now if it had one, and if it has
// not, looksLikeAscii() below still demotes the moment it does.
s_mode = KeyboardMode::Raw;
Serial.println("[keyboard] T-Deck raw mode: modifier latching enabled");
processRawFrame(frame, millis());
return;
} else if ((uint32_t)(now - s_probe_start_ms) < kProbeWindowMs) {
if (count == 1) { keyboardCommand(0x04); processLegacyKey(frame[0]); }
return; // short/garbled read: the C3 may still be waking
}
// A legacy controller answers with one resolved byte. Deliver it either way
// so nothing typed during the probe window is dropped.
if (count == 1) { keyboardCommand(0x04); processLegacyKey(frame[0]); }
if ((uint32_t)(now - s_probe_start_ms) < kProbeWindowMs) return; // try again next poll
keyboardCommand(0x04);
s_mode = KeyboardMode::Legacy;
// Say WHY, so the next report of "latching does not work" is one line to
// diagnose instead of a guess about which firmware someone has.
Serial.printf("[keyboard] T-Deck legacy mode: update keyboard C3 firmware for modifier "
"latching (probe read %d bytes, wanted %d%s)\n",
count, (int)sizeof frame,
(count == (int)sizeof frame) ? ", high bit set in a column" : "");
(void)bad_col;
Serial.printf("[keyboard] T-Deck legacy mode: no raw column frame seen in %lu ms "
"(probe read %d bytes)\n", (unsigned long)kProbeWindowMs, count);
if (count == 1) processLegacyKey(frame[0]);
return;
}
if (s_mode == KeyboardMode::Raw) {
uint8_t frame[TDeckKeyboardState::COLS] = {};
if (keyboardRead(frame, sizeof frame) != (int)sizeof frame) return;
for (size_t col = 0; col < sizeof frame; ++col) if (frame[col] & 0x80) return;
// Raw mode has to be able to admit it guessed wrong. An idle controller of
// either kind answers with zeros, so the probe cannot tell them apart until
// someone types -- and on a legacy controller that first character arrives
// in byte 0 with three or four bits set, which no single key press can
// produce. Demote on the spot and deliver the character, so the worst case
// is one odd keystroke rather than a keyboard that types nonsense forever.
if (looksLikeAscii(frame)) {
keyboardCommand(0x04);
s_mode = KeyboardMode::Legacy;
Serial.printf("[keyboard] T-Deck legacy mode: raw guess withdrawn, controller sent a "
"character (0x%02X)\n", frame[0]);
processLegacyKey(frame[0]);
return;
}
processRawFrame(frame, millis());
return;
}
+4
View File
@@ -19,6 +19,10 @@
* driver, so this just enables polling. */
void tdeckKeyboardBegin();
/** Force the older keyboard protocol, skipping raw detection. Escape hatch for a
* controller the probe gets wrong; call before/at begin and on the setting. */
void tdeckKeyboardForceLegacy(bool on);
/** Read one key over I2C and push it into the ring. CORE-0 ONLY (touch task). */
void tdeckKeyboardPoll();
+11 -1
View File
@@ -39,13 +39,17 @@ class TDeckKeyboardState {
if (!modifiers_enabled) {
symbol_.baseline(down(0, 2));
alt_.baseline(down(0, 4));
shift_.baseline(down(1, 6) || down(2, 3));
}
if (modifiers_enabled && pressed(0, 2)) symbol_.press();
if (modifiers_enabled && pressed(0, 4)) alt_.press();
if (modifiers_enabled && released(0, 2)) symbol_.release(now_ms);
if (modifiers_enabled && released(0, 4)) alt_.release(now_ms);
if (modifiers_enabled && (pressed(1, 6) || pressed(2, 3))) shift_.press();
if (modifiers_enabled && (released(1, 6) || released(2, 3))) shift_.release(now_ms);
const bool shift = modifiers_enabled && (down(1, 6) || down(2, 3));
// Held OR latched: a tap arms it for the next key, a double tap locks it.
const bool shift = modifiers_enabled && (down(1, 6) || down(2, 3) || shift_.latched());
size_t written = 0;
auto emit = [&](uint8_t key) {
if (written < out_capacity) out[written++] = key;
@@ -81,6 +85,7 @@ class TDeckKeyboardState {
if (modifiers_enabled) {
symbol_.consumeForKey();
if (!physical_alt) alt_.consumeForKey();
shift_.consumeForKey(); // one-shot shift expires on the key it capitalised
}
if (key == '\0') continue;
char resolved = key;
@@ -108,4 +113,9 @@ class TDeckKeyboardState {
uint8_t previous_[COLS] = {};
LatchedModifier symbol_;
LatchedModifier alt_;
// Shift latches like the other two (#344). It used to be a bare "is it held"
// test, so a capital letter meant holding shift down while reaching for the
// key. The board has two shift keys and they share one latch: whichever you
// press arms it, and either releases it.
LatchedModifier shift_;
};
+172 -9
View File
@@ -1917,6 +1917,18 @@ static bool tbFingerTouchOnTabBarBlocked(uint16_t y) {
static bool s_kbd_nav = true;
#endif
#if defined(ATTAKY_MESH_SERIES)
// Attaky Mesh Deck: touch is the primary input, and the front D-pad + SELECT are
// a second one — so this takes the same CAP_KEYPAD_NAV "secondary indev" path as
// the M9 block below, fed by attakyNavPump() draining the @0x59 expander queue.
// The three symbols are the ones that path needs (see the M9's note); nav is
// always on because the buttons are soldered to the board — there is nothing to
// toggle off, and with no press the group just sits unfocused and invisible.
static bool s_kbd_nav = true;
static bool s_tb_nav = false; // no trackball — read by the shared nav-rebuild gate, never set
static lv_indev_drv_t s_nav_keypad_drv;
#endif
#if defined(HAS_THINKNODE_M9)
// Keyboard-only device (no touch, no trackball): nav is always on, same as Tanmatsu above.
// Unlike Tanmatsu (which registers its KEYPAD indev as the PRIMARY one, driven by navPump()
@@ -11840,6 +11852,20 @@ static void enterSendsToggleCb(lv_event_t* e) {
static volatile uint32_t s_msgflash_until = 0; // keyboard-backlight pulse deadline (0 = idle)
static volatile bool s_msgflash_wake = false; // one-shot screen-wake request (consumed in loop())
static uint32_t s_notify_wake_ms = 0; // screen lit BY a message (not input) at this ms — re-dims after a short window (burn-in)
#if defined(HAS_TDECK_KEYBOARD)
// Escape hatch for a keyboard the protocol detection gets wrong. Reachable by
// TOUCH, which is the whole point: when detection is wrong the keyboard types
// nonsense, so any fix that needs the keyboard is no fix at all (#341, #351).
static void kbForceLegacyToggleCb(lv_event_t* e) {
if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
const bool on = lv_obj_has_state(lv_event_get_target(e), LV_STATE_CHECKED);
touchPrefsSetKbForceLegacy(on);
tdeckKeyboardForceLegacy(on); // applies now; no reboot needed to get typing back
if (g_lv.task) g_lv.task->showAlert(on ? TR("Using the older keyboard protocol")
: TR("Detecting the keyboard protocol"), 1600);
}
#endif
static void msgFlashToggleCb(lv_event_t* e) {
if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
const bool on = lv_obj_has_state(lv_event_get_target(e), LV_STATE_CHECKED);
@@ -13324,6 +13350,25 @@ static void buildDeviceSettings(int sec) {
y += settingsRowLabel(body, y, 0, TR("lights the keyboard + wakes the screen on an incoming message"),
COLOR_SUB, &g_font_12, 0) + 2;
}
#if defined(HAS_TDECK_KEYBOARD)
/* Older keyboard controllers do not speak the raw protocol that modifier
latching needs, and the detection cannot be certain while nobody is typing.
When it guesses wrong the keyboard types the wrong letters, so this switch
exists to be reachable BY TOUCH and end it. */
{
int h = settingsRowLabel(body, y, 4, TR("Older keyboard protocol"), COLOR_TEXT, &g_font_12, 56);
lv_obj_t* sw = lv_switch_create(body);
lv_obj_align(sw, LV_ALIGN_TOP_RIGHT, 0, y);
#if defined(ESP32)
if (touchPrefsGetKbForceLegacy()) lv_obj_add_state(sw, LV_STATE_CHECKED);
#endif
lv_obj_add_event_cb(sw, kbForceLegacyToggleCb, LV_EVENT_VALUE_CHANGED, nullptr);
y += LV_MAX(34, h + 10);
y += settingsRowLabel(body, y, 0, TR("turn on if your keyboard types the wrong letters; disables modifier latching"),
COLOR_SUB, &g_font_12, 0) + 2;
}
#endif
#endif
#if defined(HAS_TANMATSU)
@@ -19887,7 +19932,7 @@ static void webPushSettings() {
const bool wc = (WiFi.status() == WL_CONNECTED);
char* p = s_webdata_buf; const char* e = s_webdata_buf + WEBDATA_BUF;
p += snprintf(p, e - p,
"{\"t\":\"sg\",\"freq\":%.3f,\"bw\":%.0f,\"sf\":%d,\"cr\":%d,\"tx\":%d,\"wifi\":%d,\"ble\":%d,\"blecap\":%d,\"tcp\":%d,\"name\":\"",
"{\"t\":\"sg\",\"freq\":%.3f,\"bw\":%.1f,\"sf\":%d,\"cr\":%d,\"tx\":%d,\"wifi\":%d,\"ble\":%d,\"blecap\":%d,\"tcp\":%d,\"name\":\"",
pr ? pr->freq : 0.0f, pr ? pr->bw : 0.0f, pr ? pr->sf : 0, pr ? pr->cr : 0, pr ? (int)pr->tx_power_dbm : 0,
wc ? 1 : 0, g_lv.task->isBleEnabled() ? 1 : 0, g_lv.task->hasBleCapability() ? 1 : 0,
g_lv.task->isTcpEnabled() ? 1 : 0);
@@ -32929,15 +32974,20 @@ static void openMessageInfoPopup(int msg_idx) {
blen += snprintf(body + blen, sizeof(body) - blen, "\nRoute");
int off = 0;
for (uint8_t h = 0; h < cnt && off + (int)hsz <= m.in_path_n; ++h, off += hsz) {
if (blen >= (int)sizeof(body) - 56) break; // hard guard: never overflow body[]
if (blen >= (int)sizeof(body) - 72) break; // hard guard: never overflow body[]
// Always lead with the path-hash prefix, then the name when we know it.
// The hash is what the node is actually keyed by on the wire, so it is
// the part that stays true when a repeater is named misleadingly or two
// of them share a name -- which is exactly when you are reading a route
// (#348). Showing only the resolved name threw that away.
char hashstr[9] = {0};
for (uint8_t b = 0; b < hsz && b < 4; ++b)
snprintf(hashstr + b * 2, sizeof(hashstr) - b * 2, "%02X", m.in_path[off + b]);
char nm[33]; // ContactInfo.name is 32B; hold the full name (emoji eat 4B each)
if (the_mesh.uiHopName(&m.in_path[off], hsz, nm, sizeof(nm))) {
blen += snprintf(body + blen, sizeof(body) - blen, "\n %u. #%06X %s#",
(unsigned)(h + 1), (unsigned)nodeSigColorHex(nm), nm);
blen += snprintf(body + blen, sizeof(body) - blen, "\n %u. #%06X %s %s#",
(unsigned)(h + 1), (unsigned)nodeSigColorHex(nm), hashstr, nm);
} else {
char hashstr[9] = {0};
for (uint8_t b = 0; b < hsz && b < 4; ++b)
snprintf(hashstr + b * 2, sizeof(hashstr) - b * 2, "%02X", m.in_path[off + b]);
blen += snprintf(body + blen, sizeof(body) - blen, "\n %u. #%06X %s#",
(unsigned)(h + 1), (unsigned)nodeSigColorHex(hashstr), hashstr);
}
@@ -36938,11 +36988,14 @@ static bool popupRegistryBlocksSwipe();
// True if any popup/modal is currently up (rows flagged PF_COUNT).
static bool anyPopupOpen() { return popupRegistryAny(); }
#if CAP_KEYBOARD
#if CAP_KEYBOARD || CAP_KEYPAD_NAV
// Close the topmost popup/modal (registry order = front-to-back priority),
// like tapping its X / close button. Returns true if one was dismissed.
// Widened past CAP_KEYBOARD for the Attaky: navGoToMainTab() (inside the
// CAP_KEYPAD_NAV block) calls this, and that board navigates with a D-pad but
// has no physical keyboard, so the old gate left the declaration undefined.
static bool hwKeyDismissTopPopup() { return popupRegistryDismissTop(); }
#endif // HAS_TDECK_KEYBOARD || HAS_TANMATSU (hwKeyDismissTopPopup)
#endif // CAP_KEYBOARD || CAP_KEYPAD_NAV (hwKeyDismissTopPopup)
#if defined(HAS_TDECK_KEYBOARD) || defined(HAS_PAGER_KEYBOARD) || defined(HAS_M9_KEYBOARD)
// Keys that act as "close the popup" when no text field is focused.
@@ -38993,6 +39046,89 @@ if (g_lv.task && g_lv.task->isManualLock()) {
}
#endif
#if defined(ATTAKY_MESH_SERIES)
// ---- Attaky front D-pad -> focus navigation ---------------------------------
// The five nav buttons on the @0x59 expander (P00 down, P01 left, P02 select,
// P03 right, P04 up) drive the SAME focus group the Tanmatsu arrows and the
// T-Deck trackball use, so they reach every screen with no per-screen handling.
// Touch stays this board's primary input and the D-pad is a second one — which
// is why the focus ring starts hidden (s_nav_show is false until a press) and
// nothing here is toggleable: the buttons are soldered on, so "off" would just
// make them dead.
//
// This board compiles no handleHwKey() (its keyboard module types straight into
// the focused textarea), so the nav routing the T-Deck and M9 do from there
// lives here instead. Deliberately narrower than the M9's: that board has no
// touch, so it also needs d-pad paths for Back, Home, the chat-bubble action
// menu and map panning — all of which are still a tap away here.
static void attakyNavPump() {
// The field currently bound to the keyboard, and whether the D-pad should be
// editing it: focus has to be really ON it (a tap sets s_nav_ta_editing), so
// arrows moving through a page don't get swallowed as caret moves.
lv_obj_t* const ta_focused = g_lv.keyboard ? lv_keyboard_get_textarea(g_lv.keyboard) : nullptr;
lv_obj_t* const ta = (ta_focused && s_nav_ta_editing) ? ta_focused : nullptr;
for (int i = 0; i < 8; ++i) {
const int ev = attakyNavKeyRead();
if (ev == ATTAKY_NAV_NONE) break;
switch (ev) {
case ATTAKY_NAV_UP:
case ATTAKY_NAV_DOWN: {
const bool up = (ev == ATTAKY_NAV_UP);
// The @-mention picker owns up/down while it shows: its cells carry
// NAV_SKIP_FLAG (they're tap targets on touch boards), so the focus
// group cannot reach them at all.
if (mentionNavActive()) { mentionNavMove(up ? -1 : +1); break; }
// An open dropdown LIST owns them too — navMoveDir would move focus
// off the dropdown, which closes it. The FIFO'd LV_KEY_UP/DOWN reach
// the dropdown's own handler, which moves the highlighted option.
if (navOpenDropdown()) { navPushTap(up ? LV_KEY_UP : LV_KEY_DOWN); break; }
lv_obj_t* const was = s_nav_group ? lv_group_get_focused(s_nav_group) : nullptr;
navMoveDir(up ? NAV_UP : NAV_DOWN);
// Focus had nowhere to go: page-scroll instead. A display-only Lua app
// scrolls its own body; everything else falls to navScrollFocused, so
// pages with few or no focusable rows still move under the D-pad.
if (s_nav_group && lv_group_get_focused(s_nav_group) == was) {
if (!(luaAppIsOpen() && luaAppScroll(up))) navScrollFocused(up);
}
break;
}
case ATTAKY_NAV_LEFT:
case ATTAKY_NAV_RIGHT: {
const bool left = (ev == ATTAKY_NAV_LEFT);
if (mentionNavActive() || navOpenDropdown()) break; // vertical-only: swallow
if (ta) {
// Caret already at the edge: fall through to a focus move, so the
// pad always eventually LEAVES the field instead of no-oping.
const uint32_t p = lv_textarea_get_cursor_pos(ta);
if (left) lv_textarea_cursor_left(ta); else lv_textarea_cursor_right(ta);
if (lv_textarea_get_cursor_pos(ta) == p) navMoveDir(left ? NAV_LEFT : NAV_RIGHT);
}
else if (navOnTabBar()) navSwitchTab(left ? -1 : +1);
else navMoveDir(left ? NAV_LEFT : NAV_RIGHT);
break;
}
case ATTAKY_NAV_SELECT:
if (mentionNavActive()) { mentionNavConfirm(); break; }
if (navOnTabBar()) { navSwitchTab(+1); break; }
// First SELECT on a focused field starts typing (the caret appears and
// left/right walk it); the next one activates whatever is focused.
if (ta_focused && !ta && s_nav_group && lv_group_get_focused(s_nav_group) == ta_focused) {
s_nav_ta_editing = true;
break;
}
navMarkEntered(s_nav_group ? lv_group_get_focused(s_nav_group) : nullptr);
navPushTap(LV_KEY_ENTER);
break;
default: break;
}
s_nav_show = true;
if (g_lv.task) g_lv.task->noteUserInput();
}
}
#endif
static void refreshLiveDiag(unsigned long now) {
if (!s_live_diag_label) return;
if (now < s_live_diag_next_ms) return;
@@ -51566,6 +51702,12 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
uint16_t to_s = touchPrefsGetScreenTimeoutSecs();
_screen_timeout_ms = static_cast<uint32_t>(to_s) * 1000u;
s_lock_on_screen_off = touchPrefsGetLockOnScreenOff();
#if defined(HAS_TDECK_KEYBOARD)
// Applied here rather than at tdeckKeyboardBegin(), which runs on the touch
// task before prefs are up. The driver re-checks this every poll, so it takes
// effect on the next one either way, including demoting out of raw mode.
tdeckKeyboardForceLegacy(touchPrefsGetKbForceLegacy());
#endif
#endif
_last_input_ms = millis();
_screen_off = false;
@@ -52032,6 +52174,11 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
#if defined(ESP32)
#if defined(HAS_TANMATSU) || defined(HAS_THINKNODE_M9)
s_kbd_nav = true; // keyboard-only device: nav is always on (no touch to fall back to)
#elif defined(ATTAKY_MESH_SERIES)
// Soldered-on D-pad, and no settings row toggles it (the "Keyboard navigation"
// switch is CAP_TRACKBALL-only) — reading the pref here would let a value left
// over from another board leave the buttons dead with no way to turn them back on.
s_kbd_nav = true;
#else
s_kbd_nav = touchPrefsGetKbdNav();
#endif
@@ -55023,6 +55170,22 @@ void UITask::loop() {
if (_screen_off) wakeScreen();
else sleepScreen();
}
// Front D-pad (P00..P04) -> focus navigation. Two things have to happen before
// the drain: with the screen dark a press only WAKES (and is swallowed, so a
// wake never also activates whatever the ring happened to be on — the pager's
// encoder-wake bug), and in remote mode the panel is a placeholder the browser
// drives. Otherwise sync the focus group to the current tree first: Enter's
// tree mutations only land inside lv_timer_handler at the END of a tick, so a
// drain against a stale mirror navigates the screen behind a just-opened modal
// (the same fix the M9's branch above carries).
if (_screen_off || s_remote_mode) {
const bool any = (attakyNavKeyRead() != ATTAKY_NAV_NONE);
attakyNavKeysDiscard();
if (any && _screen_off) wakeScreen();
} else {
navMaybeRebuild();
attakyNavPump();
}
#endif
#if defined(HAS_ATTAKY_MESH_KEYBOARD)
{
+10 -7
View File
@@ -163,13 +163,16 @@
// Focus-group D-pad navigation (no pointer): Tanmatsu keypad, T-Deck trackball,
// the pager (no touch at all — the rotary encoder is its only nav input, so
// like Tanmatsu this is always-on, not an optional toggle like the T-Deck's),
// or the ThinkNode M9's d-pad. The underlying machinery (navFifo, navMoveDir,
// the focus group, the secondary KEYPAD indev) is generic — only the *pump*
// that feeds it differs per board: Tanmatsu's navPump() reads bsp-input events;
// T-Deck's WASDZ-letter nav and the M9's raw d-pad bytes are both fed straight
// from handleHwKey() instead (see UITask.cpp's `#elif defined(HAS_M9_KEYBOARD)`
// block, parallel to the T-Deck's `#if CAP_TRACKBALL` block).
#if defined(HAS_TANMATSU) || defined(HAS_TDECK_TRACKBALL) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9)
// the ThinkNode M9's d-pad, or the Attaky's front D-pad + SELECT. The underlying
// machinery (navFifo, navMoveDir, the focus group, the secondary KEYPAD indev)
// is generic — only the *pump* that feeds it differs per board: Tanmatsu's
// navPump() reads bsp-input events; T-Deck's WASDZ-letter nav and the M9's raw
// d-pad bytes are both fed straight from handleHwKey() instead (see UITask.cpp's
// `#elif defined(HAS_M9_KEYBOARD)` block, parallel to the T-Deck's `#if
// CAP_TRACKBALL` block); the Attaky drains its expander queue in attakyNavPump().
// NOTE: the Attaky is the first board here with CAP_KEYBOARD == 0, so anything
// this flag pulls in must not assume a physical keyboard is also compiled.
#if defined(HAS_TANMATSU) || defined(HAS_TDECK_TRACKBALL) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(ATTAKY_MESH_SERIES)
#define CAP_KEYPAD_NAV 1
#else
#define CAP_KEYPAD_NAV 0
+4 -2
View File
@@ -404,7 +404,7 @@ static const I18nPair kBuiltin_hu[] = {
{ "Join hashtag channel", "Csatlakozz # csatornához" },
{ "Join network", "Csatlakozás a hálózathoz" },
{ "Join private channel", "Csatlakozz privát csatornához" },
{ "Join the public channel", "Csatlakozás a nyilvános csatornához" },
{ "Join the public channel", "Belépés a nyilvánoshoz" },
{ "Join w/ password", "Csatlakozás jelszóval" },
{ "Joined %.40s", "Csatlakozott %.40s alatt" },
{ "Keep per chat (messages)", "Megőrzés csevegésenként (üzenet)" },
@@ -418,6 +418,7 @@ static const I18nPair kBuiltin_hu[] = {
{ "Keyboard nav: off", "Billentyűzetnavigáció: kikapcsolva" },
{ "Keyboard navigation", "Billentyűzetnavigáció" },
{ "Keys", "Kulcsok" },
{ "Known regions", "Ismert régiók" },
{ "LOS → %s", "Rálátás → %s" },
{ "Landscape (desktop)", "Fekvő (asztali)" },
{ "Landscape set - applies when remote mode starts.", "Fekvő beállítva - a távoli mód indulásakor lép életbe." },
@@ -482,6 +483,7 @@ static const I18nPair kBuiltin_hu[] = {
{ "Map — set your location\nin Settings → Profile to\nshow the map here.", "Térkép – a Beállítások → Profil menüpontban állítsd be a tartózkodási helyedet, hogy itt jelenjen meg a térkép." },
{ "Mark all chats and channels as read?", "Az összes csevegést és csatornát megjelölöd olvasottként?" },
{ "Mark as read", "Olvasottnak jelöl" },
{ "Mark read", "Olvasott" },
{ "Marked read", "Olvasottként megjelölve" },
{ "Max dots", "Max. pontok" },
{ "Max hops (0..64)", "Max ugrások (0-64)" },
@@ -934,7 +936,7 @@ static const I18nPair kBuiltin_hu[] = {
{ "Type", "Tipus" },
{ "Type a URL, then Go", "Írj be egy URL-t, majd Mehet" },
{ "Type a message...", "Írj be egy üzenetet..." },
{ "Type a new 6-digit code · applies after a reboot", "Írjon be egy új 6 jegyű kódot · újraindítás után érvényes" },
{ "Type a new 6-digit code · applies after a reboot", "Írjon be egy 6 jegyű kódot · újraindítás után él" },
{ "Type your network name — or switch to Wi-Fi in Settings to scan", "Írd be a hálózatod nevét – vagy válts Wi-Fi-re a beállításokban a kereséshez" },
{ "UI size (restart to apply)", "UI mérete (újraindítás az alkalmazáshoz)" },
{ "UI size saved — restart to apply", "UI mérete mentve – indítsa újra a programot az alkalmazáshoz" },
@@ -20,15 +20,50 @@ static const uint8_t POWER_BIT = (1u << 7); // P07 = POWER_BTN
static const uint8_t DEBOUNCE_POLLS = 2;
static const uint32_t POLL_INTERVAL_MS = 30;
// Auto-repeat for the four directions (never SELECT — one press must stay one
// activation). Without it, walking a long contact list means one press per row.
static const uint32_t REPEAT_DELAY_MS = 450;
static const uint32_t REPEAT_RATE_MS = 130;
// P0 bit -> nav event. 0 = not a nav button (L1/R2 are read but unbound, POWER
// has its own edge flag below).
static const uint8_t BIT_NAV[8] = {
ATTAKY_NAV_DOWN, // P00
ATTAKY_NAV_LEFT, // P01
ATTAKY_NAV_SELECT, // P02
ATTAKY_NAV_RIGHT, // P03
ATTAKY_NAV_UP, // P04
ATTAKY_NAV_NONE, // P05 BTN_L1 — unbound
ATTAKY_NAV_NONE, // P06 BTN_R2 — unbound
ATTAKY_NAV_NONE, // P07 POWER_BTN — attakyPowerKeyPressed()
};
static bool s_inited = false;
static bool s_present = false;
static uint32_t s_next_ms = 0;
static bool s_power_down = false; // debounced state
static uint8_t s_pending_n = 0; // consecutive polls agreeing with s_pending_down
static bool s_pending_down = false;
// Debounced whole-port state, active-low (a 0 bit = that button is down).
static uint8_t s_stable = 0xFF;
static uint8_t s_pending = 0xFF; // candidate awaiting DEBOUNCE_POLLS agreement
static uint8_t s_pending_n = 0;
static volatile bool s_power_event = false;
// Nav press queue. Small ring: the poll runs at ~30 ms and the UI drains every
// tick, so it only has to absorb a burst, never buffer.
static const uint8_t NAV_Q_LEN = 8;
static uint8_t s_navq[NAV_Q_LEN];
static uint8_t s_navq_head = 0, s_navq_tail = 0;
static int8_t s_repeat_bit = -1; // direction currently held, or -1
static uint32_t s_repeat_ms = 0; // when it next repeats
static void navPush(uint8_t ev) {
const uint8_t next = (uint8_t)((s_navq_head + 1) % NAV_Q_LEN);
if (next == s_navq_tail) return; // full — drop the newest rather than stall
s_navq[s_navq_head] = ev;
s_navq_head = next;
}
static bool awWrite(uint8_t reg, uint8_t val) {
Wire.beginTransmission(KEYS_ADDR);
Wire.write(reg);
@@ -75,14 +110,37 @@ void attakyKeysPoll() {
}
if (!ok) return; // bus busy or read failed — hold the previous state
const bool down = (p0 & POWER_BIT) == 0; // active low
// Every button reading pressed at once is a bad read, not a hand: acting on
// it would inject a burst of nav keys AND a panel toggle from one glitch.
// Resync the debouncer instead of treating the recovery as presses.
if (p0 == 0x00) { s_pending = s_stable; s_pending_n = 0; return; }
if (down != s_pending_down) { s_pending_down = down; s_pending_n = 1; return; }
if (p0 != s_pending) { s_pending = p0; s_pending_n = 1; return; }
if (s_pending_n < DEBOUNCE_POLLS) { s_pending_n++; return; }
if (down != s_power_down) {
s_power_down = down;
if (down) s_power_event = true; // fire on the press edge
const uint8_t changed = (uint8_t)(s_stable ^ p0);
if (changed) {
const uint8_t went_down = (uint8_t)(changed & ~p0); // active low: 1 -> 0
s_stable = p0;
if (went_down & POWER_BIT) s_power_event = true; // fire on the press edge
for (uint8_t b = 0; b < 8; b++) {
if (!(went_down & (1u << b)) || BIT_NAV[b] == ATTAKY_NAV_NONE) continue;
navPush(BIT_NAV[b]);
// Only the directions repeat, and only the most recent one: rolling a
// thumb across the pad should follow the finger, not fire two axes.
if (BIT_NAV[b] != ATTAKY_NAV_SELECT) {
s_repeat_bit = (int8_t)b;
s_repeat_ms = now + REPEAT_DELAY_MS;
}
}
// The repeating direction was released (or another button's edge arrived
// while it was already up) — stop repeating.
if (s_repeat_bit >= 0 && (s_stable & (1u << (uint8_t)s_repeat_bit))) s_repeat_bit = -1;
}
if (s_repeat_bit >= 0 && (int32_t)(now - s_repeat_ms) >= 0) {
navPush(BIT_NAV[(uint8_t)s_repeat_bit]);
s_repeat_ms = now + REPEAT_RATE_MS;
}
}
@@ -92,12 +150,26 @@ bool attakyPowerKeyPressed() {
return true;
}
int attakyNavKeyRead() {
if (s_navq_tail == s_navq_head) return ATTAKY_NAV_NONE;
const uint8_t ev = s_navq[s_navq_tail];
s_navq_tail = (uint8_t)((s_navq_tail + 1) % NAV_Q_LEN);
return (int)ev;
}
void attakyNavKeysDiscard() {
s_navq_tail = s_navq_head;
s_repeat_bit = -1;
}
bool attakyKeysPresent() { return s_present; }
#else
void attakyKeysPoll() {}
bool attakyPowerKeyPressed() { return false; }
int attakyNavKeyRead() { return ATTAKY_NAV_NONE; }
void attakyNavKeysDiscard() {}
bool attakyKeysPresent() { return false; }
#endif
@@ -3,9 +3,24 @@
#include <stdbool.h>
// Front buttons on the board's AW9523 @0x59 (P00-P07, active-low). Only
// POWER_BTN (P07) is used: short-press toggles the panel. A long press is the
// board's hardware power-cut, outside firmware control.
// Front buttons on the board's AW9523 @0x59 (P00-P07, active-low), per Attaky's
// hardware catalog:
// P00 DOWN P01 LEFT P02 SELECT P03 RIGHT P04 UP
// P05 BTN_L1 (top-left shoulder) P06 BTN_R2 (top-right shoulder)
// P07 POWER_BTN
// POWER_BTN short-press toggles the panel (a long press is the board's hardware
// power-cut, outside firmware control). The D-pad + SELECT feed the UI's focus
// navigation. L1/R2 are read but deliberately unbound — nothing claims them yet.
/** D-pad events drained by attakyNavKeyRead(). */
enum AttakyNavKey {
ATTAKY_NAV_NONE = 0,
ATTAKY_NAV_UP,
ATTAKY_NAV_DOWN,
ATTAKY_NAV_LEFT,
ATTAKY_NAV_RIGHT,
ATTAKY_NAV_SELECT,
};
/** Poll the expander. Call once per UI tick; rate-limits its own I2C traffic. */
void attakyKeysPoll();
@@ -13,5 +28,11 @@ void attakyKeysPoll();
/** True once per POWER_BTN press edge (consumes the event). */
bool attakyPowerKeyPressed();
/** Next queued D-pad event, or ATTAKY_NAV_NONE when the queue is empty. */
int attakyNavKeyRead();
/** Drop every queued D-pad event (screen-off wake, remote mode). */
void attakyNavKeysDiscard();
/** Whether the @0x59 expander answered at init (false = keys unavailable). */
bool attakyKeysPresent();