Compare commits

...
Author SHA1 Message Date
ZacharyZhang-NY 75d9ec05e9 T36 passed after two codex rounds 2026-10-12 03:07:13 -04:00
ZacharyZhang-NY 45a27629de Plan: the open PRs 2026-10-12 02:58:23 -04:00
ZacharyZhang-NY d6bea2aeba Check off T35 after two codex rounds 2026-10-12 02:57:07 -04:00
ZacharyZhang-NY 70fb355250 Plan: the full fontconfig test passes on the T34 VM 2026-10-12 02:57:07 -04:00
ZacharyZhang-NY c9494b27ec Plan: acceptance checklist for T28–T34 2026-10-12 02:57:07 -04:00
ZacharyZhang-NY a77ea36fe5 Plan: T34 evidence 2026-10-12 02:57:07 -04:00
ZacharyZhang-NY e9a3f13b9a Check off T33 after one codex round 2026-10-12 02:57:07 -04:00
ZacharyZhang-NY e2dab6ef14 Plan: the ISO PR branch and omarchy-pkgs 2026-10-12 02:57:07 -04:00
ZacharyZhang-NY 7dbaa4914f Check off T32 after two codex rounds 2026-10-12 02:57:07 -04:00
ZacharyZhang-NY 1dbe97d9b7 Refuse the China region off x86 until #13690 renders ARM pacman
Integration only.
2026-10-12 02:57:07 -04:00
ZacharyZhang-NY 84450ffc19 Plan: the PR branches and the integration branch 2026-10-12 02:57:07 -04:00
ZacharyZhang-NY b4de1c0d34 Render x86 pacman through #13690 on refresh, and on install only with a region
Integration only, until omarchy #13690 rebases onto the ARM pacman
templates. pacman-templates-test still expects x86 refresh to copy
the templates directly.
2026-10-12 02:57:07 -04:00
ZacharyZhang-NY c90afe24dc Carry the OmarchyCN task list 2026-10-12 02:57:07 -04:00
ZacharyZhang-NY f5ab724a1c Word Setup > Region and China Apps in Chinese
Needs both the Chinese language and the region switch: the Chinese
labels for Region and China Apps, the region switch's logout prompt
through omarchy-text, and the language half of manual/52-china.md.
2026-10-12 02:57:07 -04:00
ZacharyZhang-NY 096982fe69 Merge cn-region-switch (with omarchy #13690)
# Conflicts:
#	AGENTS.md
#	bin/omarchy-refresh-pacman
#	default/omarchy/omarchy-menu.jsonc
#	install/post-install/pacman.sh
#	shell/plugins/menu/MenuModel.js
#	test/shell.d/menu-test.sh
2026-10-12 02:57:00 -04:00
ZacharyZhang-NY 96e2effbe1 Merge chinese-language 2026-10-12 02:56:55 -04:00
ZacharyZhang-NY 846f7d491f Merge cjk-variant-selection 2026-10-12 02:56:55 -04:00
ZacharyZhang-NY a9b39710a2 Add Simplified Chinese as a system language
The installer asks for the language before the keyboard, and
Setup > Language (omarchy language en|zh) switches it later. Under
zh_CN the menu reads its labels from omarchy-menu.zh-cn.jsonc, the
shell from I18nZh.js through I18n.tr, and notifications are worded
by their sender through omarchy-text, which reads the same table.
User text is never translated.
2026-10-12 02:56:40 -04:00
Ryan Hughes defbf90e89 Fully revert USB and Thunderbolt device approval (#15091)
* Revert "Merge pull request #11874 from acrogenesis/feature/usb-device-authorization"

This reverts commit 5cb31317b4, reversing
changes made to 9d48fc402e.

* Undo USB and Thunderbolt device approval on machines that ran it

* Verify boot rebuilds, recover Thunderbolt journals and prove USBGuard ownership in the removal migration

* Read the boot partition from ESP_PATH and stop on failed Bolt reads in the removal migration

* Recover Bolt's original mode from the setup journal in the removal migration
2026-10-12 01:20:01 -04:00
ZacharyZhang-NY 16faf06fcf Switch the region under Setup > Region, and offer China Apps
omarchy region cn|global switches an installed machine: cn upgrades,
trusts ArchLinuxCN through the rendered China config, records the
marker and links the developer mirrors, then refreshes pacman;
global undoes only what Omarchy made. The developer mirrors reach
new sessions only, so a terminal switch offers to log out.

Install > China Apps, shown only in China, installs WeChat, QQ,
Feishu, DingTalk, Tencent Meeting and WPS Office from the AUR and
sets up Tencent Docs, Yuque and Shimo as web apps.

manual/52-china.md describes the China region.
2026-10-12 00:29:40 -04:00
ZacharyZhang-NY 466c2ca14a Point package managers at China mirrors on cn installs
npm, PyPI and uv, Go, rustup and crates.io, RubyGems and Bundler,
and Maven reach their registries slowly or not at all from China.
On a cn install, link the profile's environment, cargo and gem
drop-ins into place. A regular file the admin wrote is never
replaced; the install fails instead.
2026-10-12 00:29:13 -04:00
ZacharyZhang-NY f3b236ae82 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-12 00:11:53 -04:00
bjarneo 9480d1b6a9 Merge pull request #15065 from bjarneo/fix/bar-icon-consistency
Normalize bar icon size and alignment
2026-10-11 22:07:57 +02:00
Bjarne Oeverli 6575f06d53 Correct custom bar icon bounds 2026-10-11 22:02:37 +02:00
Bjarne Oeverli 201de1c0d6 Normalize bar icon size and alignment 2026-10-11 21:39:17 +02:00
David Heinemeier HanssonandClaude Opus 5.5 e71a816e90 Cache workspace layouts instead of running a script on every switch (#15028)
* Cache workspace layouts instead of running a script on every switch

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

* Test the workspace layout cache by running its logic, not matching its source

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

* Keep the fake layout read running while its exit handler runs

Addresses Codex review: clearing running first let an undeferred, broken retry pass the cache tests.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:03:30 +09:00
David Heinemeier HanssonandClaude Opus 5.5 1b1cc5ecb5 Track the power profile through Quickshell instead of polling busctl (#15027)
* Track the power profile through Quickshell instead of polling busctl

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

* Recover the power profile when the daemon was down at shell start

Addresses Codex review: Quickshell never attaches its PowerProfiles listener if the daemon failed at startup. One-off reads on startup, power source changes and an IPC refresh from omarchy-powerprofiles-set cover that, still with no timer.

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

* Don't let a stale power profile read win over a change signal or drop a refresh

Addresses Codex re-review: a busctl read in flight could overwrite a newer PowerProfiles signal, and refreshes requested while a read ran were dropped.

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

* Test power profile transitions and stale reads as behavior, not source text

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:01:49 +09:00
David Heinemeier HanssonandClaude Opus 5.5 3c1f7b780f Refresh the passwordless sudo indicator on change instead of polling (#15026)
* Refresh the passwordless sudo indicator on change instead of polling

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

* Refresh the sudo indicator after failed changes and on a wall-clock deadline

Addresses Codex review: a rolled-back renewal skipped the refresh, the monotonic user timer paused through suspend, and default timer accuracy allowed a minute's delay.

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

* Test the sudo indicator refreshes by running the grant flows

Replace source-string checks with runs of timed, renewed, failed, permanent and disable flows against the fixture, asserting the recorded omarchy-shell refresh and wall-clock systemd-run --user timer.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:01:45 +09:00
David Heinemeier HanssonandClaude Opus 5.5 8bd1b7c045 Tell agents to react to events instead of polling (#15029)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:42:26 +09:00
David Heinemeier Hansson c4803d7d74 Update comment 2026-10-11 22:33:14 +09:00
David Heinemeier HanssonandClaude Opus 5.5 5f25ef1da8 Let Neovim's current line and status line show the terminal background on existing installs (#15024)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:30:32 +09:00
David Heinemeier HanssonandClaude Opus 5.5 88de9ecc6c Let herdr's tab bar show the terminal background, so it turns glassy too (#15022)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:25:16 +09:00
David Heinemeier HanssonandClaude Opus 5.5 1ac31e55ae Never leave the desktop without a wallpaper after a theme intro (#15000)
A theme switch that plays an intro gave the shell the new still only when
the intro failed. The shell kept the old theme's path, which the theme swap
deletes, and loaded it again when OWE handed the desktop back, leaving it
empty. The new still is now handed over either way.

The shell also re-reads the background link whenever OWE hands the desktop
back or a wallpaper fails to load, falling back to the theme's first
background when the link itself is broken.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:42:00 +09:00
David Heinemeier HanssonandClaude Opus 5.5 754d2d04b3 Only recover a session lock no other locker may hold (#15020)
Hyprland lets any client take over a held lock, so a second shell started
beside the desktop judged the real shell's live lock stranded, took it, and
then exited, leaving the session behind the failsafe while the real shell
still reported itself secure. Recovery now waits while any other Quickshell
or hyprlock runs, and gives up if one never exits.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:26:49 +09:00
David Heinemeier HanssonandClaude Opus 5.5 a98e55eb91 Pick window transparency from Appearance > Window Transparency, and a Glassy bar background (#14991)
* Pick window transparency from Appearance > Window Transparency

Transparent, the default, keeps today's windows. Opaque turns the windows
Omarchy makes transparent solid. Glassy is the frosted glass Starship
(#14726): its blur behind every see-through window and shell surface, and a
see-through, blurred background in the theme's terminal files, since foot
turns compositor blur down and a Hyprland opacity rule fades text.

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

* Leave window transparency to Appearance when animations are off

Turning animations off no longer makes windows opaque. VM installs start
with opaque windows instead, and a migration keeps them opaque for anyone
who already has animations off.

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

* Add a Glassy bar background, under Menu Bar > Bar Background

Menu Bar > Bar Background picks Opaque Bar, Glassy Bar or Clear Bar, and
Indicators becomes Bar Indicators to match. Glassy keeps the theme
background faintly and Hyprland blurs what is behind it, with the same blur
as Glassy windows.

omarchy bar background opaque|glassy|none|cycle sets it and prints it when
given nothing. omarchy bar transparent true|false|toggle stays for
compatibility, as none and opaque. Double-clicking the bar cycles through
the three backgrounds the same way, from clear to glassy to opaque.

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

* Stop when a saved corner choice can't be removed

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

* Make btop follow glassy windows

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:21:00 +09:00
David Heinemeier HanssonandClaude Opus 5.5 be7c7bf378 Replace Xournal++ with Signed for signing PDFs (#14999)
Signed, Omacom's PDF signing app, takes Xournal++'s place in the base
install and the preinstall set, so Remove and Install Preinstalls cover
it too. A migration installs it on existing machines, unless their owner
has removed the preinstalls; Xournal++ stays on machines that already
have it. The Filling out PDFs chapter now walks through Signed, the GUIs
chapter introduces it, the setup tip points to it, and the unused
Xournal++ config is gone.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:57:16 +02:00
David Heinemeier HanssonandClaude Opus 5.5 4df545905d Pick window corners from Appearance > Window Corners (#14990)
* Expect Appearance before Toggle under Trigger

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

* Read the default workspace layout once for all the menu rows that ask

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

* Pick window corners from Appearance > Window Corners

Theme Corners leaves them to each theme. Sharp and Rounded override every
theme through a Hyprland toggle, with Rounded using Solitude's corners.

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

* Keep the corner choice when saving a new one fails, and test the load order

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:06:54 +09:00
David Heinemeier Hansson 3882c27cfd Better ordering 2026-10-11 16:11:18 +09:00
David Heinemeier HanssonandClaude Opus 5.5 5ec8bd300b Pick the bar clock format from Appearance > Menu Bar > Indicators > Clock
Every preset the clock cycles through gets its own row, labeled with how
it reads, and the current one is checked. omarchy-bar gains get <id> <key>
and a bare position to read the settings the rows compare against.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:10:33 +09:00
David Heinemeier HanssonandClaude Opus 5.5 eb1a34ecd0 Move Battery Percentage under Appearance > Menu Bar > Indicators
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:03:50 +09:00
David Heinemeier HanssonandClaude Opus 5.5 b6ceb8f4d6 Use the menu's animations icon in the toggle notifications
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:00:47 +09:00
David Heinemeier HanssonandClaude Opus 5.5 bef30c1c50 Move the Menu Bar from Style to Appearance, with Hide and Show (#14982)
* Match the menu test to the rearranged Trigger menu

Trigger now leads with Toggle and Appearance, and Appearance's entries,
now including Herdr Theme Sync, are in their new order.

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

* Move the Menu Bar from Style to Appearance, with Hide and Show

Trigger > Appearance > Menu Bar now holds the bar's Position and
Transparency from Style > Menu Bar, and offers Hide while the bar shows
and Show while it is hidden, instead of a bare toggle.

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

* Name the Menu Bar entries after the bar, and split transparency

Hide Bar, Show Bar, Position Bar, Top Bar and the rest read on their own
when searched. Transparency becomes Transparent Bar while the bar is
opaque and Opaque Bar while it is transparent.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:59:25 +09:00
David Heinemeier HanssonandClaude Opus 5.5 18e059d9a4 Show the remote session indicator only during a remote session (#14981)
Hovering the indicators revealed it dimmed alongside the ones that can be
switched on, though there is nothing to do with it there. An indicator can
now opt out of that reveal, and remote session does.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:58:19 +09:00
David Heinemeier Hansson cf473b5c5d Rearrange menu items a bit 2026-10-11 15:50:04 +09:00
David Heinemeier HanssonandClaude Opus 5.5 d1a614ce66 Decide lock requests from the current lock state (#14978)
The locked binding once read true after an unlock, though the request,
the session lock, and its secure state were all false. A lock before
suspend then took the session for locked and did nothing, and the
machine slept unlocked. Lock requests, the status reply, and stranded
lock recovery now read those three values directly.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:37:45 +02:00
1498718969 Add a floating mode with titlebars to the workspace layout cycle (#14482)
* Let the workspace layout test's Lua assertions fail the suite

Lua 5.5's standalone interpreter exits 0 when the script that raised the error came in on stdin, and exits 1 for the same error in a script file. The block here was piped in through a heredoc, so every assertion in it reported as passing however wrong the value was: changing the expected layout to one the toggle never writes still left the file green.

* Add a floating mode to the workspace layout cycle

Super + L cycled a workspace between dwindle and scrolling. It now carries on into floating, where every window on the workspace floats.

Floating cannot be a third `layout` on the workspace rule. Hyprland has no layout by that name and accepts one anyway: `hl.workspace_rule({ layout = "floating" })` returns ok and silently leaves the workspace on dwindle, exactly as a misspelt name does. So the mode floats the windows themselves, through the same `float` dispatcher SUPER + T uses.

Ending the mode has to tile the windows the mode floated and only those, so each one is tagged with `omarchy-mode-floated`. A window already floating when the mode reaches it -- an app with a float rule of its own, a dialog, one popped out with SUPER + T -- is left unclaimed and is still floating when the mode ends. The tag lives on the window rather than in a table here: it dies with the window, follows it between workspaces, and survives the config reload that plugging in a monitor performs, none of which a table on this side would do. A pinned window is left alone entirely, because tiling one unpins it as well.

Windows arrive on a workspace three ways and a window rule only covers one of them, so the mode watches events: window.open for what opens there, window.move_to_workspace for what is carried in, and the same event for what is carried back out, which gives up the floating the mode gave it rather than staying floating on a workspace that tiles. Every tiled window is claimed before any of them is floated, because floating one member of a group floats the rest, and claiming as it went would find those already floating and leave them stranded.

The mode is read back from the saved file rather than from the compositor. Hyprland reports a workspace's tiled layout and floating is not one, so a floating workspace still answers dwindle or scrolling; trusting that answer would skip a step of the cycle. A file saved before floating existed names a layout rather than a mode and falls through to what Hyprland reports, so the cycle keeps working and the next press rewrites it.

Two faults in the old script had to go first, because saving the mode makes both persistent. Named workspaces report a negative id, which passed the `^-?[0-9]+$` guard, and Hyprland reads a leading "-" as a selector relative to the current workspace: pressing Super + L on a named workspace reconfigured workspace 1 instead, and would now save a file that did it again at every login. And the saved state was written under $HOME/.local/state while the Hyprland side reads XDG_STATE_HOME, which also had to reach package.path: the loader lists that directory but loads the files by module name, so a state home anywhere else listed the saved layouts and then loaded same-named ones from the home directory.

Saving is checked too. The mode reaching the compositor and the mode reaching disk are separate things that can fail separately, and a save that failed silently would leave the next press reading a file that never got written, cycling between two modes and never reaching the third.

The guard against named workspaces stops new files being written, but an earlier version already wrote them, and they go on being loaded at every login. A migration removes the saved layouts named for a negative id.

The keyword fallback is gone. `hyprctl keyword` refuses a Lua configuration outright -- "keyword can't work with non-legacy parsers. Use eval." -- and exits 0 while doing so, so as a fallback it neither worked nor reported that it had not. A failed apply now says so and saves nothing, rather than announcing a layout the screen does not show and restoring it at the next login.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Add titlebars, edge snapping, and a window Shelf to floating workspaces

Floating workspaces become a traditional desktop that works with every
theme. Windows get themed titlebars from the omarchy-hyprland-titlebars
plugin, which is loaded only while something floats. Dragging a titlebar
to the left or right edge snaps the window to that half, and dragging it
to the top or double-clicking maximizes it. The window floating mode
places is sized, centered, and cascaded, and floating workspaces focus
on click.

Super + M, or the minus button, sets a window aside on the Shelf, which
works from any workspace and restores the window onto the current one
in that workspace's layout. Float All Workspaces makes floating the
default while Super + L still sets a single workspace's layout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Bring resize on border to tiling too

* Fix floating mode cleanup, fullscreen windows, and failed Shelf notes

Turning Float All Workspaces off with no workspace layouts left behind
started nothing to give the floated windows back. A window fullscreen
when its workspace began floating now floats once it leaves fullscreen.
A window whose Shelf note cannot be saved stays where it is, and the
titlebars read the theme from where omarchy-theme-set writes it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Place the Shelf on the bar once, instead of on every minimize

Putting it back on each minimize kept a Shelf the user moved where they
put it, but re-added one they had removed. A migration places it once for
existing bars; Super + Alt + M opens the Shelf either way.

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

* Raise titlebars off the window background

A titlebar in the theme's background color ran into the window under it.
It takes the theme's lighter background, the raised surface every theme
defines, and the selection color on themes that make the two the same.

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

* Drop minimizing and edge snapping from floating workspaces

A floating workspace is now titlebars with maximize and close, and nothing
else. The Shelf (its bar widget, panel, commands, notes, Super + M and
Super + Alt + M) goes with the minimize button, and the titlebar plugin's
edge snapping is left off, along with the shell's snap preview.

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

* Stop describing the titlebar package as snapping

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

* Name web app windows in their launchers

Chromium names a web app window after its URL, and titles it with the page
title or just the domain. StartupWMClass ties the window to its launcher,
so the titlebar plugin can show the launcher's Name instead: WhatsApp
rather than web.whatsapp.com. New web apps, the shipped ones, and existing
launchers (through a migration) all carry it.

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

* Drop the titlebar package comment from the base list

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

* Show the workspace layout in the bar and cycle it on click

The focused workspace's mark is a square for dwindle, a triangle for
scrolling, and a circle for floating, and clicking it cycles the layout
as Super + L does. Both layout toggles ask the bar to read the new mode.

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

* Show scrolling as a circle and floating as a triangle

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

* Move the look of the desktop to Trigger > Appearance

Menu Bar, Battery Percentage, Workspace Layout, Window Gaps, Animations,
and 1-Window Ratio change how the desktop looks rather than switching a
behavior, so they leave Trigger > Toggle for their own menu.

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

* Choose a default workspace layout instead of floating everything

Trigger > Appearance > Workspace Layout sets the layout of every workspace
without one of its own: Tiling, Scrolling, or Floating. A tiled default
is Hyprland's own layout, and a floating one is the floating mode. It
replaces Float All Workspaces, which also overrode and set aside the
layouts workspaces had been given with Super + L.

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

* Title the workspace layout menu Default Workspace Layout

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

* Use the animation icon for Appearance > Animations

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

* Open clipboard history from Trigger

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

* Leave edge resizing to looknfeel on floating workspaces

looknfeel now resizes from borders everywhere, so floating workspaces
only need to switch pointer focus to click.

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

* Convert saved workspace layout rules to modes

Released versions saved a workspace's layout as a plain workspace rule,
which names no mode, so a floating default would override a workspace
set to scrolling, and the bar and Super + L would misread it. A migration
rewrites those files as modes.

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

* Cycle workspace layouts backwards with Super + Alt + L

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

* Test the Trigger > Appearance split and Clipboard History placement

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

* Decode escaped web app URLs before naming their windows

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

* Describe the workspace widget's layout mark and click

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

* Let a workspace cycled onto the default follow it again

Once Super + L gave a workspace a layout of its own, it kept it for good,
so a later change of the default never reached it. Cycling onto the
default layout now forgets the workspace's own instead of saving it.
Without a saved default, the default is Hyprland's own layout, which
the user's looknfeel may have set.

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

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 08:20:45 +02:00
David Heinemeier HanssonandClaude Opus 5.5 c09ddcff90 Move the look of the desktop to Trigger > Appearance (#14973)
* Move the look of the desktop to Trigger > Appearance

Menu Bar, Battery Percentage, Workspace Layout, Window Gaps, Animations,
and 1-Window Ratio change how the desktop looks rather than switching a
behavior, so they leave Trigger > Toggle for their own menu.

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

* Use the animation icon for Appearance > Animations

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

* Open clipboard history from Trigger

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

* Test the Trigger > Appearance split and Clipboard History placement

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:17:33 +02:00
David Heinemeier HanssonandClaude Opus 5.5 dc50bbe574 Switch out of an input method and back with a lone Shift (#14975)
* Switch out of an input method and back with a lone Shift

Fcitx's alternate trigger only acts while an engine like Pinyin is or was
active, so Shift + letter and plain Latin typing are unaffected. It needs
Shift releases intact, which the both-Shifts Caps Lock option prevented.

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

* Follow XDG_CONFIG_HOME in the Shift migration and test it

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:15:03 +09:00
35a55fd236 Remove redundant agent guidance in user customization skill (#5925)
* Remove skill body trigger conditions; bolster frontmatter content.

* Keep window behavior and screen recording in the skill's triggers

The removed "When This Skill MUST Be Used" section listed both, and the description that replaces it had dropped them, so a request to record the screen or change how windows behave no longer matched anything the agent reads before choosing the skill.

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

---------

Co-authored-by: omarchybot <omarchybot@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Codex Medium <noreply@openai.com>
Co-authored-by: Omarchybot <317366263+omarchybot@users.noreply.github.com>
2026-10-11 07:45:22 +02:00
David Heinemeier HanssonandClaude Opus 5.5 10f1cbf7cf Install clang with the Rust dev environment instead of for everyone (#14967)
Nothing in the base install builds with clang: Qt apps and Ruby's native
gems compile with gcc, and Ruby comes prebuilt from mise. Rust crates that
use bindgen, including rb-sys gems, need libclang, so the Rust dev
environment installs it. llvm was unused even there.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:37:23 +09:00
d74d6ea249 Build the calendar month grid in UTC (#10476)
* Build the calendar month grid in UTC

The grid walked a cursor at local midnight and stepped it with setDate(getDate() + 1). Chile starts DST at 24:00 on the first Saturday of September, so local midnight of 6 September 2026 does not exist, and Qt's V4 engine resolves a missing local time backward -- onto 5 September 23:00, the same calendar date, which the next iteration reads again. September 2026 draws 30 31 1 2 3 4 5 and then 5 6 7 8 9 10 11, and every later date sits one weekday early. Cuba, Lebanon, the Azores and Egypt change over at 00:00 too and break the same way in their own transition month; where the gap lands on a leading padding cell instead, the whole six-week grid shifts by a day with no duplicate in it.

Node resolves the identical gap forward, so the suite has never seen this: running the shipped monthGrid under Node across all 598 tzdata zones for 2020-2035 reports nothing, while the same code under the QML engine breaks 402 grids. A sequence of civil dates never needed a wall clock, so it walks UTC, where every day is 24 hours long, and the test pins that by building the grid with a Date whose local constructor and accessors throw.

stepMonth reads a month back off the same kind of date. No zone puts a midnight gap on a first of the month, so it is not broken today, but it moves with the grid rather than waiting for a tzdata release to make it a second bug.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Name the calendar heading's month from local noon

The heading built a local midnight on the first of the month, and that midnight does not always exist: Cuba started DST at 00:00 on 1 April 2012, so in Havana the QML engine resolves it back onto 31 March and the heading reads March 2012 above an April grid. Noon exists on every first of the month there has been, and keeping it local is what makes Qt.formatDate print the right month in every zone.

The Apia case comes from #13186: Samoa skipped 30 December 2011 entirely, so unlike a one-hour midnight gap, Node's V8 cannot resolve its way past it, and the test fails on the old grid under Node instead of only in the shell.

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

---------

Co-authored-by: Codex XHigh <noreply@openai.com>
Co-authored-by: Omarchybot <317366263+omarchybot@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 06:14:23 +02:00
David Heinemeier HanssonandClaude Opus 5.5 3609968a42 Stop shipping OBS Studio and Kdenlive to new installs (#14962)
Monologue records and Omacut trims, which covers what most people used
them for. Existing installs keep both; there is no migration, and the
preinstall removal no longer treats them as Omarchy's to uninstall.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:57:28 +09:00
David Heinemeier Hansson c1096aa36f Refresh all default theme previews (#14944) 2026-10-11 12:50:06 +09:00
ccf263611d Correct omarchy-pkg-add AUR claim in AGENTS.md (#13895)
* Correct omarchy-pkg-add AUR claim in AGENTS.md

* Say omarchy-pkg-add installs from the pacman repos, not the official ones

It runs plain pacman -S, which also reaches the [omarchy] repo every shipped pacman.conf enables, so "official repos" would send an agent to the AUR helper for Omarchy's own packages.

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

---------

Co-authored-by: Omarchybot <317366263+omarchybot@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Codex Medium <noreply@openai.com>
2026-10-11 05:33:32 +02:00
stephenlentonandClaude Sonnet 5 4d3a85969a Clarify that omarchy-toggle-bar's on/off tracks the bar-off flag (#11803)
on hides the bar and off shows it, matching the literal flag name
rather than an inferred "bar visibility on" reading. The behavior is
correct and depended on by omarchy-toggle-fullscreen-desktop; this
just makes the --help summary say so.


Claude-Session: https://claude.ai/code/session_01YQK8wd7WU8qEVnXVnNmcSW

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-11 05:32:58 +02:00
a3d4bea9d0 docs(agents): add plugin UI safety baseline (#7792)
* docs(agents): add plugin UI safety baseline

Document safe rendering and validation of dynamic plugin data after a marketplace review surfaced a QML AutoText boundary.

Assisted-by: Codex/GPT-5

* docs(agents): allow consented background refreshes

Clarify that plugin networking may refresh periodically when disclosed and user-controlled.

Assisted-by: Codex/GPT-5

* docs(agents): keep plugin guide label concise

Leave the safety baseline discoverable within the plugin guide without expanding its topic index label.

Assisted-by: Codex/GPT-5

* docs(agents): point plugin authors at their own markup helper

Omarchy ships no markup-neutralizing helper, and this skill is for installed systems, where the parent SKILL.md forbids editing the packaged shell components the old line suggested extending.

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

---------

Co-authored-by: Omarchybot <317366263+omarchybot@users.noreply.github.com>
Co-authored-by: Codex Medium <noreply@openai.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 05:17:02 +02:00
kevin koontz 9d47dd1497 Point the plugin manual at plugins.omarchy.org (#10499)
The marketplace moved to the official host on 30 Aug 2026. omarchyplugins.com redirects, but the published manual should name the current URL.
2026-10-11 05:00:53 +02:00
Vtorrealba 77b13fd026 Fix typo on 04_navigation.md (#7463) 2026-10-11 04:59:07 +02:00
Ed Schofield 9476e47a10 docs: add 2020 iMac models to the T2 Mac list (#10783) 2026-10-11 04:57:41 +02:00
Spencer Bull 9830142834 Merge pull request #14935 from omacom/startup-cursor-recovery
Recover the startup cursor across Hyprland reloads
2026-10-10 20:59:21 -05:00
Spencer BullandCodex XHigh 0bcb6a2559 Keep the default cursor alias on ordinary boots
Omarchy never sets XCURSOR_THEME, so asking GSettings on every cold boot replaced the "default" alias with whatever GSettings held, which is the schema default Adwaita when nothing was ever set. That overrode a theme chosen through ~/.icons/default and exported the substitute to every application. The saved theme and GSettings are now consulted only when recovering from the blank startup theme or an interrupted reveal.

Co-Authored-By: Codex XHigh <codex@openai.com>
2026-10-10 20:55:29 -05:00
David Heinemeier Hansson 04b374a989 Add Starship theme preview (#14942) 2026-10-11 09:15:47 +09:00
David Heinemeier HanssonandClaude Opus 5.5 1b5222be74 Return Caps Lock to normal and make Shift + Caps Lock the compose key (#14939)
* Return Caps Lock to normal and make Shift + Caps Lock the compose key

Caps Lock as compose, with both Shifts as Caps Lock, confused too many
people. Stock XKB has no option for compose on the shifted Caps key, so
Omarchy defines one in /etc/xkb, which libxkbcommon reads before the
system rules. The explicit actions keep the Shift level from toggling
Caps Lock. Lone Shift is no longer part of Caps Lock, which leaves it
free for input methods.

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

* Require xkbcli so the keymap check cannot silently skip

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:06:59 +09:00
David Heinemeier HanssonandClaude Opus 5.5 0b9dace018 Unify input methods with installer keyboard selection (#14433)
* Unify input methods with the installer keyboard selection

* Preserve inherited input keys and later keyboard changes

* Exercise input engines across desktop toolkits

* Show input modes in the keyboard indicator

* Keep Pinyin setup prompts visible

* Remember input selections before an app gains focus

* Remove the Japanese input welcome notification

* Show explicit choices on notification cards

* Suppress the Cloudpinyin first-use prompt

* Show the input switching shortcut in keyboard hints

* Verify input setup and composition stay quiet

* Use Super+I for input switching independently of Fcitx keys

* Restore linked input settings and attribute test notifications to Fcitx

* Correct the input setup shortcut hint

* Keep input tooltips to the name and switching shortcut

* Place the input language selector with the right bar widgets

* Show input switching only for multiple layouts or methods

* Unify input method and keyboard layout switching

* Select keyboard layouts and input methods in the Typing menu

* Apply typing selections immediately from the menu

* Use installer keyboard choices and keep typing selections accurate

* Queue typing changes and wait for saved selections

* Keep queued typing selections when reopening the menu

* Cycle keyboard layouts alongside composition engines

* Avoid shadowing Python typing and retain pending mixed input selections

* Keep input clicks working through controller and compositor failures

* Move the keyboard layout widget to the right side of existing bars

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

* Map keyboard choices to the layouts the installer sets

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

* Keep the switching order when changing input methods

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

* Set input method fonts only after the selection saves

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

* Restore confirmed picker checks when an immediate change fails

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

* Route Super+I through the shell's input reader so unfocused choices stick

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

* Restore the input group when the font preference cannot be written

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

* Drop the azerty conversion now that the installer no longer offers it

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

* Apply the owner's keyboard layout once before persisting the input choice

A quattro merge kept a second apply_keyboard after the retry loop, which
re-ran loadkeys and firstboot, ignored their failure, and overwrote the
XKB layout setup supplies for keymaps kbd-model-map lacks.

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

* Give the Lao keyboard choice a Lao desktop layout

The row used la-latin1, kbd's Latin American Spanish keymap, so Lao
installs typed Spanish. There is no Lao console keymap; like Korean, the
console stays US and the desktop gets la, which Omarchy keeps behind US
as a non-Latin layout.

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

* Load engines installed after Fcitx started when they are selected

The Typing menu refused an engine whose package an update had installed,
since Fcitx discovers engines only at startup. Restart input once and wait
for the engine; name the package when it really is not installed.

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

* Cycle input methods back with Super+Shift+I

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

* Drop notification action buttons now that the Pinyin prompt is suppressed

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

* Cycle input only through the shell's reader

Without the shell there is no bar or menu either, so the one-shot cycling
path only duplicated the reader's logic.

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

* Add engines from the command line through the Typing menu's path

omarchy setup input <engine> had its own restart, append, select and
live-rollback code in configure.py, parallel to the menu's. It now
installs the packages and appends through typing_setup, leaving
configure.py with install-time seeding and update defaults.

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

* Keep Ctrl+Space out of Fcitx's toggles instead of probing for it

The terminal prefix bind read the Fcitx config and profile on every press
to avoid fighting a Ctrl+Space toggle. configure --defaults now removes
that key from every trigger list, so the bind only checks for a terminal.

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

* Run the input-method scripts with the system Python

A bare python resolves through PATH, so a mise, pyenv or venv interpreter
could run them without the system's PyGObject.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:16:30 +09:00
Spencer BullandGPT-6.1-Sol XHigh f5170dd070 Recover the startup cursor across Hyprland reloads
Hyprland discards Lua globals and timers during reload, so the pending startup reveal must survive outside Lua state. Preserve the normal cursor theme for this compositor session, recover orphaned blank settings, and repair activation environments before future applications inherit them.

🤖 Generated by GPT-6.1-Sol High in Codex through T3 Code. Reviewed by GPT-6.1-Sol XHigh in Codex through T3 Code.

Co-Authored-By: GPT-6.1-Sol XHigh <codex@openai.com>
2026-10-10 17:17:28 -05:00
Kros DaiandClaude Opus 5.5 ec4579a461 🐛 fix(test): skip exact zone match without tzdata.zi
- 🐛 minimal containers lack the link table, so the exact china zone check failed on a correct profile

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuATgjU4mnqpqSLAKMQ14g
2026-10-08 16:44:15 -04:00
Kros DaiandClaude Opus 5.5 d9a781e3c9 📝 docs(region): scope the refresh render boundary
- 📝 state that same-user code is outside the refresh boundary, so reviewers see the trade-off was deliberate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuATgjU4mnqpqSLAKMQ14g
2026-10-08 16:08:36 -04:00
Kros DaiandClaude Opus 5.5 a6c3480ff8 📝 docs(region): state marker mode and archlinuxcn risk
- 📝 refresh reads the marker unprivileged, and rolling archlinuxcn packages can outpace stable snapshots

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuATgjU4mnqpqSLAKMQ14g
2026-10-08 15:31:04 -04:00
Kros DaiandClaude Opus 5.5 545190596d ♻️ refactor(pacman): branch on render with if/else
- ♻️ follow the repo style instead of exiting early to skip the install block

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuATgjU4mnqpqSLAKMQ14g
2026-10-08 15:31:04 -04:00
Kros DaiandClaude Opus 5.5 b381093e32 ✅ test(region): derive china zones from tzdata
- ✅ dropping a backward-compatible zone name from the profile now fails the test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuATgjU4mnqpqSLAKMQ14g
2026-10-08 15:31:04 -04:00
Kros DaiandClaude Opus 5.5 9d74c813f1 🐛 fix(channel): require every region fragment for dev
- 🐛 leaving dev refreshes stable or rc with the checkout's tools, so a profile missing those fragments would drop regional repositories
- ✅ refuse a profile missing any single fragment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuATgjU4mnqpqSLAKMQ14g
2026-10-08 15:31:04 -04:00
Kros DaiandClaude Opus 5.5 fce85abf48 📝 docs(region): explain mirror order and fallback
- 📝 pacman's three-error skip and first-server databases decide what the china replicas must serve

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuATgjU4mnqpqSLAKMQ14g
2026-10-08 15:21:51 -04:00
Kros DaiandClaude Opus 5.5 3c68a1bb04 ✨ feat(region): put the china channel mirrors first
- ✨ omarchy now runs per-channel china replicas, so they should lead instead of trailing as a fallback
- ♻️ prepend per-channel fragments so each channel's own mirror stays the fallback
- 🔥 drop the ustc arch mirror, which was never a stable or rc snapshot replica

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuATgjU4mnqpqSLAKMQ14g
2026-10-08 15:21:51 -04:00
Kros DaiandClaude Opus 5.5 b89def6c7f 📝 docs(region): describe timezone-derived regions
- 📝 the --region build flag is gone, so the installer contract and validation plan change with it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuATgjU4mnqpqSLAKMQ14g
2026-10-08 15:03:46 -04:00
Kros DaiandClaude Opus 5.5 0de49b231a ✨ feat(region): map timezones to regions
- ✨ one iso serves every region, so the installer derives it from the timezone the user already picks
- ✅ reject unknown zones and zones claimed by two regions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuATgjU4mnqpqSLAKMQ14g
2026-10-08 15:03:41 -04:00
Kros DaiandClaude Opus 5.5 3e00a48c00 ✅ test(pacman): sandbox the region for every boundary test
- ✅ The channel and update boundary tests copied the real render and dev-region guard, so on a China install they read the host's `/etc/omarchy/region` and failed before reaching their checks
- ♻️ The shared fixture now points both lookups at a sandbox target that starts global, and the regional test only writes `cn` there

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 a1bdc46119 ✅ test(pacman): isolate the regional refresh boundary test
- ✅ the render read the host's region marker, so a global machine never rendered China and a China machine failed on missing fragments
- ✅ check the finished China files root installs, not only the copy order

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdXqTwm63rRMwotZ1xqLC8
2026-10-06 22:00:59 -04:00
Kros DaiandClaude Opus 5.5 7a60c8dea4 🐛 fix(channel): require a usable region profile for dev
- 🐛 an empty profile directory or a helper that cannot run passed the guard, linked dev, and then broke the refresh
- ✅ the success case now uses a complete profile

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdXqTwm63rRMwotZ1xqLC8
2026-10-06 22:00:59 -04:00
Kros DaiandClaude Opus 5.5 6be88d14a5 🐛 fix(pacman): remove the render dir on every refresh exit
- 🐛 a failed render, a refused sudo prompt, or a signal left the scratch directory behind; repeated attempts piled them up
- 🔒 the cleanup runs before the sudo revocation, which stays the last step of the exit path

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdXqTwm63rRMwotZ1xqLC8
2026-10-06 21:58:48 -04:00
Kros DaiandClaude Opus 5.5 50a26be6a7 📝 docs(region): scope the cn profile to repositories
- 📝 frame USTC and ArchLinuxCN as a bridge until an official China mirror exists
- 📝 record that language, timezone, and input defaults follow as separate changes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K3Jna1ogUUviYLJzhsG6Vr
2026-10-06 14:43:25 -04:00
Kros DaiandClaude Opus 5.5 d5ef1c1d15 🔥 refactor(region): drop input defaults from the cn profile
- 🔥 keep this change to the regional build architecture; Chinese input gets its own proposal instead of fixing one engine here
- ✅ assert the shipped profile adds only its repository keyring

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K3Jna1ogUUviYLJzhsG6Vr
2026-10-06 14:43:25 -04:00
Kros DaiandClaude Opus 5.5 d024c82a9d 📝 docs(plans): move the china proposal to a discussion
- 📝 upstream plans are implementation plans; the open questions belong in the discussion that links these changes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K3Jna1ogUUviYLJzhsG6Vr
2026-10-06 14:43:25 -04:00
Kros DaiandClaude Opus 5.5 d7f17a0424 ♻️ refactor(pacman): render regional config without root
- 🔒 upstream refresh now gives root only command-scoped copies, so regional files are rendered as the caller and installed with the same four `sudo cp` calls
- ✅ exercise refresh only inside the sudo-boundary sandbox; the old host-level test reached real sudo once refresh pinned its PATH

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:43:25 -04:00
Kros DaiandClaude Opus 5.5 87a2931209 📝 docs(region): drop the unreachable menu input gate
- 📝 the menu bypasses the input method, so requiring Pinyin there made the release procedure impossible until upstream fixes it
- 📝 record the required `skel` profile and the dev-switch region check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:43:25 -04:00
Kros DaiandClaude Opus 5.5 647979bde7 🐛 fix(pacman): keep existing config file modes
- 🐛 refresh used to copy over the existing files, so a customized mode must survive the shared regional applicator too

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:43:25 -04:00
Kros DaiandClaude Opus 5.5 123535ea14 🐛 fix(channel): keep regional repos when switching to dev
- 🛡️ refuse a dev checkout that lacks this machine's region profile before linking it, since its own refresh would silently replace the regional pacman files
- ✅ isolate channel tests from the host region marker

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:43:25 -04:00
Kros Dai 645cfc2a36 📝 docs(region): explain china bootstrap choices
- 📝 clarify optional defaults, trust requirements, and opt-out behavior
- ⚠️ document snapshot compatibility and release validation limits

Amp-Thread-ID: T-01a0df38-0ea7-759b-bcff-29da223cc567
2026-10-06 14:43:25 -04:00
Kros Dai e989960895 ✅ test(region): guard runtime defaults
- ✅ protect global behavior and regional mirror ordering
- ⌨️ guard personal settings and reserved input shortcuts

Amp-Thread-ID: T-01a0df38-0ea7-759b-bcff-29da223cc567
2026-10-06 14:43:25 -04:00
Kros Dai 48f7c411c0 ✨ feat(pacman): preserve regional defaults
- ✨ share regional repository generation across install and refresh
- 🛡️ reject invalid profiles before replacing configuration

Amp-Thread-ID: T-01a0df38-0ea7-759b-bcff-29da223cc567
2026-10-06 14:43:25 -04:00
Kros DaiandAmp 7727d40c92 ✨ feat(region): seed offline chinese input
- ✨ make first-login pinyin available without downloads
- ⌨️ preserve compose and terminal prefix shortcuts

Amp-Thread-ID: https://ampcode.com/threads/T-01a0df38-0ea7-759b-bcff-29da223cc567
Co-authored-by: Amp <amp@ampcode.com>
2026-10-06 14:43:25 -04:00
416 changed files with 11389 additions and 7290 deletions

No files matched your search

+18 -1
View File
@@ -104,7 +104,7 @@ Use these instead of raw shell commands:
- `omarchy-cmd-missing` / `omarchy-cmd-present` - check for commands
- `omarchy-pkg-missing` / `omarchy-pkg-present` - check for packages (don't use these if you can just use `omarchy-pkg-add`/`omarchy-pkg-drop`)
- `omarchy-pkg-add` - install packages (handles both pacman and AUR)
- `omarchy-pkg-add` - install packages from the configured pacman repos, Omarchy's included; use `omarchy-pkg-aur-add` for AUR packages
- `omarchy-pkg-drop` - remove packages; use this instead of raw `pacman -R*`
- `omarchy-notification-send` - send desktop notifications; do not call `notify-send` directly
- `omarchy-hw-asus-rog` - detect ASUS ROG hardware (and similar `hw-*` commands)
@@ -113,6 +113,10 @@ Commands installed by Omarchy's default package set are runtime invariants. Invo
Exceptions are allowed for migration and package-helper scripts where the helper may not be available yet, where the helper itself is being implemented, or where direct package-manager behavior is required.
# Background Work
- Long-running code (the shell, daemons, watchers) reacts to events instead of polling with a timer. Polling is the last resort, for state with no change signal; [`agents/skills/shell-dev.md`](agents/skills/shell-dev.md) lists the event sources and the push-refresh pattern.
# Menu
- The menu definition lives in `default/omarchy/omarchy-menu.jsonc`;
@@ -120,6 +124,19 @@ Exceptions are allowed for migration and package-helper scripts where the helper
- Do not add `aliases` to new menu entries. Aliases are reserved for
established alternate names users already type, kept for compatibility.
# Language
- `omarchy-language en|zh` (Setup > Language) owns `LANG` in `/etc/locale.conf`; the installer sets it from its first screen. Nothing in `omarchy update` or migrations may change `/etc/locale.conf`.
- 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.
# Region
- `/etc/omarchy/region` holds the region the installer derived from the timezone (`cn`); no file means Global. The profile lives in `default/regions/cn/`, its contract in `docs/regions.md`.
- `install/config/region.sh` links the `cn` developer mirrors into `/etc` and `/.cargo` and refuses to replace a regular file it did not create. It serves both the install (the marker) and `omarchy region cn|global` (`OMARCHY_REGION`), which also records or removes the marker; Global removes only the links it made.
- `omarchy-region` prints the region (`Global` or `China`) and switches it. Nothing in `omarchy update` or migrations may change the region.
- China-only menu rows carry `"when":"[[ \"$(omarchy-region)\" == \"China\" ]]"` on every row, not only the submenu, because search checks each row's own guard. User-facing docs: `manual/52-china.md`.
# Config Structure
- `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)
- [Dual Boot Install](manual/50-dual-boot-install.md)
- [Unattended Installs](manual/51-unattended-installs.md)
- [China](manual/52-china.md)
## License
+15
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
`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
- `bin/omarchy-shell` is the canonical IPC entry point. It forwards to
@@ -38,6 +42,17 @@ Run `omarchy-restart-shell` after making changes to QML files.
bar widgets register one target each — `omarchy.indicators`,
`omarchy.system-update`, `omarchy.clock`. There is no `bar` target.
## Keeping state current without polling
The shell runs all day on battery, and a widget is built once per monitor, so a `Timer` that starts a `Process` costs that process times the number of monitors, every tick, forever. Don't poll for state that only changes through known events. Pick the first of these that fits:
- A native Quickshell service that signals on change: `UPower` and `PowerProfiles` (`Quickshell.Services.UPower`), `Hyprland` and each workspace's `lastIpcObject`, `Pipewire`, `Mpris`, `SystemTray`, `Notifications`.
- An event stream the shell already runs, such as the Hyprland IPC events or the long-running `pactl subscribe` in `Commons/UntypedInput.qml`. Extend it rather than starting a second one.
- A file watch: `FileView` with `watchChanges`, or the directory watch in `plugins/bar/Bar.qml`, when the state lives in a file the user can read.
- A push from the command that changes the state. If only Omarchy commands change it, have them call `omarchy-shell -q <target> refresh` after the change, the way `omarchy-reminder` and `omarchy-sudo-passwordless` do. When the change happens later, without a command running (an expiry, say), schedule the refresh with a transient `systemd-run --user --on-active=…` timer. A root-owned timer cannot reach the user's shell.
Polling is the last resort, kept for state with no change signal at all (a remote API, say). Then gate the timer on the panel being open where you can, pick the longest interval the UI can tolerate, and put the poll in one shared service rather than in a per-monitor widget. Never poll a privileged probe: every `sudo -n` can write to the auth log.
## Editing widget files with glyphs
Widget files in `shell/plugins/bar/widgets/` contain Nerd Font glyphs as raw
+1
View File
@@ -2,6 +2,7 @@
Version=1.0
Name=Basecamp
Exec=omarchy-launch-webapp https://launchpad.37signals.com
StartupWMClass=chrome-launchpad.37signals.com__-Default
Terminal=false
Type=Application
Icon=basecamp
+1
View File
@@ -2,6 +2,7 @@
Version=1.0
Name=Discord
Exec=omarchy-launch-webapp https://discord.com/channels/@me
StartupWMClass=chrome-discord.com__channels_@me-Default
Terminal=false
Type=Application
Icon=omarchy-discord
+1
View File
@@ -2,6 +2,7 @@
Version=1.0
Name=Google Contacts
Exec=omarchy-launch-webapp https://contacts.google.com/
StartupWMClass=chrome-contacts.google.com__-Default
Terminal=false
Type=Application
Icon=google-contacts
+1
View File
@@ -2,6 +2,7 @@
Version=1.0
Name=Google Maps
Exec=omarchy-launch-webapp https://maps.google.com
StartupWMClass=chrome-maps.google.com__-Default
Terminal=false
Type=Application
Icon=google-maps
+1
View File
@@ -2,6 +2,7 @@
Version=1.0
Name=Google Messages
Exec=omarchy-launch-webapp https://messages.google.com/web/conversations
StartupWMClass=chrome-messages.google.com__web_conversations-Default
Terminal=false
Type=Application
Icon=google-messages
+1
View File
@@ -2,6 +2,7 @@
Version=1.0
Name=Google Photos
Exec=omarchy-launch-webapp https://photos.google.com/
StartupWMClass=chrome-photos.google.com__-Default
Terminal=false
Type=Application
Icon=google-photos
+1
View File
@@ -2,6 +2,7 @@
Version=1.0
Name=WhatsApp
Exec=omarchy-launch-webapp https://web.whatsapp.com/
StartupWMClass=chrome-web.whatsapp.com__-Default
Terminal=false
Type=Application
Icon=whatsapp
+1
View File
@@ -2,6 +2,7 @@
Version=1.0
Name=X
Exec=omarchy-launch-webapp https://x.com/
StartupWMClass=chrome-x.com__-Default
Terminal=false
Type=Application
Icon=x
+1
View File
@@ -2,6 +2,7 @@
Version=1.0
Name=YouTube
Exec=omarchy-launch-webapp https://youtube.com/
StartupWMClass=chrome-youtube.com__-Default
Terminal=false
Type=Application
Icon=youtube
+2
View File
@@ -59,6 +59,7 @@ GROUP_DESCRIPTIONS[hw]="Hardware detection and controls"
GROUP_DESCRIPTIONS[hyprland]="Hyprland window, monitor, and toggle controls"
GROUP_DESCRIPTIONS[install]="Optional software installers"
GROUP_DESCRIPTIONS[installed]="Installed optional service checks"
GROUP_DESCRIPTIONS[language]="System language"
GROUP_DESCRIPTIONS[launch]="Application launchers"
GROUP_DESCRIPTIONS[menu]="Omarchy menu commands"
GROUP_DESCRIPTIONS[migrate]="Migration runner"
@@ -74,6 +75,7 @@ GROUP_DESCRIPTIONS[plymouth]="Plymouth boot theme management"
GROUP_DESCRIPTIONS[power]="Power supply detection"
GROUP_DESCRIPTIONS[powerprofiles]="Power profile management"
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[reminder]="Desktop notification reminders"
GROUP_DESCRIPTIONS[remove]="Removal workflows"
+16 -16
View File
@@ -85,7 +85,7 @@ finish() {
fail() {
if [[ $events == "true" ]]; then
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
echo "$1" >&2
fi
@@ -113,9 +113,9 @@ interruptible() {
}
if omarchy-cmd-missing "$provider"; then
say "Installing the $name CLI…"
say "$(omarchy-text "Installing the %1 CLI…" "$name")"
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
mise use -g "$package" || exit 1
fi
@@ -150,26 +150,26 @@ if [[ -n $reauth ]]; then
else
account_home=$(omarchy-agent-account-state homes "$provider" | awk -F'\t' -v id="$reauth" '$1 == id { print $2 }')
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
say "Sign in to $name again in the browser window that opens."
login || fail "The $name sign-in didn't finish."
say "$(omarchy-text "Sign in to %1 again in the browser window that opens." "$name")"
login || fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")"
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
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
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
fi
# 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.
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
# 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
@@ -179,9 +179,9 @@ if ! primary_signed_in "$provider"; then
printf '%s\n' "$provider" >"$HOME/.config/omarchy/defaults/agent"
fi
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
finish "Signed in to $name."
finish "$(omarchy-text "Signed in to %1." "$name")"
else
fail "The $name sign-in didn't finish."
fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")"
fi
else
if [[ -z $label && $events == "false" && -t 0 ]]; then
@@ -189,7 +189,7 @@ else
fi
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
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
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
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")
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
if [[ $events == "true" ]]; then
finish "Added $added."
finish "$(omarchy-text "Added %1." "$added")"
else
finish "Added $added."
finish "$(omarchy-text "Added %1." "$added")"
echo "Switch to it with: omarchy agent account use $provider $(jq -r .id <<<"$account")"
fi
else
+29 -22
View File
@@ -73,6 +73,11 @@ class AccountError(Exception):
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
@@ -335,12 +340,12 @@ def begin(provider: str) -> Path:
def register(provider: str, label: str, pending: Path) -> dict[str, Any]:
pending = 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)
if not found["accountId"]:
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:
# 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"]:
if account.get("accountId") == found["accountId"]:
shutil.rmtree(pending, ignore_errors=True)
raise AccountError(
f"That's {account['label']} ({found['email'] or 'already added'}). "
"Try again, and sign in as the other account in the private window."
)
raise AccountError(text(
"That's %1 (%2). 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"
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]:
label = label.strip()
if not label:
raise AccountError("Give the account a name")
raise AccountError(text("Give the account a name"))
with Locked(provider) as registry:
account = find(registry, target)
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)
if not account.get("primary") and not account.get("home"):
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)]
account = find(registry, target)
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["alert"] = ""
if not account.get("primary"):
@@ -456,12 +461,12 @@ def remove(provider: str, target: str) -> dict[str, Any]:
with Locked(provider) as registry:
account = find(registry, target)
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"):
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)
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
# home, and rmtree removes a link without following it.
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]:
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:
registry["switch"] = mode
if threshold is not None:
try:
registry["threshold"] = max(50, min(100, int(threshold)))
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"] = ""
return dict(registry)
@@ -556,10 +561,10 @@ def duration(delta: dt.timedelta) -> str:
hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24)
if days:
return f"{days}d {hours}h"
return text("%1d %2h", days, hours)
if hours:
return f"{hours}h {minutes}m"
return f"{minutes}m"
return text("%1h %2m", hours, minutes)
return text("%1m", minutes)
# 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"]
if decision["action"] == "exhausted":
soonest = decision["soonest"]
when = f" {soonest[1]['label']} resets in {duration(soonest[0] - now)}." if soonest else ""
notify(f"All {name} accounts are over {threshold}%", f"Staying on {source['label']}.{when}")
staying = text("Staying on %1.", source["label"])
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
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":
registry["active"] = target["id"]
if not target.get("primary"):
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
notify(
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"]],
)
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
echo "New $name sessions already use $active."
else
message="Running sessions stay on $previous."
message=$(omarchy-text "Running sessions stay on %1." "$previous")
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.
omarchy-agent-usage-update --limits-only "$1" >/dev/null 2>&1 &
fi
+11 -4
View File
@@ -22,6 +22,7 @@ import json
import os
import re
import sqlite3
import subprocess
import sys
import tempfile
import time
@@ -32,6 +33,11 @@ from typing import Any
AGENT_ID = "claude"
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."
USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage"
PROBE_MIN_INTERVAL_SECONDS = 15
@@ -989,11 +995,12 @@ def probe_limits(access_token: str) -> dict[str, Any]:
except urllib.error.HTTPError as error:
retry_after = error.headers.get("retry-after", "") if error.headers else ""
if error.code == 429:
help_text = "Anthropic's usage endpoint is rate limiting checks right now" + (
f" (retry after {retry_after}s)" if retry_after else ""
) + ". Local Claude Code stats are still shown."
if retry_after:
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)
else:
help_text = text("Anthropic's usage endpoint is rate limiting checks right now. Local Claude Code stats are still shown.")
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}
except Exception:
# A transport failure reached no server at all — no route, no DNS. Any
+77
View File
@@ -0,0 +1,77 @@
#!/bin/bash
# omarchy:summary=Restore channel and regional pacman defaults in an installed target
# omarchy:args=<stable|rc|edge> [target-root] | --render <stable|rc|edge> <output-dir> [target-root]
# omarchy:requires-sudo=true
# omarchy:hidden=true
set -euo pipefail
usage() {
echo "Usage: omarchy-apply-pacman <stable|rc|edge> [target-root]" >&2
echo " omarchy-apply-pacman --render <stable|rc|edge> <output-dir> [target-root]" >&2
exit 1
}
# --render only builds the two files, so an unprivileged caller can hand root
# nothing but plain copies of the finished result.
render_dir=""
if [[ ${1:-} == "--render" ]]; then
(( $# == 3 || $# == 4 )) || usage
channel="$2"
render_dir="$3"
[[ -d $render_dir ]] || { echo "Render directory not found: $render_dir" >&2; exit 1; }
etc="${4:-/}/etc"
else
(( $# >= 1 && $# <= 2 )) || usage
channel="$1"
etc="${2:-/}/etc"
fi
case "$channel" in
stable|rc|edge) ;;
*) echo "Invalid pacman channel: $channel" >&2; exit 1 ;;
esac
# The ISO writes this plain-text marker before target finalization. Missing
# markers keep existing installations global; never source it as shell code.
region=global
if [[ -e $etc/omarchy/region ]]; then
region=$(<"$etc/omarchy/region")
fi
case "$region" in
global|cn) ;;
*) echo "Unsupported region: $region (expected global or cn)" >&2; exit 1 ;;
esac
# Build both files before replacing either. Explicit refreshes retain backups;
# ordinary updates do not call this command or enforce regional defaults.
if [[ -n $render_dir ]]; then
staging="$render_dir"
else
staging=$(mktemp -d)
trap 'rm -rf "$staging"' EXIT
fi
cp "$OMARCHY_PATH/default/pacman/pacman-$channel.conf" "$staging/pacman.conf"
if [[ $region == "global" ]]; then
cp "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" "$staging/mirrorlist"
else
profile="$OMARCHY_PATH/default/regions/$region/pacman"
printf '\n' >> "$staging/pacman.conf"
cat "$profile/pacman.conf.append" >> "$staging/pacman.conf"
# pacman tries servers in order, so the regional mirror leads and the
# channel's own mirror stays as the fallback.
cat "$profile/mirrorlist-$channel.prepend" "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" > "$staging/mirrorlist"
fi
if [[ -z $render_dir ]]; then
mkdir -p "$etc/pacman.d"
for file in pacman.conf pacman.d/mirrorlist; do
if [[ -e $etc/$file ]]; then
cp -f "$etc/$file" "$etc/$file.bak"
fi
done
# Copying over an existing file keeps its owner and mode, as refresh always has.
cp -f "$staging/pacman.conf" "$etc/pacman.conf"
cp -f "$staging/mirrorlist" "$etc/pacman.d/mirrorlist"
fi
+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
omarchy-brightness-keyboard-mute on
omarchy-osd -i microphone-muted -m "Microphone muted"
omarchy-osd -i microphone-muted -m "$(omarchy-text "Microphone muted")"
else
omarchy-brightness-keyboard-mute off
omarchy-osd -i microphone -m "Microphone on"
omarchy-osd -i microphone -m "$(omarchy-text "Microphone on")"
fi
+1 -1
View File
@@ -13,7 +13,7 @@ sinks=$(timeout 2 pactl -f json list sinks |
sinks_count=$(jq 'length' <<<"$sinks")
if (( sinks_count == 0 )); then
omarchy-osd -m "No audio devices found"
omarchy-osd -m "$(omarchy-text "No audio devices found")"
exit 1
fi
+104 -10
View File
@@ -2,7 +2,7 @@
# omarchy:summary=Configure the bar and its widget layout
# omarchy:group=bar
# omarchy:args=use <id> | reset | defaults | position <top|bottom|left|right> | transparent <true|false|toggle> | put <id> [placement] | move <id> [placement] | set <id> <key> <value> [--json] [placement]
# omarchy:args=use <id> | reset | defaults | position [top|bottom|left|right] | background [opaque|glassy|none|cycle] | put <id> [placement] | move <id> [placement] | set <id> <key> <value> [--json] [placement] | get <id> <key>
# omarchy:examples=omarchy bar use local.neon-bar | omarchy bar put omarchy.keyboard-layout --after omarchy.clock | omarchy bar move omarchy.clock --section center --index 0 | omarchy bar set omarchy.clock format HH:mm
set -euo pipefail
@@ -16,13 +16,19 @@ Usage: omarchy bar <command> [args...]
use <id> Use a bar option as the active bar
reset Return to the built-in Omarchy bar
defaults Restore the default bar and service widgets
position <top|bottom|left|right> Bar position
transparent <true|false|toggle> Bar transparency
position [top|bottom|left|right] Bar position; prints the current one
when given none
background [opaque|glassy|none|cycle]
Bar background: glassy shows what is
behind the bar blurred, cycle steps
none, glassy, opaque; prints the
current one when given none
put <id> [placement] Put a widget on the bar, leaving one
that is already there where it is
move <id> [placement] Move a widget within or between sections
set <id> <key> <value> [--json] [placement]
Set a per-widget option
get <id> <key> Print a per-widget option
Placement:
--section <left|center|right> Target section
@@ -192,30 +198,92 @@ cmd_defaults() {
" \
--slurpfile defaults "$DEFAULTS_FILE" \
--argjson widgets "$optional_widgets"
sync_glass || fail "could not change the blur behind the bar"
echo "Restored the default Omarchy bar"
}
cmd_position() {
local position="${1:-}"
[[ -n $position ]] || fail "position is required"
if (( $# == 0 )); then
jq -r '.bar.position // "top"' "$(source_file)"
return
fi
local position="$1"
(( $# == 1 )) || fail "position takes a single value"
[[ $position =~ ^(top|bottom|left|right)$ ]] || fail "position must be top, bottom, left, or right"
commit "$NORMALIZE | .bar.position = \$position" --arg position "$position"
echo "Bar position set to $position"
}
# The bar background is opaque, glassy, or none. A shell.json from before it
# had one says transparent instead, which is none, and setting a background
# drops it.
current_background() {
jq -r '.bar.background // (if .bar.transparent == true then "none" else "opaque" end)' "$(source_file)"
}
cmd_background() {
if (( $# == 0 )); then
current_background
return
fi
local background="$1"
(( $# == 1 )) || fail "background takes a single value"
[[ $background =~ ^(opaque|glassy|none|cycle)$ ]] || fail "background must be opaque, glassy, none, or cycle"
if [[ $background == "cycle" ]]; then
# Each step changes the bar by one degree: clear, then glassy, then opaque.
case $(current_background) in
none) background="glassy" ;;
glassy) background="opaque" ;;
*) background="none" ;;
esac
fi
local previous
previous=$(current_background)
commit "$NORMALIZE | .bar.background = \$background | del(.bar.transparent)" --arg background "$background"
if ! sync_glass; then
commit "$NORMALIZE | .bar.background = \$background | del(.bar.transparent)" --arg background "$previous"
fail "could not change the blur behind the bar"
fi
echo "Bar background set to $background"
}
# The older on/off form of the background, kept for scripts and dotfiles:
# transparent is none, and not transparent is opaque.
cmd_transparent() {
local transparent="${1:-}"
[[ -n $transparent ]] || fail "transparent is required"
(( $# == 1 )) || fail "transparent takes a single value"
[[ $transparent =~ ^(true|false|toggle)$ ]] || fail "transparent must be true, false, or toggle"
if [[ $transparent == "toggle" ]]; then
commit "$NORMALIZE | .bar.transparent = (.bar.transparent != true)"
echo "Bar transparency toggled"
else
commit "$NORMALIZE | .bar.transparent = \$transparent" --argjson transparent "$transparent"
echo "Bar transparency set to $transparent"
[[ $(current_background) == "none" ]] && transparent="false" || transparent="true"
fi
if [[ $transparent == "true" ]]; then
cmd_background none
else
cmd_background opaque
fi
}
# Hyprland blurs behind the bar only while the bar-glassy toggle is on, so it
# follows the bar background, reloading Hyprland only when it changes. The
# toggle sits where Hyprland reads it, under XDG_STATE_HOME.
sync_glass() {
local dir="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/toggles/hypr"
local flag="$dir/bar-glassy.lua"
if [[ $(current_background) == "glassy" ]]; then
[[ ! -f $flag ]] || return 0
mkdir -p "$dir" && cp "$OMARCHY_PATH/default/hypr/toggles/bar-glassy.lua" "$flag" || return 1
else
[[ -f $flag ]] || return 0
rm -f "$flag" || return 1
fi
hyprctl reload >/dev/null 2>&1 || true
}
bar_widget_default_section() {
@@ -363,6 +431,21 @@ cmd_set() {
echo "Set $key on $id"
}
# Prints the option from the first instance of the widget on the bar, raw for a
# string and as JSON otherwise, and fails when no instance sets it.
cmd_get() {
local id="${1:-}"
local key="${2:-}"
[[ -n $id ]] || fail "get requires a widget id"
[[ -n $key ]] || fail "get requires a setting key"
(( $# == 2 )) || fail "get takes a widget id and a setting key"
jq -e -r --arg id "$id" --arg key "$key" '
first(.bar.layout[]?[]? | select(.id == $id) | .[$key] | select(. != null))
| if type == "string" then . else tojson end
' "$(source_file)" 2>/dev/null || exit 1
}
# --------------------------------------------------------------------- dispatch
command="${1:-}"
@@ -385,6 +468,14 @@ case "$command" in
transparent)
cmd_transparent "$@"
;;
background)
cmd_background "$@"
;;
# Internal: the shell runs this when it loads a bar background, so one set
# by editing shell.json gets its blur too.
sync-glass)
sync_glass || fail "could not change the blur behind the bar"
;;
put)
cmd_put "$@"
;;
@@ -394,6 +485,9 @@ case "$command" in
set)
cmd_set "$@"
;;
get)
cmd_get "$@"
;;
-h | --help | help | "")
usage
;;
+1 -1
View File
@@ -13,5 +13,5 @@ fi
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"
+3 -3
View File
@@ -150,9 +150,9 @@ if [[ $charge_holding == "true" ]]; then
threshold_label="${threshold_end}%"
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
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
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
+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)
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
fi
@@ -33,4 +33,4 @@ fi
# the notification history, and an unmarked copy would be retained by clipboard
# history. Pasting still works; only the recorded copy is given up.
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")"
+7 -5
View File
@@ -86,7 +86,7 @@ esac
# --status only answers whether our recording runs, for the bar and the menu, so
# it needs no recordings directory and never notifies.
if [[ $STATUS != "true" && ! -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
fi
@@ -97,7 +97,7 @@ start_webcam_overlay() {
if [[ -z $WEBCAM_DEVICE ]]; then
WEBCAM_DEVICE=$(omarchy-capture-webcam-list | sed -n '1s/[[:space:]].*//p')
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
fi
fi
@@ -235,7 +235,9 @@ wf_audio_device() {
}
wf_audio_unavailable() {
omarchy-notification-send -u critical -t 5000 "Recording without $1 audio" "The $1 audio could not be added to this recording." >/dev/null 2>&1
local audio
audio=$(omarchy-text "$1")
omarchy-notification-send -u critical -t 5000 "$(omarchy-text "Recording without %1 audio" "$audio")" "$(omarchy-text "The %1 audio could not be added to this recording." "$audio")" >/dev/null 2>&1
}
wf_audio_cleanup() {
@@ -424,7 +426,7 @@ stop_screenrecording() {
if omarchy-capture-screenrecording-process "${RECORDER[@]}"; then
omarchy-capture-screenrecording-process "${RECORDER[@]}" --signal KILL
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
finalize_recording
local filename=$(cat "$RECORDING_FILE" 2>/dev/null)
@@ -434,7 +436,7 @@ stop_screenrecording() {
# 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
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}" \
--exec mpv -- "$filename"
@@ -5,7 +5,7 @@
mapfile -t devices < <(omarchy-capture-webcam-list)
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
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
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")"
+22
View File
@@ -47,9 +47,31 @@ validate_dev_checkout() {
fi
}
# The dev channel refreshes pacman with the checkout's own tools. A checkout
# without this machine's region profile would silently drop its repositories.
validate_dev_region() {
local checkout="$1" region_file="${OMARCHY_REGION_FILE:-/etc/omarchy/region}" region=global
[[ -f $region_file ]] && region=$(<"$region_file")
[[ $region == "global" ]] && return 0
# The refresh after linking needs a runnable helper and every fragment, not
# just a directory with the region's name: dev refreshes edge, and leaving
# dev later refreshes stable or rc with this checkout's tools.
local profile="$checkout/default/regions/$region/pacman" fragment supported=true
[[ -x $checkout/bin/omarchy-apply-pacman ]] || supported=false
for fragment in pacman.conf.append mirrorlist-stable.prepend mirrorlist-rc.prepend mirrorlist-edge.prepend; do
[[ -r $profile/$fragment ]] || supported=false
done
if ! $supported; then
fail "$checkout does not support the '$region' region, so switching would drop its package repositories. Check out a branch with default/regions/$region, or remove $region_file to use global defaults."
fi
}
link_dev_checkout() {
local checkout="$1" required
[[ -d $checkout/.git ]] || git clone https://github.com/omacom/omarchy.git "$checkout"
validate_dev_region "$checkout"
# Check the destination before changing /etc/omarchy.conf or sudo's path.
# An existing checkout is not pulled automatically and may predate this policy.
+1 -1
View File
@@ -18,7 +18,7 @@ copy_url() {
[[ -n $url ]] || return 1
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() {
+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.
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
fi
@@ -148,7 +148,7 @@ download_url() {
# toast would exit before the thumbnail cleanup below is ever scheduled.
# `--` keeps mpv from parsing a leading-dash filename as an option; the path
# 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}" \
--exec mpv -- "$filepath" || true
@@ -160,7 +160,7 @@ download_url() {
rm -f "$preview"
) &
else
omarchy-notification-send -u critical -g 󰅖 "Download failed" "$url"
omarchy-notification-send -u critical -g 󰅖 "$(omarchy-text "Download failed")" "$url"
fi
exit 0
+2 -2
View File
@@ -40,8 +40,8 @@ announce() {
omarchy-notification-send \
--urgency critical \
--glyph "$CRASH_GLYPH" \
"Process crashed: $comm" \
"Click to diagnose with AI" \
"$(omarchy-text "Process crashed: %1" "$comm")" \
"$(omarchy-text "Click to diagnose with AI")" \
--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
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")"
+1 -1
View File
@@ -59,4 +59,4 @@ fi
mkdir -p "$(dirname "$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
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")"
+1 -1
View File
@@ -169,7 +169,7 @@ set_terminal_size() {
[[ $prev_id =~ ^[0-9]+$ ]] && replace=(-r "$prev_id")
local new_id
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
[[ $new_id =~ ^[0-9]+$ ]] && printf '%s\n' "$new_id" >"$id_file"
fi
+2 -2
View File
@@ -74,11 +74,11 @@ XML
omarchy-restart-shell
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
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
omarchy-hook font-set "$font_name"
+2 -2
View File
@@ -10,7 +10,7 @@ if (( $# == 0 )); then
mapfile -t cores < <(omarchy-games-retro-cores)
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
fi
@@ -69,4 +69,4 @@ EOF
chmod +x "$desktop_file"
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")"
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
# omarchy:summary=Show or set window corners: theme, sharp, or rounded
# omarchy:args=[theme|sharp|rounded]
# omarchy:examples=omarchy hyprland corners | omarchy hyprland corners rounded
TOGGLES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/toggles/hypr"
# Sharp and rounded are toggle files loaded after every theme, so they override
# the corners of whichever theme is current. Theme removes both.
if (( $# == 0 )); then
if [[ -f $TOGGLES_DIR/corners-sharp.lua ]]; then
echo "sharp"
elif [[ -f $TOGGLES_DIR/corners-rounded.lua ]]; then
echo "rounded"
else
echo "theme"
fi
exit 0
fi
case $1 in
theme) LABEL=$(omarchy-text "Corners follow the theme") ;;
sharp) LABEL=$(omarchy-text "Sharp corners for all themes") ;;
rounded) LABEL=$(omarchy-text "Rounded corners for all themes") ;;
*)
echo "Usage: omarchy-hyprland-corners [theme|sharp|rounded]" >&2
exit 1
;;
esac
# Save the new choice before dropping the old one, so a failed save keeps it.
if [[ $1 != "theme" ]]; then
mkdir -p "$TOGGLES_DIR" && cp "$OMARCHY_PATH/default/hypr/toggles/corners-$1.lua" "$TOGGLES_DIR/corners-$1.lua" || exit 1
fi
for mode in sharp rounded; do
[[ $mode == "$1" ]] || rm -f "$TOGGLES_DIR/corners-$mode.lua" || exit 1
done
hyprctl reload >/dev/null || exit 1
omarchy-notification-send -g 󰘇 "$LABEL"
+5 -5
View File
@@ -16,7 +16,7 @@ wake() {
on() {
if omarchy-hyprland-toggle-enabled $TOGGLE; then
omarchy-hyprland-toggle $TOGGLE off
omarchy-notification-send -g 󰍹 "Laptop display enabled"
omarchy-notification-send -g 󰍹 "$(omarchy-text "Laptop display enabled")"
fi
wake
@@ -24,25 +24,25 @@ on() {
off() {
if [[ -z $INTERNAL ]]; then
omarchy-notification-send -g 󰍹 "No laptop display found"
omarchy-notification-send -g 󰍹 "$(omarchy-text "No laptop display found")"
exit 1
fi
# The name is written into generated Lua below, so only a plain connector
# name may pass; anything else could execute on the next reload.
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
fi
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
fi
if omarchy-hyprland-toggle-disabled $TOGGLE && omarchy-hyprland-toggle-disabled $MIRROR_TOGGLE; then
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
fi
}
+5 -5
View File
@@ -13,12 +13,12 @@ EXTERNAL=$(hyprctl monitors -j | jq -r '.[] | select(.name | test("^(eDP|LVDS|DS
on() {
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
fi
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
fi
@@ -26,7 +26,7 @@ on() {
# names may pass; a user-created headless output can carry any name.
for output in "$INTERNAL" "$EXTERNAL"; do
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
fi
done
@@ -35,7 +35,7 @@ on() {
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"
omarchy-notification-send -g 󰍹 "Mirroring enabled ($EXTERNAL)"
omarchy-notification-send -g 󰍹 "$(omarchy-text "Mirroring enabled (%1)" "$EXTERNAL")"
hyprctl reload
fi
}
@@ -43,7 +43,7 @@ on() {
off() {
if omarchy-hyprland-toggle-enabled $TOGGLE; then
omarchy-hyprland-toggle $TOGGLE off
omarchy-notification-send -g 󰍹 "Extended mode restored"
omarchy-notification-send -g 󰍹 "$(omarchy-text "Extended mode restored")"
fi
}
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# omarchy:summary=Returns true when the compositor holds a session lock no other locker is alive to own
# omarchy:args=<own-pid>
# omarchy:hidden=true
# Hyprland lets any client take over a held lock (misc:allow_session_lock_restore),
# so a shell that recovers a lock it merely sees can take the live lock screen of
# another shell, and strand it when it exits. A lock is stranded only while no
# other Quickshell or hyprlock is running; until then the answer is undetermined,
# so the caller keeps asking until that locker exits or it gives up.
#
# Exits 0 stranded, 1 unlocked, 2 undetermined. The caller passes its own pid,
# as a crash re-execs the shell in place.
omarchy-hyprland-session-locked || exit
if pgrep -u "$UID" -x 'quickshell|qs|hyprlock' | grep -qvxF "$1"; then
exit 2
fi
@@ -3,6 +3,6 @@
# omarchy:summary=Toggle single-window square aspect ratio.
case $(omarchy-hyprland-toggle single-window-aspect-ratio) in
on) omarchy-notification-send -g  "Enable single-window square aspect ratio" ;;
off) omarchy-notification-send -g  "Disable single-window square aspect ratio" ;;
on) omarchy-notification-send -g  "$(omarchy-text "Enable single-window square aspect ratio")" ;;
off) omarchy-notification-send -g  "$(omarchy-text "Disable single-window square aspect ratio")" ;;
esac
+70
View File
@@ -0,0 +1,70 @@
#!/bin/bash
# omarchy:summary=Show or set how see-through windows are: opaque, transparent, or glassy
# omarchy:args=[opaque|transparent|glassy]
# omarchy:examples=omarchy hyprland window transparency | omarchy hyprland window transparency glassy
TOGGLES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/toggles/hypr"
# Opaque and glassy are toggle files loaded after every theme: opaque makes the
# windows Omarchy makes transparent solid, and glassy blurs what is behind any
# see-through window and gives terminals a see-through background. Transparent,
# the default, removes both.
current_mode() {
if [[ -f $TOGGLES_DIR/window-opaque.lua ]]; then
echo "opaque"
elif [[ -f $TOGGLES_DIR/window-glassy.lua ]]; then
echo "glassy"
else
echo "transparent"
fi
}
# Save the new choice before dropping the old one, so a failed save keeps it.
save_mode() {
if [[ $1 != "transparent" ]]; then
mkdir -p "$TOGGLES_DIR" && cp "$OMARCHY_PATH/default/hypr/toggles/window-$1.lua" "$TOGGLES_DIR/window-$1.lua" || return 1
fi
local mode
for mode in opaque glassy; do
[[ $mode == "$1" ]] || rm -f "$TOGGLES_DIR/window-$mode.lua" || return 1
done
}
if (( $# == 0 )); then
current_mode
exit 0
fi
case $1 in
opaque) LABEL=$(omarchy-text "Opaque windows") ;;
transparent) LABEL=$(omarchy-text "Transparent windows") ;;
glassy) LABEL=$(omarchy-text "Glassy windows") ;;
*)
echo "Usage: omarchy-hyprland-window-transparency [opaque|transparent|glassy]" >&2
exit 1
;;
esac
# A theme switch renders the next theme's terminal files from the saved choice
# and then replaces the current theme, so the choice and the glass written into
# the current theme are changed under its lock, or the switch could drop them.
exec 9>"${XDG_RUNTIME_DIR:-/tmp}/omarchy-theme-set.lock"
flock 9
# A choice whose glass cannot be written is taken back, so the saved choice
# is always the one on screen.
previous=$(current_mode)
save_mode "$1" || exit 1
if ! omarchy-theme-set-glass; then
save_mode "$previous"
omarchy-theme-set-glass
exit 1
fi
flock -u 9
hyprctl reload >/dev/null || exit 1
omarchy-restart-terminal
omarchy-theme-set-foot
omarchy-restart-btop
omarchy-notification-send -g 󰂵 "$LABEL"
+2 -2
View File
@@ -40,14 +40,14 @@ notify_missing_width() {
local key="$1"
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() {
local key="$1"
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() {
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
# omarchy:summary=Print the layout of a workspace: dwindle, scrolling, or floating
# omarchy:args=[workspace-id]
if [[ -n $1 ]]; then
WORKSPACE_ID=$1
TILED_LAYOUT=$(hyprctl workspaces -j | jq -r --arg id "$WORKSPACE_ID" 'first(.[] | select((.id | tostring) == $id) | .tiledLayout) // empty')
else
read -r WORKSPACE_ID TILED_LAYOUT < <(hyprctl activeworkspace -j | jq -r '"\(.id) \(.tiledLayout // "")"')
fi
MODES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/workspace-layouts"
# Hyprland only reports the tiled layout, and floating is not one, so the mode is
# read back from the file omarchy-hyprland-workspace-layout-toggle wrote. Files
# saved before floating existed name no mode, and a workspace without a mode of
# its own follows the default layout.
if [[ $WORKSPACE_ID =~ ^[0-9]+$ ]] && (( WORKSPACE_ID >= 1 )); then
MODE=$(sed -n 's/.*mode = "\([a-z]*\)".*/\1/p' "$MODES_DIR/$WORKSPACE_ID.lua" 2>/dev/null)
if [[ -z $MODE && -f $MODES_DIR/default.lua ]]; then
MODE=$(omarchy-hyprland-workspace-layout-default)
fi
fi
MODE=${MODE:-$TILED_LAYOUT}
echo "${MODE:-dwindle}"
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
# omarchy:summary=Show or set the layout of workspaces without one of their own: dwindle, scrolling, or floating
# omarchy:args=[dwindle|scrolling|floating]
# omarchy:examples=omarchy hyprland workspace layout default | omarchy hyprland workspace layout default scrolling
MODES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/workspace-layouts"
DEFAULT_FILE="$MODES_DIR/default.lua"
# Without a saved default, Hyprland's own layout is the default, which the
# user's looknfeel may have set.
if (( $# == 0 )); then
MODE=$(sed -n 's/.*default = "\([a-z]*\)".*/\1/p' "$DEFAULT_FILE" 2>/dev/null)
[[ -n $MODE ]] || MODE=$(hyprctl getoption general.layout -j 2>/dev/null | jq -r '.str // empty')
echo "${MODE:-dwindle}"
exit 0
fi
case $1 in
dwindle) LABEL=tiling ;;
scrolling | floating) LABEL=$1 ;;
*)
echo "Usage: omarchy-hyprland-workspace-layout-default [dwindle|scrolling|floating]" >&2
exit 1
;;
esac
# Saved even for dwindle, so the choice also wins over a layout set in the
# user's own looknfeel. Workspaces given a layout with Super + L keep theirs.
mkdir -p "$MODES_DIR"
printf 'o.workspace_mode({ default = "%s" })\n' "$1" >"$DEFAULT_FILE"
hyprctl reload >/dev/null
omarchy-shell -q omarchy.workspaces refresh
omarchy-notification-send -g 󱂬 "$(omarchy-text "New workspaces use the %1 layout" "$LABEL")"
+62 -14
View File
@@ -1,21 +1,69 @@
#!/bin/bash
# omarchy:summary=Toggle the layout on the current active workspace between dwindle and scrolling
# omarchy:summary=Cycle the current active workspace through the dwindle, scrolling, and floating layouts
# omarchy:args=[back]
DIRECTION=${1:-next}
if [[ $DIRECTION != "next" && $DIRECTION != "back" ]]; then
echo "Usage: omarchy-hyprland-workspace-layout-toggle [back]" >&2
exit 1
fi
ACTIVE_WORKSPACE=$(hyprctl activeworkspace -j | jq -r '.id')
[[ $ACTIVE_WORKSPACE =~ ^-?[0-9]+$ ]] || exit 1
CURRENT_LAYOUT=$(hyprctl activeworkspace -j | jq -r '.tiledLayout')
LAYOUTS_DIR="$HOME/.local/state/omarchy/workspace-layouts"
LAYOUT_FILE="$LAYOUTS_DIR/$ACTIVE_WORKSPACE.lua"
# Named workspaces report a negative id, and Hyprland reads a leading "-" as a
# selector relative to the current workspace: targeting one lands on workspace 1
# and reconfigures that instead.
[[ $ACTIVE_WORKSPACE =~ ^[0-9]+$ ]] || exit 1
MODES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/workspace-layouts"
MODE_FILE="$MODES_DIR/$ACTIVE_WORKSPACE.lua"
case "$CURRENT_LAYOUT" in
dwindle) NEW_LAYOUT=scrolling ;;
*) NEW_LAYOUT=dwindle ;;
esac
CURRENT_MODE=$(omarchy-hyprland-workspace-layout-current)
mkdir -p "$LAYOUTS_DIR"
printf 'hl.workspace_rule({ workspace = "%s", layout = "%s" })\n' "$ACTIVE_WORKSPACE" "$NEW_LAYOUT" >"$LAYOUT_FILE"
if [[ $DIRECTION == "back" ]]; then
case "$CURRENT_MODE" in
dwindle) NEW_MODE=floating ;;
floating) NEW_MODE=scrolling ;;
*) NEW_MODE=dwindle ;;
esac
else
case "$CURRENT_MODE" in
dwindle) NEW_MODE=scrolling ;;
scrolling) NEW_MODE=floating ;;
*) NEW_MODE=dwindle ;;
esac
fi
hyprctl eval "hl.workspace_rule({ workspace = \"$ACTIVE_WORKSPACE\", layout = \"$NEW_LAYOUT\" })" >/dev/null 2>&1 || \
hyprctl keyword workspace "$ACTIVE_WORKSPACE, layout:$NEW_LAYOUT"
omarchy-notification-send -g 󱂬 "Workspace layout set to $NEW_LAYOUT"
APPLY="o.workspace_mode({ workspace = \"$ACTIVE_WORKSPACE\", mode = \"$NEW_MODE\" })"
# Saving a mode that did not apply would leave the file naming one layout while
# the screen shows another, and restore it at the next login.
APPLIED=0
if hyprctl eval "$APPLY" >/dev/null 2>&1; then
APPLIED=1
fi
if (( ! APPLIED )); then
omarchy-notification-send -g 󱂬 "$(omarchy-text "Workspace layout needs a Hyprland reload")"
elif [[ $NEW_MODE == "$(omarchy-hyprland-workspace-layout-default)" ]] && rm -f "$MODE_FILE"; then
# Cycled onto the default, a workspace goes back to following it, so a later
# change of the default reaches it too.
omarchy-notification-send -g 󱂬 "$(omarchy-text "Workspace layout set to %1" "$NEW_MODE")"
elif mkdir -p "$MODES_DIR" && printf '%s\n' "$APPLY" >"$MODE_FILE"; then
omarchy-notification-send -g 󱂬 "$(omarchy-text "Workspace layout set to %1" "$NEW_MODE")"
else
# The mode is on screen but nothing will bring it back, and the next press
# reads the mode from this file, so say so rather than counting it as set.
omarchy-notification-send -g 󱂬 "$(omarchy-text "Workspace layout set to %1 but could not be saved" "$NEW_MODE")"
fi
# The bar shows the focused workspace's layout.
if (( APPLIED )); then
omarchy-shell -q omarchy.workspaces refresh
fi
# Titlebars and shadows are set up while the config loads, and only
# when something floats, so the first floating workspace needs a reload to get
# them. Once the titlebar plugin is in, later ones have them already.
if (( APPLIED )) && [[ $NEW_MODE == "floating" ]] && ! hyprctl plugin list -j 2>/dev/null | jq -e 'any(.[]?; .name == "hyprbars")' >/dev/null 2>&1; then
hyprctl reload >/dev/null
fi
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# omarchy:summary=Configure, inspect, or switch input methods
# omarchy:args=<status|cycle [back]|configure --seed|--defaults>
# omarchy:hidden=true
set -euo pipefail
if [[ ${1:-} == "cycle" ]]; then
direction=${2:-next}
if [[ $direction != "next" && $direction != "back" ]]; then
echo "Usage: omarchy-input-method cycle [back]" >&2
exit 1
fi
# The shell's reader retains a choice made while nothing has focus.
if [[ $(omarchy-shell shell cycleInput "$direction") != "ok" ]]; then
echo "The shell's input reader is not running" >&2
exit 1
fi
else
/usr/bin/python3 "$OMARCHY_PATH/default/input-methods/configure.py" "$@"
fi
+3
View File
@@ -92,6 +92,9 @@ phoenix)
;;
rust)
echo -e "Installing Rust...\n"
# Crates that generate C bindings with bindgen, including Ruby gems built
# with rb-sys, load libclang from clang.
omarchy-pkg-add clang
bash -c "$(curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs)" -- -y
;;
java)
+1 -1
View File
@@ -23,5 +23,5 @@ 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  "Superwhisper Dictation Ready" "Hold Right Alt or F9 to dictate (or toggle with Super + Ctrl + X)." -t 10000
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
+1 -1
View File
@@ -33,4 +33,4 @@ systemctl --user enable --now voxtype.service
omarchy-dictation-use voxtype
hyprctl reload >/dev/null
omarchy-notification-send -g  "Voxtype Dictation Ready" "Hold Right Alt or F9 to dictate (or toggle with Super + Ctrl + X)." -t 10000
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
+2 -4
View File
@@ -15,17 +15,15 @@ if gum confirm "Are you sure you want to restore all preinstalled web apps, TUI
aether \
cliamp \
libreoffice-fresh \
xournalpp \
pinta \
obsidian \
obs-studio \
kdenlive \
moonlight-qt \
omacut \
monologue \
omacalc \
omawrite \
hype; then
hype \
signed; then
echo -e "\nPreinstalls are still marked as removed. Fix the errors above and try again."
exit 1
fi
+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
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"
+1 -1
View File
@@ -16,7 +16,7 @@ terminal=$(xdg-terminal-exec --print-id)
case $terminal in
*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
;;
esac
+1 -1
View File
@@ -555,7 +555,7 @@ output_binding_records_uncached() {
keybindings_cache_key() {
{
printf 'v14\n'
printf 'v15\n'
hyprctl devices 2>/dev/null | grep -F 'active keymap:'
cat "$key_names_file" 2>/dev/null
hyprctl binds 2>/dev/null
+1 -1
View File
@@ -28,7 +28,7 @@ rows=$(jq -r --arg icon "$PLUGIN_ICON" \
". as \$plugins
| .[] | select($filter)
| \$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
[[ -n $selection ]] || exit 0
+46 -4
View File
@@ -1,9 +1,9 @@
#!/bin/bash
# omarchy:summary=Pick one option from a menu
# omarchy:summary=Pick one or multiple options from a menu
# omarchy:group=menu
# omarchy:name=select
# omarchy:args=prompt [option...] [-- menu args...]
# omarchy:args=prompt [option...] [-- --multiple --selected <value> --on-change <command-json> --change-key <key> --width <pixels> --maxheight <pixels>]
# omarchy:examples=omarchy menu select Format jpg png|omarchy-menu-select Resolution 4k 1080p 720p -- --width 400
# An option may lead with an icon, as "<glyph><TAB><label>", and may trail a
@@ -12,6 +12,12 @@
# alone; an option with a subtext returns "<label><TAB><subtext>", so callers
# with same-named rows get the subtext back as the stable key.
# With --multiple, clicking/Enter toggles rows and Apply returns a JSON array.
# --on-change takes a JSON command array; each toggle runs it with the selection
# JSON appended, applies immediately, and needs no Apply row. A successful
# command may print a JSON array of actual selections to update the checkmarks.
# --selected takes a returned value (label plus any subtext), and can be repeated.
set -euo pipefail
if (( $# < 1 )); then
@@ -25,12 +31,43 @@ shift
options=()
menu_width=""
menu_maxheight=""
multiple=0
on_change="[]"
change_key=""
selected=()
while (( $# > 0 )); do
if [[ $1 == "--" ]]; then
shift
while (( $# > 0 )); do
case "$1" in
--on-change)
shift
if (( $# == 0 )); then
echo "omarchy-menu-select: --on-change requires a JSON command array" >&2
exit 1
fi
on_change="$1"
;;
--change-key)
shift
if (( $# == 0 )); then
echo "omarchy-menu-select: --change-key requires a value" >&2
exit 1
fi
change_key="$1"
;;
--multiple)
multiple=1
;;
--selected)
shift
if (( $# == 0 )); then
echo "omarchy-menu-select: --selected requires a value" >&2
exit 1
fi
selected+=("$1")
;;
--width)
shift
if (( $# == 0 )); then
@@ -73,20 +110,25 @@ rm -f "$done_file"
trap 'rm -f "$selection_file" "$done_file"' EXIT
options_json=$(perl -MEncode=decode -MJSON::PP=encode_json -e 'print encode_json([map { decode("UTF-8", $_) } @ARGV])' "${options[@]}")
selected_json=$(perl -MEncode=decode -MJSON::PP=encode_json -e 'print encode_json([map { decode("UTF-8", $_) } @ARGV])' "${selected[@]}")
payload=$(perl -MEncode=decode -MJSON::PP=encode_json,decode_json -e '
my $payload = {
mode => "select",
prompt => decode("UTF-8", $ARGV[0]),
options => decode_json($ARGV[1]),
multiple => $ARGV[6] ? JSON::PP::true : JSON::PP::false,
selected => decode_json($ARGV[7]),
onChange => decode_json($ARGV[8]),
changeKey => $ARGV[9],
selectionFile => decode("UTF-8", $ARGV[2]),
doneFile => decode("UTF-8", $ARGV[3])
};
$payload->{width} = int($ARGV[4]) if length($ARGV[4] // "");
$payload->{maxHeight} = int($ARGV[5]) if length($ARGV[5] // "");
print encode_json($payload)
' "$prompt" "$options_json" "$selection_file" "$done_file" "$menu_width" "$menu_maxheight")
' "$prompt" "$options_json" "$selection_file" "$done_file" "$menu_width" "$menu_maxheight" "$multiple" "$selected_json" "$on_change" "$change_key")
omarchy-shell shell summon omarchy.menu "$payload" >/dev/null
omarchy-shell shell summon omarchy.menu "$payload" >/dev/null || exit 2
while [[ ! -e $done_file ]]; do
sleep 0.05
+1 -1
View File
@@ -33,7 +33,7 @@ else
picked=$(omarchy-file-select "${select_args[@]}") || status=$?
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
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
sudo timedatectl set-timezone "$timezone"
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)
if (( pending_count == 1 )); then
message="Click to run 1 pending migration."
message=$(omarchy-text "Click to run 1 pending migration.")
else
message="Click to run $pending_count pending migrations."
message=$(omarchy-text "Click to run %1 pending migrations." "$pending_count")
fi
# 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
# 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
# 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-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
clone_complete=1
omarchy-notification-send -g 󰐱 \
"Editing Cloned Plugin" \
"Original plugin has been replace by clone."
"$(omarchy-text "Editing Cloned Plugin")" \
"$(omarchy-text "Original plugin has been replace by clone.")"
echo "Cloned $source_id to $target_dir and switched to $new_id"
if (( edit_clone )); then
# Word-splitting is deliberate: EDITOR may carry flags.
+1
View File
@@ -56,6 +56,7 @@ if [[ -z ${profile:-} ]] || ! profile_available "$profile"; then
fi
powerprofilesctl set "$profile" || exit
omarchy-shell -q battery refreshPowerProfile >/dev/null 2>&1 || true
if [[ -n $requested_profile ]]; then
mkdir -p "$state_dir"
+4 -11
View File
@@ -187,11 +187,11 @@ tips=(
"Super + Space opens the Omarchy menu for apps, settings, and more"
"Super + K shows all the key bindings"
"Super is the Windows or command key on your keyboard"
"Use Xournal++ to sign PDFs"
"Sign PDFs and fill them in with Signed"
"Share files with phones and laptops using LocalSend"
"Turn any website into an app with Install > Web App in the menu"
"Super + Return opens a terminal, Super + Shift + Return the browser"
"Edit images with Pinta, videos with Kdenlive, docs with LibreOffice"
"Edit images with Pinta, trim videos with Omacut, docs with LibreOffice"
"Super + Ctrl + Print grabs text off the screen with OCR"
"Print takes a screenshot, Alt + Print records the screen"
"Set a reminder with Super + Ctrl + R"
@@ -615,6 +615,8 @@ keyboard_form() {
# re-key below. Confirm it so a stray Ctrl+C doesn't bounce the machine.
confirm_reboot && exec systemctl reboot
done
omarchy_persist_input_selection
}
confirm_reboot() {
@@ -1172,15 +1174,6 @@ run_provisioning() {
return 1
fi
if omarchy-pkg-present usbguard; then
log_step "enrolling the owner's USB devices"
source "$OMARCHY_PATH/install/helpers/usb-authorization.sh"
usb_authorization_provision_owner "$username"
fi
log_step "enrolling the owner's Thunderbolt accessories"
/usr/bin/omarchy-thunderbolt-authorization-admin owner
log_step "cleaning up provisioning state"
cleanup_oem_state
log_step "first-boot setup complete"
+20 -2
View File
@@ -42,11 +42,29 @@ if [[ ! -f $templates/pacman-$channel.conf || ! -f $templates/mirrorlist-$channe
exit 1
fi
conf=$templates/pacman-$channel.conf mirrorlist=$templates/mirrorlist-$channel
# Regions render x86 pacman only (omarchy #13690).
if [[ $platform == x86 ]]; then
# Render the channel templates plus this machine's regional additions as the
# caller, so root only copies finished files. The scratch directory goes on
# every exit, including a failed render, a refused sudo prompt, or a signal;
# revoking the sudo timestamp stays the last thing the exit path does.
rendered=$(mktemp -d)
cleanup_refresh() {
local status=$?
rm -rf "$rendered"
omarchy_security_exit_with_revoked_sudo "$status"
}
trap cleanup_refresh EXIT
"$OMARCHY_PATH/bin/omarchy-apply-pacman" --render "$channel" "$rendered"
conf=$rendered/pacman.conf mirrorlist=$rendered/mirrorlist
fi
sudo cp -f /etc/pacman.conf /etc/pacman.conf.bak
sudo cp -f /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak
echo "Setting channel to $channel"
sudo cp -f "$templates/pacman-$channel.conf" /etc/pacman.conf
sudo cp -f "$templates/mirrorlist-$channel" /etc/pacman.d/mirrorlist
sudo cp -f "$conf" /etc/pacman.conf
sudo cp -f "$mirrorlist" /etc/pacman.d/mirrorlist
# Allow user customization of /etc/pacman.conf before the upgrade runs, so
# custom repositories and IgnorePkg entries shape the downgrade-capable
+63
View File
@@ -0,0 +1,63 @@
#!/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
# The cn profile renders x86 pacman only (omarchy #13690).
if [[ $1 == "cn" && $(omarchy-hw-platform) != "x86" ]]; then
echo "The China region needs an x86 machine; $(omarchy-hw-platform) has no China profile yet" >&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?")"; 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")
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
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
done < <(active_reminder_timers "$now")
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
omarchy-notification-send -g 󰢌 "Upcoming reminders" "${body%$'\n'}"
omarchy-notification-send -g 󰢌 "$(omarchy-text "Upcoming reminders")" "${body%$'\n'}"
fi
}
@@ -75,7 +75,8 @@ show_json() {
local reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders"
local now=$(date +%s)
local count=0
local tooltip="Set Reminder"
local tooltip
tooltip=$(omarchy-text "Set Reminder")
while IFS=$'\t' read -r timer next; do
count=$((count + 1))
@@ -91,7 +92,7 @@ show_json() {
if [[ -n $reminder_message ]]; then
label=$reminder_message
else
label="${reminder_minutes}-min reminder"
label=$(omarchy-text "%1-min reminder" "$reminder_minutes")
fi
item_json=$(jq -cn \
@@ -109,9 +110,9 @@ show_json() {
done < <(active_reminder_timers "$now")
if ((count == 1)); then
tooltip="1 reminder"
tooltip=$(omarchy-text "1 reminder")
elif ((count > 1)); then
tooltip="$count reminders"
tooltip=$(omarchy-text "%1 reminders" "$count")
fi
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
refresh_indicator
omarchy-notification-send -g 󰢌 "All reminders have been cleared"
omarchy-notification-send -g 󰢌 "$(omarchy-text "All reminders have been cleared")"
}
usage() {
@@ -176,7 +177,7 @@ if [[ -z $minutes ]] || [[ ! $minutes =~ ^[0-9]+$ ]] || ((minutes == 0)); then
fi
if [[ -z $message ]]; then
message="Your ${minutes} minutes are up"
message=$(omarchy-text "Your %1 minutes are up" "$minutes")
fi
set_at=$(date +%s)
@@ -184,18 +185,18 @@ remind_at=$(date -d "+${minutes} minutes" +%H:%M)
unit="omarchy-reminder-${minutes}m-$set_at"
reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders"
message_file="$reminder_dir/$unit.message"
confirmation="You'll be reminded at $remind_at"
confirmation_title="Reminder set for ${minutes} minutes"
confirmation=$(omarchy-text "You'll be reminded at %1" "$remind_at")
confirmation_title=$(omarchy-text "Reminder set for %1 minutes" "$minutes")
mkdir -p "$reminder_dir"
if [[ -n $custom_message ]]; then
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
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
omarchy-notification-send -g 󰢌 "$confirmation_title" "$confirmation"
+2 -4
View File
@@ -34,15 +34,13 @@ if gum confirm "Are you sure you want to remove all preinstalled web apps, TUI w
aether \
cliamp \
libreoffice-fresh \
xournalpp \
pinta \
obsidian \
obs-studio \
kdenlive \
moonlight-qt \
omacut \
monologue \
omacalc \
omawrite \
hype
hype \
signed
fi
@@ -1,10 +0,0 @@
#!/bin/bash
# omarchy:summary=Restore Bolt's default authorization behavior
set -euo pipefail
gum confirm "Restore Bolt's previous policy? New accessories may be authorized automatically when IOMMU protection is active." || exit 0
sudo /usr/bin/omarchy-thunderbolt-authorization-admin disable
systemctl --user disable --now omarchy-thunderbolt-authorization.service
echo "Previous Bolt policy restored. Saved Omarchy trust is retained for re-enabling."
@@ -1,9 +0,0 @@
#!/bin/bash
# omarchy:summary=Restore saved Thunderbolt firmware boot permissions
set -euo pipefail
gum confirm "Restore the previous Thunderbolt firmware boot allowlists? Device approval will remain enabled." || exit 0
sudo /usr/bin/omarchy-thunderbolt-authorization-admin boot-disable
echo "Previous firmware boot permissions restored and verified."
@@ -1,89 +0,0 @@
#!/bin/bash
# omarchy:summary=Stop asking before new USB devices can access the system
# omarchy:args=[--boot-only] [--yes]
# omarchy:requires-sudo=true
set -euo pipefail
assume_yes=0
boot_only=0
while (( $# > 0 )); do
case "$1" in
--yes) assume_yes=1 ;;
--boot-only) boot_only=1 ;;
-h | --help)
echo "Usage: omarchy-remove-security-usb-authorization [--boot-only] [--yes]"
echo
echo "Disables Omarchy's USB approval prompts and restores the kernel's"
echo "normal default-allow behavior."
echo "Use --boot-only to keep USBGuard enabled after the system starts."
exit 0
;;
*)
echo "omarchy-remove-security-usb-authorization: unknown option '$1'. Try --help." >&2
exit 2
;;
esac
shift
done
if (( boot_only )); then
if (( ! assume_yes )); then
gum style --foreground 212 --bold "Remove USB authorization from boot"
echo
echo "USBGuard will continue blocking unknown devices after its service starts."
echo "Devices can reach kernel drivers earlier during boot."
echo
gum confirm "Disable USB authorization from kernel boot?" || exit 130
fi
sudo omarchy-usb-authorization-boot disable
echo
echo "Boot-time USB authorization is disabled and takes effect after reboot."
exit 0
fi
state_dir="$HOME/.local/state/omarchy/usb-authorization"
unit="$HOME/.config/systemd/user/omarchy-usb-authorization.service"
if (( ! assume_yes )); then
gum style --foreground 212 --bold "Remove USB device authorization"
echo
echo "New USB devices will be allowed to bind to kernel drivers without approval."
echo
gum confirm "Disable USB device authorization?" || exit 130
fi
# Rebuild a permissive boot image before stopping USBGuard. If rebuilding
# fails, the command stops here and leaves the complete policy enabled.
sudo omarchy-usb-authorization-boot disable
if omarchy-pkg-present usbguard; then
# Keep the watcher and its IPC access until blocking has actually stopped.
# In particular, a failed stop must not strand a running daemon without UI.
sudo systemctl disable --now usbguard.service
if systemctl is-active --quiet usbguard.service || systemctl is-enabled --quiet usbguard.service; then
echo "USBGuard is still active or enabled; approval prompts have been preserved." >&2
exit 1
fi
sudo omarchy-usb-authorization-restore-default
sudo usbguard remove-user "$USER"
fi
systemctl --user disable --now omarchy-usb-authorization.service 2>/dev/null || true
rm -f "$unit"
systemctl --user daemon-reload
if [[ -d $state_dir/requests ]]; then
find "$state_dir/requests" -mindepth 1 -maxdepth 1 -type f -delete
rmdir "$state_dir/requests" 2>/dev/null || true
fi
rm -f "$state_dir/package-installed-by-omarchy" \
"$state_dir/policy-generated-by-omarchy" \
"$state_dir/service-enabled-by-omarchy"
rmdir "$state_dir" 2>/dev/null || true
echo "USB device authorization is disabled."
+22 -1
View File
@@ -2,9 +2,30 @@
# 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
# instance would exit on arrival and the stale process would keep serving the
# old ~/.XCompose -- a restart that reports success and changes nothing.
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
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
# omarchy:summary=Select input methods for 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
/usr/bin/python3 "$OMARCHY_PATH/default/input-methods/typing_setup.py" input
exit
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[@]}"
/usr/bin/python3 "$OMARCHY_PATH/default/input-methods/typing_setup.py" add "$method"
echo "Input method added. Use Super+I or the Japanese/Korean input key to switch; existing custom Fcitx keys are preserved."
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
# omarchy:summary=Select the keyboard layouts available for switching
set -euo pipefail
/usr/bin/python3 "$OMARCHY_PATH/default/input-methods/typing_setup.py" keyboard
@@ -1,42 +0,0 @@
#!/bin/bash
# omarchy:summary=Require approval for new Thunderbolt PCIe accessories
# omarchy:args=[--quiet|status|recover]
set -euo pipefail
source "$OMARCHY_PATH/install/helpers/thunderbolt-policy.sh"
source "$OMARCHY_PATH/install/helpers/thunderbolt-review.sh"
if [[ ${1:-} == "status" ]]; then
tb_status
elif [[ ${1:-} == "recover" && $# == 1 ]]; then
sudo /usr/bin/omarchy-thunderbolt-authorization-admin recover
elif [[ $# == 0 || ( $# == 1 && $1 == "--quiet" ) ]]; then
if [[ ${1:-} != "--quiet" ]]; then
if [[ -f $TB_PENDING ]]; then tb_status; fi
echo "First setup, including a deferred setup, trusts connected accessories. Re-enabling a completed setup preserves saved trust."
gum confirm "Require approval for new Thunderbolt accessories?" || exit 0
fi
# System units and rules are shipped by omarchy-settings.
sudo install -Dm644 "$OMARCHY_PATH/default/polkit/org.omarchy.thunderbolt.policy" /usr/share/polkit-1/actions/org.omarchy.thunderbolt.policy
sudo systemctl daemon-reload
action=enable
if [[ ${1:-} == "--quiet" ]]; then
action=migrate
fi
sudo /usr/bin/omarchy-thunderbolt-authorization-admin "$action"
systemctl --user daemon-reload
systemctl --user enable --now omarchy-thunderbolt-authorization.service
if [[ -f /etc/omarchy/thunderbolt-authorization.enabled ]]; then
for ((attempt=0; attempt<10; attempt++)); do
if tb_public_snapshot >/dev/null 2>&1; then break; fi
sleep 1
done
tb_status
elif [[ -f $TB_PENDING ]]; then
tb_status
fi
else
echo "Usage: omarchy-setup-security-thunderbolt-authorization [--quiet|status|recover]" >&2
exit 1
fi
@@ -1,23 +0,0 @@
#!/bin/bash
# omarchy:summary=Clear supported Thunderbolt firmware boot allowlists
# omarchy:args=[recover]
set -euo pipefail
if [[ ${1:-} == "recover" && $# == 1 ]]; then
sudo /usr/bin/omarchy-thunderbolt-authorization-admin boot-recover
elif [[ $# == 0 ]]; then
gum style --bold "Thunderbolt at Boot"
echo "This trusts connected accessories for use after Linux starts, then clears the firmware boot allowlist."
echo "Trusted Thunderbolt keyboards, storage, and network adapters may be unavailable at disk unlock."
echo "You must be able to unlock and recover without Thunderbolt accessories."
echo "All controllers must be connected and support a boot allowlist in user or secure mode."
echo "Older snapshots or another operating system may restore firmware boot permissions."
gum confirm "Clear firmware boot access with these recovery limits?" || exit 0
sudo /usr/bin/omarchy-thunderbolt-authorization-admin boot-enable
echo "Firmware boot allowlists cleared and verified. Current connections were not disconnected."
else
echo "Usage: omarchy-setup-security-thunderbolt-boot [recover]" >&2
exit 1
fi
@@ -1,191 +0,0 @@
#!/bin/bash
# omarchy:summary=Ask before new USB devices can access the system
# omarchy:args=[--boot] [--yes] [--review-existing]
# omarchy:requires-sudo=true
set -euo pipefail
assume_yes=0
from_boot=0
review_existing=0
while (( $# > 0 )); do
case "$1" in
--yes) assume_yes=1 ;;
--boot) from_boot=1 ;;
--review-existing) review_existing=1 ;;
-h | --help)
echo "Usage: omarchy-setup-security-usb-authorization [--boot] [--yes] [--review-existing]"
echo
echo "Trusts USB devices that are connected now, then blocks new devices"
echo "until you approve them from an Omarchy notification."
echo
echo "--boot also blocks USB devices from the start of kernel boot. Connected"
echo "devices are added to USBGuard's policy, but remain unavailable before"
echo "USBGuard starts, including at an encrypted-disk password prompt."
echo "Older snapshots without USBGuard keep all USB disabled after startup,"
echo "including keyboards, network adapters, and storage. Use --boot only if"
echo "disk unlock and recovery work without any USB devices."
echo
echo "--review-existing reviews connected devices with older port-bound approvals."
echo "Each portable approval requires confirmation; existing rules are preserved."
exit 0
;;
*)
echo "omarchy-setup-security-usb-authorization: unknown option '$1'. Try --help." >&2
exit 2
;;
esac
shift
done
# Boot-time blocking goes into Limine's own configuration, which a Mac's boot
# package owns, so it is not offered there.
if (( from_boot )) && omarchy-hw-aarch64-apple; then
echo "USB authorization from boot is not available on Apple Silicon yet; USB device authorization after login still works." >&2
exit 2
fi
state_dir="$HOME/.local/state/omarchy/usb-authorization"
unit_source="$OMARCHY_PATH/default/systemd/user/omarchy-usb-authorization.service"
unit="$HOME/.config/systemd/user/omarchy-usb-authorization.service"
rules_file=/etc/usbguard/rules.conf
daemon_config=/etc/usbguard/usbguard-daemon.conf
source "$OMARCHY_PATH/install/helpers/usb-authorization.sh"
if (( review_existing )); then
# Older Omarchy rules have no per-rule ownership marker. They cannot safely
# be distinguished from intentionally restricted administrator rules.
(( ! assume_yes && ! from_boot )) || { echo "Review requires individual confirmation; do not combine it with --yes or --boot." >&2; exit 2; }
policy=$(usbguard list-rules)
declare -A reviewed=()
# Keep stdin attached to the terminal for gum (and any sudo prompt).
mapfile -t policy_lines <<<"$policy"
for line in "${policy_lines[@]}"; do
if [[ $line =~ ^[0-9]+:[[:space:]] ]]; then
rule=${line#*: }
[[ $rule == allow\ * ]] || continue
portable=$(usb_authorization_portable_rule "$rule") || continue
[[ ${portable% label *} != "$rule" ]] || continue
devices=$(usbguard list-devices "$rule")
mapfile -t candidates <<<"$devices"
for device in "${candidates[@]}"; do
[[ $device =~ ^([0-9]+):[[:space:]](allow[[:space:]].*)$ ]] || continue
id=${BASH_REMATCH[1]} current=${BASH_REMATCH[2]}
[[ ! -v reviewed[$id] ]] || continue
reviewed[$id]=1
echo
printf '%s\n' "$current" | LC_ALL=C tr -c '\11\12\15\40-\176' '?'
echo "This device has a location-bound approval. Details are supplied by the device."
if gum confirm "Trust this device on any USB port or hub?"; then
sudo /usr/bin/omarchy-usb-authorization-approve "$id" "$current"
fi
done
fi
done
echo "Review complete. Disconnected devices can be approved when reconnected."
exit 0
fi
# The watcher runs in the session's environment, which may differ from a
# command invoked in a checkout. Fail before enabling blocking on mismatched
# or incomplete installations, including dangling dev-link unit symlinks.
session_environment=$(systemctl --user show-environment)
grep -Fqx -- "OMARCHY_PATH=$OMARCHY_PATH" <<<"$session_environment" || {
echo "The session and this command use different Omarchy installations. Update the session before enabling USB protection." >&2
exit 1
}
for required in "$unit_source" /usr/share/omarchy/install/helpers/usb-authorization-policy.sh; do
[[ -r $required ]] || { echo "USB authorization installation is incomplete: $required" >&2; exit 1; }
done
for required in "$OMARCHY_PATH/bin/omarchy-usb-authorization-watch" \
"$OMARCHY_PATH/bin/omarchy-usb-authorization-event" \
"$OMARCHY_PATH/bin/omarchy-usb-authorization-review" \
/usr/bin/omarchy-usb-authorization-approve; do
[[ -r $required && -x $required ]] || { echo "USB authorization command is unavailable: $required" >&2; exit 1; }
done
if (( ! assume_yes )); then
if (( from_boot )); then
gum style --foreground 212 --bold "USB authorization from boot"
else
gum style --foreground 212 --bold "USB device authorization"
fi
echo
echo "Devices connected now will be trusted. Disconnect anything you do not trust."
echo "New USB devices will remain blocked until you approve them."
if (( from_boot )); then
echo
echo "Boot-time protection blocks all USB devices until USBGuard starts."
echo "A USB keyboard will not work at an encrypted-disk password prompt, even"
echo "when it is connected now."
echo
echo "Older snapshots without USBGuard keep ALL USB disabled after startup,"
echo "including keyboards, network adapters, and storage."
echo "Continue only if disk unlock and recovery work without any USB devices."
fi
echo
if (( from_boot )); then
gum confirm "Enable USB authorization from kernel boot?" || exit 130
else
gum confirm "Enable USB device authorization?" || exit 130
fi
fi
omarchy-pkg-add usbguard
usb_authorization_require_secure_settings "$daemon_config"
mkdir -p "$state_dir" "${unit%/*}"
chmod 700 "$state_dir"
if ! usb_authorization_rules_present "$rules_file"; then
policy=$(mktemp "${TMPDIR:-/tmp}/omarchy-usb-policy.XXXXXXXXXX")
trap 'rm -f "$policy"' EXIT
chmod 600 "$policy"
usb_authorization_generate_policy "$policy"
sudo install -Dm600 -o root -g root "$policy" "$rules_file"
touch "$state_dir/policy-generated-by-omarchy"
fi
# This user can inspect USBGuard events and approve the device named by an
# event. Policy and daemon parameters remain unavailable through the IPC ACL.
sudo usbguard add-user "$USER" \
--devices=list,listen,modify \
--policy=list \
--exceptions=listen
sudo install -Dm644 "$OMARCHY_PATH/default/polkit/org.omarchy.usb.policy" /usr/share/polkit-1/actions/org.omarchy.usb.policy
# Install a real file: a link into a checkout can become dangling on a branch
# switch. Start the waiting watcher before USBGuard can block new devices.
unit_temporary=$(mktemp "${unit}.XXXXXXXXXX")
install -m644 "$unit_source" "$unit_temporary"
mv -fT "$unit_temporary" "$unit"
systemctl --user daemon-reload
systemctl --user enable omarchy-usb-authorization.service
systemctl --user restart omarchy-usb-authorization.service
systemctl --user is-active --quiet omarchy-usb-authorization.service
sudo systemctl enable usbguard.service
# add-user ACL changes are loaded only when the daemon starts, so restart an
# existing service as well as starting a fresh one.
sudo systemctl restart usbguard.service
systemctl is-active --quiet usbguard.service
systemctl --user is-active --quiet omarchy-usb-authorization.service
if (( from_boot )); then
# The fixed helper independently verifies each full live device snapshot.
usb_authorization_trust_present_devices
sudo omarchy-usb-authorization-boot enable
fi
echo
echo "USB device authorization is enabled."
echo "Devices connected during setup remain trusted; new devices require approval."
echo "To review older port-bound approvals, run: omarchy-setup-security-usb-authorization --review-existing"
if (( from_boot )); then
echo "Boot-time authorization is enabled and takes effect after reboot."
fi
+30 -3
View File
@@ -462,7 +462,34 @@ root_dispatch() {
esac
}
# The bar indicator only probes on demand, so every grant change pushes a
# refresh. Root's expiry timer can't reach the user's shell, so a user timer
# refreshes once the deadline has passed. Both are best effort: a stale red
# indicator still revokes cleanly when clicked.
refresh_indicator() {
/usr/bin/omarchy-shell -q omarchy.indicators refresh >/dev/null 2>&1 || true
}
# A failed enable can still change the grant (a renewal that rolls back removes
# the previous policy), so refresh whatever the privileged action returned.
change_grant() {
local status=0
/usr/bin/sudo "$@" || status=$?
refresh_indicator
return "$status"
}
# A wall-clock deadline, like the sudoers rule and root's timer, so expiry
# during suspend still refreshes on resume.
schedule_expiry_refresh() {
[[ $minutes == "permanent" ]] && return 0
/usr/bin/systemd-run --user --quiet --collect --on-calendar="@$((EPOCHSECONDS + 10#$minutes * 60 + 2))" \
--timer-property=AccuracySec=1s \
-- /usr/bin/omarchy-shell -q omarchy.indicators refresh >/dev/null 2>&1 || true
}
show_enabled() {
schedule_expiry_refresh
if [[ $minutes == "permanent" ]]; then
echo "Passwordless sudo has been ENABLED permanently. Run this command again to disable it."
else
@@ -542,12 +569,12 @@ else
fi
fi
if (( status == 0 )) && { (($# == 0)) || [[ $minutes == "--disable" ]]; }; then
/usr/bin/sudo -n -N -- "$INSTALLED_SELF" __disable "$uid"
change_grant -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"
change_grant -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
show_enabled
else
if (( status != 0 && status != STATUS_INACTIVE )); then
@@ -587,7 +614,7 @@ else
echo ""
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"
change_grant -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
echo ""
show_enabled
else
-21
View File
@@ -462,12 +462,6 @@ scrub_factory_accounts() {
sanitize_factory_baseline() {
local factory="$1"
btrfs property set -ts "$factory" ro false
if [[ -d $factory/var/lib/omarchy/thunderbolt-authorization || -f $factory/etc/omarchy/thunderbolt-authorization.enabled || -f $factory/etc/omarchy/thunderbolt-authorization.pending ]]; then
if ! /usr/bin/omarchy-thunderbolt-authorization-admin reset-root "$factory"; then
btrfs property set -ts "$factory" ro true
fail "could not reset Thunderbolt authorization in the factory baseline"
fi
fi
if ! scrub_factory_accounts "$factory"; then
btrfs property set -ts "$factory" ro true
@@ -493,14 +487,6 @@ stage_full_reset() {
[[ -f $unit_src/omarchy-provision-owner.service && -x $next/usr/bin/omarchy-provision-owner ]] ||
fail "the factory snapshot predates provisioning support; cannot reset from it"
# A normal install's factory snapshot includes the original owner's USB
# policy and enabled daemon. Keep input usable during the new owner's setup;
# provisioning enrolls their devices before enabling USBGuard again.
if [[ -f $next/usr/lib/systemd/system/usbguard.service ]]; then
systemctl --root="$next" disable usbguard.service >>"$LOG_FILE" 2>&1 ||
fail "could not disable USBGuard in the factory system (see $LOG_FILE)"
fi
log "Scrubbing machine identity from the factory system"
systemd-id128 new >"$next/etc/machine-id"
rm -f "$next"/etc/ssh/ssh_host_*
@@ -516,13 +502,6 @@ stage_full_reset() {
scrub_factory_accounts "$next" ||
fail "could not remove account credentials from the factory system (see $LOG_FILE)"
# Leave the new owner's input available before interactive enrollment. Clear
# inherited accessory identities in both roots; never change the live daemon.
if [[ -d $next/var/lib/omarchy/thunderbolt-authorization || -f $next/etc/omarchy/thunderbolt-authorization.enabled || -f $next/etc/omarchy/thunderbolt-authorization.pending ]]; then
/usr/bin/omarchy-thunderbolt-authorization-admin reset-root "$next" ||
fail "could not reset Thunderbolt authorization in the staged root"
fi
# @factory itself survives the wipe as the baseline for future resets. If it
# came from a normal install it still holds the seller's account and
# /etc/shadow, which the new wheel user could mount and read — and a second
+1 -1
View File
@@ -6,7 +6,7 @@
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
omarchy-hyprland-window-close-all
+1 -1
View File
@@ -8,7 +8,7 @@
# 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
omarchy-osd -i reboot -m "Rebooting" -d 5000
omarchy-osd -i reboot -m "$(omarchy-text "Rebooting")" -d 5000
omarchy-state clear re*-required
+2 -2
View File
@@ -7,7 +7,7 @@
if [[ ${1:-} == "--inhibited" ]]; then
# 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-hyprland-window-close-all
) &
@@ -21,7 +21,7 @@ elif [[ ${1:-} == "--inhibit" ]]; then
--who=Omarchy --why="Shutdown in progress" --mode=block \
"$0" --inhibited || {
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"
}
else
+2 -2
View File
@@ -98,8 +98,8 @@ report_unsecured() {
"$1" >&2
omarchy-notification-send -u critical -g 󰌾 \
"Screen did not lock before suspend" \
"The session was left unlocked ($1)." >/dev/null 2>&1 || true
"$(omarchy-text "Screen did not lock before suspend")" \
"$(omarchy-text "The session was left unlocked (%1)." "$1")" >/dev/null 2>&1 || true
exit 1
}
+1 -1
View File
@@ -59,7 +59,7 @@ claim_path() {
announce() {
local path="$1"
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
*.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=$?
if ((${status:-0} > 1)); then
omarchy-notification-send -g "󰒊" -u critical "Could not send to $name" \
"The file chooser did not open"
omarchy-notification-send -g "󰒊" -u critical "$(omarchy-text "Could not send to %1" "$name")" \
"$(omarchy-text "The file chooser did not open")"
exit 1
fi
@@ -43,8 +43,8 @@ else
fi
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
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
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"
+2 -2
View File
@@ -10,12 +10,12 @@ toggle="$HOME/.local/state/omarchy/toggles/background-intros-off"
case ${1:-} in
enable)
rm -f "$toggle"
omarchy-notification-send "Background Intros" "Enabled"
omarchy-notification-send "$(omarchy-text "Background Intros")" "$(omarchy-text "Enabled")"
;;
disable)
mkdir -p "$(dirname "$toggle")"
touch "$toggle"
omarchy-notification-send "Background Intros" "Disabled"
omarchy-notification-send "$(omarchy-text "Background Intros")" "$(omarchy-text "Disabled")"
;;
toggle)
if [[ -f $toggle ]]; then
+1 -1
View File
@@ -17,7 +17,7 @@ mapfile -d '' -t BACKGROUNDS < <(
TOTAL=${#BACKGROUNDS[@]}
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
# Get current background from symlink
if [[ -L $CURRENT_BACKGROUND_LINK ]]; then
+2 -2
View File
@@ -20,6 +20,6 @@ fi
# Create symlink to the new background media
ln -nsf "$BACKGROUND" "$CURRENT_BACKGROUND_LINK"
# Update the live shell background immediately when it is running. The
# background plugin also polls this symlink, but IPC avoids the visible delay.
# Update the live shell background when it is running. Nothing watches the
# symlink, so this is how the shell learns of the new background.
omarchy-shell -q background set "$BACKGROUND"
+1 -1
View File
@@ -35,5 +35,5 @@ fi
# Now remove the theme directory for THEME_NAME
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"
+9 -6
View File
@@ -190,7 +190,7 @@ set_theme_background() {
if [[ -z $CHOSEN_THEME_BACKGROUND || ! -f $CHOSEN_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
return
fi
@@ -207,11 +207,14 @@ set_theme_background() {
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
# Played or not, the shell gets the new still: it still holds the old
# theme's path, which the theme swap above deleted, and loads it again
# when OWE hands the desktop back after the intro. While the intro plays
# the shell's background is suspended under it, so the swap is unseen.
omarchy-theme-bg-boot-intro --theme-switch "$new_background" "$theme_id" "$intro_prepared" || true
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
shell_ipc shell finishThemeIntro "$theme_id" || true
wait_for_theme_intro themeIntroStatus "$theme_id" "pending"
+12 -1
View File
@@ -1,9 +1,10 @@
#!/bin/bash
# omarchy:summary=Apply current Omarchy theme colors to running Foot terminals
# omarchy:summary=Apply current Omarchy theme colors and background alpha to running Foot terminals
# omarchy:hidden=true
colors_toml=$HOME/.local/state/omarchy/current/theme/colors.toml
foot_ini=$HOME/.local/state/omarchy/current/theme/foot.ini
if [[ ! -f $colors_toml ]] || ! pgrep -x foot >/dev/null; then
exit 0
@@ -12,6 +13,16 @@ fi
foot_osc=$(omarchy-theme-osc "$colors_toml")
[[ -n $foot_osc ]] || exit 0
# foot takes a background alpha as an urxvt-style [percent] prefix, so a theme
# going see-through or back, as with Glassy windows, reaches running terminals.
# The last alpha in the theme's foot.ini wins, as it does when foot reads it.
alpha=$(sed -n 's/^[[:space:]]*alpha[[:space:]]*=[[:space:]]*\([0-9.]*\).*/\1/p' "$foot_ini" 2>/dev/null | tail -n 1)
background=$(omarchy-theme-color --file "$colors_toml" background 2>/dev/null)
if [[ -n $background ]]; then
percent=$(awk -v alpha="${alpha:-1}" 'BEGIN { printf "%d", alpha * 100 + 0.5 }')
foot_osc+=$(printf '\033]11;[%d]%s\007' "$percent" "$background")
fi
for foot_pid in $(pgrep -x foot); do
for child_pid in $(pgrep -P "$foot_pid"); do
tty=$(readlink "/proc/$child_pid/fd/1" 2>/dev/null)
+122
View File
@@ -0,0 +1,122 @@
#!/bin/bash
# omarchy:summary=Give terminals, btop, and GTK apps a see-through background while windows are glassy
# omarchy:args=[theme-dir]
# omarchy:hidden=true
# A Hyprland opacity rule fades a terminal's text along with its background,
# and foot turns compositor blur down unless its own config asks for it, so the
# glass belongs in each terminal's theme file, as in the glassy Starship
# (#14726). The lines sit between markers so leaving glassy takes them out
# again, and a theme that sets its own opacity keeps it.
THEME_DIR="${1:-$HOME/.local/state/omarchy/current/theme}"
TOGGLES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/toggles/hypr"
GLASSY=false
[[ -f $TOGGLES_DIR/window-glassy.lua ]] && GLASSY=true
status=0
# A literal for a sed address, whose delimiter is /.
regex() {
sed 's/[][\\.*^$/]/\\&/g' <<<"$1"
}
# A block added to a file that did not end in a newline has to supply one, so
# its begin marker says so, and taking the block out takes that newline back.
# Leaving glassy then gives the file back exactly as it was. Markers come as
# an opening and a closing part, so CSS can wrap them in a comment.
remove_block() {
local file="$1" open="$2" close="$3"
local begin end
begin=$(regex "$open omarchy-glass begin")
end=$(regex "$open omarchy-glass end$close")
[[ -f $file ]] && grep -qF "$open omarchy-glass begin" "$file" || return 0
local added_newline=false
grep -qxF "$open omarchy-glass begin after a line without a newline$close" "$file" && added_newline=true
sed -i "/^$begin/,/^$end\$/d" "$file" || return 1
if [[ $added_newline == "true" && -s $file && -z $(tail -c 1 "$file") ]]; then
truncate -s -1 "$file" || return 1
fi
}
append_block() {
local file="$1" open="$2" close="$3" lines="$4"
if [[ -s $file && -n $(tail -c 1 "$file") ]]; then
printf '\n%s\n%s\n%s\n' "$open omarchy-glass begin after a line without a newline$close" "$lines" "$open omarchy-glass end$close" >>"$file"
else
printf '%s\n%s\n%s\n' "$open omarchy-glass begin$close" "$lines" "$open omarchy-glass end$close" >>"$file"
fi
}
# Each setting comes with a pattern for the theme setting it already, which
# keeps the theme's own; an empty pattern always adds it. Lines that belong in
# a section go in under its header. A file that already has the section gets
# them inside it, since TOML refuses a table declared twice; otherwise the
# header comes along with them.
glass() {
local file="$THEME_DIR/$1" section="$2"
shift 2
[[ -f $file ]] || return 0
remove_block "$file" "#" "" || return 1
[[ $GLASSY == "true" ]] || return 0
local lines=""
while (( $# >= 2 )); do
if [[ -z $1 ]] || ! grep -qE "$1" "$file"; then
lines+="${lines:+$'\n'}$2"
fi
shift 2
done
[[ -n $lines ]] || return 0
# A header may carry trailing space or a comment.
local header="^$(regex "$section")[[:space:]]*(#.*)?\$"
if [[ -n $section ]] && grep -qE "$header" "$file"; then
local block
block=$(mktemp) || return 1
printf '# omarchy-glass begin\n%s\n# omarchy-glass end\n' "$lines" >"$block"
sed -i -E "0,/$header/{/$header/r $block
}" "$file"
local result=$?
rm -f "$block"
return $result
fi
append_block "$file" "#" "" "${section:+$section$'\n'}$lines"
}
# Alacritty's own blur works only on macOS and KDE; Hyprland blurs it anyway.
glass alacritty.toml '[window]' \
'^[[:space:]]*opacity[[:space:]]*=' 'opacity = 0.65' || status=1
glass foot.ini '[colors-dark]' \
'^[[:space:]]*alpha[[:space:]]*=' 'alpha=0.65' \
'^[[:space:]]*blur[[:space:]]*=' 'blur=yes' || status=1
glass kitty.conf '' \
'^[[:space:]]*background_opacity[[:space:]]' 'background_opacity 0.65' || status=1
# btop paints its own background over the terminal's glass. An empty main_bg
# uses the terminal background; the last theme value wins, so removing the
# block restores the theme's colour without changing btop's user config.
glass btop.theme '' \
'^[[:space:]]*theme\[main_bg\][[:space:]]*=[[:space:]]*""' 'theme[main_bg]=""' || status=1
# GTK only asks Hyprland for blur under a CSS backdrop-filter, which Ghostty
# takes as custom CSS and reloads live; its background-blur speaks only KDE's
# blur protocol.
glass ghostty.conf '' \
'^[[:space:]]*background-opacity[[:space:]]*=' 'background-opacity = 0.65' \
'' "gtk-custom-css = $OMARCHY_PATH/default/gtk/glass.css" || status=1
# Other GTK apps, such as Nautilus, read the user's gtk.css at launch, so the
# same backdrop blur is imported there. It follows the mode alone, so a theme
# switch, which names the theme it is rendering, leaves it be.
if (( $# == 0 )); then
gtk_css="${XDG_CONFIG_HOME:-$HOME/.config}/gtk-4.0/gtk.css"
remove_block "$gtk_css" "/*" " */" || status=1
if [[ $GLASSY == "true" ]]; then
{ mkdir -p "${gtk_css%/*}" &&
append_block "$gtk_css" "/*" " */" "@import url(\"file://$OMARCHY_PATH/default/gtk/glass.css\");"; } || status=1
fi
fi
exit $status
+1
View File
@@ -451,3 +451,4 @@ if [[ -f $COLORS_FILE ]]; then
fi
apply_shell_section_overrides
omarchy-theme-set-glass "$NEXT_THEME_DIR"
@@ -1,17 +0,0 @@
#!/bin/bash -p
# omarchy:summary=Manage system Thunderbolt authorization settings
# omarchy:hidden=true
# This root entrypoint uses installed code and rejects unsafe Bash startup.
if [[ $- != *p* ]]; then exit 126; fi
source /usr/bin/omarchy-security-functions || exit 126
omarchy_security_require_privileged_bash_startup || exit 126
omarchy_security_sanitize_bash_environment "$0" "$@"
set -euo pipefail
(( EUID == 0 )) || exit 126
export PATH=/usr/bin:/bin LC_ALL=C
unset DBUS_SYSTEM_BUS_ADDRESS DBUS_SESSION_BUS_ADDRESS
source /usr/share/omarchy/install/helpers/thunderbolt-policy.sh
source /usr/share/omarchy/install/helpers/thunderbolt-setup.sh
tb_admin "$@"
@@ -1,25 +0,0 @@
#!/bin/bash -p
# omarchy:summary=Approve one verified Thunderbolt connection
# omarchy:hidden=true
# pkexec permits this exact program for an active local wheel user. Suppress
# startup injection before reading input; it cannot dispatch general root work.
if [[ $- != *p* ]]; then exit 126; fi
source /usr/bin/omarchy-security-functions || exit 126
omarchy_security_require_privileged_bash_startup || exit 126
omarchy_security_sanitize_bash_environment "$0" "$@"
set -euo pipefail
(( EUID == 0 )) || exit 126
export PATH=/usr/bin:/bin LC_ALL=C
unset DBUS_SYSTEM_BUS_ADDRESS DBUS_SESSION_BUS_ADDRESS
source /usr/share/omarchy/install/helpers/thunderbolt-policy.sh
[[ $# == 2 && ( $1 == "true" || $1 == "false" ) ]] || exit 64
(( ${#2} <= 16384 )) || exit 64
jq -e 'type == "object"' <<< "$2" >/dev/null
exec 9> "$TB_LOCK"
flock -x 9
result=$(tb_approve "$2" "$1" true)
tb_json_write "$TB_RUNTIME/snapshot.json" 644 <<< "$result"
printf '%s\n' "$result"
@@ -1,8 +0,0 @@
#!/bin/bash
# omarchy:hidden=true
# omarchy:summary=Check whether Thunderbolt firmware boot access is restricted
set -euo pipefail
source "$OMARCHY_PATH/install/helpers/thunderbolt-policy.sh"
tb_public_snapshot | jq -e .boot_protection >/dev/null
@@ -1,16 +0,0 @@
#!/bin/bash -p
# omarchy:summary=Restore trusted Thunderbolt devices and publish their status
# omarchy:hidden=true
# Keep root startup independent of a caller's environment or checkout.
if [[ $- != *p* ]]; then exit 126; fi
source /usr/bin/omarchy-security-functions || exit 126
omarchy_security_require_privileged_bash_startup || exit 126
omarchy_security_sanitize_bash_environment "$0" "$@"
set -euo pipefail
(( EUID == 0 )) || exit 126
export PATH=/usr/bin:/bin LC_ALL=C
unset DBUS_SYSTEM_BUS_ADDRESS DBUS_SESSION_BUS_ADDRESS
source /usr/share/omarchy/install/helpers/thunderbolt-policy.sh
tb_daemon
@@ -1,10 +0,0 @@
#!/bin/bash
# omarchy:hidden=true
# omarchy:summary=Handle Thunderbolt device approval review
set -euo pipefail
source "$OMARCHY_PATH/install/helpers/thunderbolt-policy.sh"
source "$OMARCHY_PATH/install/helpers/thunderbolt-review.sh"
[[ $# == 1 ]] || exit 64
tb_review "$1"
Loaded 100 of 416 files, more files were not shown because too many files have changed in this diff. Show more