diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-10-21 03:55:10 +0200 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-10-21 03:55:10 +0200 |
| commit | 5e8ac1e2f5a660e55ea132996e1d5730cfa4fa64 (patch) | |
| tree | 9bb99bdca1ef8f33c93c5100816448681329d810 /source/settings | |
| parent | 88f1cac4fa530fc30fd6ef01970af11c7a959b8e (diff) | |
feat(glance): show the favourite code in the glance (opt-in)
With "Show the favourite code in the glance" on, the glance shows the
code of the first favourite TOTP or Steam entry without the PIN, with a
countdown bar. The glance can't open the vault, so the About scan seals
that entry under "g" (device key, aad OTPW-GL-1) after every change, and
removes it when the option is off or no entry qualifies; the glance also
reads the option itself. HKDF moves to its own module so the glance loads
only the crypto it needs (37 % of its 64 KB). Manage entry gains "Mark as
favourite (glance)" and "Remove favourite", queued for locked entries
like the other changes. Lines are inset on round screens so the
Instinct 3's curved glance corner doesn't cut them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'source/settings')
| -rw-r--r-- | source/settings/AboutScan.mc | 20 | ||||
| -rw-r--r-- | source/settings/ManageForm.mc | 25 |
2 files changed, 37 insertions, 8 deletions
diff --git a/source/settings/AboutScan.mc b/source/settings/AboutScan.mc index 8fc898a..559cce9 100644 --- a/source/settings/AboutScan.mc +++ b/source/settings/AboutScan.mc @@ -1,5 +1,5 @@ // Author: cirakg & claude ai -// Summary: Writes the read-only About fields (catalog, version, storage use) and applies "Encrypt names" to existing entries. +// Summary: Writes the read-only About fields (catalog, version, storage use), applies "Encrypt names" to existing entries and updates the glance code. // // One pass over every entry header, a few per step: // * catalog: "K7F2 GitHub (alice) · 9QXR Gym card · ...", so the Manage @@ -11,8 +11,11 @@ // "Encrypt names" option are re-sealed, one per step. Locked ones need // the session; if one can't be done now, the next scan retries (after // the next PIN entry, which runs the processor again). +// * The glance code (GlanceCode.mc): the first favourite TOTP or Steam +// entry without the PIN is sealed for the glance, or the glance item +// removed when there is none or the option is off. // The scan only runs when something it reports changed: a form was -// applied, or the signature of the entry list (IDs, pending, option, +// applied, or the signature of the entry list (IDs, pending, options, // version) differs from the last complete scan. import Toybox.Lang; @@ -35,6 +38,7 @@ class AboutScan extends Jobs.Job { private var _left as Number = 0; private var _bytes as Number = 0; private var _incomplete as Boolean = false; + private var _glanceId as String? = null; function initialize(vault as Vault, store as Forms.Store) { Job.initialize(); @@ -55,6 +59,7 @@ class AboutScan extends Jobs.Job { content.add(vault.ids().toString()); content.add(vault.pendingIds().toString()); content.add(vault.encryptNames); + content.add(vault.glanceCode); content.add(ChangeQueue.count(vault)); content.add(AppInfo.VERSION); return Forms.hash(vault, content); @@ -102,6 +107,9 @@ class AboutScan extends Jobs.Job { } } addItem(id + " " + describe(header) + (header.locked ? " (PIN)" : "")); + if (_glanceId == null && GlanceStore.qualifies(header)) { + _glanceId = id; + } return converted; } @@ -156,6 +164,14 @@ class AboutScan extends Jobs.Job { setIfChanged("catalog", catalog); setIfChanged("storageUse", use); setIfChanged("version", AppInfo.VERSION); + try { + GlanceStore.update(_vault, _glanceId); + } catch (e instanceof Lang.Exception) { + // Damaged entry or a full store: no glance code, and the scan + // runs again next time. + _vault.storage().deleteValue(GlanceCode.KEY); + _incomplete = true; + } if (!_incomplete) { try { _vault.storage().setValue(SIGNATURE_KEY, signature(_vault)); diff --git a/source/settings/ManageForm.mc b/source/settings/ManageForm.mc index b92c40d..a075880 100644 --- a/source/settings/ManageForm.mc +++ b/source/settings/ManageForm.mc @@ -6,8 +6,8 @@ // * ManageForm.read(): the mg* fields to a ManageAction or a problem. // * ManageRun: a job that checks the action against the entry and then // applies it, or queues it (ChangeQueue) when it touches a locked -// entry: edit, replace, counter, "Don't require PIN" and delete wait -// until the PIN is entered on the watch. "Require PIN" waits too while +// entry: edit, replace, counter, "Don't require PIN", the favourite +// mark and delete wait until the PIN is entered on the watch. "Require PIN" waits too while // the watch is locked (it needs the master key). Moving never waits: // the order isn't protected and a position is absolute, so applying a // move twice can't hit another entry. @@ -28,8 +28,11 @@ class ManageAction { static const REPLACE = 4; static const COUNTER = 5; static const DELETE = 6; + static const FAVORITE = 7; + static const UNFAVORITE = 8; - static const NAMES = ["Edit details", "Move", "Require PIN", "Don't require PIN", "Replace contents", "Set counter", "Delete"]; + static const NAMES = ["Edit details", "Move", "Require PIN", "Don't require PIN", "Replace contents", "Set counter", "Delete", + "Mark as favourite", "Remove favourite"]; // Codec tags of the queued form. static const T_ID = 1; @@ -114,7 +117,7 @@ class ManageAction { if (!(id instanceof String) || !(action instanceof Long) || !(position instanceof Long) || !(counter instanceof Long)) { return null; } - if (action < EDIT || action > DELETE || !Entry.isValidId(id)) { + if (action < EDIT || action > UNFAVORITE || !Entry.isValidId(id)) { return null; } a.id = id; @@ -173,7 +176,7 @@ module ManageForm { return "Entry ID: '" + Text.clip(a.id, 8) + "' is not an ID. IDs have 4 characters, like K7F2"; } var action = Forms.number(store, "mgAction"); - if (action == null || action < ManageAction.EDIT || action > ManageAction.DELETE) { + if (action == null || action < ManageAction.EDIT || action > ManageAction.UNFAVORITE) { return "Action: choose one from the list"; } a.action = action; @@ -334,6 +337,12 @@ class ManageRun extends Jobs.Job { if (a.action == ManageAction.UNLOCK && !header.locked) { return a.id + " doesn't require the PIN"; } + if (a.action == ManageAction.FAVORITE && header.favorite) { + return a.id + " is already a favourite"; + } + if (a.action == ManageAction.UNFAVORITE && !header.favorite) { + return a.id + " is not a favourite"; + } if (a.action == ManageAction.REPLACE && isOtp && !a.decoded) { var raw = a.material as ByteArray; var text = Bytes.toUtf8(raw); @@ -435,7 +444,8 @@ class ManageRun extends Jobs.Job { return true; } - // Edit, lock, unlock or replace: open, change, re-seal. Null or the problem. + // Edit, lock, unlock, (un)favourite or replace: open, change, re-seal. + // Null or the problem. private function changeEntry() as String? { var a = _a; var target = a.id + (a.label.length() > 0 ? " " + a.label : ""); @@ -453,6 +463,9 @@ class ManageRun extends Jobs.Job { } else if (a.action == ManageAction.LOCK || a.action == ManageAction.UNLOCK) { entry.locked = a.action == ManageAction.LOCK; message = target + (entry.locked ? " now requires the PIN" : " no longer requires the PIN"); + } else if (a.action == ManageAction.FAVORITE || a.action == ManageAction.UNFAVORITE) { + entry.favorite = a.action == ManageAction.FAVORITE; + message = target + (entry.favorite ? " is now a favourite" : " is no longer a favourite"); } else { var material = a.material as ByteArray; if (entry.kind == Entry.KIND_OTP) { |
