Compare commits

..
Author SHA1 Message Date
David Heinemeier HanssonandClaude Opus 5.5 a044bec0ab 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>
2026-10-11 08:08:09 +09:00
David Heinemeier HanssonandClaude Opus 5.5 0cd685946b 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>
2026-10-11 08:05:33 +09:00
David Heinemeier HanssonandClaude Opus 5.5 cf9e8f3625 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>
2026-10-11 07:54:21 +09:00
David Heinemeier HanssonandClaude Opus 5.5 b0af02555e 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>
2026-10-11 07:52:51 +09:00
David Heinemeier HanssonandClaude Opus 5.5 8b5ebc57bf Drop notification action buttons now that the Pinyin prompt is suppressed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 07:51:59 +09:00
David Heinemeier HanssonandClaude Opus 5.5 d579157881 Cycle input methods back with Super+Shift+I
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 07:39:50 +09:00
David Heinemeier HanssonandClaude Opus 5.5 3aa498a6d7 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>
2026-10-11 07:36:45 +09:00
David Heinemeier HanssonandClaude Opus 5.5 87c4e2c8b1 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>
2026-10-11 07:32:34 +09:00
David Heinemeier HanssonandClaude Opus 5.5 4eda649324 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>
2026-10-11 07:18:10 +09:00
David Heinemeier HanssonandClaude Opus 5.5 f2bbf0d6c6 Drop the azerty conversion now that the installer no longer offers it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 07:16:53 +09:00
David Heinemeier Hansson b673d9e942 Merge remote-tracking branch 'origin/quattro' into unified-input-method-setup 2026-10-11 07:16:39 +09:00
David Heinemeier HanssonandClaude Opus 5.5 fd0c47e157 Restore the input group when the font preference cannot be written
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 07:16:36 +09:00
David Heinemeier HanssonandClaude Opus 5.5 a494f21ab9 Route Super+I through the shell's input reader so unfocused choices stick
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:13:38 +09:00
David Heinemeier HanssonandClaude Opus 5.5 1371ea7b5d Restore confirmed picker checks when an immediate change fails
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:12:31 +09:00
David Heinemeier HanssonandClaude Opus 5.5 2774a09e0b Set input method fonts only after the selection saves
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:10:52 +09:00
David Heinemeier HanssonandClaude Opus 5.5 d1e96fd86d Keep the switching order when changing input methods
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:10:52 +09:00
David Heinemeier HanssonandClaude Opus 5.5 7b260bed17 Map keyboard choices to the layouts the installer sets
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:10:52 +09:00
David Heinemeier HanssonandClaude Opus 5.5 bcdcb11cfa Move the keyboard layout widget to the right side of existing bars
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:03:34 +02:00
David Heinemeier Hansson c85ffa2930 Merge branch 'quattro' into unified-input-method-setup
# Conflicts:
#	config/omarchy/shell.json
2026-10-10 15:54:09 +02:00
David Heinemeier Hansson 101bb547bb Merge branch 'quattro' into unified-input-method-setup 2026-10-10 15:51:52 +02:00
David Heinemeier Hansson 4152f0320c Keep input clicks working through controller and compositor failures 2026-10-09 17:02:46 +02:00
David Heinemeier Hansson f28a652347 Avoid shadowing Python typing and retain pending mixed input selections 2026-10-09 17:00:14 +02:00
David Heinemeier Hansson 639cf3ee5e Cycle keyboard layouts alongside composition engines 2026-10-09 16:57:31 +02:00
David Heinemeier Hansson 99f32458d6 Keep queued typing selections when reopening the menu 2026-10-09 16:51:38 +02:00
David Heinemeier Hansson fbc6daea8b Queue typing changes and wait for saved selections 2026-10-09 16:49:00 +02:00
David Heinemeier Hansson e210b47e33 Use installer keyboard choices and keep typing selections accurate 2026-10-09 16:45:46 +02:00
David Heinemeier Hansson 9421e961ea Apply typing selections immediately from the menu 2026-10-09 16:37:52 +02:00
David Heinemeier Hansson 58d7429f53 Select keyboard layouts and input methods in the Typing menu 2026-10-09 16:33:06 +02:00
David Heinemeier Hansson 73c0660fde Unify input method and keyboard layout switching 2026-10-09 15:22:47 +02:00
David Heinemeier Hansson e8b1a1f874 Show input switching only for multiple layouts or methods 2026-10-09 15:18:33 +02:00
David Heinemeier Hansson 59fb2fd304 Place the input language selector with the right bar widgets 2026-10-09 14:46:33 +02:00
David Heinemeier Hansson 203654bead Keep input tooltips to the name and switching shortcut 2026-10-09 14:44:27 +02:00
David Heinemeier Hansson f0ff594ee3 Correct the input setup shortcut hint 2026-10-09 13:45:26 +02:00
David Heinemeier Hansson 32d81c76c6 Restore linked input settings and attribute test notifications to Fcitx 2026-10-09 13:44:08 +02:00
David Heinemeier Hansson e0e6225fac Use Super+I for input switching independently of Fcitx keys 2026-10-09 13:41:17 +02:00
David Heinemeier Hansson d040abb66f Merge branch 'quattro' into unified-input-method-setup 2026-10-09 13:19:17 +02:00
David Heinemeier Hansson 40642c0d35 Verify input setup and composition stay quiet 2026-10-07 18:10:20 +02:00
David Heinemeier Hansson ee358c251b Show the input switching shortcut in keyboard hints 2026-10-07 18:10:20 +02:00
David Heinemeier Hansson f273eed840 Suppress the Cloudpinyin first-use prompt 2026-10-07 17:48:56 +02:00
David Heinemeier Hansson 036e501e58 Show explicit choices on notification cards 2026-10-07 17:35:03 +02:00
David Heinemeier Hansson 35f8eef1bc Remove the Japanese input welcome notification 2026-10-07 17:35:02 +02:00
David Heinemeier Hansson 2841563e6c Remember input selections before an app gains focus 2026-10-07 17:17:02 +02:00
David Heinemeier Hansson cd73dc6cfd Keep Pinyin setup prompts visible 2026-10-07 17:17:02 +02:00
David Heinemeier Hansson fcda55944a Show input modes in the keyboard indicator 2026-10-07 16:57:03 +02:00
David Heinemeier Hansson 22514b59f2 Exercise input engines across desktop toolkits 2026-10-07 15:59:46 +02:00
David Heinemeier Hansson 0f14a84934 Preserve inherited input keys and later keyboard changes 2026-10-07 15:19:06 +02:00
David Heinemeier Hansson cd0025b62f Merge branch 'quattro' into unified-input-method-setup 2026-10-07 15:02:38 +02:00
David Heinemeier Hansson 9fe6a04715 Unify input methods with the installer keyboard selection 2026-10-07 12:59:36 +02:00
175 changed files with 720 additions and 5090 deletions

No files matched your search

+1 -5
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 from the configured pacman repos, Omarchy's included; use `omarchy-pkg-aur-add` for AUR packages
- `omarchy-pkg-add` - install packages (handles both pacman and AUR)
- `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,10 +113,6 @@ 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`;
-11
View File
@@ -38,17 +38,6 @@ 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,7 +2,6 @@
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,7 +2,6 @@
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,7 +2,6 @@
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,7 +2,6 @@
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,7 +2,6 @@
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,7 +2,6 @@
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,7 +2,6 @@
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,7 +2,6 @@
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,7 +2,6 @@
Version=1.0
Name=YouTube
Exec=omarchy-launch-webapp https://youtube.com/
StartupWMClass=chrome-youtube.com__-Default
Terminal=false
Type=Application
Icon=youtube
+9 -103
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] | background [opaque|glassy|none|cycle] | put <id> [placement] | move <id> [placement] | set <id> <key> <value> [--json] [placement] | get <id> <key>
# 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: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,19 +16,13 @@ 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; 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
position <top|bottom|left|right> Bar position
transparent <true|false|toggle> Bar transparency
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
@@ -198,94 +192,32 @@ 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() {
if (( $# == 0 )); then
jq -r '.bar.position // "top"' "$(source_file)"
return
fi
local position="$1"
local position="${1:-}"
[[ -n $position ]] || fail "position is required"
(( $# == 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
[[ $(current_background) == "none" ]] && transparent="false" || transparent="true"
fi
if [[ $transparent == "true" ]]; then
cmd_background none
commit "$NORMALIZE | .bar.transparent = (.bar.transparent != true)"
echo "Bar transparency toggled"
else
cmd_background opaque
commit "$NORMALIZE | .bar.transparent = \$transparent" --argjson transparent "$transparent"
echo "Bar transparency set to $transparent"
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() {
local catalog
catalog=$(omarchy-plugin-catalog 2>/dev/null) || {
@@ -431,21 +363,6 @@ 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:-}"
@@ -468,14 +385,6 @@ 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 "$@"
;;
@@ -485,9 +394,6 @@ case "$command" in
set)
cmd_set "$@"
;;
get)
cmd_get "$@"
;;
-h | --help | help | "")
usage
;;
-41
View File
@@ -1,41 +0,0 @@
#!/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="Corners follow the theme" ;;
sharp) LABEL="Sharp corners for all themes" ;;
rounded) LABEL="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"
@@ -1,19 +0,0 @@
#!/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
-70
View File
@@ -1,70 +0,0 @@
#!/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="Opaque windows" ;;
transparent) LABEL="Transparent windows" ;;
glassy) LABEL="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"
@@ -1,28 +0,0 @@
#!/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}"
@@ -1,35 +0,0 @@
#!/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 󱂬 "New workspaces use the $LABEL layout"
+14 -62
View File
@@ -1,69 +1,21 @@
#!/bin/bash
# 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
# omarchy:summary=Toggle the layout on the current active workspace between dwindle and scrolling
ACTIVE_WORKSPACE=$(hyprctl activeworkspace -j | jq -r '.id')
# 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"
[[ $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"
CURRENT_MODE=$(omarchy-hyprland-workspace-layout-current)
case "$CURRENT_LAYOUT" in
dwindle) NEW_LAYOUT=scrolling ;;
*) NEW_LAYOUT=dwindle ;;
esac
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
mkdir -p "$LAYOUTS_DIR"
printf 'hl.workspace_rule({ workspace = "%s", layout = "%s" })\n' "$ACTIVE_WORKSPACE" "$NEW_LAYOUT" >"$LAYOUT_FILE"
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 󱂬 "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 󱂬 "Workspace layout set to $NEW_MODE"
elif mkdir -p "$MODES_DIR" && printf '%s\n' "$APPLY" >"$MODE_FILE"; then
omarchy-notification-send -g 󱂬 "Workspace layout set to $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 󱂬 "Workspace layout set to $NEW_MODE but could not be saved"
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
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"
-3
View File
@@ -92,9 +92,6 @@ 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)
+4 -2
View File
@@ -15,15 +15,17 @@ 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 \
signed; then
hype; then
echo -e "\nPreinstalls are still marked as removed. Fix the errors above and try again."
exit 1
fi
-1
View File
@@ -56,7 +56,6 @@ 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"
+2 -2
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"
"Sign PDFs and fill them in with Signed"
"Use Xournal++ to sign PDFs"
"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, trim videos with Omacut, docs with LibreOffice"
"Edit images with Pinta, videos with Kdenlive, 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"
+4 -2
View File
@@ -34,13 +34,15 @@ 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 \
signed
hype
fi
+3 -30
View File
@@ -462,34 +462,7 @@ 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
@@ -569,12 +542,12 @@ else
fi
fi
if (( status == 0 )) && { (($# == 0)) || [[ $minutes == "--disable" ]]; }; then
change_grant -n -N -- "$INSTALLED_SELF" __disable "$uid"
/usr/bin/sudo -n -N -- "$INSTALLED_SELF" __disable "$uid"
echo "Passwordless sudo has been DISABLED. Sudo will require a password again."
elif (( status == STATUS_INACTIVE )) && [[ $minutes == "--disable" ]]; then
echo "Passwordless sudo is already DISABLED."
elif (( status == 0 )) && [[ $minutes != "permanent" ]]; then
change_grant -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
show_enabled
else
if (( status != 0 && status != STATUS_INACTIVE )); then
@@ -614,7 +587,7 @@ else
echo ""
if /usr/bin/gum confirm "Enable passwordless sudo $duration? This is a significant security risk!"; then
change_grant -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
echo ""
show_enabled
else
+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 when it is running. Nothing watches the
# symlink, so this is how the shell learns of the new background.
# Update the live shell background immediately when it is running. The
# background plugin also polls this symlink, but IPC avoids the visible delay.
omarchy-shell -q background set "$BACKGROUND"
+5 -8
View File
@@ -207,14 +207,11 @@ set_theme_background() {
shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true
ln -nsf "$new_background" "$CURRENT_BACKGROUND_LINK"
(
# 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
if ! omarchy-theme-bg-boot-intro --theme-switch "$new_background" "$theme_id" "$intro_prepared"; then
if [[ $(readlink -f "$CURRENT_BACKGROUND_LINK") == "$(readlink -f "$new_background")" &&
$(stat -Lc '%d:%i' "$CURRENT_THEME_PATH" 2>/dev/null) == "$theme_id" ]]; then
shell_ipc background setInstant "$new_background" || true
fi
fi
shell_ipc shell finishThemeIntro "$theme_id" || true
wait_for_theme_intro themeIntroStatus "$theme_id" "pending"
+1 -12
View File
@@ -1,10 +1,9 @@
#!/bin/bash
# omarchy:summary=Apply current Omarchy theme colors and background alpha to running Foot terminals
# omarchy:summary=Apply current Omarchy theme colors 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
@@ -13,16 +12,6 @@ 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
@@ -1,122 +0,0 @@
#!/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,4 +451,3 @@ if [[ -f $COLORS_FILE ]]; then
fi
apply_shell_section_overrides
omarchy-theme-set-glass "$NEXT_THEME_DIR"
+3 -3
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# omarchy:summary=Toggle animations, blur and shadows, which are slow without a GPU
# omarchy:summary=Toggle animations, transparency and other effects that are slow without a GPU
# omarchy:args=[toggle|on|off]
# omarchy:examples=omarchy toggle animations | omarchy toggle animations off | omarchy toggle animations on
@@ -16,7 +16,7 @@ off) omarchy-hyprland-toggle no-animations on ;;
esac
if omarchy-hyprland-toggle-enabled no-animations; then
omarchy-notification-send -g 󰗘 "Animations disabled"
omarchy-notification-send -g 󰕟 "Animations disabled"
else
omarchy-notification-send -g 󰗘 "Animations enabled"
omarchy-notification-send -g 󰕟 "Animations enabled"
fi
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# omarchy:summary=Toggle bar visibility without killing the Omarchy shell (on hides the bar, off shows it)
# omarchy:summary=Toggle bar visibility without killing the Omarchy shell
# omarchy:args=[toggle|on|off]
# omarchy:examples=omarchy toggle bar | omarchy toggle bar off | omarchy toggle bar on
-5
View File
@@ -234,11 +234,6 @@ if [[ -n $MIME_TYPES ]]; then
printf 'MimeType=%s\n' "$(desktop_string_escape "$MIME_TYPES")" >>"$DESKTOP_FILE"
fi
# Floating titlebars name the window after this entry rather than its page title.
if [[ -z $CUSTOM_EXEC ]]; then
printf 'StartupWMClass=%s\n' "$(desktop_string_escape "$(omarchy-webapp-window-class "$APP_URL")")" >>"$DESKTOP_FILE"
fi
chmod +x "$DESKTOP_FILE"
if [[ $INTERACTIVE_MODE == "true" ]]; then
-24
View File
@@ -1,24 +0,0 @@
#!/bin/bash
# omarchy:summary=Print the window class Chromium gives a web app opened at a URL
# omarchy:args=<url>
# omarchy:hidden=true
# Chromium names an --app window after the URL's host and path, so a web app's
# launcher can name its window in StartupWMClass, which titlebars use to show
# the app's name rather than the page title.
url=${1:-}
if [[ -z $url ]]; then
echo "Usage: omarchy-webapp-window-class <url>" >&2
exit 1
fi
address=${url#*://}
address=${address%%[?#]*}
host=${address%%/*}
path=${address:${#host}}
host=${host##*@}
host=${host%%:*}
printf 'chrome-%s_%s-Default\n' "${host,,}" "${path:-/}" | sed 's|/|_|g'
-2
View File
@@ -9,9 +9,7 @@
[Hotkey/DeactivateKeys]
0=Muhenkan
# A lone Shift leaves an engine like Pinyin for Latin input and returns to it.
[Hotkey/AltTriggerKeys]
0=Shift_L
[Hotkey/EnumerateGroupForwardKeys]
+3 -6
View File
@@ -6,12 +6,9 @@
name = "terminal"
[theme.custom]
# The tab bar sits on the terminal's own background, like tmux's bg=default, so
# it turns see-through with the rest of the terminal under Glassy windows. With
# no panel_bg the active tab's text falls back to surface_dim, so that is black
# for dark text on the accent - same colors as status-left's "#[fg=black,bg=blue]"
panel_bg = "reset"
surface_dim = "black"
# The active tab is drawn as panel_bg text on an accent background, so panel_bg
# has to be dark for it to read - same colors as status-left's "#[fg=black,bg=blue]"
panel_bg = "black"
[terminal]
# Matches -c "#{pane_current_path}" on every split, window, and session
+1 -1
View File
@@ -7,7 +7,7 @@
-- input = {
-- -- Use multiple keyboard layouts and switch between them with Left Alt + Right Alt.
-- kb_layout = "us,dk,eu",
-- kb_options = "omarchy:shift_caps_compose,grp:alts_toggle",
-- kb_options = "compose:caps,shift:both_capslock_cancel,grp:alts_toggle",
--
-- -- Use a specific keyboard variant if needed (e.g. intl for international keyboards).
-- kb_variant = "intl",
+1 -1
View File
@@ -6,7 +6,7 @@
},
"bar": {
"position": "top",
"background": "opaque",
"transparent": false,
"centerAnchor": "omarchy.clock",
"layout": {
"left": [
+268
View File
@@ -0,0 +1,268 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--The Xournal++ settings file. Do not edit this file! Most settings are available in the Settings dialog, the others are commented in this file, but handle with care!-->
<settings>
<property name="pressureSensitivity" value="true"/>
<property name="minimumPressure" value="0.05"/>
<property name="pressureMultiplier" value="1"/>
<property name="zoomGesturesEnabled" value="true"/>
<property name="selectedToolbar" value="Portrait"/>
<property name="lastSavePath" value=""/>
<property name="lastOpenPath" value=""/>
<property name="lastImagePath" value=""/>
<property name="edgePanSpeed" value="20"/>
<property name="edgePanMaxMult" value="5"/>
<property name="zoomStep" value="10"/>
<property name="zoomStepScroll" value="2"/>
<property name="displayDpi" value="72"/>
<property name="mainWndWidth" value="800"/>
<property name="mainWndHeight" value="600"/>
<property name="maximized" value="true"/>
<property name="showToolbar" value="true"/>
<property name="showSidebar" value="true"/>
<property name="sidebarWidth" value="150"/>
<property name="sidebarNumberingStyle" value="1"/>
<property name="sidebarOnRight" value="false"/>
<property name="scrollbarOnLeft" value="false"/>
<property name="menubarVisible" value="true"/>
<property name="filepathShownInTitlebar" value="false"/>
<property name="numColumns" value="1"/>
<property name="numRows" value="1"/>
<property name="viewFixedRows" value="false"/>
<property name="showPairedPages" value="false"/>
<property name="layoutVertical" value="false"/>
<property name="layoutRightToLeft" value="false"/>
<property name="layoutBottomToTop" value="false"/>
<property name="numPairsOffset" value="1"/>
<!--The icon theme, allowed values are "disabled", "onDrawOfLastPage", and "onScrollOfLastPage"-->
<property name="emptyLastPageAppend" value="disabled"/>
<property name="presentationMode" value="false"/>
<!--Which GUI elements are shown in default view mode, separated by a colon (,)-->
<property name="defaultViewModeAttributes" value="showMenubar,showToolbar,showSidebar"/>
<!--Which GUI elements are shown in fullscreen view mode, separated by a colon (,)-->
<property name="fullscreenViewModeAttributes" value="goFullscren,showToolbar,showSidebar"/>
<!--Which GUI elements are shown in presentation view mode, separated by a colon (,)-->
<property name="presentationViewModeAttributes" value="goFullscren,showToolbar"/>
<!--The cursor icon used with a stylus, allowed values are "none", "dot", "big", "arrow"-->
<property name="stylusCursorType" value="dot"/>
<!--The eraser cursor visibility used with a stylus, allowed values are "never", "always", "hover", "touch"-->
<property name="eraserVisibility" value="always"/>
<!--The icon theme, allowed values are "iconsColor", "iconsLucide"-->
<property name="iconTheme" value="iconsColor"/>
<!--Dark/light mode, allowed values are "useSystem", "forceLight", "forceDark"-->
<property name="themeVariant" value="forceDark"/>
<property name="highlightPosition" value="false"/>
<property name="cursorHighlightColor" value="2164260608"/>
<property name="cursorHighlightBorderColor" value="2147483903"/>
<property name="cursorHighlightRadius" value="30"/>
<property name="cursorHighlightBorderWidth" value="0"/>
<property name="useStockIcons" value="false"/>
<property name="disableScrollbarFadeout" value="false"/>
<property name="disableAudio" value="false"/>
<!--Hides scroolbars in the main window, allowed values: "none", "horizontal", "vertical", "both"-->
<property name="scrollbarHideType" value="none"/>
<property name="autoloadMostRecent" value="false"/>
<property name="autoloadPdfXoj" value="true"/>
<property name="defaultSaveName" value="%F-Note-%H-%M"/>
<property name="defaultPdfExportName" value="%{name}_annotated"/>
<property name="autosaveEnabled" value="true"/>
<property name="autosaveTimeout" value="3"/>
<property name="addHorizontalSpace" value="false"/>
<property name="addHorizontalSpaceAmount" value="150"/>
<property name="addVerticalSpace" value="false"/>
<property name="addVerticalSpaceAmount" value="150"/>
<property name="drawDirModsEnabled" value="false"/>
<property name="drawDirModsRadius" value="50"/>
<property name="snapRotation" value="true"/>
<property name="snapRotationTolerance" value="0.3"/>
<property name="snapGrid" value="true"/>
<property name="snapGridTolerance" value="0.5"/>
<property name="snapGridSize" value="14.17"/>
<property name="strokeRecognizerMinSize" value="40"/>
<property name="touchDrawing" value="false"/>
<property name="gtkTouchInertialScrolling" value="true"/>
<property name="pressureGuessing" value="false"/>
<property name="selectionBorderColor" value="4294901760"/>
<property name="backgroundColor" value="4292664021"/>
<property name="selectionMarkerColor" value="4285702095"/>
<property name="touchZoomStartThreshold" value="0"/>
<property name="pageRerenderThreshold" value="5"/>
<!--The count of rendered PDF pages which will be cached.-->
<property name="pdfPageCacheSize" value="10"/>
<property name="preloadPagesBefore" value="3"/>
<property name="preloadPagesAfter" value="5"/>
<property name="eagerPageCleanup" value="true"/>
<!--Config for new pages-->
<property name="pageTemplate" value="xoj/template&#10;copyLastPageSettings=true&#10;size=595.275591x841.889764&#10;backgroundType=lined&#10;backgroundColor=#ffffff&#10;"/>
<property name="sizeUnit" value=""/>
<property name="audioFolder" value=""/>
<property name="audioInputDevice" value="-1"/>
<property name="audioOutputDevice" value="-1"/>
<property name="audioSampleRate" value="44100"/>
<property name="audioGain" value="1"/>
<property name="defaultSeekTime" value="5"/>
<property name="pluginEnabled" value=""/>
<property name="pluginDisabled" value=""/>
<property name="strokeFilterIgnoreTime" value="150"/>
<property name="strokeFilterIgnoreLength" value="1"/>
<property name="strokeFilterSuccessiveTime" value="500"/>
<property name="strokeFilterEnabled" value="false"/>
<property name="doActionOnStrokeFiltered" value="false"/>
<property name="trySelectOnStrokeFiltered" value="false"/>
<property name="snapRecognizedShapesEnabled" value="false"/>
<property name="restoreLineWidthEnabled" value="false"/>
<property name="numIgnoredStylusEvents" value="0"/>
<property name="inputSystemTPCButton" value="false"/>
<property name="inputSystemDrawOutsideWindow" value="true"/>
<property name="preferredLocale" value=""/>
<property name="stabilizerAveragingMethod" value="0"/>
<property name="stabilizerPreprocessor" value="0"/>
<property name="stabilizerBuffersize" value="20"/>
<property name="stabilizerSigma" value="0.5"/>
<property name="stabilizerDeadzoneRadius" value="1.3"/>
<property name="stabilizerDrag" value="0.4"/>
<property name="stabilizerMass" value="5"/>
<property name="stabilizerCuspDetection" value="true"/>
<property name="stabilizerFinalizeStroke" value="true"/>
<property name="latexSettings.autoCheckDependencies" value="true"/>
<property name="latexSettings.defaultText" value="x^2"/>
<property name="latexSettings.globalTemplatePath" value="/usr/share/xournalpp/resources/default_template.tex"/>
<property name="latexSettings.genCmd" value="pdflatex -halt-on-error -interaction=nonstopmode '{}'"/>
<property name="latexSettings.sourceViewThemeId" value=""/>
<property name="latexSettings.editorFont" value="Monospace 12"/>
<property name="latexSettings.useCustomEditorFont" value="false"/>
<property name="latexSettings.editorWordWrap" value="true"/>
<property name="latexSettings.sourceViewAutoIndent" value="true"/>
<property name="latexSettings.sourceViewSyntaxHighlight" value="true"/>
<property name="latexSettings.sourceViewShowLineNumbers" value="false"/>
<property name="font" font="Sans" size="12"/>
<data name="buttonConfig">
<data name="default">
<attribute name="color" type="hex" value="ff000000"/>
<attribute name="drawingType" type="string" value="default"/>
<attribute name="size" type="string" value="thin"/>
<attribute name="tool" type="string" value="pen"/>
</data>
<data name="eraser">
<attribute name="eraserMode" type="string" value="none"/>
<attribute name="size" type="string" value="none"/>
<attribute name="tool" type="string" value="eraser"/>
</data>
<data name="middle">
<attribute name="tool" type="string" value="hand"/>
</data>
<data name="right">
<attribute name="tool" type="string" value="none"/>
</data>
<data name="stylus">
<attribute name="tool" type="string" value="none"/>
</data>
<data name="stylus2">
<attribute name="tool" type="string" value="none"/>
</data>
<data name="touch">
<attribute name="device" type="string" value=""/>
<attribute name="disableDrawing" type="boolean" value="false"/>
<attribute name="tool" type="string" value="none"/>
</data>
</data>
<data name="deviceClasses">
<data name="Wayland Finger Scrolling">
<attribute name="deviceClass" type="int" value="1"/>
<attribute name="deviceSource" type="int" value="6"/>
</data>
<data name="Wayland Pointer">
<attribute name="deviceClass" type="int" value="1"/>
<attribute name="deviceSource" type="int" value="0"/>
</data>
</data>
<data name="tools">
<attribute name="current" type="string" value="text"/>
<data name="drawArrow">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="drawCoordinateSystem">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="drawDoubleArrow">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="drawEllipse">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="drawRect">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="drawSpline">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="eraser">
<attribute name="drawingType" type="string" value="default"/>
<attribute name="size" type="string" value="MEDIUM"/>
<attribute name="type" type="string" value="default"/>
</data>
<data name="hand">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="highlighter">
<attribute name="color" type="hex" value="ffffff00"/>
<attribute name="drawingType" type="string" value="default"/>
<attribute name="fill" type="int" value="0"/>
<attribute name="fillAlpha" type="int" value="128"/>
<attribute name="size" type="string" value="MEDIUM"/>
</data>
<data name="image">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="pen">
<attribute name="color" type="hex" value="ff3333cc"/>
<attribute name="drawingType" type="string" value="default"/>
<attribute name="fill" type="int" value="0"/>
<attribute name="fillAlpha" type="int" value="128"/>
<attribute name="size" type="string" value="MEDIUM"/>
<attribute name="style" type="string" value="plain"/>
</data>
<data name="playObject">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="selectMultiLayerRect">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="selectMultiLayerRegion">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="selectObject">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="selectPdfTextLinear">
<attribute name="color" type="hex" value="ff000000"/>
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="selectPdfTextRect">
<attribute name="color" type="hex" value="ff000000"/>
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="selectRect">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="selectRegion">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="showFloatingToolbox">
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="text">
<attribute name="color" type="hex" value="ff000000"/>
<attribute name="drawingType" type="string" value="default"/>
</data>
<data name="verticalSpace">
<attribute name="drawingType" type="string" value="default"/>
</data>
</data>
<data name="touch">
<attribute name="cmdDisable" type="string" value=""/>
<attribute name="cmdEnable" type="string" value=""/>
<attribute name="disableTouch" type="boolean" value="false"/>
<attribute name="method" type="string" value="auto"/>
<attribute name="timeout" type="int" value="1000"/>
</data>
</settings>
+23 -5
View File
@@ -2,12 +2,12 @@
name: omarchy
description: >
REQUIRED for end-user customization of Linux desktop, window manager, or system config.
Use when editing ANY file in ~/.config/, ~/.config/hypr/, ~/.config/omarchy/,
Use when editing ~/.config/hypr/, ~/.config/omarchy/,
~/.config/alacritty/, ~/.config/foot/, ~/.config/kitty/, or ~/.config/ghostty/.
Triggers: Hyprland, window rules, window behavior, animations, keybindings, monitors, gaps, borders,
blur, opacity, omarchy-shell, bar, widgets, terminal config, themes, fonts, background,
night light, idle, lock screen, screenshots, screen recording, reminders, layer rules, workspace
settings, display config, appearance changes, and user-facing omarchy commands. Excludes Omarchy
Triggers: Hyprland, window rules, animations, keybindings, monitors, gaps, borders,
blur, opacity, omarchy-shell, bar, terminal config, themes, background,
night light, idle, lock screen, screenshots, reminders, layer rules, workspace
settings, display config, and user-facing omarchy commands. Excludes Omarchy
source development through `omarchy dev link` workflows.
---
@@ -18,6 +18,24 @@ Manage [Omarchy](https://omarchy.org/) Linux systems - a beautiful, fun, agentic
This skill is for end-user customization on installed systems.
It is not for contributing to Omarchy source code.
## When This Skill MUST Be Used
**ALWAYS invoke this skill for end-user requests involving ANY of these:**
- Editing ANY file in `~/.config/hypr/` (window rules, animations, keybindings, monitors, etc.)
- Editing `~/.config/omarchy/shell.json` (status bar layout, widgets)
- Editing terminal configs (alacritty, foot, kitty, ghostty)
- Editing ANY file in `~/.config/omarchy/`
- Window behavior, animations, opacity, blur, gaps, borders
- Layer rules, workspace settings, display/monitor configuration
- Themes, backgrounds, fonts, appearance changes
- User-facing `omarchy` commands (`omarchy theme ...`, `omarchy refresh ...`, `omarchy restart ...`, etc.)
- Screenshots, screen recording, reminders, night light, idle behavior, lock screen
**If you're about to edit a config file in ~/.config/ on this system, STOP and use this skill first.**
**Do NOT use this skill for Omarchy development tasks** (editing the Omarchy source tree, creating migrations, or running `omarchy dev ...` workflows).
## Topic Guides
Deeper instructions for common areas live next to this file. Read the
-19
View File
@@ -17,25 +17,6 @@ changes. `idle.screensaver` and `idle.lock` are seconds since user idle began.
**Commands:** `omarchy restart shell`, `omarchy refresh shell`
## Plugin UI Safety
Plugins run inside Omarchy's long-lived, unsandboxed shell. Treat text from network responses, files, IPC, settings, environment variables, and command output as untrusted.
- QML `Text` defaults to `Text.AutoText`. Do not bind untrusted data to an AutoText sink: markup-shaped input such as `<img src="https://example.invalid/tracker">` can be interpreted as rich text and load a resource.
- For display text owned by the plugin, set `textFormat: Text.PlainText`. Prefer a local `PlainText` component when the plugin has multiple text elements.
```qml
Text {
textFormat: Text.PlainText
text: modelData.name
}
```
- When passing dynamic text to an Omarchy-owned component whose internal `Text` format the plugin cannot set, neutralize markup in the plugin first with a helper of its own, and add a regression test; Omarchy ships no such helper. Never edit the packaged component to add a plain-text option: that belongs in a pull request to Omarchy.
- Bound and validate downloaded data before displaying or saving it: limit response and collection sizes, string lengths, identifiers, and timestamps.
- Keep network access explicit. Use fixed HTTPS sources, timeouts, response-size limits, and validation. For periodic background refreshes, disclose the behavior and let users enable or disable it. Never execute downloaded content or interpolate untrusted values into shell commands.
- Add regression coverage for markup-shaped input and every shared tooltip or notification sink that receives dynamic text.
## Bar Layout
Use the `omarchy bar` group to move and manage widgets:
@@ -1,298 +1,6 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- CJK: fold the language tag onto one tag per Noto CJK variant -->
<match target="pattern">
<test name="lang" qual="first" compare="eq">
<string>zh</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="eq">
<string>zh-hant</string>
</test>
<edit name="lang" mode="assign">
<string>zh-tw</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hant-hk</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hant-mo</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hant</string>
</test>
<edit name="lang" mode="assign">
<string>zh-tw</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hans</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-sg</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-mo</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="eq">
<string>yue</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>yue-hans</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>yue-cn</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>yue</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<!-- Then append that variant after the generic family it falls back from -->
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-cn</string>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK SC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-cn</string>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK SC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-cn</string>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK SC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hk</string>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK HK</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hk</string>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK HK</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hk</string>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK HK</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-tw</string>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK TC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-tw</string>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK TC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-tw</string>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK TC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ja</string></langset>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK JP</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ja</string></langset>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK JP</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ja</string></langset>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK JP</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ko</string></langset>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK KR</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ko</string></langset>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK KR</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ko</string></langset>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK KR</string>
</edit>
</match>
<match target="pattern">
<test name="family" qual="any">
<string>sans-serif</string>
-7
View File
@@ -1,7 +0,0 @@
/* GTK asks the compositor to blur behind a see-through window only where a
backdrop-filter blur covers it, so Glassy windows load this into GTK apps,
such as Ghostty and Nautilus. The compositor's own blur settings decide
how it looks. */
window {
backdrop-filter: blur(20px);
}
+1 -2
View File
@@ -11,8 +11,7 @@ o.bind("SUPER + ALT + F", "Full width", hl.dsp.window.fullscreen({ mode = "maxim
o.bind("SUPER + O", "Pop window out (float & pin)", "omarchy-hyprland-window-pop")
o.bind("SUPER + ALT + Home", "Save window width", "omarchy-hyprland-window-width save")
o.bind("SUPER + Home", "Restore window width", "omarchy-hyprland-window-width restore")
o.bind("SUPER + L", "Cycle workspace layout", "omarchy-hyprland-workspace-layout-toggle")
o.bind("SUPER + ALT + L", "Cycle workspace layout back", "omarchy-hyprland-workspace-layout-toggle back")
o.bind("SUPER + L", "Toggle workspace layout", "omarchy-hyprland-workspace-layout-toggle")
o.bind("SUPER + LEFT", "Focus on left window", hl.dsp.focus({ direction = "l" }))
o.bind("SUPER + RIGHT", "Focus on right window", hl.dsp.focus({ direction = "r" }))
-22
View File
@@ -1,22 +0,0 @@
-- Frosted glass from the glassy Starship (#14726), shared by Glassy windows and
-- the Glassy bar: whatever is behind a see-through surface is blurred.
return function()
hl.config({
decoration = {
blur = {
enabled = true,
size = 4,
passes = 2,
new_optimizations = true,
xray = false,
contrast = 1.0,
brightness = 0.75,
noise = 0.01,
vibrancy = 0.35,
vibrancy_darkness = 0.35,
popups = true,
special = true,
},
},
})
end
+2 -3
View File
@@ -281,8 +281,7 @@ function o.window(match, rules)
hl.window_rule(rules)
end
-- Opt a window in to Omarchy's standard active/inactive transparency. The tag
-- lets Appearance > Window Transparency turn these windows opaque or glassy.
-- Opt a window in to Omarchy's standard active/inactive transparency.
function o.transparent_window(match, opacity)
o.window(match, { opacity = opacity or "0.985 0.96", tag = "+transparent-window" })
o.window(match, { opacity = opacity or "0.985 0.96" })
end
+5 -3
View File
@@ -11,9 +11,11 @@ local vconsole = keyboard.selected()
local kb_layout = vconsole.XKBLAYOUT or "us"
local kb_variant = vconsole.XKBVARIANT or ""
-- Caps Lock is Caps Lock, and Shift + Caps Lock is the compose key. Stock XKB
-- has no such option, so Omarchy defines it in /etc/xkb.
local kb_options = "omarchy:shift_caps_compose"
-- CapsLock is the compose key, so Caps Lock itself has to live somewhere else.
-- Both Shifts together is the usual home for it, but it's easy to hit by
-- accident while typing. The _cancel variant sets Caps Lock the same way and
-- releases it on the next lone Shift, so a misfire clears itself.
local kb_options = "compose:caps,shift:both_capslock_cancel"
-- Hyprland resolves keybindings against the first entry in kb_layout, not the
-- layout that's currently active, so Omarchy's Latin-keysym bindings (SUPER + W
+1 -3
View File
@@ -14,9 +14,7 @@ hl.config({
inactive_border = inactive_border_color,
},
resize_on_border = true,
extend_border_grab_area = 8,
hover_icon_on_border = true,
resize_on_border = false,
allow_tearing = false,
layout = "dwindle",
},
+8 -61
View File
@@ -1,38 +1,9 @@
local function cursor_theme(value)
if value and value ~= "" and value ~= "omarchy-startup" then return value end
end
local function normal_theme(interrupted)
local current = os.getenv("XCURSOR_THEME")
local theme = cursor_theme(current)
-- Only a lost preference needs recovering; an unset theme still means the default alias.
if theme or not (interrupted or current == "omarchy-startup") then return theme or "default" end
theme = cursor_theme(os.getenv("OMARCHY_STARTUP_CURSOR_THEME"))
if theme then return theme end
local pipe = io.popen("gsettings get org.gnome.desktop.interface cursor-theme 2>/dev/null", "r")
if pipe then
theme = cursor_theme((pipe:read("*a") or ""):match("^'(.-)'%s*$"))
pipe:close()
end
return theme or "default"
end
local function normal_path()
local kept = {}
for entry in (os.getenv("XCURSOR_PATH") or "~/.local/share/icons:~/.icons:/usr/share/icons:/usr/share/pixmaps"):gmatch("[^:]+") do
if not entry:match("/default/hypr/cursors$") then table.insert(kept, entry) end
end
return table.concat(kept, ":")
end
-- Hyprland polls cursor.invisible, so its first frames need a blank cursor too.
function omarchy_startup_cursor_restore(loaded)
if not omarchy_startup_cursor_pending then return end
local cursor = omarchy_startup_cursor
if loaded then
omarchy_startup_cursor_pending = false
hl.env("OMARCHY_STARTUP_CURSOR_SESSION", "")
hl.env("OMARCHY_STARTUP_CURSOR_THEME", "")
hl.config({ cursor = cursor.config })
if cursor.config.enable_hyprcursor and cursor.hyprcursor then
hl.exec_cmd("hyprctl setcursor " .. o.shell_quote(cursor.hyprcursor) .. " " .. cursor.size)
@@ -40,25 +11,15 @@ function omarchy_startup_cursor_restore(loaded)
elseif not cursor.restoring then
-- Reload the normal Xcursor fallback before enabling Hyprcursor or GSettings.
cursor.restoring = true
hl.env("XCURSOR_PATH", cursor.path)
hl.env("XCURSOR_THEME", cursor.xcursor)
hl.exec_cmd("dbus-update-activation-environment --systemd XCURSOR_PATH XCURSOR_THEME")
local complete = "if omarchy_startup_cursor and omarchy_startup_cursor.xcursor == " .. string.format("%q", cursor.xcursor)
.. " and omarchy_startup_cursor.size == " .. cursor.size .. " then omarchy_startup_cursor_restore(true) end"
hl.exec_cmd("hyprctl setcursor " .. o.shell_quote(cursor.xcursor) .. " " .. cursor.size
.. " && hyprctl eval " .. o.shell_quote(complete))
.. " && hyprctl eval 'omarchy_startup_cursor_restore(true)'")
end
end
hl.on("config.reloaded", function()
local recover = false
if omarchy_startup_cursor_pending == nil then
-- An ordinary reload must not hide the pointer, but an interrupted reveal survives.
local cold = #hl.get_monitors() == 0
local session = os.getenv("HYPRLAND_INSTANCE_SIGNATURE")
local interrupted = session and os.getenv("OMARCHY_STARTUP_CURSOR_SESSION") == session
recover = not cold and not interrupted and os.getenv("XCURSOR_THEME") == "omarchy-startup"
omarchy_startup_cursor_pending = cold or interrupted or recover
-- Config updates in an existing compositor must not hide its pointer.
omarchy_startup_cursor_pending = #hl.get_monitors() == 0
if omarchy_startup_cursor_pending then
local hyprcursor = hl.get_config("cursor.enable_hyprcursor")
omarchy_startup_cursor = {
@@ -69,31 +30,17 @@ hl.on("config.reloaded", function()
},
hyprcursor = os.getenv("HYPRCURSOR_THEME"),
size = tonumber((hyprcursor and os.getenv("HYPRCURSOR_SIZE")) or os.getenv("XCURSOR_SIZE")) or 24,
path = normal_path(),
xcursor = normal_theme(interrupted),
path = os.getenv("XCURSOR_PATH") or "~/.local/share/icons:~/.icons:/usr/share/icons:/usr/share/pixmaps",
xcursor = os.getenv("XCURSOR_THEME") or "default",
}
if omarchy_startup_cursor.size <= 0 then omarchy_startup_cursor.size = 24 end
-- Compositor environment survives Lua reloads; a session id prevents stale inheritance.
hl.env("OMARCHY_STARTUP_CURSOR_THEME", omarchy_startup_cursor.xcursor)
hl.env("OMARCHY_STARTUP_CURSOR_SESSION", os.getenv("HYPRLAND_INSTANCE_SIGNATURE"))
if cold then
hl.env("XCURSOR_PATH", os.getenv("OMARCHY_PATH") .. "/default/hypr/cursors:" .. omarchy_startup_cursor.path)
hl.env("XCURSOR_THEME", "omarchy-startup")
else
hl.env("XCURSOR_PATH", omarchy_startup_cursor.path)
hl.env("XCURSOR_THEME", omarchy_startup_cursor.xcursor)
end
hl.env("XCURSOR_PATH", os.getenv("OMARCHY_PATH") .. "/default/hypr/cursors:" .. omarchy_startup_cursor.path)
hl.env("XCURSOR_THEME", "omarchy-startup")
end
end
if omarchy_startup_cursor_pending then
-- Keep the temporary cursor private to the compositor, including GSettings.
hl.config({ cursor = { invisible = true, enable_hyprcursor = false, sync_gsettings_theme = false } })
hl.timer(function()
if omarchy_startup_cursor_pending then
omarchy_startup_cursor.restoring = false
omarchy_startup_cursor_restore()
end
end, { timeout = 15000, type = "oneshot" })
if recover then omarchy_startup_cursor_restore() end
hl.timer(function() omarchy_startup_cursor_restore() end, { timeout = 15000, type = "oneshot" })
end
end)
-9
View File
@@ -1,9 +0,0 @@
-- The Glassy bar: the shell draws the bar see-through, and Hyprland blurs what
-- is behind it.
require("default.hypr.glass")()
hl.layer_rule({
match = { namespace = "^omarchy-bar$" },
blur = true,
ignore_alpha = 0.3,
})
-7
View File
@@ -1,7 +0,0 @@
-- Round window corners, whatever the theme sets. Matches Solitude's corners.
hl.config({
decoration = {
rounding = 6,
rounding_power = 3,
},
})
-6
View File
@@ -1,6 +0,0 @@
-- Square window corners, whatever the theme sets.
hl.config({
decoration = {
rounding = 0,
},
})
+4 -2
View File
@@ -1,6 +1,6 @@
-- Draw nothing just for show, for machines that render on the CPU, like a VM:
-- no animations, blur or shadows. How see-through windows are is left to
-- Appearance > Window Transparency.
-- no animations, blur or shadows, and opaque windows, so nothing behind a
-- window has to be blended into it.
hl.config({
animations = {
enabled = false,
@@ -16,3 +16,5 @@ hl.config({
},
},
})
o.window(".*", { opacity = "1 1" })
-14
View File
@@ -1,14 +0,0 @@
-- Frosted glass from the glassy Starship (#14726): whatever is behind a
-- see-through window or shell surface is blurred. Terminals get the see-through
-- background itself from omarchy-theme-set-glass, which keeps their text crisp.
require("default.hypr.glass")()
-- GTK apps fade as a whole, since GTK draws their backgrounds solid, and get
-- their blur from the backdrop-filter Glassy adds to the user's gtk.css.
o.window("org\\.gnome\\.Nautilus", { opacity = "0.75 0.75" })
hl.layer_rule({
match = { namespace = "^(omarchy-bar|omarchy-menu|omarchy-notifications|omarchy-osd|omarchy-polkit|omarchy-reminders|omarchy-clipboard|omarchy-emojis|omarchy-image-selector|omarchy-keyboard-panel|omarchy-network-qr)$" },
blur = true,
ignore_alpha = 0.3,
})
-2
View File
@@ -1,2 +0,0 @@
-- Opaque windows, even those Omarchy makes transparent by default.
o.window({ tag = "transparent-window" }, { opacity = "1 1" })
-13
View File
@@ -3,19 +3,6 @@
local paths = require("default.hypr.paths")
local require_all = require("default.hypr.require_all")
-- The saved files call o.workspace_mode, so it has to exist before they load.
local workspace_modes = require("default.hypr.workspace-modes")
local layouts_dir = paths.state_home .. "/omarchy/workspace-layouts"
-- The directory is found through XDG_STATE_HOME but the files are loaded by
-- module name, and bootstrap only puts ~/.local/state on the search path. A
-- state home anywhere else would list the saved layouts and then fail to require
-- them, or load same-named ones from under the home directory instead.
package.path = paths.state_home .. "/?.lua;" .. package.path
require_all.files(layouts_dir, "omarchy.workspace-layouts", { reload = true })
-- With every saved mode known, bring the open windows in line and set up what a
-- floating workspace needs.
workspace_modes.apply()
-385
View File
@@ -1,385 +0,0 @@
-- Workspace modes for omarchy-hyprland-workspace-layout-toggle.
--
-- Hyprland has dwindle and scrolling as tiled layouts, but nothing that floats a
-- whole workspace: it reports a workspace's `tiledLayout` and takes an unknown
-- layout name in silence, falling back to dwindle. So floating is applied to the
-- windows themselves.
--
-- Ending the mode has to tile the windows the mode floated and only those, so
-- each one is tagged. The tag lives on the window: it dies with it, follows it
-- between workspaces, and survives the config reload that a monitor being
-- plugged in performs, none of which a table on this side would do.
--
-- Every window on a floating workspace also carries a second tag, whether the
-- mode floated it or it was floating already. Titlebars, shadows, and full
-- opacity follow that tag, so a dialog on a floating workspace can be dragged by
-- its titlebar too, and a window carried out to a tiled one loses it.
require("default.hypr.helpers")
local paths = require("default.hypr.paths")
local FLOATED_TAG = "omarchy-mode-floated"
local WORKSPACE_TAG = "omarchy-floating-workspace"
local TITLEBARS = "/usr/lib/omarchy-hyprland-titlebars/titlebars.so"
local M = {}
local modes = {}
local default_mode = nil
local watching = false
local cascade = 0
local function set_floating(window, floating)
hl.dispatch(hl.dsp.window.float({ action = floating and "on" or "off", window = window }))
end
local function set_claimed(window, claimed)
hl.dispatch(hl.dsp.window.tag({ tag = (claimed and "+" or "-") .. FLOATED_TAG, window = window }))
end
local function set_on_floating_workspace(window, on)
hl.dispatch(hl.dsp.window.tag({ tag = (on and "+" or "-") .. WORKSPACE_TAG, window = window }))
end
local function claimed()
local addresses = {}
for _, window in ipairs(hl.get_windows({ tag = FLOATED_TAG })) do
addresses[window.address] = true
end
return addresses
end
-- Special workspaces, such as the scratchpad, and named ones, which report a
-- negative id, keep whatever state a window arrived with.
local function floating_workspace(id)
id = tonumber(id)
if not id or id < 1 then
return false
end
local mode = modes[tostring(id)]
if mode then
return mode == "floating"
end
return default_mode == "floating"
end
local function anything_floats()
if default_mode == "floating" then
return true
end
for _, mode in pairs(modes) do
if mode == "floating" then
return true
end
end
return false
end
-- A window the mode floats opens at a comfortable size in the middle of its
-- monitor, each one stepped from the last so a workspace floated all at once does
-- not leave every window stacked exactly on the one beneath it.
local function place(window)
local monitor = window.monitor
if not monitor or not monitor.width then
return
end
local width = math.floor(monitor.width / monitor.scale)
local height = math.floor(monitor.height / monitor.scale)
if (monitor.transform or 0) % 2 == 1 then
width, height = height, width
end
local reserved = monitor.reserved or {}
width = width - (reserved.left or 0) - (reserved.right or 0)
height = height - (reserved.top or 0) - (reserved.bottom or 0)
hl.dispatch(hl.dsp.window.resize({
x = math.min(1100, math.floor(width * 0.64)),
y = math.min(760, math.floor(height * 0.68)),
relative = false,
window = window,
}))
hl.dispatch(hl.dsp.window.center({ window = window }))
cascade = (cascade + 1) % 5
hl.dispatch(hl.dsp.window.move({ x = cascade * 24 - 48, y = cascade * 24 - 48, relative = true, window = window }))
end
-- A window already floating on its own account -- an app with a float rule of
-- its own, a dialog, one popped out with SUPER + T -- is left unclaimed, so it
-- is still floating when the mode ends. A fullscreen window is left as it is.
--
-- Every tiled window is claimed before any of them is floated: floating one
-- member of a group floats the rest, and a single pass would then find those
-- already floating and leave them to be stranded when the mode ends.
local function float_for_mode(windows)
local claiming = {}
for _, window in ipairs(windows) do
set_on_floating_workspace(window, true)
if not window.floating and (window.fullscreen or 0) == 0 then
set_claimed(window, true)
table.insert(claiming, window)
end
end
for _, window in ipairs(claiming) do
set_floating(window, true)
place(window)
end
end
-- A pinned window is one the user asked to keep above everything, and tiling it
-- would unpin it as well, so the mode leaves it be.
local function tile_for_mode(window, mine)
set_on_floating_workspace(window, false)
if not mine[window.address] or window.pinned then
return
end
set_claimed(window, false)
set_floating(window, false)
end
-- The workspace is passed in when a window is still on its way to it.
local function arrange(windows, workspace)
local floating = {}
local mine = claimed()
for _, window in ipairs(windows) do
local id = tonumber(workspace and workspace.id or (window.workspace and window.workspace.id))
if id and id >= 1 then
if floating_workspace(id) then
table.insert(floating, window)
else
tile_for_mode(window, mine)
end
end
end
float_for_mode(floating)
end
-- Overlapping windows are placed with the pointer, so a floating workspace
-- focuses on click rather than on hover. Every other workspace keeps the user's
-- own setting.
local input = {}
local function update_input()
local workspace = hl.get_active_workspace()
local active = workspace ~= nil and floating_workspace(workspace.id)
if active == input.floating then
return
end
input.floating = active
hl.config({ input = { follow_mouse = active and 0 or input.follow_mouse } })
end
-- Windows arrive by opening and by being carried in, and a window rule only ever
-- fires for the first. One carried back out gives up the floating the mode gave
-- it rather than staying floating somewhere that tiles.
local function watch()
if watching then
return
end
watching = true
input.follow_mouse = hl.get_config("input.follow_mouse")
hl.on("window.open", function(window)
arrange({ window })
end)
-- A move is still being committed when this fires, and changing the window's
-- floating state underneath it loses the move. Let it land first.
hl.on("window.move_to_workspace", function(window, workspace)
hl.timer(function()
arrange({ window }, workspace)
end, { type = "oneshot", timeout = 1 })
end)
-- A fullscreen window is left as it is, so it is arranged once it comes back
-- out: a tiled one leaving fullscreen on a floating workspace floats then.
hl.on("window.fullscreen", function(window)
hl.timer(function()
arrange({ window })
end, { type = "oneshot", timeout = 1 })
end)
hl.on("workspace.active", update_input)
hl.on("monitor.focused", update_input)
end
function o.workspace_mode(spec)
watch()
-- The default for every workspace that has no mode of its own, chosen under
-- Trigger > Appearance > Workspace Layout. A tiled default is Hyprland's own
-- layout; a floating one is arranged once the saved modes have all loaded.
if spec.default then
default_mode = spec.default
if spec.default ~= "floating" then
hl.config({ general = { layout = spec.default } })
end
return
end
local workspace = tostring(spec.workspace)
-- Floating keeps whatever tiled layout the workspace had, so nothing has to be
-- chosen for it on the way back out.
if spec.mode ~= "floating" then
hl.workspace_rule({ workspace = workspace, layout = spec.mode })
end
modes[workspace] = spec.mode
arrange(hl.get_workspace_windows(workspace))
update_input()
end
local function theme_colors()
local colors = {}
-- omarchy-theme-set writes the current theme under ~/.local/state whatever
-- XDG_STATE_HOME says, and Hyprland loads the theme's own config from there.
local file = io.open(paths.home .. "/.local/state/omarchy/current/theme/colors.toml", "r")
if file then
for line in file:lines() do
local key, value = line:match('^%s*([%w_]+)%s*=%s*"#(%x%x%x%x%x%x)"')
if key then
colors[key] = "rgb(" .. value .. ")"
end
end
file:close()
end
local background = colors.background or "rgb(1a1b26)"
-- A titlebar in the window's own background color runs into the window, so it
-- takes the theme's raised surface. Themes that make that the background too
-- fall back to their selection color.
local titlebar = colors.lighter_background
if not titlebar or titlebar == background then
titlebar = colors.selection or background
end
return {
background = background,
foreground = colors.foreground or "rgb(c0caf5)",
titlebar = titlebar,
}
end
-- Titlebars come from omarchy-hyprland-titlebars, loaded only while something
-- floats. Hyprland keeps a plugin loaded after the config stops asking for it,
-- but resets its options, and the plugin draws nothing until enabled here.
-- The plugin reloads the config once it is in, which is the pass that finds its
-- options here.
local function titlebars()
local file = io.open(TITLEBARS, "r")
if not file then
return
end
file:close()
hl.plugin.load(TITLEBARS)
if not hl.plugin.hyprbars then
return
end
local colors = theme_colors()
local window = [[window = "address:%WINDOW%"]]
hl.config({
plugin = {
hyprbars = {
enabled = true,
workspace_tag = WORKSPACE_TAG,
bar_height = 30,
bar_color = colors.titlebar,
col = { text = colors.foreground },
bar_text_font = "monospace",
bar_text_size = 12,
bar_text_weight = "normal",
bar_text_align = "left",
bar_buttons_alignment = "right",
bar_part_of_window = true,
bar_precedence_over_border = true,
bar_padding = 10,
bar_button_padding = 8,
button_rounding = 0,
icon_on_hover = false,
inactive_button_color = colors.titlebar,
on_double_click = "hyprctl dispatch 'hl.dsp.window.fullscreen({ mode = \"maximized\", action = \"toggle\", " .. window .. " })'",
},
},
})
for _, button in ipairs({
{ icon = "×", action = "hyprctl dispatch 'hl.dsp.window.close({ " .. window .. " })'" },
{ icon = "□", action = "hyprctl dispatch 'hl.dsp.window.fullscreen({ mode = \"maximized\", action = \"toggle\", " .. window .. " })'" },
}) do
hl.plugin.hyprbars.add_button({
bg_color = colors.titlebar,
fg_color = colors.foreground,
size = 20,
icon = button.icon,
action = button.action,
})
end
end
-- Runs once every saved mode has loaded. A reload is a fresh start on this side
-- but not for the windows, so each one is brought in line with the modes as they
-- now stand: moving the default away from floating has to give back what it
-- floated, even though nothing names those workspaces any more.
function M.apply()
-- A default that no longer floats can leave no saved mode at all to start the
-- watchers, while the windows it floated still carry its tags.
local leftovers = #hl.get_windows({ tag = FLOATED_TAG }) > 0 or #hl.get_windows({ tag = WORKSPACE_TAG }) > 0
if anything_floats() or leftovers then
watch()
end
if watching then
arrange(hl.get_windows())
update_input()
end
if not anything_floats() then
return
end
-- Overlapping windows need depth to tell apart, and a translucent window over
-- another is hard to read.
if not hl.get_config("decoration.shadow.enabled") then
hl.config({ decoration = { shadow = { enabled = true, range = 16, render_power = 3, color = "rgba(00000035)" } } })
o.window(".*", { no_shadow = true })
o.window({ tag = WORKSPACE_TAG }, { no_shadow = false })
end
o.window({ tag = WORKSPACE_TAG }, { opacity = "1.0 override 1.0 override" })
titlebars()
end
return M
+1
View File
@@ -108,6 +108,7 @@ def defaults(config_home, fresh=False):
stock_lists = {
"Hotkey/ActivateKeys": {"0": "Hangul_Hanja"},
"Hotkey/DeactivateKeys": {"0": "Hangul_Romaja"},
"Hotkey/AltTriggerKeys": {"0": "Shift_L"},
"Hotkey/EnumerateGroupForwardKeys": {"0": "Super+space"},
"Hotkey/EnumerateGroupBackwardKeys": {"0": "Super+Shift+space"},
"TriggerKey": {"0": "Super+grave", "1": "Super+semicolon"},
+20 -53
View File
@@ -53,60 +53,7 @@
"learn.community": {"icon":"󰙯","label":"Community","action":"omarchy-launch-discord-community"},
// Trigger
"trigger.appearance": {"icon":"󰏘","label":"Appearance"},
"trigger.appearance.animations": {"icon":"󰗘","label":"Animations","action":"omarchy-toggle-animations"},
"trigger.appearance.top-bar": {"icon":"󰍜","label":"Menu Bar"},
"trigger.appearance.top-bar.hide": {"icon":"󰈉","label":"Hide Bar","when":"! omarchy-toggle-enabled bar-off","action":"omarchy-toggle-bar"},
"trigger.appearance.top-bar.show": {"icon":"󰈈","label":"Show Bar","when":"omarchy-toggle-enabled bar-off","action":"omarchy-toggle-bar"},
"trigger.appearance.top-bar.position": {"icon":"","label":"Position Bar"},
"trigger.appearance.top-bar.background": {"icon":"󰂵","label":"Bar Background"},
"trigger.appearance.top-bar.background.opaque": {"icon":"󰝤","label":"Opaque Bar","checked":"[[ $(omarchy-bar background) == \"opaque\" ]]","action":"omarchy-bar background opaque"},
"trigger.appearance.top-bar.background.glassy": {"icon":"󰂵","label":"Glassy Bar","checked":"[[ $(omarchy-bar background) == \"glassy\" ]]","action":"omarchy-bar background glassy"},
"trigger.appearance.top-bar.background.clear": {"icon":"󰝣","label":"Clear Bar","checked":"[[ $(omarchy-bar background) == \"none\" ]]","action":"omarchy-bar background none"},
"trigger.appearance.top-bar.indicators": {"icon":"󰋽","label":"Bar Indicators"},
"trigger.appearance.top-bar.indicators.battery-percentage": {"icon":"󰁹","label":"Battery Percentage","when":"omarchy-hw-laptop","action":"omarchy-shell omarchy.power togglePercentage"},
"trigger.appearance.top-bar.indicators.clock": {"icon":"󰥔","label":"Clock"},
"trigger.appearance.top-bar.indicators.clock.sunday-24h": {"icon":"󰥔","label":"Sunday 14:30","when":"[[ $(omarchy-bar position) != \"left\" && $(omarchy-bar position) != \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock format) == \"dddd HH:mm\" ]]","action":"omarchy-bar set omarchy.clock format \"dddd HH:mm\""},
"trigger.appearance.top-bar.indicators.clock.sunday-12h": {"icon":"󰥔","label":"Sunday 2:30 PM","when":"[[ $(omarchy-bar position) != \"left\" && $(omarchy-bar position) != \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock format) == \"dddd h:mm AP\" ]]","action":"omarchy-bar set omarchy.clock format \"dddd h:mm AP\""},
"trigger.appearance.top-bar.indicators.clock.sunday-seconds-24h": {"icon":"󰥔","label":"Sunday 14:30:05","when":"[[ $(omarchy-bar position) != \"left\" && $(omarchy-bar position) != \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock format) == \"dddd HH:mm:ss\" ]]","action":"omarchy-bar set omarchy.clock format \"dddd HH:mm:ss\""},
"trigger.appearance.top-bar.indicators.clock.sunday-seconds-12h": {"icon":"󰥔","label":"Sunday 2:30:05 PM","when":"[[ $(omarchy-bar position) != \"left\" && $(omarchy-bar position) != \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock format) == \"dddd h:mm:ss AP\" ]]","action":"omarchy-bar set omarchy.clock format \"dddd h:mm:ss AP\""},
"trigger.appearance.top-bar.indicators.clock.time-24h": {"icon":"󰥔","label":"14:30","when":"[[ $(omarchy-bar position) != \"left\" && $(omarchy-bar position) != \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock format) == \"HH:mm\" ]]","action":"omarchy-bar set omarchy.clock format \"HH:mm\""},
"trigger.appearance.top-bar.indicators.clock.time-12h": {"icon":"󰥔","label":"2:30 PM","when":"[[ $(omarchy-bar position) != \"left\" && $(omarchy-bar position) != \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock format) == \"h:mm AP\" ]]","action":"omarchy-bar set omarchy.clock format \"h:mm AP\""},
"trigger.appearance.top-bar.indicators.clock.date-24h": {"icon":"󰥔","label":"Sun 3 May 14:30","when":"[[ $(omarchy-bar position) != \"left\" && $(omarchy-bar position) != \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock format) == \"ddd d MMM HH:mm\" ]]","action":"omarchy-bar set omarchy.clock format \"ddd d MMM HH:mm\""},
"trigger.appearance.top-bar.indicators.clock.date-12h": {"icon":"󰥔","label":"Sun 3 May 2:30 PM","when":"[[ $(omarchy-bar position) != \"left\" && $(omarchy-bar position) != \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock format) == \"ddd d MMM h:mm AP\" ]]","action":"omarchy-bar set omarchy.clock format \"ddd d MMM h:mm AP\""},
"trigger.appearance.top-bar.indicators.clock.week": {"icon":"󰥔","label":"3 May W18 2026","when":"[[ $(omarchy-bar position) != \"left\" && $(omarchy-bar position) != \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock format) == \"d MMMM 'W'ww yyyy\" ]]","action":"omarchy-bar set omarchy.clock format \"d MMMM 'W'ww yyyy\""},
"trigger.appearance.top-bar.indicators.clock.iso": {"icon":"󰥔","label":"2026-05-03 14:30","when":"[[ $(omarchy-bar position) != \"left\" && $(omarchy-bar position) != \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock format) == \"yyyy-MM-dd HH:mm\" ]]","action":"omarchy-bar set omarchy.clock format \"yyyy-MM-dd HH:mm\""},
"trigger.appearance.top-bar.indicators.clock.stacked-24h": {"icon":"󰥔","label":"14 — 30","when":"[[ $(omarchy-bar position) == \"left\" || $(omarchy-bar position) == \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock verticalFormat) == $'HH\\n—\\nmm' ]]","action":"omarchy-bar set omarchy.clock verticalFormat $'HH\\n—\\nmm'"},
"trigger.appearance.top-bar.indicators.clock.stacked-12h": {"icon":"󰥔","label":"2 — 30 PM","when":"[[ $(omarchy-bar position) == \"left\" || $(omarchy-bar position) == \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock verticalFormat) == $'h\\n—\\nmm\\nAP' ]]","action":"omarchy-bar set omarchy.clock verticalFormat $'h\\n—\\nmm\\nAP'"},
"trigger.appearance.top-bar.indicators.clock.stacked-date": {"icon":"󰥔","label":"03 May W18 '26","when":"[[ $(omarchy-bar position) == \"left\" || $(omarchy-bar position) == \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock verticalFormat) == $'dd\\nMMM\\n\\'W\\'ww\\n\\'\\'yy' ]]","action":"omarchy-bar set omarchy.clock verticalFormat $'dd\\nMMM\\n\\'W\\'ww\\n\\'\\'yy'"},
"trigger.appearance.top-bar.indicators.clock.stacked-time": {"icon":"󰥔","label":"14 30","when":"[[ $(omarchy-bar position) == \"left\" || $(omarchy-bar position) == \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock verticalFormat) == $'HH\\nmm' ]]","action":"omarchy-bar set omarchy.clock verticalFormat $'HH\\nmm'"},
"trigger.appearance.top-bar.position.top": {"icon":"󰁝","label":"Top Bar","action":"omarchy-bar position top"},
"trigger.appearance.top-bar.position.bottom": {"icon":"󰁅","label":"Bottom Bar","action":"omarchy-bar position bottom"},
"trigger.appearance.top-bar.position.left": {"icon":"󰁍","label":"Left Bar","action":"omarchy-bar position left"},
"trigger.appearance.top-bar.position.right": {"icon":"󰁔","label":"Right Bar","action":"omarchy-bar position right"},
"trigger.appearance.window-gaps": {"icon":"","label":"Window Gaps","action":"omarchy-hyprland-window-gaps-toggle"},
"trigger.appearance.window-corners": {"icon":"󰘇","label":"Window Corners"},
"trigger.appearance.window-corners.theme": {"icon":"󰏘","label":"Theme Corners","checked":"[[ $(omarchy-hyprland-corners) == \"theme\" ]]","action":"omarchy-hyprland-corners theme"},
"trigger.appearance.window-corners.sharp": {"icon":"󰝣","label":"Sharp Corners","checked":"[[ $(omarchy-hyprland-corners) == \"sharp\" ]]","action":"omarchy-hyprland-corners sharp"},
"trigger.appearance.window-corners.rounded": {"icon":"󰘇","label":"Rounded Corners","checked":"[[ $(omarchy-hyprland-corners) == \"rounded\" ]]","action":"omarchy-hyprland-corners rounded"},
"trigger.appearance.window-transparency": {"icon":"󰂵","label":"Window Transparency"},
"trigger.appearance.window-transparency.opaque": {"icon":"󰝤","label":"Opaque Windows","checked":"[[ $(omarchy-hyprland-window-transparency) == \"opaque\" ]]","action":"omarchy-hyprland-window-transparency opaque"},
"trigger.appearance.window-transparency.transparent": {"icon":"󰝣","label":"Transparent Windows","checked":"[[ $(omarchy-hyprland-window-transparency) == \"transparent\" ]]","action":"omarchy-hyprland-window-transparency transparent"},
"trigger.appearance.window-transparency.glassy": {"icon":"󰂵","label":"Glassy Windows","checked":"[[ $(omarchy-hyprland-window-transparency) == \"glassy\" ]]","action":"omarchy-hyprland-window-transparency glassy"},
"trigger.appearance.one-window-ratio": {"icon":"","label":"1-Window Ratio","action":"omarchy-hyprland-window-single-square-aspect-toggle"},
"trigger.appearance.workspace-layout": {"icon":"󱂬","label":"Workspace Layout","title":"Default Workspace Layout"},
"trigger.appearance.workspace-layout.dwindle": {"icon":"󱓻","label":"Tiling","checked":"[[ $(omarchy-hyprland-workspace-layout-default) == \"dwindle\" ]]","action":"omarchy-hyprland-workspace-layout-default dwindle"},
"trigger.appearance.workspace-layout.scrolling": {"icon":"󰝥","label":"Scrolling","checked":"[[ $(omarchy-hyprland-workspace-layout-default) == \"scrolling\" ]]","action":"omarchy-hyprland-workspace-layout-default scrolling"},
"trigger.appearance.workspace-layout.floating": {"icon":"󰔶","label":"Floating","checked":"[[ $(omarchy-hyprland-workspace-layout-default) == \"floating\" ]]","action":"omarchy-hyprland-workspace-layout-default floating"},
"trigger.appearance.theme-sync": {"icon":"󰸌","label":"Herdr Theme Sync","when":"omarchy-theme-set-herdr-machines --available","action":"omarchy-toggle-theme-sync"},
"trigger.toggle": {"icon":"󰔎","label":"Toggle"},
"trigger.toggle.idle-lock": {"icon":"󰅶","label":"Stay Awake","action":"omarchy-toggle-idle"},
"trigger.toggle.notifications": {"icon":"󰂛","label":"Notifications","action":"omarchy-toggle-notification-silencing"},
"trigger.toggle.nightlight": {"icon":"󰔎","label":"Nightlight","action":"omarchy-toggle-nightlight"},
"trigger.toggle.screensaver": {"icon":"󱄄","label":"Screensaver","action":"omarchy-toggle-screensaver"},
"trigger.toggle.crash-capture": {"icon":"󱚡","label":"Crash Capture","action":"omarchy-toggle-crash-capture"},
"trigger.emoji": {"icon":"","label":"Emoji","aliases":["emoji","emojis"],"action":"omarchy-menu-emoji"},
"trigger.clipboard-history": {"icon":"󰅇","label":"Clipboard History","action":"omarchy-shell shell summon omarchy.clipboard"},
"trigger.reminder": {"icon":"󰢌","label":"Reminder","aliases":["reminder"]},
"trigger.capture": {"icon":"","label":"Capture","aliases":["capture","screenshot","screenrecord","screen-record","screenrecording"]},
"trigger.capture.screenshot": {"icon":"","label":"Screenshot","action":"omarchy-capture-screenshot"},
@@ -121,6 +68,7 @@
"trigger.capture.screenrecord.webcam": {"icon":"","label":"With desktop + microphone audio + webcam","when":"omarchy-hw-webcam","action":"omarchy-capture-screenrecording-with-webcam"},
"trigger.transcode": {"icon":"󰧸","label":"Transcode","action":"omarchy-transcode"},
"trigger.share": {"icon":"","label":"Share","aliases":["share"]},
"trigger.toggle": {"icon":"󰔎","label":"Toggle","aliases":["toggle","toggles"]},
"trigger.hardware": {"icon":"","label":"Hardware","aliases":["hardware","hw"]},
"trigger.tests": {"icon":"󰓅","label":"Speed Test"},
"trigger.hardware.laptop-display": {"icon":"󰛧","label":"Laptop Display","when":"omarchy-hw-laptop","action":"omarchy-hyprland-monitor-internal toggle"},
@@ -139,6 +87,18 @@
"trigger.share.file": {"icon":"","label":"File","action":"omarchy-menu-share file"},
"trigger.share.folder": {"icon":"","label":"Folder","action":"omarchy-menu-share folder"},
"trigger.share.receive": {"icon":"󰥦","label":"Receive","action":"uwsm-app -- localsend"},
"trigger.toggle.idle-lock": {"icon":"󰅶","label":"Stay Awake","action":"omarchy-toggle-idle"},
"trigger.toggle.notifications": {"icon":"󰂛","label":"Notifications","action":"omarchy-toggle-notification-silencing"},
"trigger.toggle.crash-capture": {"icon":"󱚡","label":"Crash Capture","action":"omarchy-toggle-crash-capture"},
"trigger.toggle.screensaver": {"icon":"󱄄","label":"Screensaver","action":"omarchy-toggle-screensaver"},
"trigger.toggle.theme-sync": {"icon":"󰸌","label":"Herdr Theme Sync","when":"omarchy-theme-set-herdr-machines --available","action":"omarchy-toggle-theme-sync"},
"trigger.toggle.nightlight": {"icon":"󰔎","label":"Nightlight","action":"omarchy-toggle-nightlight"},
"trigger.toggle.top-bar": {"icon":"󰍜","label":"Menu Bar","action":"omarchy-toggle-bar"},
"trigger.toggle.battery-percentage": {"icon":"󰁹","label":"Battery Percentage","when":"omarchy-hw-laptop","action":"omarchy-shell omarchy.power togglePercentage"},
"trigger.toggle.workspace-layout": {"icon":"󱂬","label":"Workspace Layout","action":"omarchy-hyprland-workspace-layout-toggle"},
"trigger.toggle.window-gaps": {"icon":"","label":"Window Gaps","action":"omarchy-hyprland-window-gaps-toggle"},
"trigger.toggle.animations": {"icon":"󰕟","label":"Animations","action":"omarchy-toggle-animations"},
"trigger.toggle.one-window-ratio": {"icon":"","label":"1-Window Ratio","action":"omarchy-hyprland-window-single-square-aspect-toggle"},
"trigger.tests.network-speedtest": {"icon":"󰓅","label":"Network Speed Test","action":"omarchy-shell shell summon omarchy.speedtest"},
"trigger.tests.disk-speedtest": {"icon":"󰋊","label":"Disk Speed Test","action":"omarchy-shell shell summon omarchy.disk-speedtest"},
@@ -147,9 +107,16 @@
"style.background": {"icon":"","label":"Background","aliases":["background","wallpaper"],"action":"background=$(omarchy-theme-bg-switcher); [[ -n $background ]] && omarchy-theme-bg-set \"$background\""},
"style.unlock": {"icon":"󰟵","label":"Unlock","aliases":["unlock"],"action":"unlock=$(omarchy-plymouth-switcher); if [[ $unlock == default ]]; then omarchy-launch-floating-terminal-with-presentation omarchy-plymouth-reset; elif [[ -n $unlock ]]; then omarchy-launch-floating-terminal-with-presentation \"omarchy-plymouth-set-by-theme $(printf %q \"$unlock\")\"; fi"},
"style.font": {"icon":"","label":"Font","provider":"fonts"},
"style.bar": {"icon":"󰍜","label":"Menu Bar"},
"style.bar.position": {"icon":"","label":"Position"},
"style.bar.transparency": {"icon":"󰂵","label":"Transparency","action":"omarchy-bar transparent toggle"},
"style.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/looknfeel.lua\""},
"style.screensaver": {"icon":"󱄄","label":"Screensaver"},
"style.about": {"icon":"","label":"About"},
"style.bar.position.top": {"icon":"󰁝","label":"Top","action":"omarchy-bar position top"},
"style.bar.position.bottom": {"icon":"󰁅","label":"Bottom","action":"omarchy-bar position bottom"},
"style.bar.position.left": {"icon":"󰁍","label":"Left","action":"omarchy-bar position left"},
"style.bar.position.right": {"icon":"󰁔","label":"Right","action":"omarchy-bar position right"},
"style.about.text": {"icon":"","label":"Edit Text","action":"omarchy-branding-about text"},
"style.about.image": {"icon":"","label":"Set From Image","action":"omarchy-branding-about image"},
"style.about.default": {"icon":"","label":"Restore Default","action":"omarchy-branding-about reset"},
+1 -1
View File
@@ -1,6 +1,6 @@
[Unit]
Description=Fcitx5 input methods and XCompose sequences
# fcitx5 turns the Shift + CapsLock compose sequences in ~/.XCompose into text for
# fcitx5 turns the CapsLock compose sequences in ~/.XCompose into text for
# Wayland clients.
#
# Wait for the compositor: fcitx5 needs WAYLAND_DISPLAY and DISPLAY, which uwsm
-3
View File
@@ -1,9 +1,6 @@
[colors-dark]
foreground={{ foreground_strip }}
background={{ background_strip }}
# Blur only applies while the background is see-through, as with Glassy windows,
# and foot reads it only at launch, so it is on from the start.
blur=yes
selection-foreground={{ selection_foreground_strip }}
selection-background={{ selection_background_strip }}
-4
View File
@@ -6,10 +6,6 @@ selection_background {{ selection_background }}
cursor {{ bright_foreground }}
cursor_text_color {{ background }}
# Lets a reload change the background opacity, as Glassy windows do, since
# kitty only allows that when this is on from launch.
dynamic_background_opacity yes
active_border_color {{ accent }}
active_tab_background {{ accent }}
-2
View File
@@ -91,8 +91,6 @@ config/** ──► omarchy-settings /etc/skel/.config/
/usr/share/omarchy/config/** (resync source)
etc/fastfetch/config.jsonc ──► omarchy-settings /etc/fastfetch/config.jsonc
etc/xdg/kitty/kitty.conf ──► omarchy-settings /etc/xdg/kitty/kitty.conf
etc/xkb/{rules/evdev,symbols/omarchy}
──► omarchy-settings /etc/xkb/ (Shift + Caps Lock compose option)
applications/*.desktop ──► omarchy-settings /etc/skel/.local/share/applications/
/usr/share/omarchy/applications/
+4 -4
View File
@@ -94,8 +94,8 @@ You can still install by hand: drop a plugin into
`~/.config/omarchy/plugins/<id>/`, run `omarchy-shell shell rescanPlugins`, then
`omarchy plugin enable <id>`. A bar widget starts in its declared default
section; enabling a full bar replaces the one in use. `omarchy bar` drives the
bar from the CLI — `use | reset | defaults | position | background | put |
move | set | get`, with placement flags such as `--section` and `--index`.
bar from the CLI — `use | reset | defaults | position | transparent | put |
move | set`, with placement flags such as `--section` and `--index`.
The lower-level IPC methods remain available through `omarchy-shell shell ...`.
## Elsewhen
@@ -121,7 +121,7 @@ or `omarchy.power`. There is no `bar` target.
| `rescanPlugins` | re-walk plugin dirs and hot-reload plugin code |
| `reloadConfig` | reload shell.json |
| `applyTheme <colorsB64> <shellB64>` | push theme colors + shell.toml |
| `toggleBarTransparency` | cycle the bar background from clear to glassy to opaque |
| `toggleBarTransparency` | flip the bar background between solid and transparent |
| `cycleInput <next\|back>` | switch to the next or previous input method or layout (Super+I, Super+Shift+I); `not running` without the input reader |
| `setPluginEnabled <id> <"true"\|…>` | flip enabled bit (`ok` / `unknown`) |
| `enablePlugin <id> <placementJson>` | enable and place in one mutation |
@@ -148,7 +148,7 @@ string on a miss.
"bar": {
"id": "omarchy.bar",
"position": "top",
"background": "opaque",
"transparent": false,
"centerAnchor": "omarchy.clock",
"layout": {
"left": [ { "id": "omarchy.menu" } ],
+1 -1
View File
@@ -2,7 +2,7 @@
`omarchy-sudo-passwordless` publishes a timed or permanent grant for the numeric UID authenticated by sudo. Its user interface inspects sudo policy without prompting and authenticates only the enable action when access is off, without a reusable sudo timestamp; fixed installed internal actions run as root and serialize on `/run/lock/omarchy-sudo-passwordless.lock`.
The menu bar's `PasswordlessSudo` indicator runs `--active` when the shell starts and whenever `omarchy.indicators` is refreshed, and shows the passwordless sudo icon in red while access is active. The probe normally reads sudo's noninteractive long policy listing for the installed `__status` action and checks its `!authenticate` tag. If listing requires authentication, it falls back to a noninteractive `__status` call that ignores cached credentials. Neither path prompts or updates the credential cache. Interactive setup likewise falls back to noninteractive status and, when authentication is unavailable, leaves validation to the single confirmed enable action. Clicking the active indicator runs `--disable` noninteractively without opening a terminal, so a grant expiring between display and click cannot start the enable flow. The indicator never polls: every enable or disable attempt refreshes it directly, failed ones included since a rolled-back renewal can remove the previous grant, and enabling a timed grant also starts a transient user timer on the same wall-clock deadline that refreshes it two seconds later, so expiry during suspend shows on resume. The root expiry timer cannot reach the user's shell. If that refresh is missed, the indicator stays red past expiry, and clicking it reports access is already disabled and clears it.
The menu bar's `PasswordlessSudo` indicator polls `--active` every five seconds and shows the passwordless sudo icon in red while access is active. The probe normally reads sudo's noninteractive long policy listing for the installed `__status` action and checks its `!authenticate` tag. If listing requires authentication, it falls back to a noninteractive `__status` call that ignores cached credentials. Neither path prompts or updates the credential cache. Interactive setup likewise falls back to noninteractive status and, when authentication is unavailable, leaves validation to the single confirmed enable action. Clicking the active indicator runs `--disable` noninteractively without opening a terminal, so a grant expiring between display and click cannot start the enable flow.
## Grant lifecycle
+1 -1
View File
@@ -10,7 +10,7 @@ Beyond `colors.toml` and hand-written config overrides, a first-party theme can
A theme can pair a still background with a silent intro video using matching filenames: `backgrounds/0-winding-road.webp` and `backgrounds/intros/0-winding-road.mp4`. The video plays automatically at login and when switching to that theme with this background selected. No registration, checksum file or manual setting is needed. Supported video extensions are `mp4`, `m4v`, `mov`, `webm`, `mkv` and `avi`. Intro videos stay inside the nested `intros/` directory so the background picker does not list them as looping wallpapers. An image selected from another directory looks for its intro in that directory's own `intros/`, so a same-named custom image cannot inherit a theme's video.
Aim for a video between five and seven seconds long that opens on its scene setup and ends on the matching still. The shell provides the opening crossfade, so an added fade from black is unnecessary. OWE owns decoding, mute and the handoff back to that image. The theme picker prepares the highlighted theme's remembered intro offscreen, paused at its opening frame. OWE keeps one prepared clip and an idle renderer for up to one minute, then releases them. A theme switch also starts preparation while its templates render; OWE compares the selected video with the prepared clip before resuming it. Older OWE versions use the ordinary startup path. Hyprland and the SDDM greeter use a black compositor background. On a fresh login, the persistent shell host keeps an opaque black cover above the bar and wallpaper until the bar has loaded and OWE has a moving video frame, then fades both in together over 420 ms. Hyprland initializes with a transparent cursor so its visibility polling cannot expose a pointer in the first frames. The temporary Xcursor theme stays private to the compositor; applications inherit the normal cursor environment. The opening fade restores the selected cursor and settings. Shell restarts and a fifteen-second compositor timeout recover a cursor left hidden by an interrupted startup. With intros disabled or unavailable, it waits for the still to be ready instead. A ten-second deadline releases the cover if a missing wallpaper or failed plugin never becomes ready. Ordinary shell restarts preload the selected still alongside the bar without mapping that login cover. Login intros use the Hyprland instance signature, so logging out and back in plays the intro again. The launcher consumes the current session before waiting up to five seconds for OWE. Shell restarts and a daemon starting after that window cannot play a delayed intro. Theme switches synchronize OWE with the selected image and use its first-frame start mode. An interrupted video supplies its currently playing frame as the outgoing snapshot. Rapid selections wait for the previous opening fade to finish before capturing it. Theme replacement and playback wait for the outgoing cover to decode and reach the compositor on every screen. A persistent layer holds that outgoing wallpaper until OWE hides the shell background and reports a ready video with an advancing playback position and no outgoing still transition, then crossfades it over 420 ms while applying the new bar palette. The shell retains its background service and decoded wallpaper while it is hidden, so handing the still back does not recreate them. App retints wait until the opening crossfade completes, with a three-second bound, so they do not compete with its first frames. The compositor reload waits until playback finishes because reloading Hyprland pauses video presentation. A superseded intro skips that reload. Failed playback releases the pending palette, and a newer theme invalidates the older handoff. Refreshing a theme, headless setup, and cycling backgrounds within the same theme do not trigger intros. Appearance > Animations suppresses playback along with the other animations. The `omarchy theme bg intro toggle` command can toggle intros separately.
Aim for a video between five and seven seconds long that opens on its scene setup and ends on the matching still. The shell provides the opening crossfade, so an added fade from black is unnecessary. OWE owns decoding, mute and the handoff back to that image. The theme picker prepares the highlighted theme's remembered intro offscreen, paused at its opening frame. OWE keeps one prepared clip and an idle renderer for up to one minute, then releases them. A theme switch also starts preparation while its templates render; OWE compares the selected video with the prepared clip before resuming it. Older OWE versions use the ordinary startup path. Hyprland and the SDDM greeter use a black compositor background. On a fresh login, the persistent shell host keeps an opaque black cover above the bar and wallpaper until the bar has loaded and OWE has a moving video frame, then fades both in together over 420 ms. Hyprland initializes with a transparent cursor so its visibility polling cannot expose a pointer in the first frames. The temporary Xcursor theme stays private to the compositor; applications inherit the normal cursor environment. The opening fade restores the selected cursor and settings. Shell restarts and a fifteen-second compositor timeout recover a cursor left hidden by an interrupted startup. With intros disabled or unavailable, it waits for the still to be ready instead. A ten-second deadline releases the cover if a missing wallpaper or failed plugin never becomes ready. Ordinary shell restarts preload the selected still alongside the bar without mapping that login cover. Login intros use the Hyprland instance signature, so logging out and back in plays the intro again. The launcher consumes the current session before waiting up to five seconds for OWE. Shell restarts and a daemon starting after that window cannot play a delayed intro. Theme switches synchronize OWE with the selected image and use its first-frame start mode. An interrupted video supplies its currently playing frame as the outgoing snapshot. Rapid selections wait for the previous opening fade to finish before capturing it. Theme replacement and playback wait for the outgoing cover to decode and reach the compositor on every screen. A persistent layer holds that outgoing wallpaper until OWE hides the shell background and reports a ready video with an advancing playback position and no outgoing still transition, then crossfades it over 420 ms while applying the new bar palette. The shell retains its background service and decoded wallpaper while it is hidden, so handing the still back does not recreate them. App retints wait until the opening crossfade completes, with a three-second bound, so they do not compete with its first frames. The compositor reload waits until playback finishes because reloading Hyprland pauses video presentation. A superseded intro skips that reload. Failed playback releases the pending palette, and a newer theme invalidates the older handoff. Refreshing a theme, headless setup, and cycling backgrounds within the same theme do not trigger intros. Toggle > Animations suppresses playback along with the other animations. The `omarchy theme bg intro toggle` command can toggle intros separately.
A theme installed from a git repo is held to a much shorter list; see [What an installed theme may not ship](#what-an-installed-theme-may-not-ship).
-6
View File
@@ -1,6 +0,0 @@
// Omarchy's own XKB option. libxkbcommon reads /etc/xkb before the system
// rules, which supply everything else.
! include %S/evdev
! option = symbols
omarchy:shift_caps_compose = +omarchy(shift_caps_compose)
-11
View File
@@ -1,11 +0,0 @@
// Caps Lock stays Caps Lock; Shift + Caps Lock is the Compose key. The actions
// are explicit so the Shift level never inherits the key's Lock modifier,
// which would toggle Caps Lock on every compose.
partial modifier_keys
xkb_symbols "shift_caps_compose" {
key <CAPS> {
type[Group1] = "TWO_LEVEL",
symbols[Group1] = [ Caps_Lock, Multi_key ],
actions[Group1] = [ LockMods(modifiers = Lock), NoAction() ]
};
};
+5 -2
View File
@@ -15,6 +15,7 @@ bolt
brightnessctl
btop
chromium
clang
cliamp
cups
cups-filters
@@ -69,6 +70,7 @@ inotify-tools
inxi
networkmanager
jq
kdenlive
kernel-modules-hook
lazygit
less
@@ -76,6 +78,7 @@ libsecret
libvips
libyaml
libreoffice-fresh
llvm
localsend
lua51
luarocks
@@ -94,12 +97,12 @@ noto-fonts-cjk
noto-fonts-emoji
nss-mdns
nvim
obs-studio
obsidian
omacalc
omacut
omawrite
omarchy-nvim
omarchy-hyprland-titlebars
omasnap
# OWE owns desktop video backgrounds and feeds the lock screen its frames.
owe
@@ -126,7 +129,6 @@ quickshell
ripgrep
ruby
sddm
signed
slurp
socat
starship
@@ -159,6 +161,7 @@ woff2-font-awesome
xdg-desktop-portal-gtk
xdg-desktop-portal-hyprland
xdg-terminal-exec
xournalpp
yaru-icon-theme
yay
yt-dlp
+3 -5
View File
@@ -1,11 +1,9 @@
# A VM usually renders on the CPU, where animations and transparency cost
# every frame, so start it without them and with opaque windows.
# omarchy-toggle-animations and Appearance > Window Transparency bring them
# back. This runs before any Hyprland session exists, so place the flags
# directly rather than through the commands, which reload Hyprland.
# every frame, so start it without them. omarchy-toggle-animations brings them
# back. This runs before any Hyprland session exists, so place the flag
# directly rather than through omarchy-hyprland-toggle, which reloads Hyprland.
if omarchy-hw-vm; then
echo "Detected a virtual machine. Turning off animations and transparency."
mkdir -p "$HOME/.local/state/omarchy/toggles/hypr"
cp "$OMARCHY_PATH/default/hypr/toggles/no-animations.lua" "$HOME/.local/state/omarchy/toggles/hypr/"
cp "$OMARCHY_PATH/default/hypr/toggles/window-opaque.lua" "$HOME/.local/state/omarchy/toggles/hypr/"
fi
+1 -1
View File
@@ -1,4 +1,4 @@
# Set default XCompose that is triggered with Shift + CapsLock
# Set default XCompose that is triggered with CapsLock
tee ~/.XCompose >/dev/null <<EOF
# Run omarchy-restart-xcompose to apply changes
+1 -1
View File
@@ -1,6 +1,6 @@
# Welcome to Omarchy!
Omarchy is an [omakase](https://manuals.omamix.org/3/omacom/76/omakase-computing) Linux distribution based on [Arch](https://archlinux.org/), the tiling window manager [Hyprland](https://hypr.land/), and the desktop construction-kit [Quickshell](https://quickshell.org/). It ships with everything a modern, savvy computer user needs to be productive immediately. From [Neovim](https://neovim.io/) (btw) to Chromium, [Obsidian](https://obsidian.md/) to LibreOffice, and [Hype](https://github.com/omacom/hype) to [Monologue](https://github.com/omacom/monologue). Hell, even a retro Winamp-style music player is there!
Omarchy is an [omakase](https://manuals.omamix.org/3/omacom/76/omakase-computing) Linux distribution based on [Arch](https://archlinux.org/), the tiling window manager [Hyprland](https://hypr.land/), and the desktop construction-kit [Quickshell](https://quickshell.org/). It ships with everything a modern, savvy computer user needs to be productive immediately. From [Neovim](https://neovim.io/) (btw) to Chromium, [Obsidian](https://obsidian.md/) to LibreOffice, and Kdenlive to OBS Studio. Hell, even a retro Winamp-style music player is there!
This isn't just a grab bag of preinstalled packages, though. It's a complete system designed with both aesthetics and productivity in mind. Because a _beautiful_ system is a _motivating_ system, and productivity has always been [downstream from motivation](https://world.hey.com/dhh/beautiful-motivations-6fef7c73). There's zero bloat here: Just everything I use.
+1 -9
View File
@@ -34,11 +34,9 @@ Omarchy's default layout is called dwindle. It keeps all the windows you open on
But you can also choose to turn a workspace into the scrolling layout where windows are lined up side-by-side, beyond the visible edge of the display. You turn a single workspace into this layout via `Super + L`.
Pressing `Super + L` again turns the workspace floating, where windows overlap freely and you place them yourself, and a third press returns it to dwindle. `Super + Alt + L` goes through them the other way. A window that floats anyway — one you popped out with `Super + T`, or an app that always opens floating — keeps floating when the workspace goes back to tiling. The top bar marks the workspace you're on with its layout: a square for dwindle, a circle for scrolling, and a triangle for floating. Clicking that mark cycles the layout too.
![navigation-scrolling-layout](images/navigation-scrolling-layout.webp)
The choice is per workspace, and it sticks. So you can keep workspace 1 on dwindle for browsing and workspace 2 on scrolling for code, and they'll come back that way after a restart.
The choice is per workspace, and it sticks. So you can keep workspace 1 on dwindle for browsing and workspace 2 on scrolling for code, and they'll come back that way after a restart. (The same toggle is under _Trigger > Toggle > Workspace Layout_ in the Omarchy menu).
If you wish to use the scrolling layout as the default, you can set that in `~/.config/hypr/looknfeel.lua`:
@@ -50,12 +48,6 @@ hl.config({
})
```
### Floating workspaces
A floating workspace works like a traditional desktop: windows overlap, and you move and size them yourself. Each window gets a titlebar in your theme's colors, with buttons to maximize and close. Click a window to bring it forward. Resize it from any edge or corner. Double-click a titlebar to maximize the window while keeping the top bar in view, and again to get its old size back.
New workspaces, and any you haven't given a layout of their own with `Super + L`, use the default layout. Pick it under _Trigger > Appearance > Workspace Layout_: Tiling, Scrolling, or Floating. `omarchy-hyprland-workspace-layout-default scrolling` sets it from a terminal.
### Grouping windows
Windows can be grouped using `Super + G`. Once you're in a group, every window you start while that's active will belong to the group. You can move between these grouped windows using `Super + Ctrl + Arrow Left/Right` or `Super + Alt + 1/2/3/4` to go directly to grouped window in order.
+5 -5
View File
@@ -81,15 +81,15 @@ If you'd rather they were always visible, set `alwaysShow` to `true` on the widg
The bar configures itself. You don't have to open a config file to move things.
Grab an empty patch of the bar around the center and drag it toward another screen edge, and the bar moves there — left, right, top, or bottom all work, and every widget adapts (vertical bars fall back to compact icon-only forms). A click-and-hold starts the same drag. Double-left-click that same empty space to cycle the bar background from clear to glassy to opaque. And drag any widget to reorder it or throw it into another section.
Grab an empty patch of the bar around the center and drag it toward another screen edge, and the bar moves there — left, right, top, or bottom all work, and every widget adapts (vertical bars fall back to compact icon-only forms). A click-and-hold starts the same drag. Double-left-click that same empty space to toggle transparency. And drag any widget to reorder it or throw it into another section.
If you'd rather pick from a menu, **Trigger → Appearance → Menu Bar** has the position, and **Bar Background** picks how the bar is drawn: **Opaque Bar**, **Glassy Bar** (see-through, with what's behind it blurred), or **Clear Bar**.
If you'd rather pick from a menu, **Style → Menu Bar** has both position and transparency.
The same things have commands, which is what you want for a [dotfiles](31-dotfiles.md) setup:
```bash
omarchy bar position bottom
omarchy bar background glassy # opaque, glassy, none, or cycle
omarchy bar transparent toggle
omarchy bar move omarchy.clock --section center --index 0
omarchy bar set omarchy.clock format "HH:mm"
omarchy bar defaults # back to the shipped layout
@@ -104,7 +104,7 @@ omarchy plugin disable omarchy.weather
## Hiding the bar
`Super + Shift + Space` toggles the bar off and back on without killing the shell — panels and hotkeys keep working, you just get the pixels back. It's also in the menu under **Trigger → Appearance → Menu Bar → Hide Bar**.
`Super + Shift + Space` toggles the bar off and back on without killing the shell — panels and hotkeys keep working, you just get the pixels back. It's also in the menu under **Trigger → Toggle → Menu Bar**.
## The config file
@@ -115,7 +115,7 @@ All of it is stored in `~/.config/omarchy/shell.json`, under the `bar` key. Here
"version": 1,
"bar": {
"position": "top",
"background": "opaque",
"transparent": false,
"centerAnchor": "omarchy.clock",
"layout": {
"left": [{ "id": "omarchy.menu" }, { "id": "omarchy.workspaces" }],
+27 -28
View File
@@ -15,8 +15,7 @@ You can see all the main keyboard bindings with `Super + K` (Tmux bindings with
| `Super + T` | Toggle window between tiling/floating |
| `Super + J` | Toggle window position (horizontal/vertical) |
| `Super + O` | Toggle popping window into sticky'n'floating |
| `Super + L` | Cycle through the dwindle, scrolling, and floating layouts |
| `Super + Alt + L` | Cycle through the layouts in reverse |
| `Super + L` | Toggle between dwindle and scrolling layout |
| `Super + P` | Toggle pseudo window style (natural v stretch) |
| `Super + F` | Go full screen |
| `Super + Alt + F` | Go full width |
@@ -340,36 +339,36 @@ You can use `Super + Ctrl + E` to show a complete emoji picker that'll put the s
| Hotkey | EM | Clue |
| ------------ | -- | ---------- |
| `Shift + CapsLock M S` | 😄 | smile |
| `Shift + CapsLock M C` | 😂 | cry |
| `Shift + CapsLock M L` | 😍 | love |
| `Shift + CapsLock M V` | ✌️ | victory |
| `Shift + CapsLock M H` | ❤️ | heart |
| `Shift + CapsLock M Y` | 👍 | yes |
| `Shift + CapsLock M N` | 👎 | no |
| `Shift + CapsLock M F` | 🖕 | fuck |
| `Shift + CapsLock M W` | 🤞 | wish |
| `Shift + CapsLock M R` | 🤘 | rock |
| `Shift + CapsLock M K` | 😘 | kiss |
| `Shift + CapsLock M E` | 🙄 | eyeroll |
| `Shift + CapsLock M I` | 😉 | wink |
| `Shift + CapsLock M P` | 🙏 | pray |
| `Shift + CapsLock M D` | 🤤 | drool |
| `Shift + CapsLock M M` | 💰 | money |
| `Shift + CapsLock M X` | 🎉 | xellebrate |
| `Shift + CapsLock M 1` | 💯 | 100% |
| `Shift + CapsLock M T` | 🥂 | toast |
| `Shift + CapsLock M O` |👌 | ok |
| `Shift + CapsLock M G` |👋 | greeting |
| `Shift + CapsLock M A` |💪 | arm |
| `Shift + CapsLock M B` |🤯 | blowing |
| `CapsLock M S` | 😄 | smile |
| `CapsLock M C` | 😂 | cry |
| `CapsLock M L` | 😍 | love |
| `CapsLock M V` | ✌️ | victory |
| `CapsLock M H` | ❤️ | heart |
| `CapsLock M Y` | 👍 | yes |
| `CapsLock M N` | 👎 | no |
| `CapsLock M F` | 🖕 | fuck |
| `CapsLock M W` | 🤞 | wish |
| `CapsLock M R` | 🤘 | rock |
| `CapsLock M K` | 😘 | kiss |
| `CapsLock M E` | 🙄 | eyeroll |
| `CapsLock M I` | 😉 | wink |
| `CapsLock M P` | 🙏 | pray |
| `CapsLock M D` | 🤤 | drool |
| `CapsLock M M` | 💰 | money |
| `CapsLock M X` | 🎉 | xellebrate |
| `CapsLock M 1` | 💯 | 100% |
| `CapsLock M T` | 🥂 | toast |
| `CapsLock M O` |👌 | ok |
| `CapsLock M G` |👋 | greeting |
| `CapsLock M A` |💪 | arm |
| `CapsLock M B` |🤯 | blowing |
## Quick Completions
| Hotkey | Completion |
| ------------ | ---------- |
| `Shift + CapsLock Space Space` | — (mdash) |
| `Shift + CapsLock Space N` | Your name (as entered on setup) |
| `Shift + CapsLock Space E` | Your email (as entered on setup) |
| `CapsLock Space Space` | — (mdash) |
| `CapsLock Space N` | Your name (as entered on setup) |
| `CapsLock Space E` | Your email (as entered on setup) |
You can add more of your own by editing `~/.XCompose`, then running `omarchy-restart-xcompose` in the terminal to get the changes picked up.
+2 -2
View File
@@ -4,7 +4,7 @@ A lot of what you change day to day isn't really a setting. It's a mode you flip
### The toggle menu
`Super + Ctrl + O` opens _Trigger > Toggle_ directly, or you can walk there from the Omarchy menu (`Super + Space`). Everything in that list is a switch you can flip without thinking about where the state lives. Switches for how the desktop looks sit next to it, under _Trigger > Appearance_.
`Super + Ctrl + O` opens _Trigger > Toggle_ directly, or you can walk there from the Omarchy menu (`Super + Space`). Everything in that list is a switch you can flip without thinking about where the state lives.
From the terminal, the same switches are `omarchy toggle <thing>`. Run `omarchy toggle` on its own to see the whole group.
@@ -24,7 +24,7 @@ From the terminal, the same switches are `omarchy toggle <thing>`. Run `omarchy
The touchpad, touchscreen, and hybrid GPU switches live under _Trigger > Hardware_ (`Super + Ctrl + H`) rather than under Toggle, since they only show up when you actually have that hardware. The touchpad and touchscreen ones survive a Hyprland reload — the disabled device's name is saved to a small state file that Hyprland reads on startup to disable it again.
_Trigger > Appearance_ holds the menu bar and animations, plus a few things that aren't `omarchy toggle` commands but behave the same: battery percentage in the bar, the default workspace layout, window gaps (`Super + Shift + Backspace`), and the 1-window square aspect (`Super + Ctrl + Backspace`).
The Toggle menu also carries a few things that aren't `omarchy toggle` commands but behave the same: battery percentage in the bar, workspace layout (`Super + L`), window gaps (`Super + Shift + Backspace`), and the 1-window square aspect (`Super + Ctrl + Backspace`).
Most of these are just a flag file under `~/.local/state/omarchy/toggles/`. If you want to branch on one in a script, `omarchy-toggle-enabled` gives you an exit code instead of making you go looking:
+12 -6
View File
@@ -69,12 +69,6 @@ You start LibreOffice via the application launcher (`Super + Space`).
You start Hype via the application launcher (`Super + Space`). Press `?` inside it to see every shortcut.
## Signed
[Signed](https://github.com/omacom/signed) is Omarchy's own dead-simple way to sign PDFs and fill them in. Put your saved signature, text, and today's date anywhere on a page, then save a signed copy beside the original. See [Filling out PDFs](27-filling-out-pdfs.md) for how.
You start Signed via the application launcher (`Super + Space`), or open a PDF with it from the file manager.
## Omacalc
[Omacalc](https://github.com/omacom-io/omacalc) is Omarchy's own dead-simple calculator, which opens in a floating window.
@@ -93,6 +87,18 @@ You start Signal with `Super + Shift + G`. It's not part of the base install, so
You start mpv via the application launcher (`Super + Space`) or just double-click on a video in the file manager.
## OBS Studio
[OBS Studio](https://obsproject.com/) lets you record or stream video from multiple inputs. You can mix a screencast with a webcam with a microphone input. It's what was used to record the Omarchy screencasts.
You start OBS Studio via the application launcher (`Super + Space`).
## Kdenlive
[Kdenlive](https://kdenlive.org/) is an excellent video editor. Perfect for working on video that comes out of OBS Studio before sharing it.
You start Kdenlive via the application launcher (`Super + Space`).
## Omacut
[Omacut](https://github.com/omacom-io/omacut) is Omarchy's own dead-simple video trimmer. When all you need is to cut the start and end off a clip, it beats firing up a full video editor.
+3 -5
View File
@@ -2,10 +2,8 @@
Omarchy ships with a nice, basic PDF viewer called Document Viewer. This is the program that'll open any PDFs you just double click on.
But you can only use Document Viewer to fill out PDFs that have been setup as forms. If you need to fill out PDFs that haven't, or you need to sign a PDF, use [Signed](https://github.com/omacom/signed). Start it via the application launcher (`Super + Space`), or right-click the file, select _Open With..._, and pick Signed.
But you can only use Document Viewer to fill out PDFs that have been setup as forms. If you need to fill out PDFs that haven't, or you need to sign a PDF, you'll have to right-click the file, select _Open With..._, and pick Xournal++.
Press `T` to type text and `D` to put down today's date. Both land where your mouse pointer is. Drag anything to move it, drag its corner to make it bigger or smaller, and double-click text to change it.
Xournal++ will let you write anywhere on a PDF using the T tool. If you need to sign a document, you'll need an image of your signature, and you can use the Image tool to insert this signature and resize it.
To sign, you'll need a picture of your signature: a scan, or a well-lit photo of it on white paper. Open the _Signature_ menu (`Shift + S`), choose _Add signature..._, pick the picture, and give it a name. Signed removes the paper and keeps the ink. From then on, `S` puts your signature under the mouse pointer.
When you're done, use _Save signed copy..._ (`Ctrl + S`) to save the filled-out PDF beside the original, which is never changed. Press `?` inside Signed to see every shortcut.
When you're done filling out the PDF, use _File > Export as PDF_ to save the final version.
+1 -1
View File
@@ -101,4 +101,4 @@ For plugin development, see the [shell reference](https://github.com/omacom/omar
Once you've made something you like, put it in a public git repo. That's the whole distribution mechanism — anyone can then run `omarchy plugin add` against your URL and have it running in seconds.
To help people actually find it, list it at [plugins.omarchy.org](https://plugins.omarchy.org). That's the marketplace for Omarchy shell plugins, and it's the first place to look when you're wondering whether someone has already built the widget you're about to write. Browse it before you start!
To help people actually find it, list it at [omarchyplugins.com](https://omarchyplugins.com). That's the community directory of Omarchy shell plugins, and it's the first place to look when you're wondering whether someone has already built the widget you're about to write. Browse it before you start!
+5 -5
View File
@@ -9,7 +9,7 @@ hl.config({
input = {
-- Use multiple keyboard layouts and switch between them with Left Alt + Right Alt
kb_layout = "us,dk",
kb_options = "omarchy:shift_caps_compose,grp:alts_toggle",
kb_options = "compose:caps,shift:both_capslock_cancel,grp:alts_toggle",
-- Change speed of keyboard repeat
repeat_rate = 40,
@@ -37,7 +37,7 @@ o.window("(Alacritty|kitty|foot)", { scroll_touchpad = 1.5 })
You can [see all the input options](https://wiki.hypr.land/Configuring/Basics/Variables/#input) on the Hyprland wiki for inputs.
CapsLock works as a normal Caps Lock, and `Shift + CapsLock` is the compose key for [quick emojis](07-hotkeys.md#quick-emojis) and [other completions](07-hotkeys.md#quick-completions). If you'd rather have the compose key elsewhere, replace `omarchy:shift_caps_compose` in `kb_options`. For example, this moves the compose key to Right Alt:
By default, Omarchy uses CapsLock as the compose key for [quick emojis](07-hotkeys.md#quick-emojis) and [other completions](07-hotkeys.md#quick-completions). If you'd rather use CapsLock as Caps Lock, move the compose key elsewhere by changing `compose:caps` in `kb_options`. For example, this moves the compose key to Right Alt:
```lua
hl.config({
@@ -61,7 +61,7 @@ On Dell XPS laptops with a haptic touchpad, you can also set the click strength
Select Japanese, Korean, Chinese (Simplified, Pinyin), or Chinese (Traditional, Zhuyin) when the installer asks for your keyboard. Omarchy configures the keyboard and matching input engine automatically, including during offline first-boot setup. Japanese uses Mozc, Korean uses standard two-set Hangul, Simplified Chinese uses Pinyin, and Traditional Chinese uses Chewing's standard Zhuyin arrangement. Japanese (US keyboard) provides Mozc with a US keyboard layout.
Typing starts in Latin mode. Press `Super + I` to cycle through configured input methods, and `Super + Shift + I` to cycle back. Tap `Shift` on its own to step out of an input method like Pinyin into Latin letters, and tap it again to go back, the way Sogou, Microsoft Pinyin, and macOS work; `Shift` plus a letter still types a capital. On Japanese keyboards, 全角/半角 toggles input, 変換 turns it on, and 無変換 turns it off. On a Korean keyboard layout, dedicated 한/영 and 한자 keys switch Hangul and convert to Hanja. `Ctrl + Space` stays available for Tmux and Herdr; `Shift + CapsLock` stays the compose key.
Typing starts in Latin mode. Press `Super + I` to cycle through configured input methods, and `Super + Shift + I` to cycle back. On Japanese keyboards, 全角/半角 toggles input, 変換 turns it on, and 無変換 turns it off. On a Korean keyboard layout, dedicated 한/영 and 한자 keys switch Hangul and convert to Hanja. `Ctrl + Space` stays available for Tmux and Herdr; CapsLock stays the compose key.
The keyboard indicator appears on the right of the bar when you have multiple keyboard layouts or input methods. Click it to switch input languages; Japanese displays あ for hiragana. For Japanese character choices, switch to Japanese, type a word such as `nihongo`, then press Space twice. Press Enter to accept your choice. If you also have multiple keyboard layouts, right-click the indicator to cycle those.
@@ -69,7 +69,7 @@ Use _Setup > Typing > Keyboard Layouts_ to select layouts such as English and Fr
Run `omarchy setup keyboard` or `omarchy setup input` to open the same pickers. To append one language from the command line, use `omarchy setup input mozc`, `hangul`, `pinyin`, or `chewing`. Your custom switching keys are preserved. For alternative engine layouts or keys, install `fcitx5-configtool` with `omarchy pkg add fcitx5-configtool` and launch it from a terminal. Selecting an input language does not change the desktop language.
Use `Super + I` to cycle through input methods or keyboard layouts, and `Super + Shift + I` to go back the other way, so with three configured you can move between two neighbours. The input indicator appears on the right of the bar only when more than one input method or layout is configured. This desktop binding works alongside your existing Fcitx switching keys; you can override it in `~/.config/hypr/bindings.lua`. Input engines and the compose sequences use the [Fcitx5](https://fcitx-im.org/) service already running in every session.
Use `Super + I` to cycle through input methods or keyboard layouts, and `Super + Shift + I` to go back the other way, so with three configured you can move between two neighbours. The input indicator appears on the right of the bar only when more than one input method or layout is configured. This desktop binding works alongside your existing Fcitx switching keys; you can override it in `~/.config/hypr/bindings.lua`. Tapping Shift alone is not an input switch: Omarchy uses both Shift keys for CapsLock, which changes how input methods see Shift release. Input engines and the CapsLock compose sequences use the [Fcitx5](https://fcitx-im.org/) service already running in every session.
### Use ALT as SUPER
@@ -78,7 +78,7 @@ On some keyboards, it's not convenient to use the primary meta key (Windows/cmd
```lua
hl.config({
input = {
kb_options = "omarchy:shift_caps_compose,altwin:swap_alt_win",
kb_options = "compose:caps,shift:both_capslock_cancel,altwin:swap_alt_win",
},
})
```
+1 -1
View File
@@ -8,7 +8,7 @@ Backgrounds can be videos as well as stills. Drop an `mp4`, `m4v`, `mov`, `webm`
Themes can include a short intro for a still background. For `backgrounds/road.webp`, the matching video lives at `backgrounds/intros/road.mp4`. It plays automatically at boot and when switching to a theme with that background selected, then returns to the still. Intro videos stay out of the background picker and never loop.
Turning off _Trigger > Appearance > Animations_ also turns off background intros. Omarchy does this by default in a virtual machine. The `omarchy theme bg intro toggle` command can toggle intros separately. Refreshing a theme or choosing another background within the same theme does not play an intro.
Turning off _Toggle > Animations_ also turns off background intros. Omarchy does this by default in a virtual machine. The `omarchy theme bg intro toggle` command can toggle intros separately. Refreshing a theme or choosing another background within the same theme does not play an intro.
You can find a huge collection of cool curated backgrounds on https://github.com/dharmx/walls.
-1
View File
@@ -65,7 +65,6 @@ The Apple T2 Security Chip was introduced in 2017. The T2 chip was discontinued
- MacBook Pro 13-inch (2019, two Thunderbolt 3 ports) – Model: A2159
- MacBook Pro 13-inch (2019, four Thunderbolt 3 ports) – Model: A2178
- MacBook Pro 15-inch (2019) – Model: A1990
- iMac (Retina 5K, 27-inch, 2020) – Model: A2115 (iMac20,1 and iMac20,2)
- MacBook Pro 13-inch (2020, two Thunderbolt 3 ports) – Model: A2265
- MacBook Pro 15-inch (2020) – Model: A1990
+12
View File
@@ -10,6 +10,18 @@ Omarchy assumes a 2x high-resolution display, which requires setting `GDK_SCALE`
For Spotify, you can use `Ctrl + Minus` to shrink the UI (and `Ctrl + Plus` to make it bigger).
### Why isn't Caps Lock working?
In Omarchy, Caps Lock has been designated to be the xcompose key. That's how you get [quick emojis](07-hotkeys.md#quick-emojis) and [other autocompletions](07-hotkeys.md#quick-completions) done. If you really miss using Caps Lock, you can remap the xcompose key to something else by editing `~/.config/hypr/input.lua`, like setting it to the right alt key:
```
hl.config({
input = {
kb_options = "compose:ralt",
},
})
```
### My Wi-Fi, Bluetooth, audio, or trackpad just stopped working
Before you reboot, try restarting the offending subsystem on its own. _Update > Hardware_ in the Omarchy menu has Wi-Fi, Bluetooth, Audio, and Trackpad, and reloading one of those clears up the majority of "it worked five minutes ago" situations — a Bluetooth headset that won't reconnect, a trackpad that went dead after a suspend, sound that vanished when you unplugged a monitor.
+1 -1
View File
@@ -9,7 +9,7 @@ hl.config({
input = {
-- Use multiple keyboard layouts and switch between them with Left Alt + Right Alt
kb_layout = "us,fr",
kb_options = "omarchy:shift_caps_compose,grp:alts_toggle",
kb_options = "compose:caps,shift:both_capslock_cancel,grp:alts_toggle",
},
})
```
-31
View File
@@ -1,31 +0,0 @@
echo "Drop saved workspace layouts belonging to named workspaces"
# omarchy-hyprland-workspace-layout-toggle acted on whatever id activeworkspace
# reported, and a named workspace reports a negative one. Hyprland reads a
# leading "-" as a selector relative to the current workspace, so a layout saved
# for one is applied to workspace 1 at every login instead. The command no longer
# writes these; this clears the ones already on disk. It wrote to ~/.local/state
# whatever XDG_STATE_HOME said, so both are cleared.
removed=0
for state_home in "$HOME/.local/state" "${XDG_STATE_HOME:-}"; do
[[ -n $state_home ]] || continue
layouts_dir="$state_home/omarchy/workspace-layouts"
[[ -d $layouts_dir ]] || continue
for saved in "$layouts_dir"/-*.lua; do
if [[ -f $saved ]]; then
rm -f -- "$saved"
removed=1
fi
done
done
# The package hook reloads Hyprland before migrations run, so the rule from the
# file just deleted is already applied to workspace 1. Hyprland watches the files
# it required and picks the deletion up on its own, but not for a user who has
# turned autoreload off, so ask for one.
if (( removed )); then
hyprctl reload >/dev/null 2>&1 || true
fi
-3
View File
@@ -1,3 +0,0 @@
echo "Install titlebars for floating workspaces"
omarchy-pkg-add omarchy-hyprland-titlebars
-10
View File
@@ -1,10 +0,0 @@
echo "Let a lone Shift switch out of an input method and back"
# An earlier migration wrote this list empty. Fill only that empty list: a
# missing one already falls back to Fcitx's Shift_L, and a filled one is the
# user's choice.
config="${XDG_CONFIG_HOME:-$HOME/.config}/fcitx5/config"
if [[ -f $config ]] && awk '/^\[Hotkey\/AltTriggerKeys\]$/ { in_list = 1; found = 1; next } /^\[/ { in_list = 0 } in_list && /=/ { filled = 1 } END { exit !(found && !filled) }' "$config"; then
sed -i --follow-symlinks '/^\[Hotkey\/AltTriggerKeys\]$/a 0=Shift_L' "$config"
busctl --user call org.fcitx.Fcitx5 /controller org.fcitx.Fcitx.Controller1 ReloadConfig 2>/dev/null || true
fi
-27
View File
@@ -1,27 +0,0 @@
echo "Name web app windows in their launchers, so titlebars show the app's name"
# Chromium names a web app window after its URL, and the launcher's
# StartupWMClass is how titlebars find the launcher's Name for it. Launchers
# that already name a class, and those with custom commands, are left alone.
for desktop in "${XDG_DATA_HOME:-$HOME/.local/share}"/applications/*.desktop; do
[[ -f $desktop ]] || continue
grep -q '^StartupWMClass=' "$desktop" && continue
exec_line=$(grep -m1 '^Exec=omarchy-launch-webapp ' "$desktop") || continue
# The installer escaped the URL twice: as one Exec argument, then as desktop
# file text. Undo the file layer, then the argument quoting.
url=${exec_line#Exec=omarchy-launch-webapp }
url=${url//\\\\/\\}
if [[ $url == \"*\" ]]; then
url=$(sed 's/\\\(.\)/\1/g' <<<"${url:1:${#url}-2}")
fi
url=${url//%%/%}
[[ $url =~ ^https?:// && $url != *[[:space:]]* ]] || continue
class=$(omarchy-webapp-window-class "$url")
target=$(realpath "$desktop")
CLASS=${class//\\/\\\\} awk '!done && /^Exec=/ { print; print "StartupWMClass=" ENVIRON["CLASS"]; done = 1; next } { print }' "$target" >"$target.omarchy-migrate" &&
cat "$target.omarchy-migrate" >"$target"
rm -f "$target.omarchy-migrate"
done
-14
View File
@@ -1,14 +0,0 @@
echo "Save workspace layouts as modes, so a default layout keeps them"
# Workspace layouts were saved as plain Hyprland rules. Floating and the default
# layout read the mode a workspace was given, and a rule names none, so a
# workspace set to scrolling would follow a floating default instead. The old
# toggle wrote to ~/.local/state whatever XDG_STATE_HOME said, so both are read.
for state_home in "$HOME/.local/state" "${XDG_STATE_HOME:-}"; do
[[ -n $state_home ]] || continue
for saved in "$state_home"/omarchy/workspace-layouts/*.lua; do
[[ -f $saved ]] || continue
sed -i --follow-symlinks 's/^hl\.workspace_rule({ workspace = "\([0-9]*\)", layout = "\([a-z]*\)" })$/o.workspace_mode({ workspace = "\1", mode = "\2" })/' "$saved"
done
done
-20
View File
@@ -1,20 +0,0 @@
echo "Keep windows opaque where animations are turned off"
# Turning animations off used to make windows opaque too. It no longer touches
# transparency, so drop that rule from the saved flag, where it would keep
# windows opaque whatever Appearance > Window Transparency says, and carry it
# over as Window Transparency > Opaque for anyone who has not picked one yet.
# omarchy-hyprland-toggle saved flags under ~/.local/state whatever
# XDG_STATE_HOME said, while Hyprland reads them from XDG_STATE_HOME, so both
# are fixed.
for state_home in "$HOME/.local/state" "${XDG_STATE_HOME:-}"; do
[[ -n $state_home ]] || continue
toggles_dir="$state_home/omarchy/toggles/hypr"
[[ -f $toggles_dir/no-animations.lua ]] || continue
sed -i '/^o\.window("\.\*", { opacity = "1 1" })$/d' "$toggles_dir/no-animations.lua"
if [[ ! -f $toggles_dir/window-opaque.lua && ! -f $toggles_dir/window-glassy.lua ]]; then
cp "$OMARCHY_PATH/default/hypr/toggles/window-opaque.lua" "$toggles_dir/"
fi
done
-5
View File
@@ -1,5 +0,0 @@
echo "Install Signed, the PDF signing app"
if [[ ! -f $HOME/.local/state/omarchy/preinstalls-removed ]]; then
omarchy-pkg-add signed
fi
-21
View File
@@ -1,21 +0,0 @@
echo "Let herdr's tab bar show the terminal's background, so it turns glassy too"
config="$HOME/.config/herdr/config.toml"
# Dotfile managers may own the file or a parent directory through a symlink.
# Preserve that layout rather than detaching or editing its target.
path="$config"
while [[ $path != "$HOME" && $path != / ]]; do
if [[ -L $path ]]; then
echo "Preserving symlink-managed herdr config: $config"
exit 0
fi
path=$(dirname "$path")
done
# Only a tab bar still on the shipped black changes; any other choice is kept,
# and a surface_dim of the user's own is never given a second one.
if [[ -f $config ]] && grep -qx 'panel_bg = "black"' "$config" && ! grep -q '^[[:space:]]*surface_dim[[:space:]]*=' "$config"; then
sed -i 's/^panel_bg = "black"$/panel_bg = "reset"\nsurface_dim = "black"/' "$config"
omarchy-restart-herdr || true
fi
-55
View File
@@ -1,55 +0,0 @@
echo "Let Neovim's current line and status line show the terminal background"
# ~/.config/nvim is copied once from omarchy-nvim, so it misses the package's
# later changes. Bring in the transparency file and lualine spec it now ships.
config="$HOME/.config/nvim"
package="/usr/share/omarchy-nvim/config"
files=(plugin/after/transparency.lua lua/plugins/lualine-transparent.lua)
[[ -d $config ]] || exit 0
# Dotfile managers may own a file or a parent directory through a symlink.
# Preserve that layout rather than detaching or editing its target.
for file in "${files[@]}"; do
path="$config/$file"
while [[ $path != "$HOME" && $path != / ]]; do
if [[ -L $path ]]; then
echo "Preserving symlink-managed Neovim config: $path"
exit 0
fi
path=$(dirname "$path")
done
done
# Updates install packages before migrations. An older package leaves this
# pending rather than copying files it does not have yet.
if [[ ! -f $package/${files[1]} ]]; then
echo "Update omarchy-nvim to 2026.9.21-5 or newer before rerunning this migration." >&2
exit 1
fi
# A copy staged beside its target and moved into place whole, so a failed
# write leaves the original for a retry.
staged=""
trap 'rm -f -- ${staged:+"$staged"}' EXIT
install_from_package() {
staged=$(mktemp "$config/$1.new.XXXXXX")
install -m 0644 "$package/$1" "$staged"
mv -fT -- "$staged" "$config/$1"
}
# Only a transparency file still as Omarchy shipped it is replaced, so one the
# user changed is kept.
if [[ -f $config/${files[0]} ]]; then
case $(sha256sum <"$config/${files[0]}") in
bd833dc094e3546b5fad512e61cfb09f3032c2a4461c23be7bd90851de80688e* | \
6e31b917f13e16514ac94ff9270d47f24e76509a3e559ca85116a5aa2d61306a* | \
33d29360df84ed48c70606580749536520384ec64afe650151d4e26e7922b410*)
install_from_package "${files[0]}"
;;
esac
fi
if [[ -d $config/lua/plugins && ! -e $config/${files[1]} ]]; then
install_from_package "${files[1]}"
fi
+1 -4
View File
@@ -353,7 +353,6 @@ QtObject {
readonly property int iconSlot: root.barToken("icon-slot", 27)
readonly property int iconCanvas: root.barToken("icon-canvas", 16)
readonly property int iconFont: root.barToken("icon-font", 13)
readonly property int iconSize: Math.min(iconCanvas, root.barToken("icon-size", 12))
readonly property int statusSlot: root.barToken("status-slot", 21)
}
@@ -425,9 +424,7 @@ QtObject {
} else if (section === "bar") {
if (key === "scale-with-font") {
nextBarScaleWithFont = boolToken(raw, nextBarScaleWithFont)
} else if (key === "size-horizontal" || key === "size-vertical" || key === "notch-height"
|| key === "icon-slot" || key === "icon-canvas" || key === "icon-font"
|| key === "icon-size" || key === "status-slot") {
} else if (key === "size-horizontal" || key === "size-vertical" || key === "notch-height") {
var b = parseInt(raw, 10)
if (isFinite(b)) barOut[key] = b
}
-3
View File
@@ -18,8 +18,6 @@ Item {
property size decodeSize: Qt.size(0, 0)
readonly property var current: imageLoader.item
readonly property bool ready: current ? current.ready : false
// The file behind path could not be loaded, as when it was deleted.
readonly property bool failed: current ? current.failed : false
readonly property bool video: Util.isVideoPath(path)
// Cache-bust images selected in a running lock session, so a theme switch
// that replaces the file behind an unchanged path shows the new pixels.
@@ -37,7 +35,6 @@ Item {
Image {
readonly property bool ready: status === Image.Ready
readonly property bool failed: status === Image.Error
source: !root.constrainDecode || root.decodeSize.width > 0 ? root.imageUrl : ""
fillMode: Image.PreserveAspectCrop
asynchronous: true
+28 -67
View File
@@ -8,91 +8,52 @@ WidgetButton {
property Component iconComponent: null
property real slotSize: Style.bar.iconSlot
property real opticalSize: Style.bar.iconCanvas
property real iconSize: Math.min(Style.bar.iconSize, opticalSize)
readonly property real visualIconSize: Style.bar.iconSize
readonly property real canvasSize: Math.max(visualIconSize, opticalSize)
property string labelText: ""
property bool debugOpticalBounds: Quickshell.env("OMARCHY_DEBUG_BAR_ICONS") === "1"
// A label shifts the icon within the centered group.
readonly property real opticalCenterErrorX:
content.x + opticalCanvas.x + (glyph.visible ? glyph.paintedCenterX : customIcon.width / 2 + customIcon.centerErrorX) - root.width / 2
- (label.visible ? (label.implicitWidth + Style.space(4)) / 2 : 0)
readonly property real opticalCenterErrorY:
content.y + (glyph.visible ? glyph.paintedCenterY : customIcon.height / 2 + customIcon.centerErrorY) - root.height / 2
readonly property real glyphPaintedWidth: glyph.visible ? glyph.paintedWidth : 0
readonly property real glyphPaintedHeight: glyph.visible ? glyph.paintedHeight : 0
// Measured against the slot, which the open-panel underline centers on.
readonly property real opticalCenterErrorX: glyph.visible ? opticalCanvas.x + glyph.paintedCenterX - root.width / 2 : 0
readonly property real glyphPaintedWidth: glyph.visible ? glyph.tightWidth : 0
readonly property real glyphBaselineY: glyph.visible ? glyph.baselineY : 0
readonly property int glyphFontSize: glyph.visible ? glyph.renderedFontSize : 0
readonly property real componentPaintedWidth: customIcon.visible ? customIcon.paintedWidth : 0
readonly property real componentPaintedHeight: customIcon.visible ? customIcon.paintedHeight : 0
readonly property rect componentSourceBounds: customIcon.sourceBounds
labelVisible: false
hasVisualContent: text !== "" || iconComponent !== null || labelText !== ""
hasVisualContent: text !== "" || iconComponent !== null
fontSize: Style.bar.iconFont
fixedWidth: vertical ? -1 : slotSize
fixedHeight: vertical ? slotSize : -1
Item {
id: content
// Exact coordinates preserve the center of odd-width slots.
id: opticalCanvas
// Placed exactly, not anchored: centerIn snaps an even canvas in an odd slot
// to a whole pixel, pulling every glyph off the open-panel underline.
x: (root.width - width) / 2
y: (root.height - height) / 2
width: root.canvasSize + (label.visible ? label.implicitWidth + Style.space(4) : 0)
height: root.canvasSize
width: root.opticalSize
height: root.opticalSize
Text {
id: label
visible: root.labelText !== ""
textFormat: Text.PlainText
text: root.labelText
font.family: root.fontFamily
font.pixelSize: root.fontSize
OpticalGlyph {
id: glyph
anchors.fill: parent
visible: root.iconComponent === null
text: root.text
fontFamily: root.fontFamily
fontSize: root.fontSize
color: root.active && root.useActiveColor ? root.activeColor : root.foreground
y: (parent.height - height) / 2
+ (labelMetrics.descent - labelMetrics.ascent + labelMetrics.tightBoundingRect("0").height) / 2
renderType: Text.NativeRendering
rotation: root.textRotation
debugBounds: root.debugOpticalBounds
}
FontMetrics {
id: labelMetrics
font: label.font
Loader {
anchors.fill: parent
visible: root.iconComponent !== null
sourceComponent: root.iconComponent
}
Item {
id: opticalCanvas
x: label.visible ? label.implicitWidth + Style.space(4) : 0
width: root.canvasSize
height: root.canvasSize
OpticalGlyph {
id: glyph
anchors.fill: parent
visible: root.iconComponent === null
text: root.text
fontFamily: root.fontFamily
fontSize: root.fontSize
paintedSize: root.visualIconSize
color: root.active && root.useActiveColor ? root.activeColor : root.foreground
rotation: root.textRotation
debugBounds: root.debugOpticalBounds
}
BarIconContent {
id: customIcon
anchors.fill: parent
visible: root.iconComponent !== null
sourceComponent: root.iconComponent
sourceSize: root.opticalSize
}
Rectangle {
visible: root.debugOpticalBounds && root.iconComponent !== null
anchors.fill: parent
color: "transparent"
border.width: 1
border.color: "#4488ff"
}
Rectangle {
visible: root.debugOpticalBounds && root.iconComponent !== null
anchors.fill: parent
color: "transparent"
border.width: 1
border.color: "#4488ff"
}
}
-38
View File
@@ -1,38 +0,0 @@
import QtQuick
import qs.Commons
Item {
id: root
property Component sourceComponent: null
property real sourceSize: Style.bar.iconCanvas
readonly property rect sourceBounds: bounds.mappedBounds
readonly property real contentScale: sourceBounds.width > 0 && sourceBounds.height > 0
? Style.bar.iconSize / Math.max(sourceBounds.width, sourceBounds.height) : 1
readonly property real paintedWidth: sourceBounds.width * contentScale
readonly property real paintedHeight: sourceBounds.height * contentScale
readonly property real centerErrorX: content.x + (sourceBounds.x + sourceBounds.width / 2) * contentScale - width / 2
readonly property real centerErrorY: content.y + (sourceBounds.y + sourceBounds.height / 2) * contentScale - height / 2
Loader {
id: content
sourceComponent: root.sourceComponent
onLoaded: {
// Keep declared dimensions. Empty wrapper items receive a source canvas.
if (!(item instanceof Image)) {
if (item.width <= 0) item.width = Qt.binding(function() { return root.sourceSize })
if (item.height <= 0) item.height = Qt.binding(function() { return root.sourceSize })
}
}
transformOrigin: Item.TopLeft
scale: root.contentScale
x: root.width / 2 - (root.sourceBounds.x + root.sourceBounds.width / 2) * scale
y: root.height / 2 - (root.sourceBounds.y + root.sourceBounds.height / 2) * scale
}
IconContentBounds {
id: bounds
sourceItem: content.item
boundsParent: content
}
}
+3 -5
View File
@@ -13,12 +13,9 @@ BarIconButton {
property string indicatorBlock: "single"
property var indicatorHost: null
property var activeOverride: null
// An indicator with nothing to turn on, such as a remote session, shows only
// while it is active, never among the inactive ones a hover reveals.
property bool revealWhenInactive: true
readonly property bool effectiveActive: activeOverride === null || activeOverride === undefined ? active : activeOverride === true
readonly property bool belongsInBlock: indicatorBlock === "active" ? effectiveActive : (indicatorBlock === "inactive" ? !effectiveActive && revealWhenInactive : true)
readonly property bool inactiveRevealed: !effectiveActive && revealWhenInactive && !!indicatorHost && indicatorHost.revealInactiveIndicators
readonly property bool belongsInBlock: indicatorBlock === "active" ? effectiveActive : (indicatorBlock === "inactive" ? !effectiveActive : true)
readonly property bool inactiveRevealed: !effectiveActive && !!indicatorHost && indicatorHost.revealInactiveIndicators
function extractData(raw) {
return Util.parseModuleJson(raw)
@@ -46,6 +43,7 @@ BarIconButton {
useActiveColor: false
maintainIndicatorReveal: indicatorBlock === "inactive"
revealHost: indicatorHost
fontSize: Style.font.caption
horizontalMargin: 5
verticalPadding: 5
fixedWidth: vertical ? -1 : Style.bar.statusSlot
-141
View File
@@ -1,141 +0,0 @@
import QtQuick
import QtQuick.Shapes
Item {
id: root
property Item sourceItem: null
property Item boundsParent: null
property rect mappedBounds: Qt.rect(0, 0, 0, 0)
visible: false
readonly property bool textItem: sourceItem instanceof Text
readonly property bool imageItem: sourceItem instanceof Image
readonly property bool shapeItem: sourceItem instanceof Shape
readonly property bool boxItem: sourceItem instanceof Rectangle
|| sourceItem instanceof Canvas || sourceItem instanceof ShaderEffect
function unionBounds(a, b) {
if (b.width <= 0 || b.height <= 0) return a
if (a.width <= 0 || a.height <= 0) return b
var x = Math.min(a.x, b.x)
var y = Math.min(a.y, b.y)
return Qt.rect(x, y, Math.max(a.x + a.width, b.x + b.width) - x,
Math.max(a.y + a.height, b.y + b.height) - y)
}
function shapeBounds(item) {
var bounds = item.boundingRect
var sx = 1
var sy = 1
if (item.fillMode !== Shape.NoResize && item.implicitWidth > 0 && item.implicitHeight > 0) {
sx = item.width / item.implicitWidth
sy = item.height / item.implicitHeight
if (item.fillMode === Shape.PreserveAspectFit) sx = sy = Math.min(sx, sy)
else if (item.fillMode === Shape.PreserveAspectCrop) sx = sy = Math.max(sx, sy)
}
var spareWidth = item.width - item.implicitWidth * sx
var spareHeight = item.height - item.implicitHeight * sy
var x = item.horizontalAlignment === Shape.AlignHCenter ? spareWidth / 2
: (item.horizontalAlignment === Shape.AlignRight ? spareWidth : 0)
var y = item.verticalAlignment === Shape.AlignVCenter ? spareHeight / 2
: (item.verticalAlignment === Shape.AlignBottom ? spareHeight : 0)
return Qt.rect(x + bounds.x * sx, y + bounds.y * sy, bounds.width * sx, bounds.height * sy)
}
readonly property rect paintedBounds: {
var item = sourceItem
var bounds = Qt.rect(0, 0, 0, 0)
if (!item || !item.visible || item.opacity <= 0) return bounds
if (textItem) {
var ink = textMetrics.tightBoundingRect
var spareWidth = item.width - item.leftPadding - item.rightPadding - item.contentWidth
var textX = item.leftPadding
if (item.effectiveHorizontalAlignment === Text.AlignHCenter) textX += spareWidth / 2
else if (item.effectiveHorizontalAlignment === Text.AlignRight) textX += spareWidth
bounds = Qt.rect(textX + ink.x, item.baselineOffset + ink.y, ink.width, ink.height)
} else if (imageItem) {
var w = item.paintedWidth
var h = item.paintedHeight
var x = item.horizontalAlignment === Image.AlignLeft ? 0
: (item.horizontalAlignment === Image.AlignRight ? item.width - w : (item.width - w) / 2)
var y = item.verticalAlignment === Image.AlignTop ? 0
: (item.verticalAlignment === Image.AlignBottom ? item.height - h : (item.height - h) / 2)
if (item.fillMode === Image.PreserveAspectFit) {
bounds = Qt.rect(x, y, w, h)
} else if (item.fillMode === Image.Pad) {
var left = Math.max(0, x)
var top = Math.max(0, y)
bounds = Qt.rect(left, top, Math.max(0, Math.min(item.width, x + w) - left),
Math.max(0, Math.min(item.height, y + h) - top))
} else {
bounds = Qt.rect(0, 0, item.width, item.height)
}
} else if (shapeItem) {
bounds = shapeBounds(item)
} else if (boxItem) {
bounds = Qt.rect(0, 0, item.width, item.height)
}
for (var i = 0; i < childrenBounds.count; i++) {
var child = childrenBounds.itemAt(i)
if (child) bounds = unionBounds(bounds, child.mappedBounds)
}
if (item.clip && bounds.width > 0 && bounds.height > 0) {
var left = Math.max(0, bounds.x)
var top = Math.max(0, bounds.y)
bounds = Qt.rect(left, top,
Math.max(0, Math.min(item.width, bounds.x + bounds.width) - left),
Math.max(0, Math.min(item.height, bounds.y + bounds.height) - top))
}
return bounds
}
function updateMappedBounds() {
var b = paintedBounds
mappedBounds = sourceItem && boundsParent && b.width > 0 && b.height > 0
? sourceItem.mapToItem(boundsParent, b.x, b.y, b.width, b.height)
: b
}
function configureSource() {
if (sourceItem instanceof Text) sourceItem.renderType = Text.CurveRendering
updateMappedBounds()
}
onPaintedBoundsChanged: updateMappedBounds()
onSourceItemChanged: configureSource()
onBoundsParentChanged: updateMappedBounds()
Component.onCompleted: updateMappedBounds()
TextMetrics {
id: textMetrics
font: root.textItem ? root.sourceItem.font : Qt.font({ pixelSize: 12 })
text: root.textItem ? root.sourceItem.text : ""
}
Connections {
target: root.sourceItem
function onXChanged() { root.updateMappedBounds() }
function onYChanged() { root.updateMappedBounds() }
function onWidthChanged() { root.updateMappedBounds() }
function onHeightChanged() { root.updateMappedBounds() }
function onScaleChanged() { root.updateMappedBounds() }
function onRotationChanged() { root.updateMappedBounds() }
function onTransformOriginChanged() { root.updateMappedBounds() }
}
Repeater {
id: childrenBounds
model: root.sourceItem ? root.sourceItem.children : []
delegate: Loader {
id: childLoader
required property var modelData
readonly property rect mappedBounds: item ? item.mappedBounds : Qt.rect(0, 0, 0, 0)
source: Qt.resolvedUrl("IconContentBounds.qml")
onLoaded: {
item.sourceItem = Qt.binding(function() { return childLoader.modelData })
item.boundsParent = Qt.binding(function() { return root.sourceItem })
}
}
}
}
+9 -18
View File
@@ -8,22 +8,14 @@ Item {
property string text: ""
property string fontFamily: Style.font.family
property real fontSize: Style.font.body
property real paintedSize: 0
property color color: Commons.Color.foreground
property bool debugBounds: false
readonly property int renderedFontSize: Math.max(1, Math.round(fontSize))
readonly property real tightWidth: Math.max(1, glyphMetrics.tightBoundingRect.width)
readonly property real tightHeight: Math.max(1, glyphMetrics.tightBoundingRect.height)
readonly property real paintedScale: paintedSize > 0
? Math.min(paintedSize, width, height) / Math.max(tightWidth, tightHeight)
: 1
readonly property real paintedWidth: tightWidth * paintedScale
readonly property real paintedHeight: tightHeight * paintedScale
readonly property real horizontalCorrection: glyph.implicitWidth / 2 - (glyphMetrics.tightBoundingRect.x + tightWidth / 2)
readonly property real paintedCenterX: glyph.x + (glyphMetrics.tightBoundingRect.x + tightWidth / 2) * paintedScale
readonly property real paintedCenterY: glyph.y + (glyph.baselineOffset + glyphMetrics.tightBoundingRect.y + tightHeight / 2) * paintedScale
readonly property real baselineY: glyph.y + glyph.baselineOffset * paintedScale
readonly property real paintedCenterX: glyph.x + glyphMetrics.tightBoundingRect.x + tightWidth / 2
readonly property real baselineY: glyph.y + glyph.baselineOffset
TextMetrics {
id: glyphMetrics
@@ -35,18 +27,17 @@ Item {
Text {
id: glyph
textFormat: Text.PlainText
// Bar icons fit their painted bounds. Text labels retain their line box.
x: root.width / 2 - (glyphMetrics.tightBoundingRect.x + root.tightWidth / 2) * root.paintedScale
y: root.paintedSize > 0
? root.height / 2 - (baselineOffset + glyphMetrics.tightBoundingRect.y + root.tightHeight / 2) * root.paintedScale
: Math.round((root.height - height) / 2)
// Keep the shared line box and baseline intact. Correcting only the
// horizontal painted bounds avoids per-glyph vertical drift.
// The correction is fractional, so x is set exactly: centerIn would snap it
// to a whole pixel and undo it. y still snaps, keeping the baseline crisp.
x: (root.width - width) / 2 + root.horizontalCorrection
y: Math.round((root.height - height) / 2)
text: root.text
color: root.color
font.family: root.fontFamily
font.pixelSize: root.renderedFontSize
transformOrigin: Item.TopLeft
scale: root.paintedScale
renderType: root.paintedSize > 0 ? Text.CurveRendering : Text.NativeRendering
renderType: Text.NativeRendering
}
Rectangle {
+3 -42
View File
@@ -65,31 +65,11 @@ Item {
// Width of the painted label, for bar chrome that wants to line up with the
// text rather than with the slot it sits in. Zero on icon-only buttons.
readonly property real labelWidth: label.visible ? label.implicitWidth : 0
readonly property bool iconLabel: labelVisible && isIconGlyph(text)
readonly property real iconPaintedWidth: iconLabel && iconLoader.item ? iconLoader.item.paintedWidth : 0
readonly property real iconPaintedHeight: iconLabel && iconLoader.item ? iconLoader.item.paintedHeight : 0
readonly property real iconCenterErrorX: iconLabel && iconLoader.item
? iconLoader.x + iconLoader.item.paintedCenterX - width / 2 : 0
readonly property real iconCenterErrorY: iconLabel && iconLoader.item
? iconLoader.y + iconLoader.item.paintedCenterY - height / 2 : 0
function isIconGlyph(value) {
var text = String(value || "")
if (!text) return false
var code = text.codePointAt(0)
if (text.length !== (code > 0xffff ? 2 : 1)) return false
return (code >= 0xe000 && code <= 0xf8ff)
|| (code >= 0xf0000 && code <= 0xffffd)
|| (code >= 0x100000 && code <= 0x10fffd)
|| (code >= 0x2190 && code <= 0x2bff)
}
visible: hasVisualContent || keepSpace
opacity: !hasVisualContent || concealed ? 0 : (dimmed ? 0.45 : 1)
implicitWidth: fixedWidth > 0 ? fixedWidth
: (vertical ? barSize : (iconLabel ? Style.bar.iconSlot : Math.max(12, label.implicitWidth + scaledHorizontalMargin * 2)))
implicitHeight: fixedHeight > 0 ? fixedHeight
: (vertical ? (iconLabel ? Style.bar.iconSlot : Math.max(12, label.implicitHeight + scaledVerticalPadding * 2)) : barSize)
implicitWidth: fixedWidth > 0 ? fixedWidth : (vertical ? barSize : Math.max(12, label.implicitWidth + scaledHorizontalMargin * 2))
implicitHeight: fixedHeight > 0 ? fixedHeight : (vertical ? Math.max(12, label.implicitHeight + scaledVerticalPadding * 2) : barSize)
Behavior on opacity {
NumberAnimation { duration: Style.duration(140); easing.type: Easing.OutCubic }
@@ -98,7 +78,7 @@ Item {
Text {
id: label
textFormat: Text.PlainText
visible: root.labelVisible && !root.iconLabel
visible: root.labelVisible
anchors.centerIn: parent
// Centering the line box puts the figures above the bar's middle, since
// the box keeps room for descenders. Center the figure height instead, so
@@ -126,25 +106,6 @@ Item {
font: label.font
}
Loader {
id: iconLoader
active: root.iconLabel
width: Style.bar.iconCanvas
height: Style.bar.iconCanvas
x: (root.width - width) / 2
y: (root.height - height) / 2
sourceComponent: Component {
OpticalGlyph {
text: root.text
fontFamily: root.fontFamily
fontSize: root.fontSize
paintedSize: Style.bar.iconSize
color: root.active && root.useActiveColor ? root.activeColor : root.foreground
rotation: root.textRotation
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
Loaded 100 of 175 files, more files were not shown because too many files have changed in this diff. Show more