Compare commits

...
Author SHA1 Message Date
ZacharyZhang-NY d4551e2767 Check off T31 after one codex round 2026-10-12 00:10:56 -04:00
ZacharyZhang-NY f2d69265d6 Check off T30 after one codex round 2026-10-11 23:54:39 -04:00
ZacharyZhang-NY 375ff9036d Check off T29 after one codex round 2026-10-11 23:50:38 -04:00
ZacharyZhang-NY 6a221de8b3 Plan: local test ISOs no longer pin their builds 2026-10-11 23:49:08 -04:00
ZacharyZhang-NY 70fc7a9e2d Check off T28 after two codex rounds 2026-10-11 23:48:42 -04:00
ZacharyZhang-NY ae065afb66 Drop Claude Code API from the plan's shipping notes 2026-10-11 23:48:00 -04:00
ZacharyZhang-NY f2af41bbc9 Drop the Claude Code API setup, Dim and Kimi, and the pkg-add change 2026-10-11 23:45:12 -04:00
ZacharyZhang-NY dd704a29cd Plan T28–T36 after the maintainers' ISO test 2026-10-11 23:43:59 -04:00
ZacharyZhang-NY 3c6e7cca55 Merge remote-tracking branch 'origin/omarchycn' into omarchycn 2026-10-09 11:47:54 -04:00
ZacharyZhang-NY 02fb6439eb Check off T27 after two codex rounds 2026-10-09 02:22:04 -04:00
ZacharyZhang-NY c8ec83d046 Correct the acceptance checklist: run B timezone, test failure scope, OCR misses, trap condition 2026-10-09 02:21:17 -04:00
ZacharyZhang-NY 8e1f9266ca Check off T26; acceptance checklist for the language and timezone-region model 2026-10-09 02:18:33 -04:00
ZacharyZhang-NY fcd681c099 Merge upstream quattro for the Qt 6.12 palette fix (#14553)
Qt 6.12 shadows the bare Color singleton, so the bar and every Color
role read undefined on fresh installs. Keep our I18n text in the four
conflicting files, qualify #14433's notification button color, give the
new dictation menu rows and notifications Chinese, and drop table
entries for the removed Voxtype and FIDO2 texts.
2026-10-09 01:24:30 -04:00
ZacharyZhang-NY 76b9331d8b Escape commas in the keyboard preselect; gum splits --selected on them 2026-10-09 00:52:26 -04:00
ZacharyZhang-NY c08a1bc3c2 Check off T25 after two codex rounds 2026-10-09 00:25:49 -04:00
ZacharyZhang-NY 2d2aa337e4 Say which mirror files region.sh links and that Global only hides China Apps 2026-10-09 00:25:23 -04:00
ZacharyZhang-NY 738d59e3a9 Describe language and timezone-derived region in the manual and docs 2026-10-09 00:21:33 -04:00
ZacharyZhang-NY 7198a7b711 Check off T24 after two codex rounds 2026-10-09 00:20:21 -04:00
ZacharyZhang-NY e5b01ace6b Ask for the language, not the region, on the installer's first screen
omarchy_prompt_keyboard takes the layout to preselect.
2026-10-09 00:15:06 -04:00
ZacharyZhang-NY 848e9610c0 Check off T23 after two codex rounds 2026-10-09 00:10:00 -04:00
ZacharyZhang-NY 95d4e57db9 Move the region screen's replacement to T24 2026-10-08 23:51:30 -04:00
ZacharyZhang-NY 6da63c8942 Check off T22 after one codex round 2026-10-08 23:50:24 -04:00
ZacharyZhang-NY 562684f9fa Check off T21 after two codex rounds 2026-10-08 23:47:57 -04:00
ZacharyZhang-NY b914d694f2 Read a quoted LANG and append LANG on its own line in omarchy-language 2026-10-08 23:46:59 -04:00
ZacharyZhang-NY 199a7f23fc Split language from region: omarchy-language and Setup > Language own LANG
omarchy-region and install/config/region.sh no longer touch the locale;
both logout prompts are worded through omarchy-text.
2026-10-08 23:44:04 -04:00
ZacharyZhang-NY 1824bc3066 Check off T20 after two codex rounds 2026-10-08 23:35:30 -04:00
ZacharyZhang-NY 371fd05f03 Keep #14433's XPS 13 cirrus firmware in the offline package list 2026-10-08 23:34:22 -04:00
ZacharyZhang-NY 557ba1d270 Give the sleep-lock and power tests omarchy-text, which they found only when installed 2026-10-08 23:30:51 -04:00
ZacharyZhang-NY fdd457c0fa Merge #14433 input methods and remove Rime
The keyboard choice and omarchy-setup-input own Chinese input now. The
new passwordless sudo and input tooltips get Chinese text.
2026-10-08 23:30:41 -04:00
ZacharyZhang-NY 8798461461 Check off T19 after two codex rounds 2026-10-08 22:46:11 -04:00
ZacharyZhang-NY 4521dac023 Merge reworked #13690: timezone-derived region and channel mirror prepends
Drop the .cn servers.sed twins, the USTC Arch fallback and the closed
#14399 timezone preselection. region.sh follows the installer's marker.
2026-10-08 22:43:55 -04:00
ZacharyZhang-NY c7402fe653 Add T19-T27: timezone region, dhh's input methods, language split 2026-10-08 22:39:01 -04:00
Emir Beganović c352b62d67 Merge pull request #14637 from omacom/install/node-tarball-by-architecture
Unpack the bundled Node tarball for the machine's architecture
2026-10-09 01:21:27 +02:00
Emir Beganovic 15b3529ef2 Test that each architecture unpacks its own Node tarball
The existing test runs mise-work.sh in the runtime context, which skips
the bundled tarball altogether. This offers both tarballs to an x86_64
and an aarch64 machine and checks that each unpacks its own and hands
Mise that version, and that a machine with only the other architecture's
tarball fails. Against the old script the aarch64 case fails.
2026-10-09 01:13:27 +02:00
Emir Beganovic 057681b33c Unpack the bundled Node tarball for the machine's architecture
An aarch64 ISO bundles node-v*-linux-arm64.tar.gz, and mise-work.sh
looked for node-v*-linux-x64.tar.gz on every machine. An offline install
on aarch64 stopped at its last step: "Error: bundled Node.js tarball
missing from /opt/packages".

The name now follows uname -m. x86_64 looks for the same file as before.
This is the change the n1x branch already carries, from Spencer Bull's
6d022e36.
2026-10-09 01:07:03 +02:00
David Heinemeier Hansson 50d687a1f2 Ship Superwhisper cloud dictation by default (#14625)
* Ship Superwhisper cloud dictation by default

* Rely on Superwhisper cloud defaults without first-run setup
2026-10-08 21:23:50 +02:00
David Heinemeier Hansson 02d28d273a Fix Superwhisper setup with existing Right Alt shortcut 2026-10-08 21:19:48 +02:00
David Heinemeier Hansson 5ad44d62f6 Alphabetical order 2026-10-08 21:00:58 +02:00
David Heinemeier Hansson c24889096e Add selectable Voxtype and Superwhisper dictation (#14552)
* Add selectable dictation backends

* Move dictation migration after repository priority update

* Extract backend-specific dictation installers

* Keep dictation installer dispatcher executable

* Call dictation backend installers directly

* Drop legacy Superwhisper migration and runtime cleanup

* Separate dictation defaults from generic recording protocol

* Add backend-specific dictation removers

* Preserve existing Voxtype selection during upgrade

* Make dictation backend selection safe and retryable

* Show the dictation indicator only for selected Voxtype

* Allow switching away from stopped Superwhisper

* Remove the built-in bar dictation indicator

* Store default dictation selection under defaults

* Rename dictation backend selector to use

* Move dictation removers into their own menu category
2026-10-08 20:37:26 +02:00
David Heinemeier Hansson 1554a522cc Update packages after prioritizing OPR 2026-10-08 10:40:56 -04:00
David Heinemeier Hansson 717a0e9c50 Keep OPR priority migration independent of Quickshell 2026-10-08 10:40:56 -04:00
David Heinemeier Hansson 34016cfb27 Prioritize the Omarchy package repository 2026-10-08 10:40:56 -04:00
David Heinemeier Hansson b83d3df084 Qualify shell palette references for Qt 6.12 (#14553) 2026-10-08 10:27:08 +02:00
902fd8aebd Play matching background intros at boot and on theme switches (#9639)
* Play matching theme background intros at boot

* Add Tokyo Night sunset lake boot intro

* Stage Tokyo Night boot intros on update

* Add user controls for background boot intros

* Optimize Tokyo Night boot intro videos

* Add Tokyo Night cityscape boot intro

* Add Ristretto boot intro videos

* Add Solitude boot intro videos

* Add White boot intro videos

* Add Vantablack boot intro videos

* Add Miasma nature boot intro

* Add Matte Black boot intro videos

* Add Lupine wave boot intro videos

* Add Last Horizon blink boot intro

* Add Everforest tree tops boot intro

* Add Ethereal cosmic boot intro

* Add four Retro 82 boot intros

* Add Tokyo Night Quattro boot intro

* Add Catppuccin Latte color fade boot intro

* Add five Gruvbox boot intros

* Expand Retro 82 boot intro coverage

* Complete Ethereal boot intros

* Complete Retro 82 boot intros

* Stage boot intros for the active theme

* Add Catppuccin boot intros

* Complete Tokyo Night boot intros

* Expand Lupine boot intros

* Add Osaka Jade mountain boot intro

* Prime boot intros before revealing wallpaper

* Complete Osaka Jade boot intros

* Keep kernel migration test valid after timestamp reuse

* Shorten packaged boot intros to six seconds

* Revert "Shorten packaged boot intros to six seconds"

This reverts commit 5530403aa9e789b977a1a1a77ea7acf37a9e3598.

* Document five to seven second intro target

* Ease Matte Black intros into six seconds

* Play boot intros through OWE instead of a shell player

The merged desktop video path removes BackgroundVideo.qml and makes
BackgroundMedia image-only. This keeps the intro feature by resolving the
matching intro as before and playing it with 'owe intro', which owns the
decode and the reveal. OWE 0.2.3 adds that command. The shell holds the
theme color while resolving and cancels with 'owe intro-stop'.

* Make boot intro startup atomic

* Retry boot intro through login window

* Document required OWE intro protocol

* Add Last Horizon video boot intros

* Play boot intros with released OWE

Use `owe intro` instead of the unreleased intro-prepare/intro-commit
protocol from omacom/owe#5. The boot is recorded before playback, and
OWE's intro_result status decides whether a failed intro never started
(release the boot for retry) or was interrupted (keep it consumed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Replace Matte Black boot intros with shorter cuts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Hide the still behind black until the boot intro starts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shorten the Everforest boot intro

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Document that boot intros should open dark

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refine the Osaka Jade boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Skip compositor fades on the background layers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shorten the Catppuccin Latte boot intro

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shorten the Catppuccin boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shorten the White boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shorten the Vantablack boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shorten the Gruvbox boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shorten the Retro 82 boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shorten the Solitude boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rework the Ristretto boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shorten the Tokyo Night boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Complete the Lupine boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Complete the Last Horizon boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Complete the Miasma boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add the Nord boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add the Rose Pine boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add the Flexoki Light boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add the Hackerman boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add the Kanagawa boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add the Lumon boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fade in the Last Horizon intros from black

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Animate the Catppuccin boot intros from their wallpapers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF

* Animate the third White boot intro from its wallpaper

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF

* Animate the Flexoki Light, Lumon and Rose Pine plants intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF

* Shorten the Ethereal boot intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF

* Animate the remaining placeholder intros from their wallpapers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF

* Replace Catppuccin intros with themotion scenes

Totoro moonrise, waves swelling from a line, and the blue eye tracing
itself open, rendered by https://github.com/yamz8/themotion
(scenes/catppuccin/*).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF

* Re-encode Catppuccin intros with exact final frames

Same motion; the settled frames are encoded at a fixed high quality so
the handoff lands on the wallpaper (themotion 0c5e86c).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Replace Lumon intros with themotion scenes

United in severance draws its lines and globe with a small pen-tip
light; Opinions equally wakes inside the picture's own screen and types
out the mark and tagline. Both keep the wallpaper's proportions, so the
handoff holds its shape. Made with https://github.com/yamz8/themotion
(scenes/lumon/*).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Land the Last Horizon intros exactly on their wallpapers

Same footage and timing; the last 0.4 s now blends into the wallpaper as
the shell draws it (libwebp decode, BT.709 limited range), with the
settled frames at high quality. Re-finished with themotion fit (09f156e).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add Nord video boot intro for Night Hawks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Replace Rose Pine intros with themotion scenes

Funky shapes pop in on a jelly spring and the doodles draw on (replacing
the reversed stock clip), the dot map spins in from a swelling dot, and
the plants spring up before the OMARCHY letters drop in. Made with
https://github.com/yamz8/themotion (scenes/rose-pine/*).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Replace Hackerman intros with themotion scenes

The synthwave terrain unfolds as the sun rises and the valley glow
flares into the floor; the geometric network bursts out from a triangle
at its core and locks into place. Made with
https://github.com/yamz8/themotion (scenes/hackerman/*).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Replace White 3-white with a themotion scene and land 1-2 exactly

3-white folds and slides its paper sheets into place along their
diagonal (themotion scenes/white/3-white). 1-white and 2-white keep
their footage and timing; their last 0.4 s now blend into the wallpaper
as the shell draws it (themotion fit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Replace Ristretto 0-1 with themotion scenes and land 2-3 exactly

0-launch lifts its stripes off and bends them into their curves, and
1-color-curves fans its colour bands in along their curves (themotion
scenes/ristretto/*). 2-coffee-beans and 3-industrial-moon keep their
footage and timing; their last 0.4 s now blend into the wallpaper as
the shell draws it (themotion fit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Replace Vantablack intros: new layer scenes, shared dot-hands cut

0-dot-hands uses the Matte Black cut, since both themes ship the same
wallpaper. 1-twisted-stairs keeps its footage and lands exactly on the
wallpaper (themotion fit). 2-layers-deep opens its depth front to back
and 3-layers-stacked lays its papercut stack down sheet by sheet
(themotion scenes/vantablack/*), replacing footage that opened on
unrelated shapes and a plain fade-in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Drop the shockwave ring from the Totoro intro

The moon now opens from its seed of light with only its halo; the thin
ring racing outward read as clutter (themotion 6970484).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Drop the shockwave ring from the blue-eye intro

The eye closes with its pulse and halo only (themotion, same change as
Totoro's).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Land the earlier footage intros exactly on their wallpapers

Everforest, Osaka Jade, Catppuccin Latte and two Matte Black intros
were finished before the exact-ending tooling and ended 0.9-4 levels
off their wallpapers (up to 134 on small spots). Their footage and
timing are unchanged; the last 0.4 s now blend into the wallpaper as the
shell draws it (themotion fit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fade two Matte Black intros up from black

ship-at-sea and dark-waters opened at brightness 17-20, so they popped
in after the black cover. They now fade up over 0.3 s; the cuts and
their exact endings are otherwise unchanged (themotion fit --fade-in).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Drop the Flexoki Light intro for now

Its orb scene isn't good enough yet, so Flexoki Light starts on its still
until a better intro is ready.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit bfe60d6c0a7a0983509a2dc2b295693bec2ceeb8)

* Skip boot intros while animations are off

A VM starts with animations off, and there Hyprland renders on the CPU, so a
video intro costs the most. The launcher now consumes the boot without playing
when Hyprland reports animations:enabled false, and the shell's cover check
treats that, like the intros toggle, as nothing to wait for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add Miasma video boot intro for Crowned

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep boot intro startup outside the background service

OWE destroys and recreates the background plugin during playback. Keep the launcher and black cover in the persistent shell host, consume the boot before waiting briefly for OWE, and start on the video's first frame. The startup check runs with a video background too, so enabling the still background service later cannot play an intro over an established desktop.

Co-Authored-By: Codex Medium <codex@openai.com>

* Release the boot marker lock before waiting for OWE

The marker settles startup once it is written. Keeping the lock through readiness and playback makes a fresh shell wait for an older launcher before it can discover that the boot was consumed, leaving the still covered during that wait.

* Keep the boot intro cover above native and cloned backgrounds

Cloned backgrounds receive a scoped shell interface without the startup coordinator. Keep the input-transparent cover in the persistent host so every background stays hidden until OWE has a frame, without widening the plugin interface.

Co-Authored-By: Codex Medium <codex@openai.com>

* Map the startup cover only for an unsettled boot

The cover must know the boot was already consumed before its first frame, so restarting the shell during playback leaves OWE visible. Snapshot the marker without watching subsequent writes: the launcher consumes this boot before the intro starts, while the cover remains needed until the first decoded frame.

* Exercise automatic boot cover release in the clone test

The fixture hid the cover itself before the fake launcher exited, so a broken exit handler still passed. Let the launcher finish before checking the actual background pixels and leave cover release to the service.

Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>

* Finish the Miasma, Kanagawa, Ethereal and Nord intros

Start each intro on its scene instead of fading in from black, since
OWE now starts intros on their first frame.

- Miasma Crowned: ejuro's intro, shortened to 6.7 s and opening on the lit hand
- Nord Night Hawks: ejuro's intro
- Kanagawa: rendered in the wallpaper's own shape, so the handoff matches on
  16:10 and 3:2 screens too
- Ethereal, Nord and Miasma Crowned: 1080p endings that blend into the exact
  wallpaper, so the handoff no longer sharpens visibly

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Finish the Tokyo Night intros

Start each intro on its scene instead of fading in from black, since
OWE now starts intros on their first frame.

- Winding Road: a new starry night to sunset with no added clouds or sun,
  ending on the wallpaper
- Quattro: a new take where the car lands in the wallpaper's exact pose,
  colour-matched to the wallpaper
- Oma Cityscape: drop the second, larger moon that faded out at the end
- Quattro, Swirl Buck, Omakub and Oma Cityscape: 1080p endings that blend
  into the exact wallpaper, so the handoff no longer sharpens visibly

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Pair background intros by filename and play them on theme switches

Keep matching clips in backgrounds/intros so the selected image determines playback without registration, overrides, or checksum sidecars. Retain one global toggle and play automatically at boot and on a different theme selection.

Launch theme playback after releasing the selection lock. Check the staged theme directory identity so an older launcher cannot play a newer selection when filenames are reused. Skipped or failed playback immediately hands the selected still to the shell, including new pixels behind an unchanged path.

Synchronize the clone screenshot fixture with capture instead of a fixed launcher lifetime, while still requiring the real exit handler to release the cover.

Co-Authored-By: Codex Medium <codex@openai.com>

* Settle pending background colors when applying the current theme

An intro applies its theme immediately while a previous still-image reveal may still hold an older palette. Replace and consume that pending palette through the background's existing handoff methods so its reveal or fallback timer cannot restore old colors. Reusing those methods also works with existing background clones.

Publish the fake renderer's PID before announcing playback readiness, so the lock test cannot read an unwritten PID file. Regressions reproduce both failures on 7b238cf1 and pass with the fixes, including a deliberate scheduling delay at PID publication.

Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>

* Use the existing animations menu toggle for background intros

Keep the separate intro command available without another Style menu control. Disabled animations already suppress playback at boot and on theme changes.

Use remembered backgrounds with matching clips when testing suppressed theme intros, and require the normal still transition so the checks cannot pass merely because no clip was selected.

Co-Authored-By: Codex Medium <codex@openai.com>

* Finish the Lupine intros

Start each intro on its scene instead of fading in from black, since
OWE now starts intros on their first frame.

- Cherry Blossom White: a new take that pulls back from fresh leaves to the
  whole tree, easing to a stop on the wallpaper
- Pastel Clouds: drop the unrelated starry opening and its dip to black
- Elegant Blue Wave: drop the empty white opening
- Cherry Blossom Bokeh, Pastel Clouds, Elegant Blue Wave and Cherry Blossom
  White: 1080p endings that blend into the exact wallpaper

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBE1V6gjrqn2aXCNNGhrUY

* Synchronize theme intro and bar crossfades

* Start theme intro fades from a cached first frame

* Open theme intros on moving scenes

* Start theme crossfades promptly

* Prepare theme intros before switching

* Finish the Catppuccin intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBE1V6gjrqn2aXCNNGhrUY

* Keep theme intro handoffs covered

* Reload Hyprland after theme intro playback

* Finish the Rose Pine intros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBE1V6gjrqn2aXCNNGhrUY

* Restore the wallpaper early on each login

* Keep the startup wallpaper cover transparent

* Wait for readiness in the intro lifecycle test

* Consume login intros for updates outside Hyprland

* Fade login directly into the ready desktop intro

* Sharpen Quattro, Swirl Buck and Oma Cityscape

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep compositor backgrounds black during login

* Keep the shell visible through compositor shutdown

* Bound the login cover when startup media fails

* Encode the 1440p intros at H.264 level 5.1

At 2560x1440 the previous encodes used 16 reference frames, which puts
them at level 6.0. Intel hardware decoding plays those with stray grey
blocks. Four reference frames keep them at level 5.1, like the other
intros. Swirl Buck's stag now changes colour as one silhouette instead
of leaving a shrinking black disc on its head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Hide the startup cursor until the desktop fades in

* Restore Swirl Buck's original stag

The stag now changes colour exactly as in the original clip again;
the previous edit made it jump from black to purple in one frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Initialize the startup cursor before the first compositor frame

* Render the Hackerman intros at 1440p

Both open on their first motion instead of fading in from black.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Render the Lumon intros at 1440p

Both open on their first motion instead of fading in from black.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sharpen the Lupine intros

Upscaled from the original 720p clips and exported at 1440p with a
gentler encode, keeping the same frames, timing and endings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Resolve login intros before the wallpaper daemon starts

* Synchronize cold-start captures with the fixture phases

* Sharpen the rest of the Tokyo Night intros

Winding Road and Sunset Lake are regenerated natively at 1440p from
their wallpapers, since their original clips were 360p and 720p.
Omakub and Oma are redrawn from their wallpapers with the same motion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Bjarne Oeverli <1419214+bjarneo@users.noreply.github.com>
Co-authored-by: Erik Johansson <172146456+ejuro@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Omarchy Bot <omarchybot@users.noreply.github.com>
Co-authored-by: Codex Medium <codex@openai.com>
Co-authored-by: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
2026-10-07 22:00:52 +02:00
David Heinemeier Hansson a137760c89 Reuse SSH connections for Herdr theme sync (#14460)
* Reuse SSH connections for remote theme sync

* Cover remote theme sync connection reuse
2026-10-07 21:45:16 +02:00
ZacharyZhang-NY 37207f1911 Merge branch 'omacom:quattro' into omarchycn 2026-10-07 14:35:21 -04:00
Rob ZolkosandDavid Heinemeier Hansson 060c57b4aa Enable overlay scrollbars in Chromium (#14456)
* Enable overlay scrollbars in Chromium

* Test overlay scrollbar migration

* Simplify Chromium overlay scrollbar migration

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
2026-10-07 20:03:36 +02:00
David Heinemeier Hansson 0a55a3c554 Keep overlays sharp without parked surfaces (#14458)
* Keep overlays sharp without parked surfaces

* Refresh overlay monitor scale after live changes

* Reset menu layout when its monitor changes
2026-10-07 19:42:41 +02:00
David Heinemeier Hansson 40642c0d35 Verify input setup and composition stay quiet 2026-10-07 18:10:20 +02:00
David Heinemeier Hansson ee358c251b Show the input switching shortcut in keyboard hints 2026-10-07 18:10:20 +02:00
ZacharyZhang-NY b333308b2d Check off T18 after three codex rounds 2026-10-07 11:54:34 -04:00
ZacharyZhang-NY 8415c8d2a2 Document the China switch order 2026-10-07 11:54:16 -04:00
David Heinemeier Hansson f273eed840 Suppress the Cloudpinyin first-use prompt 2026-10-07 17:48:56 +02:00
ZacharyZhang-NY d62dff95b6 Trust ArchLinuxCN before the China refresh upgrades through it; repair links before the cn rename 2026-10-07 11:46:38 -04:00
David Heinemeier Hansson 036e501e58 Show explicit choices on notification cards 2026-10-07 17:35:03 +02:00
David Heinemeier Hansson 35f8eef1bc Remove the Japanese input welcome notification 2026-10-07 17:35:02 +02:00
ZacharyZhang-NY 9a57690e7d Align the China region to the official cn profile
The region value is cn, the China files live under default/regions/cn/, and
omarchy-apply-pacman renders pacman for every caller: the .cn twins of the
channel servers first, then .org, USTC and ArchLinuxCN. Switching to China
installs and trusts the ArchLinuxCN keyring. A migration renames china to cn
and repoints the drop-in links.
2026-10-07 11:33:01 -04:00
David Heinemeier Hansson 2841563e6c Remember input selections before an app gains focus 2026-10-07 17:17:02 +02:00
David Heinemeier Hansson cd73dc6cfd Keep Pinyin setup prompts visible 2026-10-07 17:17:02 +02:00
ZacharyZhang-NY d2e4a95aed Merge branch 'pr-14399' into omarchycn
# Conflicts:
#	bin/omarchy-refresh-pacman
#	install/post-install/pacman.sh
2026-10-07 11:08:25 -04:00
ZacharyZhang-NY 1abe693d73 Add T18, merging the official region-profile PRs, to the task list 2026-10-07 11:08:19 -04:00
David Heinemeier Hansson fcda55944a Show input modes in the keyboard indicator 2026-10-07 16:57:03 +02:00
David Heinemeier Hansson 22514b59f2 Exercise input engines across desktop toolkits 2026-10-07 15:59:46 +02:00
David Heinemeier Hansson 0f14a84934 Preserve inherited input keys and later keyboard changes 2026-10-07 15:19:06 +02:00
David Heinemeier Hansson cd0025b62f Merge branch 'quattro' into unified-input-method-setup 2026-10-07 15:02:38 +02:00
David Heinemeier Hansson d9b970dd62 Let users choose passwordless sudo duration (#14435)
* Let users choose passwordless sudo duration

* Warn in the menu bar when passwordless sudo is active

* Fix sudo indicator hover behavior and repeated authentication

* Disable passwordless sudo from the bar without a terminal

* Shorten passwordless sudo indicator tooltip

* Recover interrupted passwordless sudo duration switches

* Allow sudo grant changes when listings require authentication

* Start passwordless sudo setup with the duration question
2026-10-07 14:41:27 +02:00
David Heinemeier Hansson 982290fa42 Nicer order 2026-10-07 13:41:30 +02:00
David Heinemeier HanssonandClaude Opus 5.5 b6f2c1cef7 Describe the agents panel's limit rows and sign-in link as they are (#14430)
The manual still said the panel shows the percentage used and that an idle
account keeps its last known limits, and the README that sign-in trouble is
only text. Rows show the time to reset with the percentage on hover, and a
lapsed sign-in is a Sign-in required link that signs the account in again.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:15:38 +02:00
David Heinemeier HanssonandClaude Opus 5.5 7b68fee056 Add Slack to Install > Service (#14434)
Slack is one of the most popular apps people install from the AUR. It now
ships from the Omarchy package repository as slack-desktop, with native
x86_64 and aarch64 builds. Install it the same way as Bitwarden: one menu
entry that installs the package and then launches Slack. The commercial
apps page of the manual gets a matching section.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:15:00 +02:00
David Heinemeier Hansson 9fe6a04715 Unify input methods with the installer keyboard selection 2026-10-07 12:59:36 +02:00
David Heinemeier Hansson 402128b6a2 Automatically approve orphan removal during updates (#14428) 2026-10-07 12:13:33 +02:00
David Heinemeier Hansson 2fa6d0ecc5 Approve Grok migration removal automatically 2026-10-07 12:03:54 +02:00
David Heinemeier Hansson 83957145a7 Replace Disk Usage TUI with Disktree (#14426) 2026-10-07 11:53:39 +02:00
Spencer Bull 0f8af9be30 Merge pull request #14017 from omacom/xps13-ptl-cirrus-firmware
Install XPS 13 Panther Lake speaker firmware
2026-10-06 22:58:04 -05:00
Spencer BullandGreptile db21abdef0 Request a reboot directly in the PTL firmware migration
Each pending user's one-time migration requests a reboot even when hardware setup has already installed the aliases. Let migration completion handle repeat runs and remove the shared marker and its privileged write.

Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-10-06 21:56:49 -05:00
Spencer Bull 499f50ffd6 Move speaker reboot bookkeeping into the migration
Keep hardware setup limited to installing the firmware package. The migration owns the pending-reboot marker and preserves prompts for every user migrating before reboot.
2026-10-06 21:47:56 -05:00
ZacharyZhang-NY 5fc227b06c Word every notification at its sender and show notifications as given 2026-10-06 22:41:59 -04:00
ZacharyZhang-NY 051952600d Word parameterized messages at the sender with omarchy-text and translate only exact text in the shell 2026-10-06 22:29:57 -04:00
Spencer BullandCodex GPT-6.1-Sol XHigh 0d8232b9db Install the XPS 13 PTL speaker alias package
Install the separately owned firmware aliases through the package helper so stock firmware updates and stable refreshes retain them. Use a distinct migration to reach machines with the prior migration completed and preserve the reboot prompt until reboot. Restore the generic updater and leave display quirks to the kernel.

Co-Authored-By: Codex GPT-6.1-Sol XHigh <noreply@openai.com>
2026-10-06 21:29:54 -05:00
ZacharyZhang-NY 619979a716 Translate every Omarchy notification and the remaining prompts, matching templates longest first 2026-10-06 22:16:55 -04:00
Kros DaiandClaude Opus 5.5 1454e04b7f ✨ feat(region): default china installs to Asia/Shanghai
- ✨ offline China first boots preselect `Asia/Shanghai` from the region profile instead of the top of the list
- 📝 profiles may name a `timezone`; first-boot setup reads it for the region in `/etc/omarchy/region`

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdXqTwm63rRMwotZ1xqLC8
2026-10-06 22:14:56 -04:00
Kros DaiandClaude Opus 5.5 d53b24cfc7 ✨ feat(form): let callers preselect a fallback timezone
- ✨ an offline install has no geo guess, so the timezone list opened on Africa/Abidjan; a caller-supplied zone now stands in for the missing guess
- ✅ a successful geo guess still outranks the fallback

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdXqTwm63rRMwotZ1xqLC8
2026-10-06 22:14:56 -04:00
ZacharyZhang-NY f63ecb6697 Translate finished messages by template and the world clock, Dropbox and scoped titles at display 2026-10-06 22:07:19 -04:00
ZacharyZhang-NY 2b2302a279 Translate the remaining shell text at display and keep translated values out of logic 2026-10-06 21:50:11 -04:00
ZacharyZhang-NY e236713d9f Show the desktop shell in Chinese under zh_CN 2026-10-06 21:29:39 -04:00
ZacharyZhang-NY e590f22de6 Mark T16 done in the OmarchyCN task list 2026-10-06 21:25:38 -04:00
ZacharyZhang-NY df44695ea7 Draw the logout prompt on the terminal and describe what a failed China switch leaves 2026-10-06 21:23:13 -04:00
ZacharyZhang-NY 7f0c94d480 Mark T15 done in the OmarchyCN task list 2026-10-06 21:18:29 -04:00
ZacharyZhang-NY f9c36b2f3b Offer to log out after a region switch, in the new region's language 2026-10-06 21:15:32 -04:00
ZacharyZhang-NY a77c246d38 Ask pacman only for the packages pkg-add finds missing 2026-10-06 21:14:50 -04:00
ZacharyZhang-NY a3e40cb8b0 Set up China before moving the mirrors so a failed switch leaves the machine Global 2026-10-06 21:13:37 -04:00
ZacharyZhang-NY 09da70c303 Add T15-T17 from the owner's China testing to the task list 2026-10-06 21:12:52 -04:00
ZacharyZhang-NY df338c4003 Mark T14 done in the OmarchyCN task list 2026-10-06 20:22:05 -04:00
ZacharyZhang-NY 1f5d29a3ac Describe the T14 console flow in the task list 2026-10-06 18:44:56 -04:00
ZacharyZhang-NY 41c491ae4c Keep the setup form's new comments to one line 2026-10-06 18:44:49 -04:00
ZacharyZhang-NY b268838479 Give the setup form Chinese text for the ISO's China installs 2026-10-06 18:29:20 -04:00
ZacharyZhang-NY bf08fedf8e Add T14, the Chinese installer, to the OmarchyCN task list 2026-10-06 18:26:12 -04:00
ZacharyZhang-NY 7f9711ed9c Name the pin's abort hook in the shipping prerequisites 2026-10-06 09:06:51 -04:00
ZacharyZhang-NY 8956964ee5 Record the local-build pin in the OmarchyCN shipping prerequisites 2026-10-06 08:48:24 -04:00
ZacharyZhang-NY ebb0273584 Qualify the acceptance checklist's agent, cargo and test-run evidence 2026-10-06 03:15:49 -04:00
ZacharyZhang-NY 3c3f23222b Add the OmarchyCN acceptance checklist 2026-10-06 03:10:18 -04:00
ZacharyZhang-NY 4e323342ca Mark T11 and T12 done in the OmarchyCN task list 2026-10-06 02:48:13 -04:00
ZacharyZhang-NY dff33f16e3 Keep the host's region out of the sudo boundary sandboxes 2026-10-06 02:23:31 -04:00
ZacharyZhang-NY 6fe83bfa47 Restart fcitx5 before Rime Ice setup so it loads the new Rime addon 2026-10-06 01:56:46 -04:00
ZacharyZhang-NY dae9edef4c Upgrade the system before installing Rime when switching to China 2026-10-06 01:52:25 -04:00
19e5941075 Fail updates that leave required Panther Lake firmware unrepaired
Honor package exclusions while returning failure when an installation leaves the required Cirrus firmware too old. Print-only and download-only operations remain successful without a repair or reboot request.

Co-Authored-By: GPT-6.1-Sol High <noreply@openai.com>
Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-10-06 00:46:53 -05:00
Spencer BullandGPT-6.1-Sol High 033b5ecd3d Retain XPS 13 Panther Lake firmware during package refresh
Constrain Cirrus firmware in the package transaction so a stable channel refresh cannot remove the required aliases. Recover old firmware independently of completed migrations, and request reboot only after verifying a successful repair. Preserve package exclusions through native pacman handling while keeping interactive conflict recovery answerable.

Co-Authored-By: GPT-6.1-Sol High <noreply@openai.com>
Co-Authored-By: GPT-6.1-Sol XHigh <noreply@openai.com>
2026-10-06 00:30:29 -05:00
ZacharyZhang-NY 77c8e3f413 Mark T10 done in the OmarchyCN task list 2026-10-06 01:06:51 -04:00
ZacharyZhang-NY ab6804f8cc Fix the local ISO build command in the shipping prerequisites 2026-10-06 01:06:27 -04:00
ZacharyZhang-NY 2ad241159e Describe the RubyGems override and record the branch's shipping prerequisites 2026-10-06 01:05:09 -04:00
ZacharyZhang-NY 4eafef5025 Correct the China docs and move .cn twins in the rc repository migration 2026-10-06 01:02:03 -04:00
ZacharyZhang-NY e1471efa42 Document the China region for users and contributors 2026-10-06 00:56:05 -04:00
ZacharyZhang-NY b51f36a9fe Mark T8 done in the OmarchyCN task list 2026-10-06 00:53:32 -04:00
ZacharyZhang-NY 07d5f49963 Rebuild .cn twins per server and leave database syncs to omarchy update 2026-10-06 00:52:28 -04:00
ZacharyZhang-NY 5f6c803b21 Switch regions from Setup > Region and show Chinese menu labels under zh_CN 2026-10-06 00:42:55 -04:00
ZacharyZhang-NY 56c8344fd2 Pass the web app installer its icon argument so China web apps skip the wizard 2026-10-06 00:37:41 -04:00
ZacharyZhang-NY 465d4da567 Add an Install > China Apps menu shown only in China 2026-10-06 00:34:51 -04:00
ZacharyZhang-NY 151a530d15 Point npm, PyPI, Go, Rust, RubyGems and Maven at China mirrors on China machines 2026-10-06 00:28:00 -04:00
ZacharyZhang-NY da6045be3f Keep user-managed Dim and Kimi launchers and write Claude settings through fresh private files 2026-10-06 00:19:55 -04:00
ZacharyZhang-NY 23072924c4 Add a China-only Claude Code third-party API setup 2026-10-06 00:06:22 -04:00
ZacharyZhang-NY f4bb97018e Add Dim Agent and Kimi Code to the agent roster 2026-10-06 00:01:58 -04:00
ZacharyZhang-NY 343501011f Indent the font test's Python harness with two spaces 2026-10-05 23:59:36 -04:00
ZacharyZhang-NY 600734790c Match ja and ko by subtag and test the string lang path apps use 2026-10-05 23:56:40 -04:00
ZacharyZhang-NY ab028e80eb Keep Latin families first and read only the tagged language for CJK variants 2026-10-05 23:48:00 -04:00
ZacharyZhang-NY a62771e2b4 Pick the matching Noto CJK variant for language-tagged text
The generic assigns swap sans-serif, serif and monospace for fonts
with no CJK glyphs, so tagged Chinese text ends up on whatever Noto
CJK variant the charset scan finds first, usually the Japanese one.
Fold the many Chinese tags down to one per variant and prepend the
matching family ahead of the assigns.
2026-10-05 23:36:57 -04:00
ZacharyZhang-NY 32ebf6b85b Set up Rime Ice from the ISO's assets on China installs 2026-10-05 23:32:21 -04:00
ZacharyZhang-NY b6bf851af4 Preselect the current region when the form asks again 2026-10-05 23:25:57 -04:00
ZacharyZhang-NY c86af5cea5 Record the installer's region choice during system setup 2026-10-05 23:20:36 -04:00
ZacharyZhang-NY 83d158ad07 List the China mirrors first when the machine's region is China 2026-10-05 23:13:20 -04:00
ZacharyZhang-NY 0c0f4881cb Add the OmarchyCN task list 2026-10-05 23:09:57 -04:00
81df05533c Invalidate the display rebuild marker before repairing its drop-in
A pending migration must rebuild after restoring its missing display configuration even when another user previously completed the machine-wide rebuild. Remove the fixed marker before writing the drop-in so failed writes and builds remain retryable. Fresh installation already rebuilds after hardware setup through the ISO finalizer.

Co-Authored-By: GPT-6.1-Sol XHigh <noreply@openai.com>

Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-10-03 17:09:14 -05:00
Spencer BullandGPT-6.1-Sol XHigh 8f7dd71fda Apply XPS 13 Panther Lake display parameters
Disable PSR2 selective fetch and Panel Replay on DX13260 Panther Lake through a Limine drop-in. Apply it during hardware setup and an update migration, rebuild once per machine, and request reboot until both parameters are booted. Reject diagnosed Limine build errors even when the wrapper returns success so interrupted repairs stay pending.

Co-Authored-By: GPT-6.1-Sol XHigh <noreply@openai.com>
2026-10-03 16:59:33 -05:00
96af8d4c02 Escape firmware test fixture paths
Quote the marker path as a Bash literal before escaping it for sed, so TMPDIR metacharacters survive both fixture copying and script evaluation. Rerun the firmware regression under a special-character TMPDIR while preserving the original PATH for real pacman resolution.

Co-Authored-By: GPT-6.1-Sol <noreply@openai.com>

Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-10-01 23:46:32 -05:00
Spencer BullandGPT-6.1-Sol 8be14d5ac6 Explain why the Cirrus firmware repair uses pacman directly
Co-Authored-By: GPT-6.1-Sol <noreply@openai.com>
2026-10-01 23:41:22 -05:00
89fcd6a1b7 Keep the firmware reboot marker destination fixed
A caller-controlled marker path would let the privileged install command replace an unrelated file. Keep the production path fixed and redirect that literal only in isolated test copies. Cover hostile environment overrides without touching system files.

Co-Authored-By: GPT-6.1-Sol <noreply@openai.com>

Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-10-01 23:09:06 -05:00
Spencer BullandGPT-6.1-Sol 45d2e807db Install the Dell XPS 13 Panther Lake speaker firmware
Explicitly select the Omarchy Cirrus firmware when the installed package predates the 1028:0e54 aliases. Apply it during hardware setup and through an update migration, and request a reboot for each user until the firmware can load. Require the same minimum version in the offline package list so newer Arch snapshots keep their genuine firmware.

Co-Authored-By: GPT-6.1-Sol <noreply@openai.com>
2026-10-01 23:00:51 -05:00
411 changed files with 9384 additions and 1254 deletions

No files matched your search

+10
View File
@@ -96,6 +96,16 @@ Exceptions are allowed for migration and package-helper scripts where the helper
- Do not add `aliases` to new menu entries. Aliases are reserved for - Do not add `aliases` to new menu entries. Aliases are reserved for
established alternate names users already type, kept for compatibility. established alternate names users already type, kept for compatibility.
# Region
- `/etc/omarchy/region` holds `cn`; no file means Global. `omarchy-region` prints the region and switches it (`omarchy region cn|global`); the profile lives in `default/regions/cn/`, its contract in `docs/regions.md`, user-facing docs in `manual/52-china.md`.
- `install/config/region.sh` is the one system leaf for both the ISO install (the `/etc/omarchy/region` marker the installer derives from the timezone) and the runtime switch (`OMARCHY_REGION`): the region file and the profile drop-ins it symlinks. Input methods are not part of a region: the installer's keyboard choice and `omarchy-setup-input` own them (`docs/input-methods.md`).
- `omarchy-apply-pacman` renders pacman for every caller: the channel templates, the profile's `pacman/mirrorlist-<channel>.prepend` ahead of the mirrorlist, and its `pacman.conf.append`. Nothing in `omarchy update` or migrations may change the region or `/etc/locale.conf`.
- Language is separate from region: `omarchy-language en|zh` (Setup > Language) owns `LANG` in `/etc/locale.conf`, the installer sets it from its first screen, and nothing keyed by region touches it.
- China-only menu rows carry `"when":"[[ \"$(omarchy-region)\" == \"China\" ]]"` on every row, not only the submenu, because search checks each row's own guard.
- Chinese menu labels live in `default/omarchy/omarchy-menu.zh-cn.jsonc`, merged when `LANG` starts with `zh_CN`; give a new menu row with translatable words a label there.
- Shell text goes through `I18n.tr("English %1", arg)` from `qs.Commons`, with its Chinese in `shell/Commons/I18nZh.js` keyed by the English. Notifications and OSD messages are worded by their sender with `omarchy-text "English %1" "$arg"` (Python: `text()`, QML: `I18n.tr`), which reads the same table; the shell shows them as given, so user text is never translated. `test/shell.d/shell-i18n-test.sh` fails on a missing entry.
# Config Structure # Config Structure
- `config/` - default configs copied to `~/.config/` - `config/` - default configs copied to `~/.config/`
+1
View File
@@ -71,6 +71,7 @@ The manual lives in [`manual/`](manual/), which is its authoritative source.
- [Omarchy on...](manual/49-omarchy-on.md) - [Omarchy on...](manual/49-omarchy-on.md)
- [Dual Boot Install](manual/50-dual-boot-install.md) - [Dual Boot Install](manual/50-dual-boot-install.md)
- [Unattended Installs](manual/51-unattended-installs.md) - [Unattended Installs](manual/51-unattended-installs.md)
- [China](manual/52-china.md)
## License ## License
+8
View File
@@ -24,6 +24,10 @@ Run `omarchy-restart-shell` after making changes to QML files.
- Panel / overlay / menu plugins must expose `open(payloadJson)` and - Panel / overlay / menu plugins must expose `open(payloadJson)` and
`close()` lifecycle methods for `shell summon` and `shell hide`. `close()` lifecycle methods for `shell summon` and `shell hide`.
## Text
Wrap every user-visible string in `I18n.tr()` (`import qs.Commons`) and add its Chinese to `shell/Commons/I18nZh.js`; plain JS models that Node tests load stay English, and their text is wrapped where QML shows it.
## IPC ## IPC
- `bin/omarchy-shell` is the canonical IPC entry point. It forwards to - `bin/omarchy-shell` is the canonical IPC entry point. It forwards to
@@ -45,3 +49,7 @@ unicode characters. Agent file-editing tools can strip multi-byte codepoints
in some positions — do **not** rewrite widget files wholesale through those in some positions — do **not** rewrite widget files wholesale through those
tools. For glyph fixes, make a targeted edit with the surrounding context, or tools. For glyph fixes, make a targeted edit with the surrounding context, or
use a Python script that inserts codepoints via `chr(0xXXXXX)`. use a Python script that inserts codepoints via `chr(0xXXXXX)`.
## Palette references
Import `qs.Commons as Commons` and use `Commons.Color` for the palette singleton, including `Connections.target`. Qt 6.12 introduces a `Color` type in `QtQuick` that shadows unqualified palette references. This applies to first-party components and third-party plugin QML alike. Keep the unqualified `qs.Commons` import when the file also uses `Style`, `Util`, or `Border`.
-8
View File
@@ -1,8 +0,0 @@
[Desktop Entry]
Version=1.0
Name=Disk Usage
Exec=xdg-terminal-exec --app-id=TUI.float -e bash -c "dua i /"
Terminal=false
Type=Application
Icon=disk-usage
StartupNotify=true
+3
View File
@@ -46,6 +46,7 @@ GROUP_DESCRIPTIONS[finalize]="Finalize user setup"
GROUP_DESCRIPTIONS[default]="Default application selection" GROUP_DESCRIPTIONS[default]="Default application selection"
GROUP_DESCRIPTIONS[dev]="Omarchy development tools" GROUP_DESCRIPTIONS[dev]="Omarchy development tools"
GROUP_DESCRIPTIONS[disk]="Disk performance helpers" GROUP_DESCRIPTIONS[disk]="Disk performance helpers"
GROUP_DESCRIPTIONS[dictation]="Dictation recording"
GROUP_DESCRIPTIONS[display]="Display and text scaling" GROUP_DESCRIPTIONS[display]="Display and text scaling"
GROUP_DESCRIPTIONS[dns]="DNS resolver configuration" GROUP_DESCRIPTIONS[dns]="DNS resolver configuration"
GROUP_DESCRIPTIONS[drive]="Drive selection and encryption" GROUP_DESCRIPTIONS[drive]="Drive selection and encryption"
@@ -58,6 +59,7 @@ GROUP_DESCRIPTIONS[hw]="Hardware detection and controls"
GROUP_DESCRIPTIONS[hyprland]="Hyprland window, monitor, and toggle controls" GROUP_DESCRIPTIONS[hyprland]="Hyprland window, monitor, and toggle controls"
GROUP_DESCRIPTIONS[install]="Optional software installers" GROUP_DESCRIPTIONS[install]="Optional software installers"
GROUP_DESCRIPTIONS[installed]="Installed optional service checks" GROUP_DESCRIPTIONS[installed]="Installed optional service checks"
GROUP_DESCRIPTIONS[language]="System language"
GROUP_DESCRIPTIONS[launch]="Application launchers" GROUP_DESCRIPTIONS[launch]="Application launchers"
GROUP_DESCRIPTIONS[menu]="Omarchy menu commands" GROUP_DESCRIPTIONS[menu]="Omarchy menu commands"
GROUP_DESCRIPTIONS[migrate]="Migration runner" GROUP_DESCRIPTIONS[migrate]="Migration runner"
@@ -73,6 +75,7 @@ GROUP_DESCRIPTIONS[plymouth]="Plymouth boot theme management"
GROUP_DESCRIPTIONS[power]="Power supply detection" GROUP_DESCRIPTIONS[power]="Power supply detection"
GROUP_DESCRIPTIONS[powerprofiles]="Power profile management" GROUP_DESCRIPTIONS[powerprofiles]="Power profile management"
GROUP_DESCRIPTIONS[refresh]="Reset config to defaults" GROUP_DESCRIPTIONS[refresh]="Reset config to defaults"
GROUP_DESCRIPTIONS[region]="Region-specific package sources and setup"
GROUP_DESCRIPTIONS[reinstall]="Reinstall and reset workflows" GROUP_DESCRIPTIONS[reinstall]="Reinstall and reset workflows"
GROUP_DESCRIPTIONS[reminder]="Desktop notification reminders" GROUP_DESCRIPTIONS[reminder]="Desktop notification reminders"
GROUP_DESCRIPTIONS[remove]="Removal workflows" GROUP_DESCRIPTIONS[remove]="Removal workflows"
+16 -16
View File
@@ -85,7 +85,7 @@ finish() {
fail() { fail() {
if [[ $events == "true" ]]; then if [[ $events == "true" ]]; then
printf '@@omarchy error %s\n' "$1" printf '@@omarchy error %s\n' "$1"
omarchy-notification-send -g 󰚩 -u critical "Couldn't add the $name account" "$1" omarchy-notification-send -g 󰚩 -u critical "$(omarchy-text "Couldn't add the %1 account" "$name")" "$1"
else else
echo "$1" >&2 echo "$1" >&2
fi fi
@@ -113,9 +113,9 @@ interruptible() {
} }
if omarchy-cmd-missing "$provider"; then if omarchy-cmd-missing "$provider"; then
say "Installing the $name CLI…" say "$(omarchy-text "Installing the %1 CLI…" "$name")"
if [[ $events == "true" ]]; then if [[ $events == "true" ]]; then
mise use -g "$package" >/dev/null 2>&1 || fail "Installing the $name CLI didn't finish." mise use -g "$package" >/dev/null 2>&1 || fail "$(omarchy-text "Installing the %1 CLI didn't finish." "$name")"
else else
mise use -g "$package" || exit 1 mise use -g "$package" || exit 1
fi fi
@@ -150,26 +150,26 @@ if [[ -n $reauth ]]; then
else else
account_home=$(omarchy-agent-account-state homes "$provider" | awk -F'\t' -v id="$reauth" '$1 == id { print $2 }') account_home=$(omarchy-agent-account-state homes "$provider" | awk -F'\t' -v id="$reauth" '$1 == id { print $2 }')
fi fi
[[ -n $account_home ]] || fail "No $name account named $reauth." [[ -n $account_home ]] || fail "$(omarchy-text "No %1 account named %2." "$name" "$reauth")"
if [[ $account_home == "$HOME/.$provider" ]]; then if [[ $account_home == "$HOME/.$provider" ]]; then
say "Sign in to $name again in the browser window that opens." say "$(omarchy-text "Sign in to %1 again in the browser window that opens." "$name")"
login || fail "The $name sign-in didn't finish." login || fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")"
else else
say "Sign in again as this account in the private window that opens." say "$(omarchy-text "Sign in again as this account in the private window that opens.")"
private_browser private_browser
interruptible env BROWSER="$browser" "$home_env=$account_home" "${login_command[@]}" || fail "The $name sign-in didn't finish." interruptible env BROWSER="$browser" "$home_env=$account_home" "${login_command[@]}" || fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")"
fi fi
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 & omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
finish "Signed in to $name again." finish "$(omarchy-text "Signed in to %1 again." "$name")"
exit 0 exit 0
fi fi
# The first subscription signs in where the CLI always looks, through your # The first subscription signs in where the CLI always looks, through your
# normal browser: nothing is signed in there yet that it could be mistaken for. # normal browser: nothing is signed in there yet that it could be mistaken for.
if ! primary_signed_in "$provider"; then if ! primary_signed_in "$provider"; then
say "Sign in to $name in the browser window that opens." say "$(omarchy-text "Sign in to %1 in the browser window that opens." "$name")"
if login && primary_signed_in "$provider"; then if login && primary_signed_in "$provider"; then
# The first agent signed in on a machine with none chosen becomes the # The first agent signed in on a machine with none chosen becomes the
# default, so `omarchy agent` and the panel's starter prompts have one to # default, so `omarchy agent` and the panel's starter prompts have one to
@@ -179,9 +179,9 @@ if ! primary_signed_in "$provider"; then
printf '%s\n' "$provider" >"$HOME/.config/omarchy/defaults/agent" printf '%s\n' "$provider" >"$HOME/.config/omarchy/defaults/agent"
fi fi
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 & omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
finish "Signed in to $name." finish "$(omarchy-text "Signed in to %1." "$name")"
else else
fail "The $name sign-in didn't finish." fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")"
fi fi
else else
if [[ -z $label && $events == "false" && -t 0 ]]; then if [[ -z $label && $events == "false" && -t 0 ]]; then
@@ -189,7 +189,7 @@ else
fi fi
if [[ $events == "true" ]]; then if [[ $events == "true" ]]; then
say "Sign in as the account you're adding in the private window that opens." say "$(omarchy-text "Sign in as the account you're adding in the private window that opens.")"
else else
cat <<EOF cat <<EOF
@@ -201,7 +201,7 @@ Don't log out of the $name CLI to do this: logging out revokes the saved sign-in
EOF EOF
fi fi
home=$(omarchy-agent-account-state begin "$provider") || fail "Couldn't set up a home for the new account." home=$(omarchy-agent-account-state begin "$provider") || fail "$(omarchy-text "Couldn't set up a home for the new account.")"
private_browser private_browser
interruptible env BROWSER="$browser" "$home_env=$home" "${login_command[@]}" interruptible env BROWSER="$browser" "$home_env=$home" "${login_command[@]}"
@@ -211,9 +211,9 @@ EOF
added=$(jq -r '.label + (if .email != "" then " (" + .email + ")" else "" end)' <<<"$account") added=$(jq -r '.label + (if .email != "" then " (" + .email + ")" else "" end)' <<<"$account")
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 & omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
if [[ $events == "true" ]]; then if [[ $events == "true" ]]; then
finish "Added $added." finish "$(omarchy-text "Added %1." "$added")"
else else
finish "Added $added." finish "$(omarchy-text "Added %1." "$added")"
echo "Switch to it with: omarchy agent account use $provider $(jq -r .id <<<"$account")" echo "Switch to it with: omarchy agent account use $provider $(jq -r .id <<<"$account")"
fi fi
else else
+29 -22
View File
@@ -73,6 +73,11 @@ class AccountError(Exception):
pass pass
def text(english: str, *args: object) -> str:
"""English with %1.. filled, in the session's language, from the shell's table."""
return subprocess.run(["omarchy-text", english, *map(str, args)], capture_output=True, text=True, check=True).stdout.rstrip("\n")
# ------------------------------------------------------------------- paths # ------------------------------------------------------------------- paths
@@ -335,12 +340,12 @@ def begin(provider: str) -> Path:
def register(provider: str, label: str, pending: Path) -> dict[str, Any]: def register(provider: str, label: str, pending: Path) -> dict[str, Any]:
pending = pending.resolve() pending = pending.resolve()
if pending.parent != (accounts_root() / provider / ".pending").resolve(): if pending.parent != (accounts_root() / provider / ".pending").resolve():
raise AccountError(f"{pending} is not a pending {PROVIDERS[provider]['name']} login") raise AccountError(text("%1 is not a pending %2 login", pending, PROVIDERS[provider]['name']))
found = identity(provider, pending) found = identity(provider, pending)
if not found["accountId"]: if not found["accountId"]:
shutil.rmtree(pending, ignore_errors=True) shutil.rmtree(pending, ignore_errors=True)
raise AccountError("The login didn't finish, so no account was added.") raise AccountError(text("The login didn't finish, so no account was added."))
with Locked(provider) as registry: with Locked(provider) as registry:
# Each home is asked who it is now, not trusted from when it was added: a # Each home is asked who it is now, not trusted from when it was added: a
@@ -350,10 +355,10 @@ def register(provider: str, label: str, pending: Path) -> dict[str, Any]:
for account in registry["accounts"]: for account in registry["accounts"]:
if account.get("accountId") == found["accountId"]: if account.get("accountId") == found["accountId"]:
shutil.rmtree(pending, ignore_errors=True) shutil.rmtree(pending, ignore_errors=True)
raise AccountError( raise AccountError(text(
f"That's {account['label']} ({found['email'] or 'already added'}). " "That's %1 (%2). Try again, and sign in as the other account in the private window.",
"Try again, and sign in as the other account in the private window." account["label"], found["email"] or text("already added"),
) ))
label = label.strip() or found["email"].split("@")[0] or "Account" label = label.strip() or found["email"].split("@")[0] or "Account"
account_id = unique_id(provider, registry, label) account_id = unique_id(provider, registry, label)
@@ -395,11 +400,11 @@ def unique_id(provider: str, registry: dict[str, Any], label: str, keep: dict[st
def rename(provider: str, target: str, label: str) -> dict[str, Any]: def rename(provider: str, target: str, label: str) -> dict[str, Any]:
label = label.strip() label = label.strip()
if not label: if not label:
raise AccountError("Give the account a name") raise AccountError(text("Give the account a name"))
with Locked(provider) as registry: with Locked(provider) as registry:
account = find(registry, target) account = find(registry, target)
if not account: if not account:
raise AccountError(f"No {PROVIDERS[provider]['name']} account named {target}") raise AccountError(text("No %1 account named %2", PROVIDERS[provider]['name'], target))
new_id = unique_id(provider, registry, label, keep=account) new_id = unique_id(provider, registry, label, keep=account)
if not account.get("primary") and not account.get("home"): if not account.get("primary") and not account.get("home"):
account["home"] = str(account_home(provider, account)) account["home"] = str(account_home(provider, account))
@@ -428,7 +433,7 @@ def use(provider: str, target: str) -> tuple[dict[str, Any], dict[str, Any]]:
target = ids[(ids.index(previous["id"]) + 1) % len(ids)] target = ids[(ids.index(previous["id"]) + 1) % len(ids)]
account = find(registry, target) account = find(registry, target)
if not account: if not account:
raise AccountError(f"No {PROVIDERS[provider]['name']} account named {target}") raise AccountError(text("No %1 account named %2", PROVIDERS[provider]['name'], target))
registry["active"] = account["id"] registry["active"] = account["id"]
registry["alert"] = "" registry["alert"] = ""
if not account.get("primary"): if not account.get("primary"):
@@ -456,12 +461,12 @@ def remove(provider: str, target: str) -> dict[str, Any]:
with Locked(provider) as registry: with Locked(provider) as registry:
account = find(registry, target) account = find(registry, target)
if not account: if not account:
raise AccountError(f"No {PROVIDERS[provider]['name']} account named {target}") raise AccountError(text("No %1 account named %2", PROVIDERS[provider]['name'], target))
if account.get("primary"): if account.get("primary"):
raise AccountError(f"{account['label']} is the login in {primary_home(provider)}; it can't be removed here.") raise AccountError(text("%1 is the login in %2; it can't be removed here.", account["label"], primary_home(provider)))
home = account_home(provider, account) home = account_home(provider, account)
if home_in_use(provider, home): if home_in_use(provider, home):
raise AccountError(f"A running {PROVIDERS[provider]['name']} session uses {account['label']}. Quit it first, then remove the account.") raise AccountError(text("A running %1 session uses %2. Quit it first, then remove the account.", PROVIDERS[provider]['name'], account["label"]))
# Only the account's own directory goes. Its links point into the primary # Only the account's own directory goes. Its links point into the primary
# home, and rmtree removes a link without following it. # home, and rmtree removes a link without following it.
if home.resolve().parent == (accounts_root() / provider).resolve(): if home.resolve().parent == (accounts_root() / provider).resolve():
@@ -474,14 +479,14 @@ def remove(provider: str, target: str) -> dict[str, Any]:
def set_mode(provider: str, mode: str, threshold: str | None) -> dict[str, Any]: def set_mode(provider: str, mode: str, threshold: str | None) -> dict[str, Any]:
if mode not in SWITCH_MODES: if mode not in SWITCH_MODES:
raise AccountError("Switch mode is manual or auto") raise AccountError(text("Switch mode is manual or auto"))
with Locked(provider) as registry: with Locked(provider) as registry:
registry["switch"] = mode registry["switch"] = mode
if threshold is not None: if threshold is not None:
try: try:
registry["threshold"] = max(50, min(100, int(threshold))) registry["threshold"] = max(50, min(100, int(threshold)))
except ValueError: except ValueError:
raise AccountError("The threshold is a percentage between 50 and 100") raise AccountError(text("The threshold is a percentage between 50 and 100"))
registry["alert"] = "" registry["alert"] = ""
return dict(registry) return dict(registry)
@@ -556,10 +561,10 @@ def duration(delta: dt.timedelta) -> str:
hours, minutes = divmod(minutes, 60) hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24) days, hours = divmod(hours, 24)
if days: if days:
return f"{days}d {hours}h" return text("%1d %2h", days, hours)
if hours: if hours:
return f"{hours}h {minutes}m" return text("%1h %2m", hours, minutes)
return f"{minutes}m" return text("%1m", minutes)
# The whole switching policy, with no I/O: given the registry and the per # The whole switching policy, with no I/O: given the registry and the per
@@ -659,21 +664,23 @@ def autoswitch(provider: str, now: dt.datetime | None = None) -> bool:
threshold = registry["threshold"] threshold = registry["threshold"]
if decision["action"] == "exhausted": if decision["action"] == "exhausted":
soonest = decision["soonest"] soonest = decision["soonest"]
when = f" {soonest[1]['label']} resets in {duration(soonest[0] - now)}." if soonest else "" staying = text("Staying on %1.", source["label"])
notify(f"All {name} accounts are over {threshold}%", f"Staying on {source['label']}.{when}") if soonest:
staying = text("Staying on %1. %2 resets in %3.", source["label"], soonest[1]["label"], duration(soonest[0] - now))
notify(text("All %1 accounts are over %2%", name, threshold), staying)
return False return False
target = decision["to"] target = decision["to"]
headline = f"{source['label']} is at {round(decision['fromPeak'])}% of a {name} limit" headline = text("%1 is at %2% of a %3 limit", source["label"], round(decision["fromPeak"]), name)
if decision["action"] == "switch": if decision["action"] == "switch":
registry["active"] = target["id"] registry["active"] = target["id"]
if not target.get("primary"): if not target.get("primary"):
link_shared(provider, account_home(provider, target)) link_shared(provider, account_home(provider, target))
notify(headline, f"New {name} sessions now use {target['label']} ({round(decision['toPeak'])}%). Running sessions stay on {source['label']}.") notify(headline, text("New %1 sessions now use %2 (%3%). Running sessions stay on %4.", name, target["label"], round(decision["toPeak"]), source["label"]))
return True return True
notify( notify(
headline, headline,
f"{target['label']} is at {round(decision['toPeak'])}%. Click to switch new sessions to it.", text("%1 is at %2%. Click to switch new sessions to it.", target["label"], round(decision["toPeak"])),
["omarchy-agent-account-use", provider, target["id"]], ["omarchy-agent-account-use", provider, target["id"]],
) )
return False return False
+2 -2
View File
@@ -31,9 +31,9 @@ active=$(jq -r '.active.label + (if (.active.plan // "") != "" then " (" + .acti
if [[ $(jq -r .previous.id <<<"$result") == "$(jq -r .active.id <<<"$result")" ]]; then if [[ $(jq -r .previous.id <<<"$result") == "$(jq -r .active.id <<<"$result")" ]]; then
echo "New $name sessions already use $active." echo "New $name sessions already use $active."
else else
message="Running sessions stay on $previous." message=$(omarchy-text "Running sessions stay on %1." "$previous")
echo "New $name sessions now use $active. $message" echo "New $name sessions now use $active. $message"
omarchy-notification-send -g 󰚩 "New $name sessions now use $active" "$message" omarchy-notification-send -g 󰚩 "$(omarchy-text "New %1 sessions now use %2" "$name" "$active")" "$message"
# The usage record's own limits describe the active account. # The usage record's own limits describe the active account.
omarchy-agent-usage-update --limits-only "$1" >/dev/null 2>&1 & omarchy-agent-usage-update --limits-only "$1" >/dev/null 2>&1 &
fi fi
+11 -4
View File
@@ -22,6 +22,7 @@ import json
import os import os
import re import re
import sqlite3 import sqlite3
import subprocess
import sys import sys
import tempfile import tempfile
import time import time
@@ -32,6 +33,11 @@ from typing import Any
AGENT_ID = "claude" AGENT_ID = "claude"
AGENT_NAME = "Claude Code" AGENT_NAME = "Claude Code"
def text(english, *args):
"""English with %1.. filled, in the session's language, from the shell's table."""
return subprocess.run(["omarchy-text", english, *map(str, args)], capture_output=True, text=True, check=True).stdout.rstrip("\n")
AUTH_HELP = "Run `claude auth login` to restore authoritative usage." AUTH_HELP = "Run `claude auth login` to restore authoritative usage."
USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage" USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage"
PROBE_MIN_INTERVAL_SECONDS = 15 PROBE_MIN_INTERVAL_SECONDS = 15
@@ -989,11 +995,12 @@ def probe_limits(access_token: str) -> dict[str, Any]:
except urllib.error.HTTPError as error: except urllib.error.HTTPError as error:
retry_after = error.headers.get("retry-after", "") if error.headers else "" retry_after = error.headers.get("retry-after", "") if error.headers else ""
if error.code == 429: if error.code == 429:
help_text = "Anthropic's usage endpoint is rate limiting checks right now" + ( if retry_after:
f" (retry after {retry_after}s)" if retry_after else "" help_text = text("Anthropic's usage endpoint is rate limiting checks right now (retry after %1s). Local Claude Code stats are still shown.", retry_after)
) + ". Local Claude Code stats are still shown." else:
help_text = text("Anthropic's usage endpoint is rate limiting checks right now. Local Claude Code stats are still shown.")
else: else:
help_text = f"Anthropic's usage endpoint returned status {error.code}. Local Claude Code stats are still shown." help_text = text("Anthropic's usage endpoint returned status %1. Local Claude Code stats are still shown.", error.code)
return {"ok": False, "helpText": help_text} return {"ok": False, "helpText": help_text}
except Exception: except Exception:
# A transport failure reached no server at all — no route, no DNS. Any # A transport failure reached no server at all — no route, no DNS. Any
+2 -2
View File
@@ -6,8 +6,8 @@ wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle >/dev/null
if wpctl get-volume @DEFAULT_AUDIO_SOURCE@ | grep -q MUTED; then if wpctl get-volume @DEFAULT_AUDIO_SOURCE@ | grep -q MUTED; then
omarchy-brightness-keyboard-mute on omarchy-brightness-keyboard-mute on
omarchy-osd -i microphone-muted -m "Microphone muted" omarchy-osd -i microphone-muted -m "$(omarchy-text "Microphone muted")"
else else
omarchy-brightness-keyboard-mute off omarchy-brightness-keyboard-mute off
omarchy-osd -i microphone -m "Microphone on" omarchy-osd -i microphone -m "$(omarchy-text "Microphone on")"
fi fi
+1 -1
View File
@@ -13,7 +13,7 @@ sinks=$(timeout 2 pactl -f json list sinks |
sinks_count=$(jq 'length' <<<"$sinks") sinks_count=$(jq 'length' <<<"$sinks")
if (( sinks_count == 0 )); then if (( sinks_count == 0 )); then
omarchy-osd -m "No audio devices found" omarchy-osd -m "$(omarchy-text "No audio devices found")"
exit 1 exit 1
fi fi
+1 -1
View File
@@ -13,5 +13,5 @@ fi
level=$1 level=$1
omarchy-notification-send -g 󱐋 -u critical "Time to recharge!" "Battery is down to ${level}%" -i battery-caution -t 30000 omarchy-notification-send -g 󱐋 -u critical "$(omarchy-text "Time to recharge!")" "$(omarchy-text "Battery is down to %1%" "$level")" -i battery-caution -t 30000
omarchy-hook battery-low "$level" omarchy-hook battery-low "$level"
+3 -3
View File
@@ -129,9 +129,9 @@ if [[ $charge_holding == "true" ]]; then
threshold_label="${threshold_end}%" threshold_label="${threshold_end}%"
fi fi
echo "Battery ${percentage}% · Holding at ${threshold_label} · ${power_rate}W / ${capacity}Wh" omarchy-text "Battery %1% · Holding at %2 · %3W / %4Wh" "$percentage" "$threshold_label" "$power_rate" "$capacity"
elif [[ $state == "charging" ]]; then elif [[ $state == "charging" ]]; then
echo "Battery ${percentage}% · ${time_remaining} to full ·  ${power_rate}W / ${capacity}Wh" omarchy-text "Battery %1% · %2 to full ·  %3W / %4Wh" "$percentage" "$time_remaining" "$power_rate" "$capacity"
else else
echo "Battery ${percentage}% · ${time_remaining} left ·  ${power_rate}W / ${capacity}Wh" omarchy-text "Battery %1% · %2 left ·  %3W / %4Wh" "$percentage" "$time_remaining" "$power_rate" "$capacity"
fi fi
+2 -2
View File
@@ -23,7 +23,7 @@ SELECTION=$(slurp 2>/dev/null)
RESULT=$(grim -g "$SELECTION" - | zbarimg -q --raw -Sdisable -Sqrcode.enable - 2>/dev/null) RESULT=$(grim -g "$SELECTION" - | zbarimg -q --raw -Sdisable -Sqrcode.enable - 2>/dev/null)
if [[ -z $RESULT ]]; then if [[ -z $RESULT ]]; then
omarchy-notification-send -g 󰐲 -u critical "No QR code found" "Select a region containing a QR code" omarchy-notification-send -g 󰐲 -u critical "$(omarchy-text "No QR code found")" "$(omarchy-text "Select a region containing a QR code")"
exit 1 exit 1
fi fi
@@ -33,4 +33,4 @@ fi
# the notification history, and an unmarked copy would be retained by clipboard # the notification history, and an unmarked copy would be retained by clipboard
# history. Pasting still works; only the recorded copy is given up. # history. Pasting still works; only the recorded copy is given up.
printf '%s' "$RESULT" | wl-copy --sensitive printf '%s' "$RESULT" | wl-copy --sensitive
omarchy-notification-send -g 󰐲 "QR code copied to clipboard" omarchy-notification-send -g 󰐲 "$(omarchy-text "QR code copied to clipboard")"
+4 -4
View File
@@ -23,7 +23,7 @@
OUTPUT_DIR="${OMARCHY_SCREENRECORD_DIR:-${XDG_VIDEOS_DIR:-$HOME/Videos}}" OUTPUT_DIR="${OMARCHY_SCREENRECORD_DIR:-${XDG_VIDEOS_DIR:-$HOME/Videos}}"
if [[ ! -d $OUTPUT_DIR ]]; then if [[ ! -d $OUTPUT_DIR ]]; then
omarchy-notification-send -u critical -t 3000 "Screen recording directory does not exist: $OUTPUT_DIR" omarchy-notification-send -u critical -t 3000 "$(omarchy-text "Screen recording directory does not exist: %1" "$OUTPUT_DIR")"
exit 1 exit 1
fi fi
@@ -76,7 +76,7 @@ start_webcam_overlay() {
if [[ -z $WEBCAM_DEVICE ]]; then if [[ -z $WEBCAM_DEVICE ]]; then
WEBCAM_DEVICE=$(omarchy-capture-webcam-list | sed -n '1s/[[:space:]].*//p') WEBCAM_DEVICE=$(omarchy-capture-webcam-list | sed -n '1s/[[:space:]].*//p')
if [[ -z $WEBCAM_DEVICE ]]; then if [[ -z $WEBCAM_DEVICE ]]; then
omarchy-notification-send -u critical -t 3000 "No webcam devices found" omarchy-notification-send -u critical -t 3000 "$(omarchy-text "No webcam devices found")"
return 1 return 1
fi fi
fi fi
@@ -231,7 +231,7 @@ stop_screenrecording() {
if pgrep -f "^gpu-screen-recorder" >/dev/null; then if pgrep -f "^gpu-screen-recorder" >/dev/null; then
pkill -9 -f "^gpu-screen-recorder" pkill -9 -f "^gpu-screen-recorder"
omarchy-notification-send -u critical -t 5000 "Screen recording error" "Recording process had to be force-killed. Video may be corrupted." omarchy-notification-send -u critical -t 5000 "$(omarchy-text "Screen recording error")" "$(omarchy-text "Recording process had to be force-killed. Video may be corrupted.")"
else else
finalize_recording finalize_recording
local filename=$(cat "$RECORDING_FILE" 2>/dev/null) local filename=$(cat "$RECORDING_FILE" 2>/dev/null)
@@ -241,7 +241,7 @@ stop_screenrecording() {
# Generate a preview thumbnail from the first frame # Generate a preview thumbnail from the first frame
ffmpeg -y -i "$filename" -ss 00:00:00.1 -vframes 1 -q:v 2 "$preview" -loglevel quiet 2>/dev/null ffmpeg -y -i "$filename" -ss 00:00:00.1 -vframes 1 -q:v 2 "$preview" -loglevel quiet 2>/dev/null
omarchy-notification-send "Screen recording saved" "Open with Super + Alt + , (or click this)" \ omarchy-notification-send "$(omarchy-text "Screen recording saved")" "$(omarchy-text "Open with Super + Alt + , (or click this)")" \
-t 10000 --image "${preview:-$filename}" \ -t 10000 --image "${preview:-$filename}" \
--exec mpv -- "$filename" --exec mpv -- "$filename"
@@ -5,7 +5,7 @@
mapfile -t devices < <(omarchy-capture-webcam-list) mapfile -t devices < <(omarchy-capture-webcam-list)
if (( ${#devices[@]} == 0 )); then if (( ${#devices[@]} == 0 )); then
omarchy-notification-send "No webcam devices found" -u critical -t 3000 omarchy-notification-send "$(omarchy-text "No webcam devices found")" -u critical -t 3000
exit 1 exit 1
fi fi
+1 -1
View File
@@ -23,4 +23,4 @@ TEXT=$(grim -g "$SELECTION" - | tesseract stdin stdout --oem 1 --psm 6 -l "${OMA
[[ -z $TEXT ]] && exit 1 [[ -z $TEXT ]] && exit 1
printf "%s" "$TEXT" | wl-copy printf "%s" "$TEXT" | wl-copy
omarchy-notification-send -g 󰴑 "Copied text from selection to clipboard" omarchy-notification-send -g 󰴑 "$(omarchy-text "Copied text from selection to clipboard")"
+1 -1
View File
@@ -18,7 +18,7 @@ copy_url() {
[[ -n $url ]] || return 1 [[ -n $url ]] || return 1
printf '%s' "$url" | wl-copy --type text/plain printf '%s' "$url" | wl-copy --type text/plain
omarchy-notification-send -g 󰅍 "URL copied to clipboard" omarchy-notification-send -g 󰅍 "$(omarchy-text "URL copied to clipboard")"
} }
reply_copied() { reply_copied() {
+3 -3
View File
@@ -87,7 +87,7 @@ download_url() {
# Don't show anything until yt-dlp confirms there's actually a video to grab. # Don't show anything until yt-dlp confirms there's actually a video to grab.
if ! yt-dlp --no-playlist --simulate --quiet --no-warnings --no-exec --no-exec-before-download -- "$url" >/dev/null 2>&1; then if ! yt-dlp --no-playlist --simulate --quiet --no-warnings --no-exec --no-exec-before-download -- "$url" >/dev/null 2>&1; then
omarchy-notification-send -u critical -g 󰅖 "No video found for download" "$url" omarchy-notification-send -u critical -g 󰅖 "$(omarchy-text "No video found for download")" "$url"
exit 0 exit 0
fi fi
@@ -148,7 +148,7 @@ download_url() {
# toast would exit before the thumbnail cleanup below is ever scheduled. # toast would exit before the thumbnail cleanup below is ever scheduled.
# `--` keeps mpv from parsing a leading-dash filename as an option; the path # `--` keeps mpv from parsing a leading-dash filename as an option; the path
# is one discrete argument, so it never reaches a shell. # is one discrete argument, so it never reaches a shell.
omarchy-notification-send -g 󰄬 "Download complete" "$title" \ omarchy-notification-send -g 󰄬 "$(omarchy-text "Download complete")" "$title" \
-t 10000 --image "${preview:-$filepath}" \ -t 10000 --image "${preview:-$filepath}" \
--exec mpv -- "$filepath" || true --exec mpv -- "$filepath" || true
@@ -160,7 +160,7 @@ download_url() {
rm -f "$preview" rm -f "$preview"
) & ) &
else else
omarchy-notification-send -u critical -g 󰅖 "Download failed" "$url" omarchy-notification-send -u critical -g 󰅖 "$(omarchy-text "Download failed")" "$url"
fi fi
exit 0 exit 0
+2 -2
View File
@@ -40,8 +40,8 @@ announce() {
omarchy-notification-send \ omarchy-notification-send \
--urgency critical \ --urgency critical \
--glyph "$CRASH_GLYPH" \ --glyph "$CRASH_GLYPH" \
"Process crashed: $comm" \ "$(omarchy-text "Process crashed: %1" "$comm")" \
"Click to diagnose with AI" \ "$(omarchy-text "Click to diagnose with AI")" \
--exec omarchy-agent-crash "$pid" "$comm" "$exe" "$signal" --exec omarchy-agent-crash "$pid" "$comm" "$exe" "$signal"
} }
+1 -1
View File
@@ -48,4 +48,4 @@ fi
env -u BROWSER xdg-settings set default-web-browser "$desktop_id" || exit 1 env -u BROWSER xdg-settings set default-web-browser "$desktop_id" || exit 1
omarchy-notification-send -g $glyph "$name is now the default browser" omarchy-notification-send -g $glyph "$(omarchy-text "%1 is now the default browser" "$name")"
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
# omarchy:summary=Get or set the default dictation backend
# omarchy:args=[backend]
# omarchy:examples=omarchy default dictation | omarchy default dictation superwhisper
if (($# > 1)); then
echo "Usage: omarchy default dictation [backend]" >&2
exit 1
fi
if (($# == 0)); then
config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
if [[ ! -s $config ]]; then
echo "No dictation backend configured. Select one in Setup > Defaults > Dictation." >&2
exit 1
fi
read -r backend < "$config"
else
backend=$1
fi
if [[ ! $backend =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
echo "Invalid dictation backend: $backend" >&2
exit 1
fi
if (($# == 0)); then
echo "$backend"
else
installer="omarchy-install-dictation-$backend"
if omarchy-cmd-missing "$installer"; then
echo "No installer available for dictation backend: $backend" >&2
exit 1
fi
omarchy-launch-floating-terminal-with-presentation "$installer"
fi
+1 -1
View File
@@ -59,4 +59,4 @@ fi
mkdir -p "$(dirname "$editor_file")" mkdir -p "$(dirname "$editor_file")"
printf '%s\n' "$editor" >"$editor_file" printf '%s\n' "$editor" >"$editor_file"
omarchy-notification-send -g $glyph "$name is now the default editor" omarchy-notification-send -g $glyph "$(omarchy-text "%1 is now the default editor" "$name")"
+1 -1
View File
@@ -48,4 +48,4 @@ cat >~/.config/xdg-terminals.list <<EOF
$desktop_id $desktop_id
EOF EOF
omarchy-notification-send -g $glyph "$name is now the default terminal" omarchy-notification-send -g $glyph "$(omarchy-text "%1 is now the default terminal" "$name")"
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# omarchy:summary=Record dictation with the configured backend
# omarchy:args=<start|stop|toggle>
# omarchy:examples=omarchy dictation start | omarchy dictation toggle
if (($# != 1)) || [[ $1 != "start" && $1 != "stop" && $1 != "toggle" ]]; then
echo "Usage: omarchy dictation <start|stop|toggle>" >&2
exit 1
fi
config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
mkdir -p "${config%/*}"
exec 9> "$config.lock"
if ! flock -w 5 9; then
echo "Dictation is busy; try again." >&2
exit 1
fi
backend=$(omarchy-default-dictation) || exit 1
adapter="omarchy-dictation-$backend"
if omarchy-cmd-missing "$adapter"; then
echo "The selected dictation backend ($backend) is not installed." >&2
exit 1
fi
"$adapter" "$1" 9>&-
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# omarchy:summary=Record dictation using Superwhisper
# omarchy:args=<start|stop|toggle>
case "${1:-}" in
start) superwhisper start ;;
stop)
if omarchy-cmd-missing superwhisper || ! systemctl --user is-active --quiet superwhisper.service; then
exit 0
else
superwhisper stop
fi
;;
toggle) superwhisper record ;;
*) echo "Usage: omarchy dictation superwhisper <start|stop|toggle>" >&2; exit 1 ;;
esac
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# omarchy:hidden=true
# omarchy:summary=Save a configured dictation backend after stopping the previous backend
# omarchy:args=<backend>
set -e
if (($# != 1)) || [[ ! $1 =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
echo "Usage: omarchy-dictation-use <backend>" >&2
exit 1
fi
config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
mkdir -p "${config%/*}"
exec 9> "$config.lock"
if ! flock -w 5 9; then
echo "Dictation is busy; try again." >&2
exit 1
fi
previous=$(omarchy-default-dictation 2>/dev/null) || previous=""
if [[ -n $previous && $previous != "$1" ]] && omarchy-cmd-present "omarchy-dictation-$previous"; then
if ! "omarchy-dictation-$previous" stop 9>&-; then
echo "Could not stop $previous; keeping the existing dictation backend." >&2
exit 1
fi
fi
temporary=$(mktemp "$config.XXXXXX")
trap 'rm -f "$temporary"' EXIT
printf '%s\n' "$1" > "$temporary"
mv -f "$temporary" "$config"
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
# omarchy:summary=Record dictation using Voxtype
# omarchy:args=<start|stop|toggle>
case "${1:-}" in
start|toggle) voxtype record "$1" ;;
stop)
if omarchy-cmd-missing voxtype || [[ $(voxtype status) == "stopped" ]]; then
exit 0
else
voxtype record stop
fi
;;
*) echo "Usage: omarchy dictation voxtype <start|stop|toggle>" >&2; exit 1 ;;
esac
+1 -1
View File
@@ -169,7 +169,7 @@ set_terminal_size() {
[[ $prev_id =~ ^[0-9]+$ ]] && replace=(-r "$prev_id") [[ $prev_id =~ ^[0-9]+$ ]] && replace=(-r "$prev_id")
local new_id local new_id
new_id="$(omarchy-notification-send \ new_id="$(omarchy-notification-send \
"Restart Foot to apply the new terminal font size" \ "$(omarchy-text "Restart Foot to apply the new terminal font size")" \
"${replace[@]}" -p 2>/dev/null)" || true "${replace[@]}" -p 2>/dev/null)" || true
[[ $new_id =~ ^[0-9]+$ ]] && printf '%s\n' "$new_id" >"$id_file" [[ $new_id =~ ^[0-9]+$ ]] && printf '%s\n' "$new_id" >"$id_file"
fi fi
+2 -2
View File
@@ -74,11 +74,11 @@ XML
omarchy-restart-shell omarchy-restart-shell
if pgrep -x ghostty; then if pgrep -x ghostty; then
omarchy-notification-send -g "You must restart Ghostty to see font change" omarchy-notification-send -g "$(omarchy-text "You must restart Ghostty to see font change")"
fi fi
if pgrep -x foot; then if pgrep -x foot; then
omarchy-notification-send -g "You must restart Foot to see font change" omarchy-notification-send -g "$(omarchy-text "You must restart Foot to see font change")"
fi fi
omarchy-hook font-set "$font_name" omarchy-hook font-set "$font_name"
+2 -2
View File
@@ -10,7 +10,7 @@ if (( $# == 0 )); then
mapfile -t cores < <(omarchy-games-retro-cores) mapfile -t cores < <(omarchy-games-retro-cores)
if (( ${#cores[@]} == 0 )); then if (( ${#cores[@]} == 0 )); then
omarchy-notification-send -g 󰯉 "No RetroArch cores found" "/usr/lib/libretro" omarchy-notification-send -g 󰯉 "$(omarchy-text "No RetroArch cores found")" "/usr/lib/libretro"
exit 1 exit 1
fi fi
@@ -69,4 +69,4 @@ EOF
chmod +x "$desktop_file" chmod +x "$desktop_file"
update-desktop-database "$desktop_dir" &>/dev/null || true update-desktop-database "$desktop_dir" &>/dev/null || true
omarchy-notification-send -g 󰯉 "$game_name installed" "Start it with Super + Space" omarchy-notification-send -g 󰯉 "$(omarchy-text "%1 installed" "$game_name")" "$(omarchy-text "Start it with Super + Space")"
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
# omarchy:summary=Match the Dell XPS 13 DX13260 with an Intel Panther Lake GPU.
omarchy-hw-match "DX13260" && omarchy-hw-intel-ptl
+5 -5
View File
@@ -16,7 +16,7 @@ wake() {
on() { on() {
if omarchy-hyprland-toggle-enabled $TOGGLE; then if omarchy-hyprland-toggle-enabled $TOGGLE; then
omarchy-hyprland-toggle $TOGGLE off omarchy-hyprland-toggle $TOGGLE off
omarchy-notification-send -g 󰍹 "Laptop display enabled" omarchy-notification-send -g 󰍹 "$(omarchy-text "Laptop display enabled")"
fi fi
wake wake
@@ -24,25 +24,25 @@ on() {
off() { off() {
if [[ -z $INTERNAL ]]; then if [[ -z $INTERNAL ]]; then
omarchy-notification-send -g 󰍹 "No laptop display found" omarchy-notification-send -g 󰍹 "$(omarchy-text "No laptop display found")"
exit 1 exit 1
fi fi
# The name is written into generated Lua below, so only a plain connector # The name is written into generated Lua below, so only a plain connector
# name may pass; anything else could execute on the next reload. # name may pass; anything else could execute on the next reload.
if [[ ! $INTERNAL =~ ^[A-Za-z0-9._-]+$ ]]; then if [[ ! $INTERNAL =~ ^[A-Za-z0-9._-]+$ ]]; then
omarchy-notification-send -g 󰍹 "Refusing unsafe monitor name" omarchy-notification-send -g 󰍹 "$(omarchy-text "Refusing unsafe monitor name")"
exit 1 exit 1
fi fi
if ! omarchy-hyprland-monitor-external-active; then if ! omarchy-hyprland-monitor-external-active; then
omarchy-notification-send -g 󰍹 "Can't disable the only active display" omarchy-notification-send -g 󰍹 "$(omarchy-text "Can't disable the only active display")"
exit 1 exit 1
fi fi
if omarchy-hyprland-toggle-disabled $TOGGLE && omarchy-hyprland-toggle-disabled $MIRROR_TOGGLE; then if omarchy-hyprland-toggle-disabled $TOGGLE && omarchy-hyprland-toggle-disabled $MIRROR_TOGGLE; then
printf 'hl.monitor({ output = "%s", disabled = true })\n' "$INTERNAL" >"$TOGGLE_FLAG" printf 'hl.monitor({ output = "%s", disabled = true })\n' "$INTERNAL" >"$TOGGLE_FLAG"
omarchy-notification-send -g 󰍹 "Laptop display disabled" omarchy-notification-send -g 󰍹 "$(omarchy-text "Laptop display disabled")"
hyprctl reload hyprctl reload
fi fi
} }
+5 -5
View File
@@ -13,12 +13,12 @@ EXTERNAL=$(hyprctl monitors -j | jq -r '.[] | select(.name | test("^(eDP|LVDS|DS
on() { on() {
if [[ -z $EXTERNAL ]]; then if [[ -z $EXTERNAL ]]; then
omarchy-notification-send -g 󰍹 "No external monitors found for mirror" omarchy-notification-send -g 󰍹 "$(omarchy-text "No external monitors found for mirror")"
exit 1 exit 1
fi fi
if [[ -z $INTERNAL ]]; then if [[ -z $INTERNAL ]]; then
omarchy-notification-send -g 󰍹 "No laptop monitor found to mirror" omarchy-notification-send -g 󰍹 "$(omarchy-text "No laptop monitor found to mirror")"
exit 1 exit 1
fi fi
@@ -26,7 +26,7 @@ on() {
# names may pass; a user-created headless output can carry any name. # names may pass; a user-created headless output can carry any name.
for output in "$INTERNAL" "$EXTERNAL"; do for output in "$INTERNAL" "$EXTERNAL"; do
if [[ ! $output =~ ^[A-Za-z0-9._-]+$ ]]; then if [[ ! $output =~ ^[A-Za-z0-9._-]+$ ]]; then
omarchy-notification-send -g 󰍹 "Refusing unsafe monitor name" omarchy-notification-send -g 󰍹 "$(omarchy-text "Refusing unsafe monitor name")"
exit 1 exit 1
fi fi
done done
@@ -35,7 +35,7 @@ on() {
if omarchy-hyprland-toggle-disabled $TOGGLE; then if omarchy-hyprland-toggle-disabled $TOGGLE; then
printf 'hl.monitor({ output = "%s", mode = "preferred", position = "auto", scale = 1, mirror = "%s" })\n' "$EXTERNAL" "$INTERNAL" >"$TOGGLE_FLAG" printf 'hl.monitor({ output = "%s", mode = "preferred", position = "auto", scale = 1, mirror = "%s" })\n' "$EXTERNAL" "$INTERNAL" >"$TOGGLE_FLAG"
omarchy-notification-send -g 󰍹 "Mirroring enabled ($EXTERNAL)" omarchy-notification-send -g 󰍹 "$(omarchy-text "Mirroring enabled (%1)" "$EXTERNAL")"
hyprctl reload hyprctl reload
fi fi
} }
@@ -43,7 +43,7 @@ on() {
off() { off() {
if omarchy-hyprland-toggle-enabled $TOGGLE; then if omarchy-hyprland-toggle-enabled $TOGGLE; then
omarchy-hyprland-toggle $TOGGLE off omarchy-hyprland-toggle $TOGGLE off
omarchy-notification-send -g 󰍹 "Extended mode restored" omarchy-notification-send -g 󰍹 "$(omarchy-text "Extended mode restored")"
fi fi
} }
@@ -3,6 +3,6 @@
# omarchy:summary=Toggle single-window square aspect ratio. # omarchy:summary=Toggle single-window square aspect ratio.
case $(omarchy-hyprland-toggle single-window-aspect-ratio) in case $(omarchy-hyprland-toggle single-window-aspect-ratio) in
on) omarchy-notification-send -g  "Enable single-window square aspect ratio" ;; on) omarchy-notification-send -g  "$(omarchy-text "Enable single-window square aspect ratio")" ;;
off) omarchy-notification-send -g  "Disable single-window square aspect ratio" ;; off) omarchy-notification-send -g  "$(omarchy-text "Disable single-window square aspect ratio")" ;;
esac esac
+2 -2
View File
@@ -40,14 +40,14 @@ notify_missing_width() {
local key="$1" local key="$1"
local workspace="$2" local workspace="$2"
omarchy-notification-send -g  "No saved width found for $key on workspace $workspace" "Use Super + Alt + Home to save one for this workspace." omarchy-notification-send -g  "$(omarchy-text "No saved width found for %1 on workspace %2" "$key" "$workspace")" "Use Super + Alt + Home to save one for this workspace."
} }
notify_saved_width() { notify_saved_width() {
local key="$1" local key="$1"
local workspace="$2" local workspace="$2"
omarchy-notification-send -g  "Saved width for $key on workspace $workspace" "Restore using Super + Home on this workspace." omarchy-notification-send -g  "$(omarchy-text "Saved width for %1 on workspace %2" "$key" "$workspace")" "Restore using Super + Home on this workspace."
} }
hypr_dispatch() { hypr_dispatch() {
+1 -1
View File
@@ -18,4 +18,4 @@ printf 'hl.workspace_rule({ workspace = "%s", layout = "%s" })\n' "$ACTIVE_WORKS
hyprctl eval "hl.workspace_rule({ workspace = \"$ACTIVE_WORKSPACE\", layout = \"$NEW_LAYOUT\" })" >/dev/null 2>&1 || \ hyprctl eval "hl.workspace_rule({ workspace = \"$ACTIVE_WORKSPACE\", layout = \"$NEW_LAYOUT\" })" >/dev/null 2>&1 || \
hyprctl keyword workspace "$ACTIVE_WORKSPACE, layout:$NEW_LAYOUT" hyprctl keyword workspace "$ACTIVE_WORKSPACE, layout:$NEW_LAYOUT"
omarchy-notification-send -g 󱂬 "Workspace layout set to $NEW_LAYOUT" omarchy-notification-send -g 󱂬 "$(omarchy-text "Workspace layout set to %1" "$NEW_LAYOUT")"
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
# omarchy:summary=Configure or inspect the input method selected at installation
# omarchy:args=<status|configure [--seed|--defaults] [none|mozc|hangul|pinyin|chewing]>
# omarchy:hidden=true
set -euo pipefail
python "$OMARCHY_PATH/default/input-methods/configure.py" "$@"
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
# omarchy:summary=Install and configure Superwhisper dictation
# omarchy:requires-sudo=true
set -e
omarchy-pkg-add superwhisper-bin
bash /usr/share/superwhisper/setup-user
# Existing profiles may still use Right Alt, which conflicts with Omarchy.
# Keep a recording shortcut active while moving away from that binding.
shortcuts=$(superwhisper shortcuts show)
if [[ $(jq -r '.shortcuts.hold' <<< "$shortcuts") == "RightAlt" ]]; then
if [[ -z $(jq -r '.shortcuts.toggle' <<< "$shortcuts") ]]; then
superwhisper shortcuts set hold "RightSuper" >/dev/null
else
superwhisper shortcuts set hold none >/dev/null
fi
fi
# Superwhisper requires a native recording shortcut; keep it separate from
# Omarchy's push-to-talk keys.
superwhisper shortcuts set toggle "Alt+Space" >/dev/null
superwhisper shortcuts set hold none >/dev/null
omarchy-dictation-use superwhisper
hyprctl reload >/dev/null
omarchy-notification-send -g  "$(omarchy-text "Superwhisper Dictation Ready")" "$(omarchy-text "Hold Right Alt or F9 to dictate (or toggle with Super + Ctrl + X).")" -t 10000
superwhisper settings
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# omarchy:summary=Install and configure Voxtype dictation
# omarchy:requires-sudo=true
set -e
if omarchy-cmd-missing voxtype; then
if gum confirm "Install Voxtype + AI model (~150MB) to enable dictation?"; then
omarchy-pkg-add wtype voxtype-bin
else
exit 0
fi
fi
config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
mkdir -p "$config_home/voxtype"
if [[ ! -e $config_home/voxtype/config.toml ]]; then
cp "$OMARCHY_PATH/default/voxtype/config.toml" "$config_home/voxtype/"
if omarchy-hw-vulkan; then
voxtype setup gpu --enable || true
fi
fi
# Setup reuses the configured model and downloads it only when missing.
# Retry these steps even when an earlier attempt already installed the binary.
voxtype setup --download --no-post-install
voxtype setup systemd
systemctl --user enable --now voxtype.service
omarchy-dictation-use voxtype
hyprctl reload >/dev/null
omarchy-notification-send -g  "$(omarchy-text "Voxtype Dictation Ready")" "$(omarchy-text "Hold Right Alt or F9 to dictate (or toggle with Super + Ctrl + X).")" -t 10000
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
# omarchy:summary=Show or switch the system language between English and Simplified Chinese
# omarchy:args=[en|zh]
# omarchy:examples=omarchy language | omarchy language zh
# omarchy:requires-sudo=true
set -euo pipefail
locale_conf=/etc/locale.conf
if (($# == 0)); then
lang=$(sed -n 's/^LANG=//p' "$locale_conf")
# locale.conf may quote the value.
lang=${lang#[\"\']}
printf '%s\n' "${lang%[\"\']}"
exit 0
fi
case ${1:-} in
en) lang=en_US.UTF-8 ;;
zh) lang=zh_CN.UTF-8 ;;
*)
echo "Usage: omarchy-language [en|zh]" >&2
exit 1
;;
esac
sudo sed -i "s/^#\(${lang/./\\.} UTF-8\)/\1/" /etc/locale.gen
sudo locale-gen
if grep -q '^LANG=' "$locale_conf"; then
sudo sed -i "s/^LANG=.*/LANG=$lang/" "$locale_conf"
else
sudo sed -i '$a\' "$locale_conf"
echo "LANG=$lang" | sudo tee -a "$locale_conf" >/dev/null
fi
# The language reaches only a new session, so offer one in the new language.
prompt=$(LANG=$lang omarchy-text "Log out now to switch the language?")
log_out=$(LANG=$lang omarchy-text "Log out now")
later=$(LANG=$lang omarchy-text "Later")
if [[ -t 0 && -t 1 ]] && gum confirm --affirmative "$log_out" --negative "$later" "$prompt" 2>&1; then
omarchy-system-logout
else
LANG=$lang omarchy-text "Log back in to switch the language."
fi
+1 -1
View File
@@ -11,5 +11,5 @@ if [[ -z $path ]]; then
exit 1 exit 1
fi fi
omarchy-notification-send -u low "Editing config file" "$path" omarchy-notification-send -u low "$(omarchy-text "Editing config file")" "$path"
exec omarchy-launch-editor "$path" exec omarchy-launch-editor "$path"
+1 -1
View File
@@ -16,7 +16,7 @@ terminal=$(xdg-terminal-exec --print-id)
case $terminal in case $terminal in
*Alacritty* | *ghostty* | *foot* | *kitty*) ;; *Alacritty* | *ghostty* | *foot* | *kitty*) ;;
*) *)
omarchy-notification-send -g ✋ "Screensaver only runs in Alacritty, Foot, Ghostty, or Kitty" omarchy-notification-send -g ✋ "$(omarchy-text "Screensaver only runs in Alacritty, Foot, Ghostty, or Kitty")"
exit 1 exit 1
;; ;;
esac esac
+3 -1
View File
@@ -15,7 +15,9 @@
# against a half-written tree, and that failed reload leaves a second engine # against a half-written tree, and that failed reload leaves a second engine
# generation behind that turns the next restart's IPC kill into a crash. # generation behind that turns the next restart's IPC kill into a crash.
run_shell() { run_shell() {
QS_DISABLE_FILE_WATCHER=1 QS_NO_RELOAD_POPUP=1 \ local startup_background
startup_background=$(readlink -f "$HOME/.local/state/omarchy/current/background" 2>/dev/null)
OMARCHY_STARTUP_BACKGROUND="$startup_background" QS_DISABLE_FILE_WATCHER=1 QS_NO_RELOAD_POPUP=1 \
systemd-cat -t omarchy-shell -- quickshell -n -p "$OMARCHY_PATH/shell" & systemd-cat -t omarchy-shell -- quickshell -n -p "$OMARCHY_PATH/shell" &
shell_pid=$! shell_pid=$!
+2 -1
View File
@@ -332,6 +332,7 @@ dynamic_bindings() {
static_bindings() { static_bindings() {
echo "SHIFT ALT,L,Copy URL from Web App,sendshortcut,SHIFT ALT,L," echo "SHIFT ALT,L,Copy URL from Web App,sendshortcut,SHIFT ALT,L,"
echo "SHIFT ALT,D,Download Video from Web App,sendshortcut,SHIFT ALT,D," echo "SHIFT ALT,D,Download Video from Web App,sendshortcut,SHIFT ALT,D,"
echo "CTRL SHIFT,SPACE,Switch input language,exec,fcitx5-remote -t"
} }
# Actions Omarchy binds to a second chord meant as an alternative, named one at # Actions Omarchy binds to a second chord meant as an alternative, named one at
@@ -533,7 +534,7 @@ output_binding_records_uncached() {
keybindings_cache_key() { keybindings_cache_key() {
{ {
printf 'v14\n' printf 'v15\n'
hyprctl devices 2>/dev/null | grep -F 'active keymap:' hyprctl devices 2>/dev/null | grep -F 'active keymap:'
hyprctl binds 2>/dev/null hyprctl binds 2>/dev/null
} | sha256sum | awk '{ print $1 }' } | sha256sum | awk '{ print $1 }'
+1 -1
View File
@@ -28,7 +28,7 @@ rows=$(jq -r --arg icon "$PLUGIN_ICON" \
". as \$plugins ". as \$plugins
| .[] | select($filter) | .[] | select($filter)
| \$icon + \"\\t\" + .name + \"\\t\" + .id" <<<"$plugins") | \$icon + \"\\t\" + .name + \"\\t\" + .id" <<<"$plugins")
[[ -n $rows ]] || { omarchy-notification-send "No plugin to ${1}"; exit 0; } [[ -n $rows ]] || { omarchy-notification-send "$(omarchy-text "No plugin to $1")"; exit 0; }
selection=$(omarchy-menu-select "${1^} plugin" <<<"$rows") || exit 0 selection=$(omarchy-menu-select "${1^} plugin" <<<"$rows") || exit 0
[[ -n $selection ]] || exit 0 [[ -n $selection ]] || exit 0
+1 -1
View File
@@ -33,7 +33,7 @@ else
picked=$(omarchy-file-select "${select_args[@]}") || status=$? picked=$(omarchy-file-select "${select_args[@]}") || status=$?
if ((${status:-0} > 1)); then if ((${status:-0} > 1)); then
omarchy-notification-send -g "" -u critical "Could not share" "The file chooser did not open" omarchy-notification-send -g "" -u critical "$(omarchy-text "Could not share")" "$(omarchy-text "The file chooser did not open")"
exit 1 exit 1
fi fi
+1 -1
View File
@@ -9,4 +9,4 @@ set -e
timezone=$(timedatectl list-timezones | omarchy-menu-select "Set timezone" -- --width 520 --maxheight 520) || exit 1 timezone=$(timedatectl list-timezones | omarchy-menu-select "Set timezone" -- --width 520 --maxheight 520) || exit 1
sudo timedatectl set-timezone "$timezone" sudo timedatectl set-timezone "$timezone"
omarchy-shell -q omarchy.clock refresh omarchy-shell -q omarchy.clock refresh
omarchy-notification-send "Timezone is now set to $timezone" omarchy-notification-send "$(omarchy-text "Timezone is now set to %1" "$timezone")"
+3 -3
View File
@@ -21,9 +21,9 @@ pending_migrations=$(omarchy-migrate --pending 2>/dev/null) || exit 0
pending_count=$(printf '%s\n' "$pending_migrations" | sed '/^[[:space:]]*$/d' | wc -l) pending_count=$(printf '%s\n' "$pending_migrations" | sed '/^[[:space:]]*$/d' | wc -l)
if (( pending_count == 1 )); then if (( pending_count == 1 )); then
message="Click to run 1 pending migration." message=$(omarchy-text "Click to run 1 pending migration.")
else else
message="Click to run $pending_count pending migrations." message=$(omarchy-text "Click to run %1 pending migrations." "$pending_count")
fi fi
# This runs from omarchy-migrate-notify.service after graphical-session.target, # This runs from omarchy-migrate-notify.service after graphical-session.target,
@@ -40,7 +40,7 @@ fi
# The shell keeps the click command with the toast, so this oneshot can hand the # The shell keeps the click command with the toast, so this oneshot can hand the
# invitation over and exit instead of staying activated until it is answered. # invitation over and exit instead of staying activated until it is answered.
omarchy-notification-send -u critical -g  "Pending Omarchy Migrations" "$message" \ omarchy-notification-send -u critical -g  "$(omarchy-text "Pending Omarchy Migrations")" "$message" \
--exec omarchy-launch-floating-terminal-with-presentation omarchy-migrate && exit 0 --exec omarchy-launch-floating-terminal-with-presentation omarchy-migrate && exit 0
# Reached when the notification could not be handed off, so fall back to telling # Reached when the notification could not be handed off, so fall back to telling
+1 -1
View File
@@ -2,4 +2,4 @@
# omarchy:summary=Show the current time and date notification # omarchy:summary=Show the current time and date notification
omarchy-notification-send -g  -u low "$(date +"%A %H:%M · %d %B %Y · Week %V")" omarchy-notification-send -g  -u low "$(date +"%A %H:%M · %d %B %Y") · $(omarchy-text "Week %1" "$(date +%V)")"
+2 -2
View File
@@ -159,8 +159,8 @@ done
omarchy-plugin-enable "$new_id" >/dev/null omarchy-plugin-enable "$new_id" >/dev/null
clone_complete=1 clone_complete=1
omarchy-notification-send -g 󰐱 \ omarchy-notification-send -g 󰐱 \
"Editing Cloned Plugin" \ "$(omarchy-text "Editing Cloned Plugin")" \
"Original plugin has been replace by clone." "$(omarchy-text "Original plugin has been replace by clone.")"
echo "Cloned $source_id to $target_dir and switched to $new_id" echo "Cloned $source_id to $target_dir and switched to $new_id"
if (( edit_clone )); then if (( edit_clone )); then
# Word-splitting is deliberate: EDITOR may carry flags. # Word-splitting is deliberate: EDITOR may carry flags.
-2
View File
@@ -68,8 +68,6 @@ run_first_run_step() {
fi fi
} }
run_first_run_step "install Voxtype post-update hook" \
omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/install-voxtype.hook"
run_first_run_step "install fingerprint setup post-update hook" \ run_first_run_step "install fingerprint setup post-update hook" \
omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/setup-fingerprint.hook" omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/setup-fingerprint.hook"
run_first_run_step "install agent setup post-update hook" \ run_first_run_step "install agent setup post-update hook" \
+1
View File
@@ -599,6 +599,7 @@ keyboard_form() {
done done
apply_keyboard "$keyboard" apply_keyboard "$keyboard"
omarchy_persist_input_selection
} }
confirm_reboot() { confirm_reboot() {
+57
View File
@@ -0,0 +1,57 @@
#!/bin/bash
# omarchy:summary=Show or switch the region this machine is set up for
# omarchy:args=[global|cn]
# omarchy:examples=omarchy region | omarchy region cn
# omarchy:requires-sudo=true
set -euo pipefail
region_file=/etc/omarchy/region
if (($# == 0)); then
if [[ ! -e $region_file ]]; then
echo "Global"
elif [[ $(<"$region_file") == "cn" ]]; then
echo "China"
else
echo "Unknown region in $region_file: $(<"$region_file")" >&2
exit 1
fi
exit 0
fi
if [[ $1 != "global" && $1 != "cn" ]]; then
echo "Usage: omarchy-region [global|cn]" >&2
exit 1
fi
channel=$(omarchy-version-channel)
if [[ $channel != "stable" && $channel != "rc" && $channel != "edge" ]]; then
echo "Cannot tell this machine's package channel ($channel); run omarchy channel set first" >&2
exit 1
fi
if [[ $1 == "cn" ]]; then
# Upgrade first: the keyring install below syncs new databases.
omarchy-update-pacman -Syu --noconfirm
sudo env OMARCHY_REGION=cn OMARCHY_PATH="$OMARCHY_PATH" bash -e "$OMARCHY_PATH/install/config/region.sh"
# Trust ArchLinuxCN before the refresh upgrades through it.
rendered=$(mktemp -d)
trap 'rm -rf "$rendered"' EXIT
omarchy-apply-pacman --render "$channel" "$rendered"
mapfile -t profile_packages < <(grep -Ev '^[[:space:]]*(#|$)' "$OMARCHY_PATH/default/regions/cn/packages")
sudo pacman --config "$rendered/pacman.conf" -Sy --noconfirm --needed "${profile_packages[@]}"
sudo pacman-key --populate archlinuxcn
omarchy-refresh-pacman "$channel"
else
sudo env OMARCHY_REGION=global OMARCHY_PATH="$OMARCHY_PATH" bash -e "$OMARCHY_PATH/install/config/region.sh"
omarchy-refresh-pacman "$channel"
fi
# The developer mirrors reach only a new session, so offer one.
if [[ -t 0 && -t 1 ]] && gum confirm --affirmative "$(omarchy-text "Log out now")" --negative "$(omarchy-text "Later")" "$(omarchy-text "Log out now to switch the developer mirrors?")" 2>&1; then
omarchy-system-logout
else
omarchy-text "Log back in to switch the developer mirrors."
fi
+15 -14
View File
@@ -57,16 +57,16 @@ show_reminders() {
[[ -f $reminder_dir/${timer%.timer}.message ]] && reminder_message=$(<"$reminder_dir/${timer%.timer}.message") [[ -f $reminder_dir/${timer%.timer}.message ]] && reminder_message=$(<"$reminder_dir/${timer%.timer}.message")
if [[ -n $reminder_message ]]; then if [[ -n $reminder_message ]]; then
body+="$reminder_message in $(format_remaining $remaining) ($(date -d "@$next" +%-H:%M))"$'\n' body+="$(omarchy-text "%1 in %2 (%3)" "$reminder_message" "$(format_remaining $remaining)" "$(date -d "@$next" +%-H:%M)")"$'\n'
else else
body+="${reminder_minutes}-min reminder in $(format_remaining $remaining) ($(date -d "@$next" +%-H:%M))"$'\n' body+="$(omarchy-text "%1-min reminder in %2 (%3)" "$reminder_minutes" "$(format_remaining $remaining)" "$(date -d "@$next" +%-H:%M)")"$'\n'
fi fi
done < <(active_reminder_timers "$now") done < <(active_reminder_timers "$now")
if [[ -z $body ]]; then if [[ -z $body ]]; then
omarchy-notification-send -g 󰢌 "Upcoming reminders" "No outstanding reminders" omarchy-notification-send -g 󰢌 "$(omarchy-text "Upcoming reminders")" "$(omarchy-text "No outstanding reminders")"
else else
omarchy-notification-send -g 󰢌 "Upcoming reminders" "${body%$'\n'}" omarchy-notification-send -g 󰢌 "$(omarchy-text "Upcoming reminders")" "${body%$'\n'}"
fi fi
} }
@@ -75,7 +75,8 @@ show_json() {
local reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders" local reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders"
local now=$(date +%s) local now=$(date +%s)
local count=0 local count=0
local tooltip="Set Reminder" local tooltip
tooltip=$(omarchy-text "Set Reminder")
while IFS=$'\t' read -r timer next; do while IFS=$'\t' read -r timer next; do
count=$((count + 1)) count=$((count + 1))
@@ -91,7 +92,7 @@ show_json() {
if [[ -n $reminder_message ]]; then if [[ -n $reminder_message ]]; then
label=$reminder_message label=$reminder_message
else else
label="${reminder_minutes}-min reminder" label=$(omarchy-text "%1-min reminder" "$reminder_minutes")
fi fi
item_json=$(jq -cn \ item_json=$(jq -cn \
@@ -109,9 +110,9 @@ show_json() {
done < <(active_reminder_timers "$now") done < <(active_reminder_timers "$now")
if ((count == 1)); then if ((count == 1)); then
tooltip="1 reminder" tooltip=$(omarchy-text "1 reminder")
elif ((count > 1)); then elif ((count > 1)); then
tooltip="$count reminders" tooltip=$(omarchy-text "%1 reminders" "$count")
fi fi
jq -cn --argjson count "$count" --arg tooltip "$tooltip" --argjson reminders "$reminders_json" '{count:$count,active:($count > 0),tooltip:$tooltip,reminders:$reminders}' jq -cn --argjson count "$count" --arg tooltip "$tooltip" --argjson reminders "$reminders_json" '{count:$count,active:($count > 0),tooltip:$tooltip,reminders:$reminders}'
@@ -129,7 +130,7 @@ clear_reminders() {
rm -f "$reminder_dir"/omarchy-reminder-*.message 2>/dev/null || true rm -f "$reminder_dir"/omarchy-reminder-*.message 2>/dev/null || true
refresh_indicator refresh_indicator
omarchy-notification-send -g 󰢌 "All reminders have been cleared" omarchy-notification-send -g 󰢌 "$(omarchy-text "All reminders have been cleared")"
} }
usage() { usage() {
@@ -176,7 +177,7 @@ if [[ -z $minutes ]] || [[ ! $minutes =~ ^[0-9]+$ ]] || ((minutes == 0)); then
fi fi
if [[ -z $message ]]; then if [[ -z $message ]]; then
message="Your ${minutes} minutes are up" message=$(omarchy-text "Your %1 minutes are up" "$minutes")
fi fi
set_at=$(date +%s) set_at=$(date +%s)
@@ -184,18 +185,18 @@ remind_at=$(date -d "+${minutes} minutes" +%H:%M)
unit="omarchy-reminder-${minutes}m-$set_at" unit="omarchy-reminder-${minutes}m-$set_at"
reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders" reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders"
message_file="$reminder_dir/$unit.message" message_file="$reminder_dir/$unit.message"
confirmation="You'll be reminded at $remind_at" confirmation=$(omarchy-text "You'll be reminded at %1" "$remind_at")
confirmation_title="Reminder set for ${minutes} minutes" confirmation_title=$(omarchy-text "Reminder set for %1 minutes" "$minutes")
mkdir -p "$reminder_dir" mkdir -p "$reminder_dir"
if [[ -n $custom_message ]]; then if [[ -n $custom_message ]]; then
printf "%s" "$custom_message" >"$message_file" printf "%s" "$custom_message" >"$message_file"
confirmation_title="$custom_message in ${minutes} minutes" confirmation_title=$(omarchy-text "%1 in %2 minutes" "$custom_message" "$minutes")
fi fi
systemd-run --user --quiet --collect --on-active="${minutes}m" --unit="$unit" \ systemd-run --user --quiet --collect --on-active="${minutes}m" --unit="$unit" \
bash -c 'omarchy-notification-send -g 󰢌 "Reminder" "$1"; rm -f "$2"; omarchy-shell -q omarchy.indicators refresh >/dev/null 2>&1 || true' bash "$message" "$message_file" bash -c 'omarchy-notification-send -g 󰢌 "$(omarchy-text "Reminder")" "$1"; rm -f "$2"; omarchy-shell -q omarchy.indicators refresh >/dev/null 2>&1 || true' bash "$message" "$message_file"
refresh_indicator refresh_indicator
omarchy-notification-send -g 󰢌 "$confirmation_title" "$confirmation" omarchy-notification-send -g 󰢌 "$confirmation_title" "$confirmation"
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
# omarchy:summary=Remove Superwhisper dictation and its desktop integration
# omarchy:requires-sudo=true
set -e
config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
systemctl --user disable --now superwhisper.service 2>/dev/null || true
omarchy-shell -q shell setPluginEnabled superwhisper.panel false >/dev/null
omarchy-pkg-drop superwhisper-bin
systemctl --user daemon-reload
# Remove the package setup's links, preserving independently installed copies
# and the user's preferences, history, and models.
unlink_package_link() {
if [[ -L $1 && $(readlink "$1") == "$2" ]]; then
rm "$1"
fi
}
unlink_package_link "$HOME/.local/bin/superwhisper" /usr/bin/superwhisper
unlink_package_link "$data_home/superwhisper/app/current" /opt/superwhisper
unlink_package_link "$config_home/omarchy/plugins/superwhisper-panel" /opt/superwhisper/assets/omarchy-plugin/superwhisper-panel
for agent in .agents .claude .codex .pi/agent; do
unlink_package_link "$HOME/$agent/skills/superwhisper" /opt/superwhisper/assets/agent-skill/superwhisper
done
backend_config="$config_home/omarchy/defaults/dictation"
if [[ -f $backend_config && $(< "$backend_config") == "superwhisper" ]]; then
rm "$backend_config"
fi
omarchy-shell -q shell rescanPlugins
hyprctl reload >/dev/null
if systemctl --user -q is-active omarchy-fcitx5.service; then
systemctl --user restart omarchy-fcitx5.service
fi
@@ -18,6 +18,10 @@ if omarchy-cmd-present voxtype; then
omarchy-pkg-drop voxtype-bin omarchy-pkg-drop voxtype-bin
rm -rf ~/.config/voxtype rm -rf ~/.config/voxtype
rm -rf ~/.local/share/voxtype rm -rf ~/.local/share/voxtype
backend_config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
if [[ -f $backend_config && $(< "$backend_config") == "voxtype" ]]; then
rm "$backend_config"
fi
hyprctl reload >/dev/null hyprctl reload >/dev/null
else else
echo "Voxtype was not installed." echo "Voxtype was not installed."
+22 -1
View File
@@ -2,9 +2,30 @@
# omarchy:summary=Restart the XCompose input method service (fcitx5) to apply new compose key settings. # omarchy:summary=Restart the XCompose input method service (fcitx5) to apply new compose key settings.
set -euo pipefail
# A fcitx5 running outside the unit owns org.fcitx.Fcitx5, so the unit's # A fcitx5 running outside the unit owns org.fcitx.Fcitx5, so the unit's
# instance would exit on arrival and the stale process would keep serving the # instance would exit on arrival and the stale process would keep serving the
# old ~/.XCompose -- a restart that reports success and changes nothing. # old ~/.XCompose -- a restart that reports success and changes nothing.
systemctl --user stop omarchy-fcitx5.service systemctl --user stop omarchy-fcitx5.service
pkill -x fcitx5 >/dev/null 2>&1 || true pkill -u "$UID" -x fcitx5 >/dev/null 2>&1 || true
for attempt in {1..50}; do
if ! pgrep -u "$UID" -x fcitx5 >/dev/null; then
break
fi
sleep 0.1
done
if pgrep -u "$UID" -x fcitx5 >/dev/null; then
# An activation concurrent with stop may have restarted the unit.
systemctl --user stop omarchy-fcitx5.service
pkill -u "$UID" -x fcitx5 >/dev/null 2>&1 || true
for attempt in {1..50}; do
if ! pgrep -u "$UID" -x fcitx5 >/dev/null; then break; fi
sleep 0.1
done
fi
if pgrep -u "$UID" -x fcitx5 >/dev/null; then
echo "Fcitx did not stop; input service was not restarted." >&2
exit 1
fi
systemctl --user start omarchy-fcitx5.service systemctl --user start omarchy-fcitx5.service
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
# omarchy:summary=Set up Japanese, Korean, or Chinese typing
# omarchy:args=[mozc|hangul|pinyin|chewing]
# omarchy:examples=omarchy setup input | omarchy setup input mozc
# omarchy:requires-sudo=true
set -euo pipefail
if (( $# > 1 )); then
echo "Usage: omarchy setup input [mozc|hangul|pinyin|chewing]" >&2
exit 1
fi
method=${1:-}
if [[ -z $method ]]; then
choice=$(jq -r 'to_entries[] | select(.key != "none") | .value.label' "$OMARCHY_PATH/default/input-methods/presets.json" |
gum choose --header "Select input language")
method=$(jq -r --arg label "$choice" 'to_entries[] | select(.value.label == $label) | .key' "$OMARCHY_PATH/default/input-methods/presets.json")
fi
case "$method" in
mozc | hangul | pinyin | chewing) ;;
*) echo "Unknown input method: $method" >&2; exit 1 ;;
esac
mapfile -t packages < <(jq -r --arg method "$method" '.[$method].packages[]' "$OMARCHY_PATH/default/input-methods/presets.json")
omarchy-pkg-add "${packages[@]}"
omarchy-input-method configure "$method"
echo "Input method added. The default switch is Ctrl+Shift+Space or the Japanese/Korean input key; existing custom keys are preserved."
+194 -47
View File
@@ -1,7 +1,7 @@
#!/bin/bash -p #!/bin/bash -p
# omarchy:summary=Toggle passwordless sudo for the current user. # omarchy:summary=Toggle passwordless sudo for the current user.
# omarchy:args=[MINUTES] # omarchy:args=[MINUTES|permanent|--active|--disable]
# omarchy:requires-sudo=true # omarchy:requires-sudo=true
if [[ $- != *p* && ${BASH_SOURCE[0]} == "$0" ]]; then if [[ $- != *p* && ${BASH_SOURCE[0]} == "$0" ]]; then
@@ -22,7 +22,6 @@ fi
set -euo pipefail set -euo pipefail
readonly DEFAULT_MINUTES=15
readonly MAX_MINUTES=1440 readonly MAX_MINUTES=1440
readonly LOCK_FILE=/run/lock/omarchy-sudo-passwordless.lock readonly LOCK_FILE=/run/lock/omarchy-sudo-passwordless.lock
readonly BOOT_CLEANUP_FILE=/etc/tmpfiles.d/omarchy-nopasswd-sudo.conf readonly BOOT_CLEANUP_FILE=/etc/tmpfiles.d/omarchy-nopasswd-sudo.conf
@@ -34,7 +33,7 @@ readonly INSTALLED_SELF=/usr/bin/omarchy-sudo-passwordless
readonly STATUS_INACTIVE=3 readonly STATUS_INACTIVE=3
usage() { usage() {
echo "Usage: omarchy-sudo-passwordless [MINUTES]" >&2 echo "Usage: omarchy-sudo-passwordless [MINUTES|permanent|--active|--disable]" >&2
echo "MINUTES must be between 1 and $MAX_MINUTES." >&2 echo "MINUTES must be between 1 and $MAX_MINUTES." >&2
exit 1 exit 1
} }
@@ -43,6 +42,10 @@ valid_minutes() {
[[ $1 =~ ^0*[1-9][0-9]{0,3}$ ]] && ((10#$1 <= MAX_MINUTES)) [[ $1 =~ ^0*[1-9][0-9]{0,3}$ ]] && ((10#$1 <= MAX_MINUTES))
} }
valid_duration() {
[[ $1 == "permanent" ]] || valid_minutes "$1"
}
valid_uid() { valid_uid() {
[[ $1 =~ ^0*[1-9][0-9]{0,9}$ ]] && ((10#$1 <= 4294967294)) [[ $1 =~ ^0*[1-9][0-9]{0,9}$ ]] && ((10#$1 <= 4294967294))
} }
@@ -96,14 +99,39 @@ with_root_lock() {
} }
rule_file() { rule_file() {
printf '/etc/sudoers.d/99-omarchy-nopasswd-%s' "$1" local uid=$((10#$1))
if [[ ${2:-} == "permanent" ]]; then
printf '/etc/sudoers.d/99-omarchy-permanent-nopasswd-%s' "$uid"
else
printf '/etc/sudoers.d/99-omarchy-nopasswd-%s' "$uid"
fi
} }
# The sudoers rule is the only grant record. A missing file is distinct from # The sudoers rule is the only grant record. A missing file is distinct from
# an unreadable, unsafe, or administrator-modified file. # an unreadable, unsafe, or administrator-modified file.
read_grant() { read_grant() {
local file contents local file permanent contents
file=$(rule_file "$1") file=$(rule_file "$1")
permanent=$(rule_file "$1" permanent)
if [[ -e $permanent || -L $permanent ]]; then
verify_root_path "$permanent" && [[ -f $permanent ]] || return 2
contents=$(/usr/bin/cat -- "$permanent") || return 2
[[ $contents =~ ^([a-z_][a-z0-9_-]*\$?)\ ALL=\(ALL\)\ NOPASSWD:\ ALL$ ]] || return 2
GRANT_NAME=${BASH_REMATCH[1]}
GRANT_DEADLINE=""
valid_account_name "$GRANT_NAME" || return 2
if [[ -e $file || -L $file ]]; then
# A SIGKILL between publication and removal can leave both policies.
# Only accept a matching generated timed rule. Permanent access already
# exists, so old expiry callbacks must not revoke it; disable removes
# both files, and the next duration change finishes their replacement.
verify_root_path "$file" && [[ -f $file ]] || return 2
contents=$(/usr/bin/cat -- "$file") || return 2
[[ $contents =~ ^([a-z_][a-z0-9_-]*\$?)\ ALL=\(ALL\)\ NOTAFTER=([0-9]{14}Z)\ NOPASSWD:\ ALL$ ]] || return 2
[[ ${BASH_REMATCH[1]} == "$GRANT_NAME" ]] || return 2
fi
return 0
fi
[[ -e $file || -L $file ]] || return "$STATUS_INACTIVE" [[ -e $file || -L $file ]] || return "$STATUS_INACTIVE"
verify_root_path "$file" && [[ -f $file ]] || return 2 verify_root_path "$file" && [[ -f $file ]] || return 2
contents=$(/usr/bin/cat -- "$file") || return 2 contents=$(/usr/bin/cat -- "$file") || return 2
@@ -121,6 +149,9 @@ classify_generated_rule() {
[[ -f $file && ! -L $file ]] || return 1 [[ -f $file && ! -L $file ]] || return 1
contents=$(/usr/bin/cat -- "$file") || return 2 contents=$(/usr/bin/cat -- "$file") || return 2
suffix=${file##*/99-omarchy-nopasswd-} suffix=${file##*/99-omarchy-nopasswd-}
if [[ $file == */99-omarchy-permanent-nopasswd-* ]]; then
suffix=${file##*/99-omarchy-permanent-nopasswd-}
fi
# The legacy command wrote the caller's unvalidated name into both the # The legacy command wrote the caller's unvalidated name into both the
# filename and the rule. That exact relationship is its fingerprint, so an # filename and the rule. That exact relationship is its fingerprint, so an
@@ -138,14 +169,21 @@ classify_generated_rule() {
valid_account_name "$name" && [[ $contents =~ ^[a-z_][a-z0-9_-]*\$?\ ALL=\(ALL\)\ NOTAFTER=[0-9]{14}Z\ NOPASSWD:\ ALL$ ]] valid_account_name "$name" && [[ $contents =~ ^[a-z_][a-z0-9_-]*\$?\ ALL=\(ALL\)\ NOTAFTER=[0-9]{14}Z\ NOPASSWD:\ ALL$ ]]
} }
cleanup_uid_locked() { cleanup_rule_file() {
local file local file=$1
file=$(rule_file "$1")
[[ -e $file || -L $file ]] || return 0 [[ -e $file || -L $file ]] || return 0
verify_root_path "$file" && classify_generated_rule "$file" || return 1 verify_root_path "$file" && classify_generated_rule "$file" || return 1
/usr/bin/rm -f -- "$file" && [[ ! -e $file && ! -L $file ]] /usr/bin/rm -f -- "$file" && [[ ! -e $file && ! -L $file ]]
} }
cleanup_uid_locked() {
local file duration
for duration in temporary permanent; do
file=$(rule_file "$1" "$duration")
cleanup_rule_file "$file" || return 1
done
}
# The generated prefix is reserved: boot cleanup and the package hook already # The generated prefix is reserved: boot cleanup and the package hook already
# remove everything in it, and the legacy writer could produce a rule whose # remove everything in it, and the legacy writer could produce a rule whose
# body differs from its filename. Nothing unrecognized may stay live there, but # body differs from its filename. Nothing unrecognized may stay live there, but
@@ -263,6 +301,7 @@ migrate_locked() {
expire_locked() { expire_locked() {
local status now local status now
if read_grant "$1"; then if read_grant "$1"; then
[[ -n $GRANT_DEADLINE ]] || return 0
now=$(/usr/bin/date -u +%Y%m%d%H%M%SZ) || return 2 now=$(/usr/bin/date -u +%Y%m%d%H%M%SZ) || return 2
[[ $now < $GRANT_DEADLINE ]] && return 0 [[ $now < $GRANT_DEADLINE ]] && return 0
cleanup_uid_locked "$1" cleanup_uid_locked "$1"
@@ -281,6 +320,7 @@ status_locked() {
resolve_account "$1" || return 2 resolve_account "$1" || return 2
if read_grant "$1"; then if read_grant "$1"; then
[[ $GRANT_NAME == "$ACCOUNT_NAME" ]] || return 2 [[ $GRANT_NAME == "$ACCOUNT_NAME" ]] || return 2
[[ -n $GRANT_DEADLINE ]] || return 0
now=$(/usr/bin/date -u +%Y%m%d%H%M%SZ) || return 2 now=$(/usr/bin/date -u +%Y%m%d%H%M%SZ) || return 2
if [[ $now < $GRANT_DEADLINE ]]; then if [[ $now < $GRANT_DEADLINE ]]; then
return 0 return 0
@@ -300,7 +340,11 @@ finish_enable() {
if cleanup_uid_locked "$uid"; then if cleanup_uid_locked "$uid"; then
[[ -z $timer ]] || /usr/bin/systemctl stop "$timer.timer" "$timer.service" >/dev/null 2>&1 || true [[ -z $timer ]] || /usr/bin/systemctl stop "$timer.timer" "$timer.service" >/dev/null 2>&1 || true
else else
echo "Could not revoke passwordless sudo; expiry remains armed. Administrator cleanup is required." >&2 if [[ -n $timer ]]; then
echo "Could not revoke passwordless sudo; expiry remains armed. Administrator cleanup is required." >&2
else
echo "Could not revoke passwordless sudo. Administrator cleanup is required." >&2
fi
fi fi
fi fi
[[ -z $pending ]] || /usr/bin/rm -f -- "$pending" [[ -z $pending ]] || /usr/bin/rm -f -- "$pending"
@@ -309,9 +353,15 @@ finish_enable() {
enable_locked() ( enable_locked() (
local uid=$1 minutes=$2 now expires deadline token timer="" pending="" file status local uid=$1 minutes=$2 now expires deadline token timer="" pending="" file status
resolve_account "$uid" && valid_minutes "$minutes" || return 1 resolve_account "$uid" && valid_duration "$minutes" || return 1
verify_boot_cleanup && verify_root_path /etc/sudoers.d || return 1 verify_root_path /etc/sudoers.d || return 1
file=$(rule_file "$uid") if [[ $minutes != "permanent" ]]; then
verify_boot_cleanup || {
echo "Temporary sudo cleanup is unavailable or a settings package transaction is incomplete. Check the omarchy-settings installation." >&2
return 1
}
fi
file=$(rule_file "$uid" "$minutes")
if read_grant "$uid"; then if read_grant "$uid"; then
[[ $GRANT_NAME == "$ACCOUNT_NAME" ]] || return 1 [[ $GRANT_NAME == "$ACCOUNT_NAME" ]] || return 1
else else
@@ -320,39 +370,69 @@ enable_locked() (
fi fi
trap finish_enable EXIT trap finish_enable EXIT
omarchy_security_install_signal_exit_traps omarchy_security_install_signal_exit_traps
now=$(/usr/bin/date +%s) || return 1
expires=$((now + 10#$minutes * 60))
deadline=$(/usr/bin/date -u -d "@$expires" +%Y%m%d%H%M%SZ) || return 1
pending=$(/usr/bin/mktemp /etc/sudoers.d/.omarchy-nopasswd.XXXXXX) || return 1 pending=$(/usr/bin/mktemp /etc/sudoers.d/.omarchy-nopasswd.XXXXXX) || return 1
/usr/bin/printf '%s ALL=(ALL) NOTAFTER=%s NOPASSWD: ALL\n' "$ACCOUNT_NAME" "$deadline" >"$pending" || return 1 if [[ $minutes == "permanent" ]]; then
/usr/bin/printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$ACCOUNT_NAME" >"$pending" || return 1
else
now=$(/usr/bin/date +%s) || return 1
expires=$((now + 10#$minutes * 60))
deadline=$(/usr/bin/date -u -d "@$expires" +%Y%m%d%H%M%SZ) || return 1
/usr/bin/printf '%s ALL=(ALL) NOTAFTER=%s NOPASSWD: ALL\n' "$ACCOUNT_NAME" "$deadline" >"$pending" || return 1
fi
/usr/bin/chown root:root "$pending" && /usr/bin/chmod 0440 "$pending" || return 1 /usr/bin/chown root:root "$pending" && /usr/bin/chmod 0440 "$pending" || return 1
/usr/sbin/visudo -cf "$pending" >/dev/null || return 1 /usr/sbin/visudo -cf "$pending" >/dev/null || return 1
token=$(/usr/bin/tr -d '-' </proc/sys/kernel/random/uuid) || return 1 if [[ $minutes != "permanent" ]]; then
[[ $token =~ ^[0-9a-f]{32}$ ]] || return 1 token=$(/usr/bin/tr -d '-' </proc/sys/kernel/random/uuid) || return 1
timer="omarchy-nopasswd-expire-$uid-$token" [[ $token =~ ^[0-9a-f]{32}$ ]] || return 1
/usr/bin/systemd-run --quiet --collect --on-calendar="@$expires" \ timer="omarchy-nopasswd-expire-$uid-$token"
--timer-property=AccuracySec=1s --unit="$timer" \ /usr/bin/systemd-run --quiet --collect --on-calendar="@$expires" \
-- "$INSTALLED_SELF" __expire "$uid" || return 1 --timer-property=AccuracySec=1s --unit="$timer" \
/usr/bin/systemctl is-active --quiet "$timer.timer" || return 1 -- "$INSTALLED_SELF" __expire "$uid" || return 1
/usr/bin/systemctl is-active --quiet "$timer.timer" || return 1
fi
# The temporary filename contains a dot, so sudo ignores it. Rename within # The temporary filename contains a dot, so sudo ignores it. Rename within
# sudoers.d publishes the complete validated policy in one operation. # sudoers.d publishes the complete validated policy in one operation.
/usr/bin/mv -fT -- "$pending" "$file" || return 1 /usr/bin/mv -fT -- "$pending" "$file" || return 1
pending="" pending=""
now=$(/usr/bin/date +%s) || return 1 # Keep access uninterrupted during renewal. When changing duration type,
(( now < expires )) && verify_boot_cleanup && /usr/bin/systemctl is-active --quiet "$timer.timer" # remove the other rule only after its replacement is published. The lock
# keeps status and expiry callbacks from observing both rules together.
if [[ $minutes == "permanent" ]]; then
cleanup_rule_file "$(rule_file "$uid")" || return 1
else
cleanup_rule_file "$(rule_file "$uid" permanent)" || return 1
fi
if [[ $minutes != "permanent" ]]; then
now=$(/usr/bin/date +%s) || return 1
(( now < expires )) && verify_boot_cleanup && /usr/bin/systemctl is-active --quiet "$timer.timer"
fi
) )
root_dispatch() { root_dispatch() {
local action="$1" local action="$1" status
shift shift
case "$action" in case "$action" in
__status) __status)
(($# == 1)) && verify_sudo_caller "$1" || return 2 (($# == 1)) && verify_sudo_caller "$1" || return 2
with_root_lock status_locked "$1" if with_root_lock status_locked "$1"; then
return 0
else
status=$?
# Reserve status 1 for sudo refusing noninteractive authentication.
# Every internal inspection failure, including lock failures, is 2.
(( status == STATUS_INACTIVE )) || status=2
return "$status"
fi
;; ;;
__enable) __enable)
(($# == 2)) && verify_sudo_caller "$1" && valid_minutes "$2" || return 1 (($# == 2)) && verify_sudo_caller "$1" && valid_duration "$2" || return 1
with_root_lock enable_locked "$1" "$2" if with_root_lock enable_locked "$1" "$2"; then
return 0
else
status=$?
echo "Could not enable passwordless sudo. Check existing sudoers policy and cleanup support." >&2
return "$status"
fi
;; ;;
__disable) __disable)
(($# == 1)) && verify_sudo_caller "$1" || return 1 (($# == 1)) && verify_sudo_caller "$1" || return 1
@@ -382,6 +462,14 @@ root_dispatch() {
esac esac
} }
show_enabled() {
if [[ $minutes == "permanent" ]]; then
echo "Passwordless sudo has been ENABLED permanently. Run this command again to disable it."
else
echo "Passwordless sudo has been ENABLED. It will automatically disable in ${minutes} minutes."
fi
}
case "${1:-}" in case "${1:-}" in
__status|__enable|__disable|__expire|__cleanup-all|__package-removing|__migrate|__migration-complete) __status|__enable|__disable|__expire|__cleanup-all|__package-removing|__migrate|__migration-complete)
action=$1 action=$1
@@ -391,9 +479,30 @@ case "${1:-}" in
;; ;;
esac esac
# Prefer matched policy tags to executing a privileged command, avoiding
# authentication/session logs under the default listing policy. Restrictive
# listpw settings require the root-status fallback. Neither path prompts or
# trusts cached credentials as evidence of passwordless access.
if [[ ${1:-} == "--active" ]]; then
(($# == 1)) || usage
uid=$(/usr/bin/id -u)
if policy=$(/usr/bin/sudo -n -N -l -l -- "$INSTALLED_SELF" __status "$uid" 2>/dev/null); then
/usr/bin/grep -q '!authenticate' <<<"$policy"
else
# listpw=always can require authentication to list a passwordless grant.
# With a command, -k ignores cached credentials without invalidating them.
status=0
/usr/bin/sudo -kn -- "$INSTALLED_SELF" __status "$uid" 2>/dev/null || status=$?
# An internal inspection error still proves that sudo ran the helper
# without authentication. Keep the warning visible for unsafe policy.
(( status == 0 || status == 2 )) || exit 1
fi
exit
fi
(($# <= 1)) || usage (($# <= 1)) || usage
minutes=${1:-$DEFAULT_MINUTES} minutes=${1:-}
valid_minutes "$minutes" || usage (($# == 0)) || [[ $minutes == "--disable" ]] || valid_duration "$minutes" || usage
uid=$(/usr/bin/id -u) uid=$(/usr/bin/id -u)
valid_uid "$uid" || { valid_uid "$uid" || {
echo "omarchy-sudo-passwordless: cannot grant passwordless sudo to this account" >&2 echo "omarchy-sudo-passwordless: cannot grant passwordless sudo to this account" >&2
@@ -411,38 +520,76 @@ omarchy_security_install_sudo_cleanup_traps
exit 1 exit 1
} }
echo "Toggle passwordless sudo..." # Inspect policy without authenticating. Only inspect the root-owned grant
if /usr/bin/sudo -N -- "$INSTALLED_SELF" __status "$uid"; then # when policy already permits it without a password; otherwise the eventual
if (($# == 0)); then # enable action is the sole authentication boundary. enable_locked validates
/usr/bin/sudo -N -- "$INSTALLED_SELF" __disable "$uid" # any existing policy before publishing, including expired or modified rules.
echo "Passwordless sudo has been DISABLED. Sudo will require a password again." status=$STATUS_INACTIVE
else if policy=$(/usr/bin/sudo -n -N -l -l -- "$INSTALLED_SELF" __status "$uid" 2>/dev/null); then
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes" if /usr/bin/grep -q '!authenticate' <<<"$policy"; then
echo "Passwordless sudo expiry updated. It will automatically disable in ${minutes} minutes." status=0
/usr/bin/sudo -n -N -- "$INSTALLED_SELF" __status "$uid" || status=$?
fi fi
else else
status=$? # A listing password is independent of command authorization. An active
if (( status != STATUS_INACTIVE )); then # grant can still be inspected/revoked without prompting under listpw=always.
status=0
/usr/bin/sudo -n -N -- "$INSTALLED_SELF" __status "$uid" 2>/dev/null || status=$?
if (( status == 1 )) && [[ $minutes != "--disable" ]]; then
# No noninteractive inspection is available. Offer the confirmed enable
# flow; its single authenticated action validates existing policy itself.
status=$STATUS_INACTIVE
fi
fi
if (( status == 0 )) && { (($# == 0)) || [[ $minutes == "--disable" ]]; }; then
/usr/bin/sudo -n -N -- "$INSTALLED_SELF" __disable "$uid"
echo "Passwordless sudo has been DISABLED. Sudo will require a password again."
elif (( status == STATUS_INACTIVE )) && [[ $minutes == "--disable" ]]; then
echo "Passwordless sudo is already DISABLED."
elif (( status == 0 )) && [[ $minutes != "permanent" ]]; then
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
show_enabled
else
if (( status != 0 && status != STATUS_INACTIVE )); then
echo "Could not safely inspect passwordless sudo; no grant will be enabled. Resolve the reported authorization or cleanup error first." >&2 echo "Could not safely inspect passwordless sudo; no grant will be enabled. Resolve the reported authorization or cleanup error first." >&2
exit 1 exit 1
fi fi
if [[ -z $minutes ]]; then
choice=$(/usr/bin/gum choose --header "How long should passwordless sudo stay enabled?" "15 minutes" "1 Hour" "1 Day" "Permanently") || exit 130
case "$choice" in
"15 minutes") minutes=15 ;;
"1 Hour") minutes=60 ;;
"1 Day") minutes=1440 ;;
"Permanently") minutes=permanent ;;
*) exit 130 ;;
esac
fi
if [[ $minutes == "permanent" ]]; then
duration="permanently"
else
duration="for ${minutes} minutes"
fi
echo "" echo ""
echo "⚠️ WARNING: This will allow ANY process running as your user to" echo "⚠️ WARNING: This will allow ANY process running as your user to"
echo "execute ANY command as root WITHOUT a password for ${minutes} minutes." echo "execute ANY command as root WITHOUT a password $duration."
echo "" echo ""
echo "This is useful for AI agents that need to run sudo commands," echo "This is useful for AI agents that need to run sudo commands,"
echo "but it significantly weakens the security of your system." echo "but it significantly weakens the security of your system."
echo "Anyone or anything with access to your user account gets full root." echo "Anyone or anything with access to your user account gets full root."
echo "" echo ""
echo "Passwordless sudo will automatically disable after ${minutes} minutes," if [[ $minutes == "permanent" ]]; then
echo "including if the machine reboots before the deadline." echo "Passwordless sudo will remain enabled across reboots until you disable it."
echo "Run this command again to disable it early." else
echo "Passwordless sudo will automatically disable after ${minutes} minutes,"
echo "including if the machine reboots before the deadline."
fi
echo "Run this command again to disable it."
echo "" echo ""
if /usr/bin/gum confirm "Enable passwordless sudo for ${minutes} minutes? This is a significant security risk!"; then if /usr/bin/gum confirm "Enable passwordless sudo $duration? This is a significant security risk!"; then
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes" /usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
echo "" echo ""
echo "Passwordless sudo has been ENABLED. It will automatically disable in ${minutes} minutes." show_enabled
else else
echo "Aborted. No changes made." echo "Aborted. No changes made."
fi fi
+1 -1
View File
@@ -6,7 +6,7 @@
nohup bash -c "sleep 2 && uwsm stop" >/dev/null 2>&1 & nohup bash -c "sleep 2 && uwsm stop" >/dev/null 2>&1 &
omarchy-osd -i logout -m "Logging out" -d 5000 omarchy-osd -i logout -m "$(omarchy-text "Logging out")" -d 5000
# Now close all windows # Now close all windows
omarchy-hyprland-window-close-all omarchy-hyprland-window-close-all
+1 -1
View File
@@ -8,7 +8,7 @@
# scope cannot terminate it before it runs. # scope cannot terminate it before it runs.
systemd-run --user --collect --quiet --on-active="2s" --timer-property=AccuracySec=100ms systemctl reboot --no-wall || exit 1 systemd-run --user --collect --quiet --on-active="2s" --timer-property=AccuracySec=100ms systemctl reboot --no-wall || exit 1
omarchy-osd -i reboot -m "Rebooting" -d 5000 omarchy-osd -i reboot -m "$(omarchy-text "Rebooting")" -d 5000
omarchy-state clear re*-required omarchy-state clear re*-required
+2 -2
View File
@@ -7,7 +7,7 @@
if [[ ${1:-} == "--inhibited" ]]; then if [[ ${1:-} == "--inhibited" ]]; then
# Close windows in the background so they cannot delay power-off. # Close windows in the background so they cannot delay power-off.
( (
omarchy-osd -i shutdown -m "Shutting down" -d 5000 omarchy-osd -i shutdown -m "$(omarchy-text "Shutting down")" -d 5000
omarchy-state clear re*-required omarchy-state clear re*-required
omarchy-hyprland-window-close-all omarchy-hyprland-window-close-all
) & ) &
@@ -21,7 +21,7 @@ elif [[ ${1:-} == "--inhibit" ]]; then
--who=Omarchy --why="Shutdown in progress" --mode=block \ --who=Omarchy --why="Shutdown in progress" --mode=block \
"$0" --inhibited || { "$0" --inhibited || {
status=$? status=$?
omarchy-notification-send -u critical "Shutdown failed" "Could not complete shutdown. Please try again." omarchy-notification-send -u critical "$(omarchy-text "Shutdown failed")" "$(omarchy-text "Could not complete shutdown. Please try again.")"
exit "$status" exit "$status"
} }
else else
+2 -2
View File
@@ -98,8 +98,8 @@ report_unsecured() {
"$1" >&2 "$1" >&2
omarchy-notification-send -u critical -g 󰌾 \ omarchy-notification-send -u critical -g 󰌾 \
"Screen did not lock before suspend" \ "$(omarchy-text "Screen did not lock before suspend")" \
"The session was left unlocked ($1)." >/dev/null 2>&1 || true "$(omarchy-text "The session was left unlocked (%1)." "$1")" >/dev/null 2>&1 || true
exit 1 exit 1
} }
+1 -1
View File
@@ -59,7 +59,7 @@ claim_path() {
announce() { announce() {
local path="$1" local path="$1"
local name="${path##*/}" local name="${path##*/}"
local args=("Received $name" "Saved to ${dir/#$HOME/~}" -u critical) local args=("$(omarchy-text "Received %1" "$name")" "$(omarchy-text "Saved to %1" "${dir/#$HOME/~}")" -u critical)
case "${name,,}" in case "${name,,}" in
*.png | *.jpg | *.jpeg | *.gif | *.webp | *.avif | *.bmp | *.tif | *.tiff) *.png | *.jpg | *.jpeg | *.gif | *.webp | *.avif | *.bmp | *.tif | *.tiff)
+4 -4
View File
@@ -27,8 +27,8 @@ if ((${#files[@]} == 0)); then
picked=$(omarchy-file-select --title "Send to $name" --multiple) || status=$? picked=$(omarchy-file-select --title "Send to $name" --multiple) || status=$?
if ((${status:-0} > 1)); then if ((${status:-0} > 1)); then
omarchy-notification-send -g "󰒊" -u critical "Could not send to $name" \ omarchy-notification-send -g "󰒊" -u critical "$(omarchy-text "Could not send to %1" "$name")" \
"The file chooser did not open" "$(omarchy-text "The file chooser did not open")"
exit 1 exit 1
fi fi
@@ -43,8 +43,8 @@ else
fi fi
if error=$(tailscale file cp --update-interval=0 -- "${files[@]}" "$machine:" 2>&1); then if error=$(tailscale file cp --update-interval=0 -- "${files[@]}" "$machine:" 2>&1); then
omarchy-notification-send -g "󰒊" "Sent to $name" "$what" omarchy-notification-send -g "󰒊" "$(omarchy-text "Sent to %1" "$name")" "$what"
else else
omarchy-notification-send -g "󰒊" -u critical "Could not send to $name" "${error:-Taildrop transfer failed}" omarchy-notification-send -g "󰒊" -u critical "$(omarchy-text "Could not send to %1" "$name")" "${error:-$(omarchy-text "Taildrop transfer failed")}"
exit 1 exit 1
fi fi
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
# omarchy:summary=Print an Omarchy message in the session's language, with %1.. filled from the arguments
# omarchy:args=<english> [args...]
# omarchy:examples=omarchy-text "Sent to %1" phone
# omarchy:hidden=true
set -euo pipefail
if (($# < 1)); then
echo "Usage: omarchy-text <english> [args...]" >&2
exit 1
fi
text=$1
shift
# The shell's table is the one source: one JSON-encoded "english": "chinese" pair per line.
if [[ ${LANG:-} == zh_CN* ]]; then
translated=$(grep -E '^ ".*": ".*",?$' "$OMARCHY_PATH/shell/Commons/I18nZh.js" | sed 's/,$//' | paste -sd, |
sed 's/^/{/; s/$/}/' | jq -r --arg key "$text" '.[$key] // empty')
[[ -z $translated ]] || text=$translated
fi
# One pass over the template, so an argument that holds %2 is printed as given.
out=""
rest=$text
while [[ $rest =~ ^([^%]*)%(.)(.*)$ ]]; do
out+=${BASH_REMATCH[1]}
mark=${BASH_REMATCH[2]}
rest=${BASH_REMATCH[3]}
if [[ $mark == [1-9] ]] && ((mark <= $#)); then
out+=${!mark}
else
out+="%$mark"
fi
done
printf '%s\n' "$out$rest"
+201
View File
@@ -0,0 +1,201 @@
#!/bin/bash
# omarchy:summary=Play the current background's matching theme intro
# omarchy:hidden=true
set -euo pipefail
state_home="$HOME/.local/state"
current_dir="$state_home/omarchy/current"
current_background_link="$current_dir/background"
marker="$state_home/omarchy/background-intro.session-id"
toggle="$state_home/omarchy/toggles/background-intros-off"
video_extensions=(mp4 m4v mov webm mkv avi)
resolve_intro() {
local background
local stem
local extension
local intro
background=$(readlink -f "${1:-$current_background_link}" 2>/dev/null) || return 1
[[ -f $background ]] || return 1
case ${background,,} in
*.jpg | *.jpeg | *.png | *.bmp | *.webp) ;;
*) return 1 ;;
esac
stem=${background##*/}
stem=${stem%.*}
for extension in "${video_extensions[@]}"; do
intro="${background%/*}/intros/$stem.$extension"
if [[ -f $intro ]]; then
printf '%s\n' "$intro"
return 0
fi
done
return 1
}
# Preparation is speculative: the selected staged clip is compared again by OWE.
prepare_theme_intro() {
local theme="$1" background remembered name source intro
local -a names=()
[[ -n $theme && $theme != .* && $theme != */* ]] || return 0
[[ ! -f $toggle ]] && ! animations_off || return 0
owe warm >/dev/null 2>&1 || return 0
[[ $(cat "$current_dir/theme.name" 2>/dev/null) != "$theme" ]] || return 0
remembered=$(cat "$state_home/omarchy/theme-backgrounds/$theme" 2>/dev/null || true)
if [[ ${remembered%/*} == "$current_dir/theme/backgrounds" ]]; then
name=${remembered##*/}
elif [[ -f $remembered ]]; then
background="$remembered"
else
mapfile -d '' -t names < <(
find -L "$HOME/.config/omarchy/backgrounds/$theme" -maxdepth 1 -type f -print0 2>/dev/null | sort -z
)
if (( ${#names[@]} > 0 )); then
background=${names[0]}
else
mapfile -d '' -t names < <(
find -L "$OMARCHY_PATH/themes/$theme/backgrounds" "$HOME/.config/omarchy/themes/$theme/backgrounds" -maxdepth 1 -type f \
\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.bmp' -o -iname '*.webp' \) -printf '%f\0' 2>/dev/null | sort -zu
)
(( ${#names[@]} > 0 )) || return 0
name=${names[0]}
fi
fi
if [[ -n ${name:-} ]]; then
for source in "$HOME/.config/omarchy/themes/$theme" "$OMARCHY_PATH/themes/$theme"; do
if [[ -f $source/backgrounds/$name ]]; then
background="$source/backgrounds/$name"
break
fi
done
fi
[[ -n ${background:-} ]] || return 0
intro=$(resolve_intro "$background" || true)
[[ -z $intro ]] || owe intro-prepare "$intro" >/dev/null 2>&1 || true
}
animations_off() {
[[ $(hyprctl -j getoption animations:enabled 2>/dev/null | jq -r '.bool' 2>/dev/null) == "false" ]]
}
theme_switch=false
intro_prepared=false
expected_background=""
expected_theme=""
case ${1:-} in
"") ;;
--prepare-theme)
prepare_theme_intro "${2:-}"
exit 0
;;
--resolve-only)
resolve_intro "${2:-}" || true
exit 0
;;
--resolve-startup)
if [[ ! -f $toggle ]] && ! animations_off; then
resolve_intro || true
fi
exit 0
;;
--theme-switch)
theme_switch=true
expected_background=$(readlink -f "${2:-}" 2>/dev/null) || exit 1
expected_theme=${3:-}
intro_prepared=${4:-false}
[[ -n $expected_theme ]] || exit 1
;;
*)
echo "Usage: omarchy-theme-bg-boot-intro [--resolve-only [background]|--resolve-startup|--prepare-theme theme|--theme-switch background theme-id [prepared]]" >&2
exit 1
;;
esac
if [[ $theme_switch == false ]]; then
mkdir -p "$(dirname "$marker")"
exec 9>"$state_home/omarchy/background-intro.lock"
flock 9
session_id=${OMARCHY_SESSION_ID:-${HYPRLAND_INSTANCE_SIGNATURE:-}}
[[ -n $session_id ]] || exit 0
if [[ -f $marker && $(<"$marker") == "$session_id" ]]; then
exit 0
fi
marker_staged=$(mktemp "$state_home/omarchy/.background-intro.session-id.XXXXXX")
trap 'rm -f "$marker_staged"' EXIT
printf '%s\n' "$session_id" >"$marker_staged"
mv -f "$marker_staged" "$marker"
trap - EXIT
exec 9>&-
fi
selection_current() {
[[ $(readlink -f "$current_background_link") == "$expected_background" &&
$(stat -Lc '%d:%i' "$current_dir/theme" 2>/dev/null) == "$expected_theme" ]]
}
if [[ $theme_switch == true ]] && ! selection_current; then
exit 0
fi
if [[ -f $toggle ]] || animations_off; then
[[ $theme_switch == false ]] || exit 1
exit 0
fi
intro=$(resolve_intro || true)
if [[ -z $intro ]]; then
[[ $theme_switch == false ]] || exit 1
exit 0
fi
if [[ $theme_switch == true && $intro_prepared != "true" ]]; then
owe refresh >/dev/null || exit 1
elif [[ $theme_switch == false ]]; then
deadline=$((SECONDS + 5))
while ! timeout 1 owe status >/dev/null 2>&1; do
if (( SECONDS >= deadline )); then
exit 0
fi
sleep 0.2
done
(( SECONDS < deadline )) || exit 0
fi
# The asynchronous outgoing cover must reach the compositor before playback
# can hide the shell background. Older shells have no readiness method.
if [[ $theme_switch == true ]]; then
deadline=$((SECONDS + 3))
while true; do
cover_status=$(omarchy-shell shell themeIntroCoverStatus "$expected_theme" 2>/dev/null || true)
case $cover_status in
loading)
(( SECONDS < deadline )) || exit 1
sleep 0.01
;;
error) exit 1 ;;
superseded) exit 0 ;;
*) break ;;
esac
done
fi
if [[ $theme_switch == true ]] && ! selection_current; then
exit 0
fi
# A selection or setting changed while OWE was starting no longer permits this intro.
if [[ ! -f $toggle && $intro == "$(resolve_intro || true)" ]] && ! animations_off; then
if [[ $theme_switch == true && $intro_prepared == "true" ]]; then
owe intro --start first-frame --refresh "$intro"
else
owe intro --start first-frame "$intro"
fi
elif [[ $theme_switch == true ]]; then
exit 1
fi
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
# omarchy:summary=Enable or disable background intros
# omarchy:args=<enable|disable|toggle|status>
# omarchy:examples=omarchy theme bg intro toggle | omarchy theme bg intro disable
set -euo pipefail
toggle="$HOME/.local/state/omarchy/toggles/background-intros-off"
case ${1:-} in
enable)
rm -f "$toggle"
omarchy-notification-send "$(omarchy-text "Background Intros")" "$(omarchy-text "Enabled")"
;;
disable)
mkdir -p "$(dirname "$toggle")"
touch "$toggle"
omarchy-notification-send "$(omarchy-text "Background Intros")" "$(omarchy-text "Disabled")"
;;
toggle)
if [[ -f $toggle ]]; then
omarchy-theme-bg-intro enable
else
omarchy-theme-bg-intro disable
fi
;;
status)
[[ ! -f $toggle ]]
;;
*)
echo "Usage: omarchy-theme-bg-intro <enable|disable|toggle|status>" >&2
exit 1
;;
esac
+1 -1
View File
@@ -17,7 +17,7 @@ mapfile -d '' -t BACKGROUNDS < <(
TOTAL=${#BACKGROUNDS[@]} TOTAL=${#BACKGROUNDS[@]}
if (( TOTAL == 0 )); then if (( TOTAL == 0 )); then
omarchy-notification-send "No background was found for theme" -t 2000 omarchy-notification-send "$(omarchy-text "No background was found for theme")" -t 2000
else else
# Get current background from symlink # Get current background from symlink
if [[ -L $CURRENT_BACKGROUND_LINK ]]; then if [[ -L $CURRENT_BACKGROUND_LINK ]]; then
+1 -1
View File
@@ -35,5 +35,5 @@ fi
# Now remove the theme directory for THEME_NAME # Now remove the theme directory for THEME_NAME
rm -rf "$THEME_PATH" rm -rf "$THEME_PATH"
omarchy-notification-send "Theme removed" "$THEME_NAME" || true omarchy-notification-send "$(omarchy-text "Theme removed")" "$THEME_NAME" || true
echo "Removed $THEME_NAME" echo "Removed $THEME_NAME"
+85 -7
View File
@@ -50,6 +50,13 @@ shell_ipc() {
timeout 2 omarchy-shell "$@" >/dev/null 2>&1 timeout 2 omarchy-shell "$@" >/dev/null 2>&1
} }
wait_for_theme_intro() {
local method="$1" token="$2" pending="$3" deadline=$((SECONDS + 3))
while (( SECONDS < deadline )) && [[ $(omarchy-shell shell "$method" "$token" 2>/dev/null) == "$pending" ]]; do
sleep 0.02
done
}
is_video_path() { is_video_path() {
[[ ${1,,} =~ \.(mp4|m4v|mov|webm|mkv|avi)$ ]] [[ ${1,,} =~ \.(mp4|m4v|mov|webm|mkv|avi)$ ]]
} }
@@ -70,10 +77,18 @@ snapshot_background_path() {
} }
snapshot_current_background() { snapshot_current_background() {
local current_background local current_background snapshot
current_background=$(readlink -f "$CURRENT_BACKGROUND_LINK" 2>/dev/null || true) # An interrupted intro is still moving; its final still would flash as a
snapshot_background_path "$current_background" "previous" # cover. Capture the frame already playing instead, when OWE supports it.
mkdir -p "$BACKGROUND_TRANSITION_CACHE"
snapshot="$BACKGROUND_TRANSITION_CACHE/previous-$$.ppm"
if owe render "$(jq -nc --arg path "$snapshot" '{cmd: "snapshot", path: $path}')" >/dev/null 2>&1; then
echo "$snapshot"
else
current_background=$(readlink -f "$CURRENT_BACKGROUND_LINK" 2>/dev/null || true)
snapshot_background_path "$current_background" "previous"
fi
} }
theme_background_state_file() { theme_background_state_file() {
@@ -171,17 +186,48 @@ set_theme_background_link() {
} }
set_theme_background() { set_theme_background() {
local new_background new_background_snapshot local new_background new_background_snapshot theme_id intro_prepared=false
if [[ -z $CHOSEN_THEME_BACKGROUND || ! -f $CHOSEN_THEME_BACKGROUND ]]; then if [[ -z $CHOSEN_THEME_BACKGROUND || ! -f $CHOSEN_THEME_BACKGROUND ]]; then
if ! choose_theme_background; then if ! choose_theme_background; then
omarchy-notification-send "No background was found for theme" -t 2000 omarchy-notification-send "$(omarchy-text "No background was found for theme")" -t 2000
shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true
return return
fi fi
fi fi
new_background="$CHOSEN_THEME_BACKGROUND" new_background="$CHOSEN_THEME_BACKGROUND"
if [[ -n $THEME_BACKGROUND_INTRO ]]; then
if [[ -n $INTRO_PREPARE_PID ]] && wait "$INTRO_PREPARE_PID"; then
intro_prepared=true
fi
theme_id=$(stat -Lc '%d:%i' "$CURRENT_THEME_PATH")
THEME_INTRO_TOKEN="$theme_id"
shell_ipc shell prepareThemeIntro "$OLD_BACKGROUND_SNAPSHOT" "$theme_id" "$colors_payload" "$shell_payload" || \
shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true
ln -nsf "$new_background" "$CURRENT_BACKGROUND_LINK"
(
if ! omarchy-theme-bg-boot-intro --theme-switch "$new_background" "$theme_id" "$intro_prepared"; then
if [[ $(readlink -f "$CURRENT_BACKGROUND_LINK") == "$(readlink -f "$new_background")" &&
$(stat -Lc '%d:%i' "$CURRENT_THEME_PATH" 2>/dev/null) == "$theme_id" ]]; then
shell_ipc background setInstant "$new_background" || true
fi
fi
shell_ipc shell finishThemeIntro "$theme_id" || true
wait_for_theme_intro themeIntroStatus "$theme_id" "pending"
# Reloading Hyprland interrupts video presentation. Wait for playback,
# then serialize with theme selection so an old intro cannot reload
# the compositor during the next theme's video.
(
flock 9
if [[ $(stat -Lc '%d:%i' "$CURRENT_THEME_PATH" 2>/dev/null) == "$theme_id" ]]; then
omarchy-restart-hyprctl
fi
) 9>"$THEME_SET_LOCK"
[[ -z $OLD_BACKGROUND_SNAPSHOT ]] || rm -f "$OLD_BACKGROUND_SNAPSHOT"
) 9>&- &
return
fi
new_background_snapshot="" new_background_snapshot=""
if [[ -n $PREPARED_BACKGROUND_SNAPSHOT && $PREPARED_BACKGROUND == "$new_background" ]]; then if [[ -n $PREPARED_BACKGROUND_SNAPSHOT && $PREPARED_BACKGROUND == "$new_background" ]]; then
new_background_snapshot="$PREPARED_BACKGROUND_SNAPSHOT" new_background_snapshot="$PREPARED_BACKGROUND_SNAPSHOT"
@@ -337,6 +383,12 @@ fi
exec 9>"$THEME_SET_LOCK" exec 9>"$THEME_SET_LOCK"
flock 9 flock 9
# A current video frame only represents the screen after its opening fade.
# Finish that short reveal before capturing the next outgoing cover.
if [[ $THEME_HEADLESS != "1" && $OMARCHY_THEME_SKIP_BACKGROUND != "1" ]]; then
wait_for_theme_intro themeIntroStatus "$(stat -Lc '%d:%i' "$CURRENT_THEME_PATH" 2>/dev/null)" "pending"
fi
PREVIOUS_THEME_NAME=$(cat "$CURRENT_THEME_NAME_PATH" 2>/dev/null || true) PREVIOUS_THEME_NAME=$(cat "$CURRENT_THEME_NAME_PATH" 2>/dev/null || true)
remember_current_theme_background remember_current_theme_background
@@ -365,11 +417,24 @@ OLD_BACKGROUND_SNAPSHOT=""
PREPARED_BACKGROUND="" PREPARED_BACKGROUND=""
PREPARED_BACKGROUND_SNAPSHOT="" PREPARED_BACKGROUND_SNAPSHOT=""
BACKGROUND_TRANSITION_SNAPSHOTS=true BACKGROUND_TRANSITION_SNAPSHOTS=true
THEME_BACKGROUND_INTRO=""
INTRO_PREPARE_PID=""
THEME_INTRO_TOKEN=""
if [[ $THEME_HEADLESS != "1" && $OMARCHY_THEME_SKIP_BACKGROUND != "1" ]]; then if [[ $THEME_HEADLESS != "1" && $OMARCHY_THEME_SKIP_BACKGROUND != "1" ]]; then
# Resolve the staged choice while the old theme still exists. Video changes # Resolve the staged choice while the old theme still exists. Video changes
# switch directly to the durable path in QML, so neither side needs a copy. # switch directly to the durable path in QML, so neither side needs a copy.
if choose_staged_theme_background; then if choose_staged_theme_background; then
if background_transition_uses_snapshots "$CHOSEN_THEME_BACKGROUND"; then if [[ $PREVIOUS_THEME_NAME != "$THEME_NAME" && ! -f $HOME/.local/state/omarchy/toggles/background-intros-off ]] &&
[[ $(hyprctl -j getoption animations:enabled 2>/dev/null | jq -r '.bool') == "true" ]]; then
THEME_BACKGROUND_INTRO=$(omarchy-theme-bg-boot-intro --resolve-only "$STAGED_THEME_BACKGROUND")
fi
if [[ -n $THEME_BACKGROUND_INTRO ]]; then
OLD_BACKGROUND_SNAPSHOT=$(snapshot_current_background)
# OWE retains the opened clip across the staged directory's rename.
owe intro-prepare "$THEME_BACKGROUND_INTRO" >/dev/null 2>&1 9>&- &
INTRO_PREPARE_PID=$!
shell_ipc shell prepareThemeIntro "$OLD_BACKGROUND_SNAPSHOT" "$(stat -Lc '%d:%i' "$NEXT_THEME_PATH")" "" "" || true
elif background_transition_uses_snapshots "$CHOSEN_THEME_BACKGROUND"; then
OLD_BACKGROUND_SNAPSHOT=$(snapshot_current_background) OLD_BACKGROUND_SNAPSHOT=$(snapshot_current_background)
# Hand the shell the next background now, so it decodes while the # Hand the shell the next background now, so it decodes while the
@@ -389,6 +454,12 @@ fi
# Generate dynamic configs # Generate dynamic configs
omarchy-theme-set-templates omarchy-theme-set-templates
# Replacing the theme's still can cancel an active OWE intro even before the
# background link changes. Present its cover before replacing either path.
if [[ -n $THEME_BACKGROUND_INTRO ]]; then
wait_for_theme_intro themeIntroCoverStatus "$(stat -Lc '%d:%i' "$NEXT_THEME_PATH")" "loading"
fi
# Swap next theme in as current # Swap next theme in as current
rm -rf "$CURRENT_THEME_PATH" rm -rf "$CURRENT_THEME_PATH"
mv "$NEXT_THEME_PATH" "$CURRENT_THEME_PATH" mv "$NEXT_THEME_PATH" "$CURRENT_THEME_PATH"
@@ -418,7 +489,6 @@ flock -u 9
post_theme_commands=( post_theme_commands=(
omarchy-restart-terminal omarchy-restart-terminal
omarchy-restart-hyprctl
omarchy-restart-btop omarchy-restart-btop
omarchy-restart-opencode omarchy-restart-opencode
omarchy-restart-helix omarchy-restart-helix
@@ -437,6 +507,14 @@ post_theme_commands=(
) )
if [[ $THEME_HEADLESS != "1" ]]; then if [[ $THEME_HEADLESS != "1" ]]; then
# Let the wallpaper and bar finish their reveal before app retints compete
# with decoding and the shell's frame rendering. An intro reloads Hyprland
# after playback; ordinary theme changes reload it with the app retints.
if [[ -n $THEME_INTRO_TOKEN ]]; then
wait_for_theme_intro themeIntroStatus "$THEME_INTRO_TOKEN" "pending"
else
post_theme_commands+=(omarchy-restart-hyprctl)
fi
run_parallel "${post_theme_commands[@]}" run_parallel "${post_theme_commands[@]}"
# Call hook on theme set # Call hook on theme set
+7 -2
View File
@@ -29,6 +29,8 @@ if [[ -n $OMARCHY_THEME_SYNC_FROM ]] || ! omarchy-toggle-enabled herdr-theme-syn
fi fi
theme=$(cat "$HOME/.local/state/omarchy/current/theme.name" 2>/dev/null) theme=$(cat "$HOME/.local/state/omarchy/current/theme.name" 2>/dev/null)
control_dir="$XDG_RUNTIME_DIR/omarchy-theme-sync"
mkdir -m 700 -p "$control_dir"
# Runs on the remote machine under a login shell, so OMARCHY_PATH and PATH are set there. # Runs on the remote machine under a login shell, so OMARCHY_PATH and PATH are set there.
# SSH carries no desktop session, so the environment comes from the live Hyprland instance. # SSH carries no desktop session, so the environment comes from the live Hyprland instance.
@@ -66,10 +68,13 @@ read -r -d '' REMOTE_SCRIPT <<'EOF'
} </dev/null } </dev/null
EOF EOF
# One machine at a time, so an SSH agent like 1Password asks for approval at most once. # Reuse each authenticated connection for ten idle minutes, so an SSH agent like
# 1Password does not need to approve every theme change. Connect one machine at a time.
# SSH joins its arguments into a shell command, so the theme travels on stdin as a quoted assignment. # SSH joins its arguments into a shell command, so the theme travels on stdin as a quoted assignment.
for target in $targets; do for target in $targets; do
result=$(printf 'theme=%q from=%q\n%s\n' "$theme" "$(hostname)" "$REMOTE_SCRIPT" | result=$(printf 'theme=%q from=%q\n%s\n' "$theme" "$(hostname)" "$REMOTE_SCRIPT" |
timeout 60 ssh -o BatchMode=yes -o ConnectTimeout=5 "$target" bash -l -s 2>&1 | tail -n 1) timeout 60 ssh -o BatchMode=yes -o ConnectTimeout=5 \
-o ControlMaster=auto -o ControlPersist=600 -o "ControlPath=$control_dir/%C" \
"$target" bash -l -s 2>&1 | tail -n 1)
echo "$(date '+%F %T') $target: ${result:-unreachable}" echo "$(date '+%F %T') $target: ${result:-unreachable}"
done done
+2 -2
View File
@@ -16,7 +16,7 @@ off) omarchy-hyprland-toggle no-animations on ;;
esac esac
if omarchy-hyprland-toggle-enabled no-animations; then if omarchy-hyprland-toggle-enabled no-animations; then
omarchy-notification-send -g 󰕟 "Animations disabled" omarchy-notification-send -g 󰕟 "$(omarchy-text "Animations disabled")"
else else
omarchy-notification-send -g 󰕟 "Animations enabled" omarchy-notification-send -g 󰕟 "$(omarchy-text "Animations enabled")"
fi fi
+2 -2
View File
@@ -11,8 +11,8 @@ omarchy-toggle crash-capture-off
# starting only settles this session; the next login reads the flag itself. # starting only settles this session; the next login reads the flag itself.
if omarchy-toggle-enabled crash-capture-off; then if omarchy-toggle-enabled crash-capture-off; then
systemctl --user stop omarchy-crash-watch.service systemctl --user stop omarchy-crash-watch.service
omarchy-notification-send -g "$CRASH_GLYPH" "Crash capture disabled" omarchy-notification-send -g "$CRASH_GLYPH" "$(omarchy-text "Crash capture disabled")"
else else
systemctl --user start omarchy-crash-watch.service systemctl --user start omarchy-crash-watch.service
omarchy-notification-send -g "$CRASH_GLYPH" "Crash capture enabled" omarchy-notification-send -g "$CRASH_GLYPH" "$(omarchy-text "Crash capture enabled")"
fi fi
+2 -2
View File
@@ -5,7 +5,7 @@
omarchy-toggle screensaver-off omarchy-toggle screensaver-off
if omarchy-toggle-enabled screensaver-off; then if omarchy-toggle-enabled screensaver-off; then
omarchy-notification-send -g 󱄄 "Screensaver disabled" omarchy-notification-send -g 󱄄 "$(omarchy-text "Screensaver disabled")"
else else
omarchy-notification-send -g 󱄄 "Screensaver enabled" omarchy-notification-send -g 󱄄 "$(omarchy-text "Screensaver enabled")"
fi fi
+2 -2
View File
@@ -5,7 +5,7 @@
omarchy-toggle suspend-off omarchy-toggle suspend-off
if omarchy-toggle-enabled suspend-off; then if omarchy-toggle-enabled suspend-off; then
omarchy-notification-send -g 󰒲 "Suspend removed from system menu" omarchy-notification-send -g 󰒲 "$(omarchy-text "Suspend removed from system menu")"
else else
omarchy-notification-send -g 󰒲 "Suspend now available in system menu" omarchy-notification-send -g 󰒲 "$(omarchy-text "Suspend now available in system menu")"
fi fi
+2 -2
View File
@@ -5,7 +5,7 @@
omarchy-toggle herdr-theme-sync omarchy-toggle herdr-theme-sync
if omarchy-toggle-enabled herdr-theme-sync; then if omarchy-toggle-enabled herdr-theme-sync; then
omarchy-notification-send -g 󰸌 "Herdr theme sync enabled" omarchy-notification-send -g 󰸌 "$(omarchy-text "Herdr theme sync enabled")"
else else
omarchy-notification-send -g 󰸌 "Herdr theme sync disabled" omarchy-notification-send -g 󰸌 "$(omarchy-text "Herdr theme sync disabled")"
fi fi
+3 -3
View File
@@ -193,14 +193,14 @@ main() {
output=$(output_path "$input" "$format" "$resolution") output=$(output_path "$input" "$format" "$resolution")
if [[ $type == "video" ]]; then if [[ $type == "video" ]]; then
omarchy-notification-send -g  "Transcoding video…" "$(basename -- "$input") to $format ($resolution)" omarchy-notification-send -g  "$(omarchy-text "Transcoding video…")" "$(omarchy-text "%1 to %2 (%3)" "$(basename -- "$input")" "$format" "$resolution")"
transcode_video "$input" "$format" "$resolution" "$output" transcode_video "$input" "$format" "$resolution" "$output"
copy_to_clipboard "$output" copy_to_clipboard "$output"
omarchy-notification-send -g  "Transcoded to $resolution $format" "Saved and copied to clipboard." omarchy-notification-send -g  "$(omarchy-text "Transcoded to %1 %2" "$resolution" "$format")" "Saved and copied to clipboard."
else else
transcode_picture "$input" "$format" "$resolution" "$output" transcode_picture "$input" "$format" "$resolution" "$output"
copy_to_clipboard "$output" copy_to_clipboard "$output"
omarchy-notification-send -g  "Transcoded to $resolution $format" "Saved and copied to clipboard." omarchy-notification-send -g  "$(omarchy-text "Transcoded to %1 %2" "$resolution" "$format")" "Saved and copied to clipboard."
fi fi
} }
+1 -1
View File
@@ -37,5 +37,5 @@ icon_name=$(printf '%s\n' "$APP_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^[:
rm -f "$DESKTOP_DIR/$APP_NAME.desktop" rm -f "$DESKTOP_DIR/$APP_NAME.desktop"
rm -f "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png" rm -f "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png"
if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then
omarchy-notification-send -g  "TUI removed" "$APP_NAME" omarchy-notification-send -g  "$(omarchy-text "TUI removed")" "$APP_NAME"
fi fi
+1 -1
View File
@@ -127,7 +127,7 @@ if [[ ${1:-} == "-y" ]] || omarchy-update-confirm; then
omarchy-update-system-pkgs omarchy-update-system-pkgs
omarchy-migrate omarchy-migrate
omarchy-update-orphan-pkgs omarchy-update-orphan-pkgs -y
omarchy-update-analyze-logs omarchy-update-analyze-logs
omarchy-update-status omarchy-update-status
+5 -3
View File
@@ -1,6 +1,8 @@
#!/bin/bash #!/bin/bash
# omarchy:summary=Review and optionally remove orphaned system packages after updates # omarchy:summary=Review and optionally remove orphaned system packages after updates
# omarchy:args=[-y]
# omarchy:examples=omarchy update orphan-pkgs | omarchy update orphan-pkgs -y
# omarchy:requires-sudo=true # omarchy:requires-sudo=true
set -e set -e
@@ -12,18 +14,18 @@ echo -e "\e[32m\nOrphan system packages\e[0m"
printf ' %s\n' "${orphans[@]}" printf ' %s\n' "${orphans[@]}"
echo echo
if [[ ! -t 0 || ! -t 1 ]]; then if [[ ${1:-} != "-y" && ( ! -t 0 || ! -t 1 ) ]]; then
echo "${#orphans[@]} orphaned package(s) found. Re-run omarchy-update-orphan-pkgs in a terminal to review/remove them." echo "${#orphans[@]} orphaned package(s) found. Re-run omarchy-update-orphan-pkgs in a terminal to review/remove them."
echo echo
exit 0 exit 0
fi fi
if ! gum confirm --default=false "Remove ${#orphans[@]} orphaned package(s)?"; then if [[ ${1:-} != "-y" ]] && ! gum confirm --default=false "Remove ${#orphans[@]} orphaned package(s)?"; then
echo "Keeping orphaned packages." echo "Keeping orphaned packages."
echo echo
exit 0 exit 0
fi fi
echo -e "\e[32m\nRemoving orphan system packages\e[0m" echo -e "\e[32m\nRemoving orphan system packages\e[0m"
sudo pacman -Rns "${orphans[@]}" omarchy-pkg-drop "${orphans[@]}"
echo echo
+2
View File
@@ -1802,6 +1802,8 @@ copy_missing_config_defaults() {
while IFS= read -r -d '' source_path; do while IFS= read -r -d '' source_path; do
rel="${source_path#$source_dir/}" rel="${source_path#$source_dir/}"
is_retired_config_file "$rel" && continue is_retired_config_file "$rel" && continue
# The input migration preserves working shortcuts in existing IME profiles.
[[ $rel == "fcitx5/config" ]] && continue
target_path="$target_dir/$rel" target_path="$target_dir/$rel"
[[ -e $target_path || -L $target_path ]] && continue [[ -e $target_path || -L $target_path ]] && continue
-26
View File
@@ -1,26 +0,0 @@
#!/bin/bash
# omarchy:summary=Install and configure Voxtype dictation
# omarchy:requires-sudo=true
set -e
# Install voxtype and configure it for use.
if gum confirm "Install Voxtype + AI model (~150MB) to enable dictation?"; then
omarchy-pkg-add wtype voxtype-bin
# Setup voxtype
mkdir -p ~/.config/voxtype
cp "$OMARCHY_PATH/default/voxtype/config.toml" ~/.config/voxtype/
voxtype setup --download --no-post-install
if omarchy-hw-vulkan; then
voxtype setup gpu --enable || true
fi
voxtype setup systemd
hyprctl reload >/dev/null
omarchy-restart-shell
omarchy-notification-send -g  "Voxtype Dictation Ready" "Hold F9 to dictate (or toggle with Super + Ctrl + X)." -t 10000
fi
-13
View File
@@ -1,13 +0,0 @@
#!/bin/bash
# omarchy:summary=Stream voxtype --follow status as bar-friendly JSON
if omarchy-cmd-missing voxtype; then
echo '{"alt": "", "class": "idle", "tooltip": ""}'
exit 0
fi
# Keep the status follower as the direct child of the bar. Quickshell
# clean up their direct child when they restart; a shell pipeline/coproc would
# leave `voxtype status --follow` orphaned under the user systemd instance.
exec setpriv --pdeathsig TERM voxtype status --follow --extended --format json
+1 -1
View File
@@ -56,6 +56,6 @@ rm -f "${desktop_file:-$DESKTOP_DIR/$APP_NAME.desktop}"
rm -f "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png" rm -f "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png"
if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then
omarchy-notification-send -g  "Web app removed" "$APP_NAME" omarchy-notification-send -g  "$(omarchy-text "Web app removed")" "$APP_NAME"
fi fi
update-desktop-database "$DESKTOP_DIR" &>/dev/null update-desktop-database "$DESKTOP_DIR" &>/dev/null
+1 -1
View File
@@ -1414,7 +1414,7 @@ launch_windows() {
if ! priv up_wait; then if ! priv up_wait; then
echo "❌ Failed to start Windows VM!" echo "❌ Failed to start Windows VM!"
echo " Try checking: omarchy-windows-vm status" echo " Try checking: omarchy-windows-vm status"
omarchy-notification-send -u critical "Windows VM" "Failed to start Windows VM" omarchy-notification-send -u critical "$(omarchy-text "Windows VM")" "$(omarchy-text "Failed to start Windows VM")"
exit 1 exit 1
fi fi
+1 -1
View File
@@ -1,5 +1,5 @@
--ozone-platform=wayland --ozone-platform=wayland
--ozone-platform-hint=wayland --ozone-platform-hint=wayland
--password-store=gnome-libsecret --password-store=gnome-libsecret
--enable-features=TouchpadOverscrollHistoryNavigation --enable-features=TouchpadOverscrollHistoryNavigation,OverlayScrollbar
--load-extension=/usr/share/omarchy/default/chromium/extensions/copy-url,/usr/share/omarchy/default/chromium/extensions/yt-dlp,/usr/share/omarchy/default/chromium/extensions/whatsapp-slim --load-extension=/usr/share/omarchy/default/chromium/extensions/copy-url,/usr/share/omarchy/default/chromium/extensions/yt-dlp,/usr/share/omarchy/default/chromium/extensions/whatsapp-slim
+3
View File
@@ -0,0 +1,3 @@
# Keep Pinyin offline without prompting to enable cloud prediction.
FirstRun=False
CloudPinyinEnabled=False
+2
View File
@@ -0,0 +1,2 @@
[TriggerKey]
0=Super+semicolon
+1
View File
@@ -0,0 +1 @@
Allow Overriding System XKB Settings=False
+20
View File
@@ -0,0 +1,20 @@
# Leave Ctrl+Space to tmux/Herdr and the Super shortcuts to the desktop.
[Hotkey/TriggerKeys]
0=Control+Shift+space
1=Zenkaku_Hankaku
2=Hangul
[Hotkey/ActivateKeys]
0=Henkan
[Hotkey/DeactivateKeys]
0=Muhenkan
[Hotkey/AltTriggerKeys]
[Hotkey/EnumerateGroupForwardKeys]
[Hotkey/EnumerateGroupBackwardKeys]
[Behavior]
ActiveByDefault=False
+1
View File
@@ -0,0 +1 @@
superwhisper
@@ -11,7 +11,7 @@
# #
# To put it into use, remove .sample from this file name. # To put it into use, remove .sample from this file name.
# Example: add an Include line above [core]. # Example: add an Include line above the first repository, [omarchy].
# Maintain the repo entries in /etc/pacman.d/custom-repos.conf yourself. # Maintain the repo entries in /etc/pacman.d/custom-repos.conf yourself.
CONF=/etc/pacman.conf CONF=/etc/pacman.conf
@@ -21,4 +21,4 @@ MARKER="Include = $SNIPPET"
[[ -r $SNIPPET ]] || exit 0 [[ -r $SNIPPET ]] || exit 0
grep -qxF "$MARKER" "$CONF" && exit 0 grep -qxF "$MARKER" "$CONF" && exit 0
sudo sed -i "0,/^\[core\]/s||$MARKER\n\n[core]|" "$CONF" sudo sed -i "0,/^\[omarchy\]/s||$MARKER\n\n[omarchy]|" "$CONF"
@@ -1,6 +1,298 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd"> <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig> <fontconfig>
<!-- CJK: fold the language tag onto one tag per Noto CJK variant -->
<match target="pattern">
<test name="lang" qual="first" compare="eq">
<string>zh</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="eq">
<string>zh-hant</string>
</test>
<edit name="lang" mode="assign">
<string>zh-tw</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hant-hk</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hant-mo</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hant</string>
</test>
<edit name="lang" mode="assign">
<string>zh-tw</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hans</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-sg</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-mo</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="eq">
<string>yue</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>yue-hans</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>yue-cn</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>yue</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<!-- Then append that variant after the generic family it falls back from -->
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-cn</string>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK SC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-cn</string>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK SC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-cn</string>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK SC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hk</string>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK HK</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hk</string>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK HK</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hk</string>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK HK</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-tw</string>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK TC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-tw</string>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK TC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-tw</string>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK TC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ja</string></langset>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK JP</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ja</string></langset>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK JP</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ja</string></langset>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK JP</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ko</string></langset>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK KR</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ko</string></langset>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK KR</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ko</string></langset>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK KR</string>
</edit>
</match>
<match target="pattern"> <match target="pattern">
<test name="family" qual="any"> <test name="family" qual="any">
<string>sans-serif</string> <string>sans-serif</string>
+8 -4
View File
@@ -6,12 +6,16 @@ hl.layer_rule({ match = { namespace = "omarchy-bar" }, no_anim = true, animation
-- Launcher, image selector, emojis, clipboard overlays, the OSD, reminders, the -- Launcher, image selector, emojis, clipboard overlays, the OSD, reminders, the
-- Wi-Fi QR code and keyboard-driven panels should pop without compositor layer -- Wi-Fi QR code and keyboard-driven panels should pop without compositor layer
-- animations. The overlays stay mapped between opens and grow from a parked -- animations. Overlays open as soon as their scale is ready, without a
-- 1x1 when shown, which Hyprland would otherwise animate as a slide in from -- compositor fade or slide. Panels keep their own QML opacity transition
-- the corner. Panels keep their own QML opacity transition for normal -- for normal open/close, and skip it for panel handoff.
-- open/close, and skip it for panel handoff.
hl.layer_rule({ match = { namespace = "^(omarchy-menu|omarchy-image-selector|omarchy-emojis|omarchy-clipboard|omarchy-keyboard-panel|omarchy-osd|omarchy-reminders|omarchy-network-qr)$" }, no_anim = true, animation = "none" }) hl.layer_rule({ match = { namespace = "^(omarchy-menu|omarchy-image-selector|omarchy-emojis|omarchy-clipboard|omarchy-keyboard-panel|omarchy-osd|omarchy-reminders|omarchy-network-qr)$" }, no_anim = true, animation = "none" })
-- A boot intro hands the desktop between OWE's video layer and the shell's
-- background layer. The media fades itself, so a compositor fade on either
-- layer only dips the screen toward the empty desktop during the handoff.
hl.layer_rule({ match = { namespace = "^(omarchy-background|owe-background)$" }, no_anim = true, animation = "none" })
-- Dev gallery is the main shell workbench; open it maximized like -- Dev gallery is the main shell workbench; open it maximized like
-- SUPER+ALT+F so component previews have the whole workspace. -- SUPER+ALT+F so component previews have the whole workspace.
o.window({ class = "^org.quickshell$", title = "^Omarchy shell – dev gallery$" }, { maximize = true }) o.window({ class = "^org.quickshell$", title = "^Omarchy shell – dev gallery$" }, { maximize = true })
+8
View File
@@ -1,4 +1,12 @@
require("default.hypr.startup-cursor")
hl.on("hyprland.start", function() hl.on("hyprland.start", function()
-- Applications inherit the user's cursor; only the compositor starts blank.
if omarchy_startup_cursor_pending then
hl.env("XCURSOR_PATH", omarchy_startup_cursor.path)
hl.env("XCURSOR_THEME", omarchy_startup_cursor.xcursor)
end
-- Slow app launch fix -- set systemd vars before starting session services. -- Slow app launch fix -- set systemd vars before starting session services.
hl.exec_cmd("systemctl --user import-environment $(env | cut -d'=' -f 1)") hl.exec_cmd("systemctl --user import-environment $(env | cut -d'=' -f 1)")
hl.exec_cmd("dbus-update-activation-environment --systemd --all") hl.exec_cmd("dbus-update-activation-environment --systemd --all")
+9
View File
@@ -0,0 +1,9 @@
if o.shell_succeeds("omarchy-default-dictation") then
o.bind("SUPER + CTRL + X", "Toggle dictation", "omarchy-dictation toggle")
o.bind("F9", "Start dictation (push-to-talk)", "omarchy-dictation start")
o.bind("F9", "Stop dictation (push-to-talk)", "omarchy-dictation stop", { release = true })
-- A modifier's mask changes between its press and release. Match the keysym
-- independently of that mask; AltGr layouts use a different keysym.
o.bind("ALT + Alt_R", "Start dictation (push-to-talk)", "omarchy-dictation start", { ignore_mods = true })
o.bind("ALT + Alt_R", "Stop dictation (push-to-talk)", "omarchy-dictation stop", { release = true, ignore_mods = true })
end
+66
View File
@@ -0,0 +1,66 @@
local paths = require("default.hypr.paths")
-- Same terminal tag test as clipboard.lua.
local function active_window_is_terminal()
local window = hl.get_active_window()
if not window then
return false
end
for _, tag in ipairs(window.tags or {}) do
if tag:gsub("%*$", "") == "terminal" then
return true
end
end
return false
end
-- Read on demand so adding an engine in a live desktop needs no reload.
local function has_input_method()
local file = io.open(paths.config_home .. "/fcitx5/profile", "r")
if not file then return false end
local in_item, found = false, false
for line in file:lines() do
if line:match("^%[") then
in_item = line:match("^%[Groups/%d+/Items/%d+%]$") ~= nil
elseif in_item then
local name = line:match("^Name=(.+)$")
if name and not name:match("^keyboard%-") then found = true end
end
end
file:close()
return found
end
-- Preserve custom Ctrl + Space triggers. Without a
-- [Hotkey/TriggerKeys] list in the config, fcitx5 uses its default, which has
-- Ctrl + Space.
local function fcitx5_toggles_on_ctrl_space()
local file = io.open(paths.config_home .. "/fcitx5/config", "r")
if not file then
return true
end
local in_triggers, has_triggers, toggles = false, false, false
for line in file:lines() do
if line:match("^%[") then
in_triggers = line == "[Hotkey/TriggerKeys]"
has_triggers = has_triggers or in_triggers
elseif in_triggers and line:match("^%d+=Control%+space$") then
toggles = true
end
end
file:close()
return toggles or not has_triggers
end
-- Pass the tmux/Herdr prefix through and return to direct input, so the next
-- key reaches the multiplexer instead of the engine's composition.
o.bind("CTRL + SPACE", "Direct input for the terminal prefix", function()
if active_window_is_terminal() and has_input_method() and not fcitx5_toggles_on_ctrl_space() then
-- Wait for the local controller reply before dispatching the next key.
o.shell_succeeds("timeout 0.3 fcitx5-remote --check -c")
end
end, { non_consuming = true })
+43
View File
@@ -0,0 +1,43 @@
-- Defaults for Japanese (JIS) keyboards, which only apply when the installed
-- layout is jp. See default/hypr/keyboard.lua.
local keyboard = require("default.hypr.keyboard")
if not keyboard.jis() then
return
end
-- Same down/up split as the universal clipboard shortcuts in clipboard.lua.
local function send_shortcut_once(mods, key)
hl.dispatch(hl.dsp.send_key_state({ mods = mods, key = key, state = "down" }))
hl.timer(function()
hl.dispatch(hl.dsp.send_key_state({ mods = mods, key = key, state = "up" }))
end, { timeout = 50, type = "oneshot" })
end
local function active_window_class()
local window = hl.get_active_window()
return ((window and window.class) or ""):lower()
end
-- On JIS, + is Shift + ;, so apps see Ctrl + Shift + semicolon and never zoom
-- in. Hand them Ctrl + keypad plus instead, which Chromium, Electron apps,
-- VS Code, and Firefox read as zoom in. Obsidian only listens for Ctrl + ^,
-- which JIS types without Shift.
local zoom_in_overrides = {
obsidian = { "CTRL", "asciicircum" },
}
o.bind("CTRL + SHIFT + semicolon", "Zoom in (JIS + key)", function()
local class = active_window_class()
for pattern, chord in pairs(zoom_in_overrides) do
if class:find(pattern, 1, true) then
send_shortcut_once(chord[1], chord[2])
return
end
end
send_shortcut_once("CTRL", "KP_Add")
end)
Loaded 100 of 411 files, more files were not shown because too many files have changed in this diff. Show more