From 75f869c094876ad397f9c4d6c10388570924301c Mon Sep 17 00:00:00 2001 From: Jonas Haugesen Date: Thu, 10 Sep 2026 11:59:00 +0200 Subject: [PATCH] Add notes for this fork (type-to-search design, build, release, install) --- AGENTS.md | 1 + CLAUDE.md | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 120000 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 000000000..681311eb9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..acd065b50 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,87 @@ +# Kvaesitso (fork with type-to-search) + +Fork of [Kvaesitso](https://github.com/MM2-0/Kvaesitso) that adds type-to-search for phones with a +physical keyboard. Built for, and installed on, a Unihertz Titan 2 Elite. + + origin = https://gitea.haugesenspil.dk/jonas/Kvaesitso (our fork — push here) + upstream = https://github.com/MM2-0/Kvaesitso (pull upstream changes from here) + +The phone also has this repository checked out in its Termux home: `~/sources/Kvaesitso`. + +## The feature + +Pressing a printable key on the physical keyboard while the launcher home screen is shown opens the +search page and inserts the character. Backspace deletes, enter launches the highlighted result. +Only the home screen (and the open search page) reacts; the widgets, notifications and recents +pages do not. Setting: *Settings → Search → "Search on typing"* (on by default, stored as +`searchOnTyping`). + +## How it works, and why it is built this way + +`SharedLauncherActivity.dispatchKeyEvent` is the only place that sees hardware key events while no +view has focus, so the handler is installed there (`launcherKeyEventHandler`, registered by +`LauncherScaffold` while it is shown). The handler itself is +`app/ui/.../ui/launcher/searchbar/TypeToSearchHandler.kt`. + +Two constraints shape the design: + +1. **The search bar's text field stays unfocused while the keyboard drives the search.** A focused + Compose text field makes the system show the on-screen keyboard. There is no public API to + prevent that: `View.setShowSoftInputOnFocus` is a hidden API (`TextView`'s version is public) and + reflection fails with `NoSuchMethodException`, and hiding the IME reactively makes it flash. + Leaving the field unfocused keeps the IME away completely — which is why the handler inserts and + deletes the characters itself instead of relying on the text field. +2. **No key may be lost.** Characters can arrive before the text field would have focus, so the + handler keeps handling keys until the field reports real focus + (`LauncherScaffoldState.isSearchBarTextFieldFocused`). `LauncherScaffoldState.isTypingSearch` + marks a keyboard-driven search: while it is set, `SearchComponent.onPreActivate` does not focus + the field and the handler owns the keys, including backspace and enter. Tapping the search bar + clears the flag and hands over to the normal editable field. + +`SearchBar` keeps a `TextFieldValue` instead of a `String` so that changing the text from the +outside moves the cursor to the end. Without that, the next typed character landed in front of the +text. + +The search is also cleared whenever the launcher is left, even for a short app switch — upstream +keeps it for returns within five seconds, which leaves a stale query on screen. + +## Building + + JAVA_HOME=~/jdk21 ANDROID_HOME=~/android-sdk \ + ./gradlew :app:app:assembleDefaultRelease + +Result: `app/app/build/outputs/apk/default/release/app-default-release.apk`, applicationId +`de.mm20.launcher2.release`. + +The `release` build type signs with the `local` signing config, which reads `keystore.properties` +in the repository root (git-ignored) or the `KEYSTORE_FILE`, `KEYSTORE_PASSWORD`, `KEY_ALIAS` and +`KEY_PASSWORD` environment variables. The desktop key is +`~/android-keystores/kvaesitso-release.jks`; the phone has its own copy under +`~/android-keystores/` in the Termux home. Termux itself has no Android SDK, so build there only +after installing one (or build on the desktop and copy the APK over). + +## Releasing (the phone updates through Obtanium) + +1. Raise `versionCode` above the installed one and set a new `versionName` in + `app/app/build.gradle.kts` (`versionName` may keep the upstream version, e.g. `1.40.2-typing.2`). +2. Build the release APK (see above) and check the signature: `apksigner verify --verbose`. +3. Tag it and push `main` and the tag to `origin` (Gitea needs a token: the desktop keeps it in + `~/.config/gitea/token`). +4. Create a Gitea release for that tag and attach the APK as `Kvaesitso-v-Signed.apk`. + The repository is public so Obtanium can read it anonymously. +5. The phone's Obtanium entry for `de.mm20.launcher2.release` points at this Gitea repo with + `overrideSource: "Codeberg"`, `versionDetection: true` and `apkFilterRegEx: "Kvaesitso-v"`. It + picks the release up on the next check. The config file lives on the phone at + `/sdcard/Android/data/dev.imranr.obtainium.fdroid/files/app_data/de.mm20.launcher2.release.json` + (force-stop Obtanium before editing it as root). + +## Installing on the phone by hand + +The phone already runs this build. The official build is signed with the upstream key, so it cannot +be updated in place and Obtanium cannot install over it either. The procedure is: back up +`/data/user/0/de.mm20.launcher2.release` with root, `pm uninstall` (a `-k` uninstall keeps the +package record *and* its signature, so the next install then fails with +`INSTALL_FAILED_UPDATE_INCOMPATIBLE`), install this APK, then copy the data back — `chown` to the +new app uid and `chcon` the label that installd created for the fresh data directory. The rest of +the phone (bootloader, Magisk, microG, rescue steps) is documented in `~/titan2-elite/CLAUDE.md` on +the desktop.