feat(shell/lock): show input method indicator on lock screen - #11091
Open
plpycoin wants to merge 2090 commits into
Open
feat(shell/lock): show input method indicator on lock screen#11091plpycoin wants to merge 2090 commits into
plpycoin wants to merge 2090 commits into
Conversation
* Fix Codex usage collector approval policy * Capture codex argv with boundaries in the scanner test The stub joined its arguments with "$*", so the assertion compared one flattened string and could not tell five arguments from fewer containing spaces. Passing "-s read-only" and "-a on-request" as single arguments -- which codex rejects as an unexpected argument -- passed the test. NUL separation and an array comparison keep the boundaries the assertion is about. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex XHigh <noreply@openai.com> --------- Co-authored-by: Omabot <omabot@omarchy.org> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex XHigh <noreply@openai.com>
The install user is no longer in the docker group by default, so a bare `once` cannot reach the Docker socket. Run with `sudo` instead. The script already requires sudo to install the command and enable the service, so we can safely use it for the initial command launch as well.
Chromium managed policy is mandatory for every profile. World-writable dirs let any local uid plant policy, including force-installed extensions. Write goes through the omarchy-browser-policy group at 2775 so theme colour still works without other-write.
The hardened gate only looked at the distribution directory. A regular policies.json planted under the old 777 mode would then be left in place if the directory later looked 755/root.
A world-writable distribution dir failed the hardened check even when policies.json was already root-owned, and setup then overwrote it.
install -d follows a planted ancestor symlink, and a writable parent can rename the managed leaf aside. chromium.theme is user-installed, so only a 0-255 RGB triple becomes a colour.
install -d follows a managed or distribution symlink and would chmod the target. Unlink those paths first, and treat a dangling symlink as a directory the migration still has to repair.
Managed policy dirs are enterprise trust roots, so they stay 0755 root:root. The menu path takes root for that one write through a sudoers glob of six hex digits, the same shape as omarchy-dns, and falls back to pkexec where the grant is not installed. Drop omarchy-browser-policy; a group member could plant any JSON, not just a colour.
Place a synthetic python3 shim first in PATH so CI catches any regression that resolves REAL_PYTHON through user-managed shims.
The Dell XPS 13 DX13260 drives its two CS35L56 sidecar speaker amplifiers through a quirk that Linux only gains in 7.2, so until Arch ships that kernel the machine plays through one amplifier with no bass. The dell-xps13-sidecar-amps package selects the same driver path with a module override; this installs it on that exact machine and nowhere else. The detector requires both the DX13260 product name and SKU 0E53, because the override forces a quirk value rather than merging into one, and a machine that gets it wrong loses whatever quirk the kernel would have chosen for itself. Pacman registers a package even when its post_install scriptlet fails, so the leaf calls dell-xps13-sidecar-amps-apply itself instead of trusting the install to have applied: a failed cleanup or boot-image rebuild has to reach the caller rather than hide behind a package pacman considers installed. That is also why the migration marks reboot-required only after the apply succeeds — a migration that exits non-zero keeps no completion marker and retries the apply on the next run, even though pacman already has the package. The leaf runs after intel/ptl-kernel.sh rather than beside the other Dell leaf at the top of install/hardware/all.sh, so its boot-image rebuild sees the Panther Lake kernel that step swaps in rather than the stock one it removes. Co-authored-by: Codex XHigh <codex@openai.com>
…acom#8194) The wildcard granted passwordless root for timedatectl set-timezone plus any trailing arguments, so -H/--host and -M/--machine reached the SSH and machine transports as root. Systemd 261 guards argv injection into ssh, but -H still drives root's SSH client at an attacker-chosen host, and the transport resolves its helper through PATH; only Defaults secure_path stands between that and a planted ssh running as root. Match the argument with an anchored POSIX ERE that admits exactly one timezone token (no whitespace, no leading-dash segment, no traversal component), so no second argument and no option can ever match. The sole caller, omarchy-menu-timezone, passes one list-timezones value and is unaffected. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Codex XHigh <noreply@openai.com>
…tation in notifications.
QQuickStyledText skips the characters between `<` and the tag name with QChar::isSpace(), which counts U+0085 NEL. JavaScript's `\s` does not, so isImageTag() read no name at all from a tag written as `<`, U+0085, `img`, kept it, and Qt then read `img` and issued the GET the stripper exists to prevent. Measured against Qt 6.11.2 with an offscreen StyledText and a local HTTP server. Read the name by skipping everything that is not part of it rather than by matching the separator, so the two definitions cannot drift apart again. Over-skipping is the safe direction: it can only classify more runs as images, and dropping a run never manufactures a tag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…k picker returns A theme installed from a git repo is named after the repo URL, and that name becomes its directory name under ~/.config/omarchy/themes. Style > Unlock built a command line out of the name the picker returned and handed it to omarchy-launch-floating-terminal-with-presentation, which runs its argument as a shell string -- so a theme directory called `a';id;'b` ran `id`. Themes are already held to contributing colour and nothing that executes, which is why omarchy-theme-set stages no .lua, terminal config, or vscode.json from one. Hold the derived name to the characters a theme name needs, which stops it from being dangerous at every place it lands rather than at the one found, and quote it with printf %q on the way into the action for the names already on disk. omarchy-theme-remove keeps its existing path-climb guard: its name reaches only a quoted rm, and the same charset would strand a theme installed before this. Reported-by: Luis Alvarez (lalvarezt) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011WFcUm5HWFyxaVYdwAeWPP
The root rule matched only a file-level root Text, of which this tree has
exactly one. QML inline components are roots for the same reason — the
`text` of `component InfoValue: Text {` comes from every caller, so the
file it lives in never binds it — but they sit inside another element, so
the depth-1 test never saw them. Six went uncovered while the test
reported green, among them the network panel's InfoValue, which callers
bind to the IP address and gateway.
Six more ways to write a Text were read as clean rather than as unreadable:
an opening brace that is not last on its line, a brace on the line after
`Text`, a one-line block containing nested braces, a wrapped binding split
by a comment or a blank line before its `+` (which exempted a dynamic
binding as a literal), and a root Text indented from column zero. Require
the forms a line scanner can read instead of parsing QML; the tree already
writes every Text that way.
Last, a run that read no files reported success. A checkout with no shell/
QML now fails instead, since an all-clear from a scan that opened nothing
is the one answer this test must never give.
Each case is covered by a fixture that fails without its fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: OpenAI Codex (gpt-5, xhigh) <noreply@openai.com>
Hermes joins Install > AI as a desktop app, sits beside it under Remove > AI, and becomes a choice in the default-agent list. The CLI installs through omarchy-install-hermes-cli rather than a bare `mise use`, so its interpreter is pinned before mise builds it. Rebased onto quattro. Ori claimed U+E909 in omacom#7709 while this branch was open, so the Hermes mark moves to U+E90A in the icon font, the menu entries, the font README, and the charset the menu test pins. The glyph outline itself is unchanged; it is spliced in beside Ori rather than over it. Co-Authored-By: witcheer <witcheer.eth@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SySdB3RtCA8BNv6Am246BP
- omarchy-webapp-install wrote all substituted values raw into the generated .desktop (Name/Comment/Icon/MimeType/Exec), with no escaping. - Adds two spec-level escapers: Desktop Entry string escaping on every field (a raw newline could inject a second key line / second Exec=), and Exec-argument quoting for the default Exec's URL (spaces, %, reserved chars). $CUSTOM_EXEC stays file-syntax-only — it is a full command line by design, not a single value. - No known exploit path: untrusted input reaches these values today only via Omarchy literals, interactive gum, or direct CLI. This is defense-in-depth for a latent sink. - Verified end-to-end: generated .desktop has one escaped key per field; gio launch passes the URL to omarchy-launch-webapp as a single unchanged argument (Sunshine literal and normal gum path both byte-identical).
… the set The name a theme installs under is derived from its repo URL, and holding it to an allowlist made that allowlist a naming convention nobody had written down. It was also tighter than the harm it exists to stop: `+` is not shell syntax and a leading `_` is neither the `..` climb nor the dash basename reads as an option, so `omarchy-c++-theme` was refused for nothing. Widen the set to those two and say what it is where a theme author is already picking a name. The leading character stays out of `.` and `-`, which is the part that does the work. Reported-by: Luis Alvarez (lalvarezt) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fd3RCHxwjEbMXoSYB9Aiso
Chromium --app= will run javascript:, file:, and data: URLs. Prefix schemeless input with https as before, then refuse anything else.
…-install Harden Hermes wrapper ownership
A crash that is understood is not a crash that stops: an upstream bug waiting on a release, a program that dumps core every time it exits. The diagnosis explains it once and the toast keeps arriving, and the only answer Omarchy had was Crash Capture, which turns off every program's notifications in order to silence one. The watcher already resolves a name to dedupe on and announces that same name in the toast, so the mute is keyed on it: a flag file under toggles/crash-ignore/, written by the existing omarchy-toggle and read by the existing omarchy-toggle-enabled. One flag per name rather than one list, so `on` mutes, `off` un-mutes, and `ls -A` shows what is muted, with no new file format and nothing to parse. It is the executable's basename wherever one was recorded, falling back to the process name, which the kernel truncates to fifteen characters -- muting the truncated form would match nothing, forever, while looking like it worked. The name is not always a name, though, and the mute turns it into a path. A program picks its own comm and prctl takes anything, including slashes, and the watcher falls back to comm whenever a crash carries no absolute executable. So it is stripped to its last component first: without that, `a/../bar-off` is a legal comm aimed at an unrelated Omarchy flag, letting a crashing program suppress its own notification and letting a user who accepted the offered mute hide their bar instead. Stripping does not always leave a component either -- `/` leaves an empty string, which is no kind of array subscript and no kind of toast, and `.` or `..` names a directory that omarchy-toggle would touch and report success on, leaving a mute that never matches. Both fall back to `unknown`, the word omarchy-agent-crash already uses for a name it does not have, and which mutes like any other. The skill offers this at the end of a diagnosis and never runs it unprompted, which makes it the single change a diagnosis may make to a system it otherwise only reads. It tells the agent to use the name it was handed rather than re-derive one, since the watcher resolved that name already and the two agree for ordinary names and not for strange ones; a diagnosis started by hand from `omarchy agent crash <pid>` is given no name and gets the derivation instead. It also says to treat the name as hostile text rather than as a word -- it is whatever the crashed program's author called a file, so a single quote inside one closes the quotes around it and the rest runs as the shell -- and to check the flag arrived rather than assume it. Co-Authored-By: Codex XHigh <noreply@openai.com>
The manual had single quotes covering "punctuation your shell would otherwise read as its own", which is more than they do: a name containing a single quote closes them, and the rest of it is read as shell. The skill states that correctly and the manual did not, so the one document a person reads before typing the command was the one making the claim that does not hold.
install/user/mise.sh is sourced through run_logged under `bash -eE`, and its status reaches omarchy-provision-user's `set -euo pipefail`. Every other line in the file writes a mise stub and cannot fail; omarchy-install-hermes-cli can, and does whenever hermes-desktop is installed but the app has not been launched yet -- what a second user on a shared machine meets on their first login. The rest of provisioning runs after that source: refreshing applications, the default browser, the mailto handler, the first-install migration markers and the finalize-user marker. Without the marker the whole step retries and fails again at every login, and omarchy-provision-first-run calls it with `|| true`, so nothing surfaces. omarchy-install-ai-hermes and the migration already guard this call the same way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rojects The stub exports UV_PYTHON so mise builds Hermes against 3.13, which Hermes requires and Arch's Python is past. Exported, it survived the exec into Hermes itself and reached every command the agent shells out to. Hermes is a coding agent that runs commands in the user's own repositories, so a `uv venv` or `uv sync` there resolved 3.13 as well: on a project declaring requires-python >=3.14, uv warns that the interpreter contradicts it and builds the venv anyway. Dropping it at the handover keeps the pin over the install, where it belongs. mise x resolves the tool it already installed without it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex XHigh <noreply@openai.com>
Quote mise-install arguments and refuse unusable command names
Use the packaged upstream installer and matching prebuilt app to prepare the writable user installation before launch. Preserve existing builds and modified sources; record the upstream build stamp only for a matching prebuilt app. Co-Authored-By: GPT-6 Codex (xhigh) <noreply@openai.com>
Align main with the packaged release and fetch connected history so the updater detects and rebuilds the first update. Guard local branch work before upstream installation, force only the admitted incomplete release pin, preserve existing command files, and explain incompatible packages. Exercise empty-directory publication races and delayed launches. Co-Authored-By: GPT-6 Codex (xhigh) <noreply@openai.com>
An open terminal can retain deleted SQLite WAL files across removal and reinstall, causing the updated desktop to refuse session writes. Check user processes before package/runtime removal and again after data confirmation, without killing sessions. Cover the failure with real SQLite writers in isolated fixtures. Co-Authored-By: GPT-6 Codex (xhigh) <noreply@openai.com>
Keep the package launcher visible when Hermes builds register a second desktop entry. Use the existing launcher hide list so in-app updates cannot restore the duplicate menu row.
…p-updates Enable Hermes in-app updates and guard removal
T3 Code reads themes an environment publishes into its state directory, so a generated t3code.json carries the palette the same way claude.json and vscode-theme.json already do, and omarchy-theme-set-t3code republishes it on every theme change for the app to pick up live. The template overrides only the roles a theme actually designs -- terminal and code surfaces, text on canvas, borders, selection -- and leaves muted text, placeholders, and status foregrounds to T3 Code, which contrast-solves them against whatever canvas it is given. Overriding those directly cost readability on light themes: White fell to 1.0:1 on placeholder text. Installing from the menu now routes through omarchy-install-ai-t3-code so a fresh install publishes the current palette and opens wearing it.
Keep mise upgrades from pruning versions still in use
Add theme support for T3 Code
) Hiding the top bar and removing the window gaps are the two things you do to give the screen entirely to your windows, and doing both took two hands and two hotkeys. `omarchy toggle fullscreen desktop` does them together. It only leaves full screen when both halves are in it, so hitting the hotkey with just the bar hidden (or just the gaps gone) pulls the other half into line instead of flipping the one you already set. Claude-Session: https://claude.ai/code/session_01JB9phxP56gnP7qSidkkUJE Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Clicking the keyboard layout widget switches one device, chosen by filtering the seat through UNTYPED_KEYBOARDS and then taking whichever survivor sits furthest through the layout list. That filter has to recognise every non-keyboard by name, and a laptop registers far more as a keyboard than it lists. On a Dell XPS 14, Hyprland reports ten keyboards and one of them is a keyboard. The filter catches three of the other nine, leaving vendor hotkey blocks (intel-hid-events, intel-hid-5-button-array, dell-privacy-driver, dell-wmi-hotkeys) and two HID endpoints ahead of at-translated-set-2-keyboard, which sorts last. Every click switches hid-sdw:...-consumer-control instead, so the label cycles convincingly while typing never changes. Device order is stable across polls, so it is deterministic rather than a race, and needs no pre-existing bad state. Switch every keyboard holding the same layout list instead, naming an absolute index. "next" advances each device from wherever it sits, so a seat that has already drifted apart stays drifted and merely inverts; one index converges it in a single click, and a seat in lockstep leaves the reading nothing to disagree about. Keyboards given their own kb_layout hold a different list and are left alone, since an index into this list would not mean the same layout to them. The evdev KEY bitmap would separate these cleanly - the real keyboard emits 167 keys, the pseudo-devices at most 19 - but hyprctl devices reports no capability information, so the switch is taken out from behind the name filter rather than the filter being lengthened. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1Password reads the display scale itself, the way Electron apps do, so on a scaled monitor it comes up oversized next to every other window. Pin it with --force-device-scale-factor=1 and let the compositor scale it. The app menu is covered by the packaged .desktop, which we build ourselves in omarchy-pkgs. This is the other route in: the hotkey runs the binary directly and never reads that file. Claude-Session: https://claude.ai/code/session_01JB9phxP56gnP7qSidkkUJE Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ches-wrong-device Stop the layout widget switching a device nobody types on
Arch removed the prebuilt broadcom-wl package on 2026-09-02 and rebuilt broadcom-wl-dkms with replaces=(broadcom-wl). A replaces entry only helps upgrades of an already-installed package, so the name is now unresolvable as an explicit pacman target and the ISO offline mirror download fails with "target not found: broadcom-wl". broadcom-wl-dkms depends on dkms, so the explicit dkms in the hardware fix is redundant; linux-headers stays because the module has to build against the running kernel.
Install broadcom-wl-dkms now that Arch dropped the prebuilt module
Keep Cursor bundled Node off the mise PATH
Retire the stock user icon font missed by Quattro upgrades
* Relay the Elgato Cam Link 4K as a 16:9 virtual camera Browser meeting apps such as Zoom's web client ask the Cam Link for a standard-definition stream, and Chromium settles on the smallest mode it offers, 640x480. The Cam Link fills that 4:3 frame by cropping its 16:9 input, and the app then paints the frame into a 16:9 tile, so everyone comes out stretched wide. The web client has no HD switch to avoid it. Hide the raw capture node from users and re-expose it through v4l2-relayd as a 1280x720 virtual camera with the same name, so there is still just one "Cam Link 4K" to pick and no way to negotiate 4:3 from it. udev starts the relay whenever the Cam Link enumerates and stops it on unplug, and the relay only pulls frames while something is watching. The sink runs unsynced because v4l2src stamps each frame with its capture time, which a synced sink treats as already late and drops. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Take the review fixes for the Cam Link 4K relay Tie only the device's stop into the relay instance. A start dependency on it left a job waiting on a device that never comes whenever the base v4l2-relayd.service is started without a Cam Link attached, since the package generator wants every configured instance. Let the loopback unit rerun on each relay start, so a deleted or unloaded device is recreated on replug instead of the oneshot staying satisfied. Start the relay outright at the end of the migration. The udev trigger only starts it when the rule is new to the device, and a failed module build would otherwise pass silently with the raw camera already hidden. Run the hardware fix after the Panther Lake kernel swap, as it pulls in a DKMS module that would otherwise build twice. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Thanks to _SiCk // afflicted.sh for independently reproducing the passwordless sudo persistence issue and reviewing the proposed fix in omacom#7990. The follow-up fix landed in omacom#9387 (945af75), with the 4.0.3 backport in cf459ba. The original boot-cleanup patch was contributed by @Adolanium. Preserve that co-author credit alongside this acknowledgment. Co-authored-by: Adolanium <94890352+Adolanium@users.noreply.github.com>
Chainfire (@Chainfire) privately reported the passwordless asdcontrol watchdog reboot on August 25, 2026. The fix landed in the 4.0.2 security work: omacom#9200 (df819a6) removed Omarchy's unrestricted sudo grant, while omacom/omarchy-pkgs#242 and omacom/omarchy-pkgs#243 restricted the package's grant. This records the reporter credit omitted from those changes. Reported-by: Chainfire (https://github.com/Chainfire)
Lots of great discussions, but it's for Omarchy Cinque, if it happens. This repo is for Quattro for now.
Mirrors the hey-cli stub: the wrapper in ~/.local/bin installs and upgrades through mise on first run, so the CLI tracks releases instead of going stale as a manually dropped binary. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…import Fix Hermes desktop installation and align menu icons
The lock UI shows a fingerprint icon when a sensor is enrolled but no indicator for the active fcitx5 input method, so users whose default IM is anything other than a bare keyboard layout (for example pinyin) have no way to tell why the password field is not accepting Latin keystrokes as-is. They keep typing, the field ignores their input, and the lock screen reports a failed authentication. Poll `fcitx5-remote -n` while the lock UI is on screen and render a short label (known IMs map to a script-native glyph, unknown IMs fall back to the first four letters of the IM name) on the left edge of the field, mirroring the fingerprint icon's position on the right. The password input reserves space on both sides for the indicator so the masked dots stay centered and never slide under it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The Omarchy shell lock UI shows a fingerprint icon when a sensor is enrolled, but no indicator for the active fcitx5 input method. Users whose default IM is anything other than a bare keyboard layout (
pinyin,anthy,hangul, ...) have no signal that the password field is not in Latin mode. They keep typing, the field ignores their input (or routes keystrokes through the IM's preedit), and the lock screen reports a failed authentication.Steps to reproduce:
fcitx5-pinyinfromomarchy-pkg-add fcitx5-pinyin)omarchy system lockExpected: a visible cue on the lock screen that the field is not in keyboard-only mode.
Actual: the password field looks identical to a normal unlock; the user has no way to know why typing fails.
Fix
Add an
imeIndicatorTextelement toLockView.qmlthat pollsfcitx5-remote -nevery 750ms while the lock UI is on screen and renders a short label on the left edge of the password field, mirroring the fingerprint icon on the right.pinyin/libpinyin→中anthy→あhangul→한rime→默keyboard-→ hidden (Latin mode)The
Process/StdioCollectorpoll only runs whileinputEnabledis true (i.e. while the lock UI is active), so there is no overhead when the user is working in an unlocked session.The password input's left/right margins reserve space for the indicator so the masked dots stay centered and never slide under it, matching the existing pattern used for
fingerprintReserve.Tests
./test/shellpasses for every lock-related fixture (lock-fingerprint-indicator-test.sh,lock-password-overflow-test.sh,qml-text-format-test.sh). The four unrelated pre-existing failures (config-test.sh,locate-test.sh,snapper-test.sh,unowned-system-paths-test.sh) reproduce on the base branch and are caused by missingOMARCHY_PKGS_PATH/ non-executable test scripts in this checkout.Visual verification: requires running
omarchy-restart-shelland locking the session in a real install with a non-default IM active. Captures should show the new indicator on the left edge of the password field.Out of scope
A complementary change that resets the fcitx5 IM back to
keyboard-usfromomarchy-system-lock(matching the existinghyprctl switchxkblayout all 0reset) would prevent the lockout entirely, but is intrusive for users who intentionally want non-Latin input on the lock screen. Keeping it as a separate proposal.