Merge upstream beta_68 into the M9 compass / GPS work

beta_68 expanded the Lua SDK (map, lists, packet delivery, discovery, private
messages/rooms, native crypto) across the same files as this branch, so four
files conflicted. Nothing was dropped from either side:

- wada.sys.gps(): both widenings merged into one binding. Upstream's
  fix_time / lat_e6 / lon_e6 and our speed_kmh / course now share a signature,
  and our stricter gate wins -- the call returns nil when the user has GPS
  switched off, not just when there is no fix.
- Altitude is upstream's `alt_m` alone. The resolution first carried `alt`
  beside it to protect a shipped app, but gpscompass has never been published
  to the store (it exists only in this branch and on a bench device), so
  carrying a duplicate key into the API forever was the wrong trade: the app
  reads alt_m instead.
- sysCaps() carries all twelve feature flags (upstream's seven, our compass,
  the four originals) with a matching table hint.
- wada.geo (upstream) and wada.sys.compass (ours) both survive; upstream's
  "no board has a magnetometer" note is corrected in the code and on the SDK
  page, since the M9 now does.
- hostTeardown frees upstream's new POST payload buffer as well as the fetch
  buffer, under the same in-flight guard.
- The catalog keeps all three new apps: upstream's wardrive and nearby, ours
  gpscompass (8 total, every referenced file present).

M9, V4, V4-R8, T-Deck and Pager all compile; the Lua host harness passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Christopher Van HooseandClaude Fable 5 committed 2026-08-22 02:43:55 -04:00
commit 9677dcfe19
103 files changed
+57796 -266

No files matched your search

+45 -1
View File
@@ -44,7 +44,29 @@ def source_keys():
k = k.lstrip(' ')
if k.strip():
keys.add(k)
keys |= {"Apps", "Built-in", "Languages"} # TR(kStoreTabs[t])
# INDIRECT keys: TR(kSomeTable[i]) / TR(tbl[i].field). The regex above only sees
# TR("literal"), so every string reached through a table looked unused — including
# "About", "Backups" and every other settings-category name. Anyone pruning the
# language files on that output would have deleted live translations, which is
# exactly what a translator asked about before doing it (#262).
#
# So: find the table names that appear inside a TR(...) subscript, then pull every
# string literal out of each table's initialiser. Deliberately greedy — over-
# collecting keeps a translation alive, under-collecting deletes one.
for f in glob.glob(os.path.join(ROOT, 'src/ui-touch/*.cpp')) + \
glob.glob(os.path.join(ROOT, 'src/ui-touch/*.h')):
src = open(f, encoding='utf-8').read()
for name in set(re.findall(r'TR\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\[', src)):
for m in re.finditer(r'\b' + re.escape(name) + r'\s*\[[^\]]*\]\s*=\s*\{', src):
i, depth = m.end(), 1
while i < len(src) and depth:
if src[i] == '{': depth += 1
elif src[i] == '}': depth -= 1
i += 1
for lit in re.findall(r'"((?:[^"\\]|\\.)*)"', src[m.end():i]):
k = unescape_c(lit)
if k.strip():
keys.add(k)
return keys
# Known untranslatable / identical-everywhere keys.
@@ -63,6 +85,28 @@ def lang_keys(path):
def main():
keys = source_keys() - SKIP
# --obsolete: the REVERSE check. Rows in the .lang files that no TR() key matches
# any more, i.e. translation work that is being carried for strings the firmware
# no longer shows. Reported, never deleted automatically.
#
# Read the caveat before acting on it: this is static analysis. A string reached in
# a way the extractor cannot see reads as unreferenced, and pruning it silently
# deletes a working translation. That is not hypothetical -- before the indirect-
# table support above, "About" and every other settings-category name appeared in
# this list. Treat the output as candidates to CHECK, not a delete list.
if "--obsolete" in sys.argv:
for path in sorted(glob.glob(os.path.join(ROOT, 'deploy/apps/lang/*.lang'))):
have = lang_keys(path)
dead = sorted(k for k in have if k not in keys)
tag = os.path.basename(path)
print(f"{tag}: {len(have)} rows, {len(dead)} unreferenced")
for k in dead:
print(f" {k!r}")
print("\nCandidates only. Grep the source for one before removing it: a string "
"reached indirectly can look unreferenced while being on screen.")
return 0
bad = 0
for path in sorted(glob.glob(os.path.join(ROOT, 'deploy/apps/lang/*.lang'))):
missing = sorted(k for k in keys if k not in lang_keys(path))