Compare commits
44
Commits
No files matched your search
@@ -104,7 +104,7 @@ Use these instead of raw shell commands:
|
|||||||
|
|
||||||
- `omarchy-cmd-missing` / `omarchy-cmd-present` - check for 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-missing` / `omarchy-pkg-present` - check for packages (don't use these if you can just use `omarchy-pkg-add`/`omarchy-pkg-drop`)
|
||||||
- `omarchy-pkg-add` - install packages (handles both pacman and AUR)
|
- `omarchy-pkg-add` - install packages from the configured pacman repos, Omarchy's included; use `omarchy-pkg-aur-add` for AUR packages
|
||||||
- `omarchy-pkg-drop` - remove packages; use this instead of raw `pacman -R*`
|
- `omarchy-pkg-drop` - remove packages; use this instead of raw `pacman -R*`
|
||||||
- `omarchy-notification-send` - send desktop notifications; do not call `notify-send` directly
|
- `omarchy-notification-send` - send desktop notifications; do not call `notify-send` directly
|
||||||
- `omarchy-hw-asus-rog` - detect ASUS ROG hardware (and similar `hw-*` commands)
|
- `omarchy-hw-asus-rog` - detect ASUS ROG hardware (and similar `hw-*` commands)
|
||||||
@@ -113,6 +113,10 @@ Commands installed by Omarchy's default package set are runtime invariants. Invo
|
|||||||
|
|
||||||
Exceptions are allowed for migration and package-helper scripts where the helper may not be available yet, where the helper itself is being implemented, or where direct package-manager behavior is required.
|
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
|
# Menu
|
||||||
|
|
||||||
- The menu definition lives in `default/omarchy/omarchy-menu.jsonc`;
|
- The menu definition lives in `default/omarchy/omarchy-menu.jsonc`;
|
||||||
|
|||||||
@@ -38,6 +38,17 @@ Run `omarchy-restart-shell` after making changes to QML files.
|
|||||||
bar widgets register one target each — `omarchy.indicators`,
|
bar widgets register one target each — `omarchy.indicators`,
|
||||||
`omarchy.system-update`, `omarchy.clock`. There is no `bar` target.
|
`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
|
## Editing widget files with glyphs
|
||||||
|
|
||||||
Widget files in `shell/plugins/bar/widgets/` contain Nerd Font glyphs as raw
|
Widget files in `shell/plugins/bar/widgets/` contain Nerd Font glyphs as raw
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Version=1.0
|
Version=1.0
|
||||||
Name=Basecamp
|
Name=Basecamp
|
||||||
Exec=omarchy-launch-webapp https://launchpad.37signals.com
|
Exec=omarchy-launch-webapp https://launchpad.37signals.com
|
||||||
|
StartupWMClass=chrome-launchpad.37signals.com__-Default
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=basecamp
|
Icon=basecamp
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Version=1.0
|
Version=1.0
|
||||||
Name=Discord
|
Name=Discord
|
||||||
Exec=omarchy-launch-webapp https://discord.com/channels/@me
|
Exec=omarchy-launch-webapp https://discord.com/channels/@me
|
||||||
|
StartupWMClass=chrome-discord.com__channels_@me-Default
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=omarchy-discord
|
Icon=omarchy-discord
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Version=1.0
|
Version=1.0
|
||||||
Name=Google Contacts
|
Name=Google Contacts
|
||||||
Exec=omarchy-launch-webapp https://contacts.google.com/
|
Exec=omarchy-launch-webapp https://contacts.google.com/
|
||||||
|
StartupWMClass=chrome-contacts.google.com__-Default
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=google-contacts
|
Icon=google-contacts
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Version=1.0
|
Version=1.0
|
||||||
Name=Google Maps
|
Name=Google Maps
|
||||||
Exec=omarchy-launch-webapp https://maps.google.com
|
Exec=omarchy-launch-webapp https://maps.google.com
|
||||||
|
StartupWMClass=chrome-maps.google.com__-Default
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=google-maps
|
Icon=google-maps
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Version=1.0
|
Version=1.0
|
||||||
Name=Google Messages
|
Name=Google Messages
|
||||||
Exec=omarchy-launch-webapp https://messages.google.com/web/conversations
|
Exec=omarchy-launch-webapp https://messages.google.com/web/conversations
|
||||||
|
StartupWMClass=chrome-messages.google.com__web_conversations-Default
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=google-messages
|
Icon=google-messages
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Version=1.0
|
Version=1.0
|
||||||
Name=Google Photos
|
Name=Google Photos
|
||||||
Exec=omarchy-launch-webapp https://photos.google.com/
|
Exec=omarchy-launch-webapp https://photos.google.com/
|
||||||
|
StartupWMClass=chrome-photos.google.com__-Default
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=google-photos
|
Icon=google-photos
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Version=1.0
|
Version=1.0
|
||||||
Name=WhatsApp
|
Name=WhatsApp
|
||||||
Exec=omarchy-launch-webapp https://web.whatsapp.com/
|
Exec=omarchy-launch-webapp https://web.whatsapp.com/
|
||||||
|
StartupWMClass=chrome-web.whatsapp.com__-Default
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=whatsapp
|
Icon=whatsapp
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Version=1.0
|
Version=1.0
|
||||||
Name=X
|
Name=X
|
||||||
Exec=omarchy-launch-webapp https://x.com/
|
Exec=omarchy-launch-webapp https://x.com/
|
||||||
|
StartupWMClass=chrome-x.com__-Default
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=x
|
Icon=x
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Version=1.0
|
Version=1.0
|
||||||
Name=YouTube
|
Name=YouTube
|
||||||
Exec=omarchy-launch-webapp https://youtube.com/
|
Exec=omarchy-launch-webapp https://youtube.com/
|
||||||
|
StartupWMClass=chrome-youtube.com__-Default
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=youtube
|
Icon=youtube
|
||||||
|
|||||||
+104
-10
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# omarchy:summary=Configure the bar and its widget layout
|
# omarchy:summary=Configure the bar and its widget layout
|
||||||
# omarchy:group=bar
|
# omarchy:group=bar
|
||||||
# omarchy:args=use <id> | reset | defaults | position <top|bottom|left|right> | transparent <true|false|toggle> | put <id> [placement] | move <id> [placement] | set <id> <key> <value> [--json] [placement]
|
# omarchy:args=use <id> | reset | defaults | position [top|bottom|left|right] | background [opaque|glassy|none|cycle] | put <id> [placement] | move <id> [placement] | set <id> <key> <value> [--json] [placement] | get <id> <key>
|
||||||
# omarchy:examples=omarchy bar use local.neon-bar | omarchy bar put omarchy.keyboard-layout --after omarchy.clock | omarchy bar move omarchy.clock --section center --index 0 | omarchy bar set omarchy.clock format HH:mm
|
# 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
|
set -euo pipefail
|
||||||
@@ -16,13 +16,19 @@ Usage: omarchy bar <command> [args...]
|
|||||||
use <id> Use a bar option as the active bar
|
use <id> Use a bar option as the active bar
|
||||||
reset Return to the built-in Omarchy bar
|
reset Return to the built-in Omarchy bar
|
||||||
defaults Restore the default bar and service widgets
|
defaults Restore the default bar and service widgets
|
||||||
position <top|bottom|left|right> Bar position
|
position [top|bottom|left|right] Bar position; prints the current one
|
||||||
transparent <true|false|toggle> Bar transparency
|
when given none
|
||||||
|
background [opaque|glassy|none|cycle]
|
||||||
|
Bar background: glassy shows what is
|
||||||
|
behind the bar blurred, cycle steps
|
||||||
|
none, glassy, opaque; prints the
|
||||||
|
current one when given none
|
||||||
put <id> [placement] Put a widget on the bar, leaving one
|
put <id> [placement] Put a widget on the bar, leaving one
|
||||||
that is already there where it is
|
that is already there where it is
|
||||||
move <id> [placement] Move a widget within or between sections
|
move <id> [placement] Move a widget within or between sections
|
||||||
set <id> <key> <value> [--json] [placement]
|
set <id> <key> <value> [--json] [placement]
|
||||||
Set a per-widget option
|
Set a per-widget option
|
||||||
|
get <id> <key> Print a per-widget option
|
||||||
|
|
||||||
Placement:
|
Placement:
|
||||||
--section <left|center|right> Target section
|
--section <left|center|right> Target section
|
||||||
@@ -192,30 +198,92 @@ cmd_defaults() {
|
|||||||
" \
|
" \
|
||||||
--slurpfile defaults "$DEFAULTS_FILE" \
|
--slurpfile defaults "$DEFAULTS_FILE" \
|
||||||
--argjson widgets "$optional_widgets"
|
--argjson widgets "$optional_widgets"
|
||||||
|
sync_glass || fail "could not change the blur behind the bar"
|
||||||
echo "Restored the default Omarchy bar"
|
echo "Restored the default Omarchy bar"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_position() {
|
cmd_position() {
|
||||||
local position="${1:-}"
|
if (( $# == 0 )); then
|
||||||
[[ -n $position ]] || fail "position is required"
|
jq -r '.bar.position // "top"' "$(source_file)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local position="$1"
|
||||||
(( $# == 1 )) || fail "position takes a single value"
|
(( $# == 1 )) || fail "position takes a single value"
|
||||||
[[ $position =~ ^(top|bottom|left|right)$ ]] || fail "position must be top, bottom, left, or right"
|
[[ $position =~ ^(top|bottom|left|right)$ ]] || fail "position must be top, bottom, left, or right"
|
||||||
commit "$NORMALIZE | .bar.position = \$position" --arg position "$position"
|
commit "$NORMALIZE | .bar.position = \$position" --arg position "$position"
|
||||||
echo "Bar position set to $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() {
|
cmd_transparent() {
|
||||||
local transparent="${1:-}"
|
local transparent="${1:-}"
|
||||||
[[ -n $transparent ]] || fail "transparent is required"
|
[[ -n $transparent ]] || fail "transparent is required"
|
||||||
(( $# == 1 )) || fail "transparent takes a single value"
|
(( $# == 1 )) || fail "transparent takes a single value"
|
||||||
[[ $transparent =~ ^(true|false|toggle)$ ]] || fail "transparent must be true, false, or toggle"
|
[[ $transparent =~ ^(true|false|toggle)$ ]] || fail "transparent must be true, false, or toggle"
|
||||||
if [[ $transparent == "toggle" ]]; then
|
if [[ $transparent == "toggle" ]]; then
|
||||||
commit "$NORMALIZE | .bar.transparent = (.bar.transparent != true)"
|
[[ $(current_background) == "none" ]] && transparent="false" || transparent="true"
|
||||||
echo "Bar transparency toggled"
|
|
||||||
else
|
|
||||||
commit "$NORMALIZE | .bar.transparent = \$transparent" --argjson transparent "$transparent"
|
|
||||||
echo "Bar transparency set to $transparent"
|
|
||||||
fi
|
fi
|
||||||
|
if [[ $transparent == "true" ]]; then
|
||||||
|
cmd_background none
|
||||||
|
else
|
||||||
|
cmd_background opaque
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Hyprland blurs behind the bar only while the bar-glassy toggle is on, so it
|
||||||
|
# follows the bar background, reloading Hyprland only when it changes. The
|
||||||
|
# toggle sits where Hyprland reads it, under XDG_STATE_HOME.
|
||||||
|
sync_glass() {
|
||||||
|
local dir="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/toggles/hypr"
|
||||||
|
local flag="$dir/bar-glassy.lua"
|
||||||
|
|
||||||
|
if [[ $(current_background) == "glassy" ]]; then
|
||||||
|
[[ ! -f $flag ]] || return 0
|
||||||
|
mkdir -p "$dir" && cp "$OMARCHY_PATH/default/hypr/toggles/bar-glassy.lua" "$flag" || return 1
|
||||||
|
else
|
||||||
|
[[ -f $flag ]] || return 0
|
||||||
|
rm -f "$flag" || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
hyprctl reload >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
bar_widget_default_section() {
|
bar_widget_default_section() {
|
||||||
@@ -363,6 +431,21 @@ cmd_set() {
|
|||||||
echo "Set $key on $id"
|
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
|
# --------------------------------------------------------------------- dispatch
|
||||||
|
|
||||||
command="${1:-}"
|
command="${1:-}"
|
||||||
@@ -385,6 +468,14 @@ case "$command" in
|
|||||||
transparent)
|
transparent)
|
||||||
cmd_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)
|
put)
|
||||||
cmd_put "$@"
|
cmd_put "$@"
|
||||||
;;
|
;;
|
||||||
@@ -394,6 +485,9 @@ case "$command" in
|
|||||||
set)
|
set)
|
||||||
cmd_set "$@"
|
cmd_set "$@"
|
||||||
;;
|
;;
|
||||||
|
get)
|
||||||
|
cmd_get "$@"
|
||||||
|
;;
|
||||||
-h | --help | help | "")
|
-h | --help | help | "")
|
||||||
usage
|
usage
|
||||||
;;
|
;;
|
||||||
|
|||||||
Executable
+41
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Show or set window corners: theme, sharp, or rounded
|
||||||
|
# omarchy:args=[theme|sharp|rounded]
|
||||||
|
# omarchy:examples=omarchy hyprland corners | omarchy hyprland corners rounded
|
||||||
|
|
||||||
|
TOGGLES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/toggles/hypr"
|
||||||
|
|
||||||
|
# Sharp and rounded are toggle files loaded after every theme, so they override
|
||||||
|
# the corners of whichever theme is current. Theme removes both.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
if [[ -f $TOGGLES_DIR/corners-sharp.lua ]]; then
|
||||||
|
echo "sharp"
|
||||||
|
elif [[ -f $TOGGLES_DIR/corners-rounded.lua ]]; then
|
||||||
|
echo "rounded"
|
||||||
|
else
|
||||||
|
echo "theme"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
theme) LABEL="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"
|
||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Returns true when the compositor holds a session lock no other locker is alive to own
|
||||||
|
# omarchy:args=<own-pid>
|
||||||
|
# omarchy:hidden=true
|
||||||
|
|
||||||
|
# Hyprland lets any client take over a held lock (misc:allow_session_lock_restore),
|
||||||
|
# so a shell that recovers a lock it merely sees can take the live lock screen of
|
||||||
|
# another shell, and strand it when it exits. A lock is stranded only while no
|
||||||
|
# other Quickshell or hyprlock is running; until then the answer is undetermined,
|
||||||
|
# so the caller keeps asking until that locker exits or it gives up.
|
||||||
|
#
|
||||||
|
# Exits 0 stranded, 1 unlocked, 2 undetermined. The caller passes its own pid,
|
||||||
|
# as a crash re-execs the shell in place.
|
||||||
|
omarchy-hyprland-session-locked || exit
|
||||||
|
|
||||||
|
if pgrep -u "$UID" -x 'quickshell|qs|hyprlock' | grep -qvxF "$1"; then
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
Executable
+70
@@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Show or set how see-through windows are: opaque, transparent, or glassy
|
||||||
|
# omarchy:args=[opaque|transparent|glassy]
|
||||||
|
# omarchy:examples=omarchy hyprland window transparency | omarchy hyprland window transparency glassy
|
||||||
|
|
||||||
|
TOGGLES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/toggles/hypr"
|
||||||
|
|
||||||
|
# Opaque and glassy are toggle files loaded after every theme: opaque makes the
|
||||||
|
# windows Omarchy makes transparent solid, and glassy blurs what is behind any
|
||||||
|
# see-through window and gives terminals a see-through background. Transparent,
|
||||||
|
# the default, removes both.
|
||||||
|
current_mode() {
|
||||||
|
if [[ -f $TOGGLES_DIR/window-opaque.lua ]]; then
|
||||||
|
echo "opaque"
|
||||||
|
elif [[ -f $TOGGLES_DIR/window-glassy.lua ]]; then
|
||||||
|
echo "glassy"
|
||||||
|
else
|
||||||
|
echo "transparent"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save the new choice before dropping the old one, so a failed save keeps it.
|
||||||
|
save_mode() {
|
||||||
|
if [[ $1 != "transparent" ]]; then
|
||||||
|
mkdir -p "$TOGGLES_DIR" && cp "$OMARCHY_PATH/default/hypr/toggles/window-$1.lua" "$TOGGLES_DIR/window-$1.lua" || return 1
|
||||||
|
fi
|
||||||
|
local mode
|
||||||
|
for mode in opaque glassy; do
|
||||||
|
[[ $mode == "$1" ]] || rm -f "$TOGGLES_DIR/window-$mode.lua" || return 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
current_mode
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
opaque) LABEL="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"
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Print the layout of a workspace: dwindle, scrolling, or floating
|
||||||
|
# omarchy:args=[workspace-id]
|
||||||
|
|
||||||
|
if [[ -n $1 ]]; then
|
||||||
|
WORKSPACE_ID=$1
|
||||||
|
TILED_LAYOUT=$(hyprctl workspaces -j | jq -r --arg id "$WORKSPACE_ID" 'first(.[] | select((.id | tostring) == $id) | .tiledLayout) // empty')
|
||||||
|
else
|
||||||
|
read -r WORKSPACE_ID TILED_LAYOUT < <(hyprctl activeworkspace -j | jq -r '"\(.id) \(.tiledLayout // "")"')
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/workspace-layouts"
|
||||||
|
|
||||||
|
# Hyprland only reports the tiled layout, and floating is not one, so the mode is
|
||||||
|
# read back from the file omarchy-hyprland-workspace-layout-toggle wrote. Files
|
||||||
|
# saved before floating existed name no mode, and a workspace without a mode of
|
||||||
|
# its own follows the default layout.
|
||||||
|
if [[ $WORKSPACE_ID =~ ^[0-9]+$ ]] && (( WORKSPACE_ID >= 1 )); then
|
||||||
|
MODE=$(sed -n 's/.*mode = "\([a-z]*\)".*/\1/p' "$MODES_DIR/$WORKSPACE_ID.lua" 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ -z $MODE && -f $MODES_DIR/default.lua ]]; then
|
||||||
|
MODE=$(omarchy-hyprland-workspace-layout-default)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODE=${MODE:-$TILED_LAYOUT}
|
||||||
|
echo "${MODE:-dwindle}"
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Show or set the layout of workspaces without one of their own: dwindle, scrolling, or floating
|
||||||
|
# omarchy:args=[dwindle|scrolling|floating]
|
||||||
|
# omarchy:examples=omarchy hyprland workspace layout default | omarchy hyprland workspace layout default scrolling
|
||||||
|
|
||||||
|
MODES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/workspace-layouts"
|
||||||
|
DEFAULT_FILE="$MODES_DIR/default.lua"
|
||||||
|
|
||||||
|
# Without a saved default, Hyprland's own layout is the default, which the
|
||||||
|
# user's looknfeel may have set.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
MODE=$(sed -n 's/.*default = "\([a-z]*\)".*/\1/p' "$DEFAULT_FILE" 2>/dev/null)
|
||||||
|
[[ -n $MODE ]] || MODE=$(hyprctl getoption general.layout -j 2>/dev/null | jq -r '.str // empty')
|
||||||
|
echo "${MODE:-dwindle}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
dwindle) LABEL=tiling ;;
|
||||||
|
scrolling | floating) LABEL=$1 ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: omarchy-hyprland-workspace-layout-default [dwindle|scrolling|floating]" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Saved even for dwindle, so the choice also wins over a layout set in the
|
||||||
|
# user's own looknfeel. Workspaces given a layout with Super + L keep theirs.
|
||||||
|
mkdir -p "$MODES_DIR"
|
||||||
|
printf 'o.workspace_mode({ default = "%s" })\n' "$1" >"$DEFAULT_FILE"
|
||||||
|
|
||||||
|
hyprctl reload >/dev/null
|
||||||
|
omarchy-shell -q omarchy.workspaces refresh
|
||||||
|
omarchy-notification-send -g "New workspaces use the $LABEL layout"
|
||||||
@@ -1,21 +1,69 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Toggle the layout on the current active workspace between dwindle and scrolling
|
# omarchy:summary=Cycle the current active workspace through the dwindle, scrolling, and floating layouts
|
||||||
|
# omarchy:args=[back]
|
||||||
|
|
||||||
|
DIRECTION=${1:-next}
|
||||||
|
if [[ $DIRECTION != "next" && $DIRECTION != "back" ]]; then
|
||||||
|
echo "Usage: omarchy-hyprland-workspace-layout-toggle [back]" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
ACTIVE_WORKSPACE=$(hyprctl activeworkspace -j | jq -r '.id')
|
ACTIVE_WORKSPACE=$(hyprctl activeworkspace -j | jq -r '.id')
|
||||||
[[ $ACTIVE_WORKSPACE =~ ^-?[0-9]+$ ]] || exit 1
|
# Named workspaces report a negative id, and Hyprland reads a leading "-" as a
|
||||||
CURRENT_LAYOUT=$(hyprctl activeworkspace -j | jq -r '.tiledLayout')
|
# selector relative to the current workspace: targeting one lands on workspace 1
|
||||||
LAYOUTS_DIR="$HOME/.local/state/omarchy/workspace-layouts"
|
# and reconfigures that instead.
|
||||||
LAYOUT_FILE="$LAYOUTS_DIR/$ACTIVE_WORKSPACE.lua"
|
[[ $ACTIVE_WORKSPACE =~ ^[0-9]+$ ]] || exit 1
|
||||||
|
MODES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/workspace-layouts"
|
||||||
|
MODE_FILE="$MODES_DIR/$ACTIVE_WORKSPACE.lua"
|
||||||
|
|
||||||
case "$CURRENT_LAYOUT" in
|
CURRENT_MODE=$(omarchy-hyprland-workspace-layout-current)
|
||||||
dwindle) NEW_LAYOUT=scrolling ;;
|
|
||||||
*) NEW_LAYOUT=dwindle ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
mkdir -p "$LAYOUTS_DIR"
|
if [[ $DIRECTION == "back" ]]; then
|
||||||
printf 'hl.workspace_rule({ workspace = "%s", layout = "%s" })\n' "$ACTIVE_WORKSPACE" "$NEW_LAYOUT" >"$LAYOUT_FILE"
|
case "$CURRENT_MODE" in
|
||||||
|
dwindle) NEW_MODE=floating ;;
|
||||||
|
floating) NEW_MODE=scrolling ;;
|
||||||
|
*) NEW_MODE=dwindle ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
case "$CURRENT_MODE" in
|
||||||
|
dwindle) NEW_MODE=scrolling ;;
|
||||||
|
scrolling) NEW_MODE=floating ;;
|
||||||
|
*) NEW_MODE=dwindle ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
hyprctl eval "hl.workspace_rule({ workspace = \"$ACTIVE_WORKSPACE\", layout = \"$NEW_LAYOUT\" })" >/dev/null 2>&1 || \
|
APPLY="o.workspace_mode({ workspace = \"$ACTIVE_WORKSPACE\", mode = \"$NEW_MODE\" })"
|
||||||
hyprctl keyword workspace "$ACTIVE_WORKSPACE, layout:$NEW_LAYOUT"
|
|
||||||
omarchy-notification-send -g "Workspace layout set to $NEW_LAYOUT"
|
# 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
|
||||||
Executable
+22
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Configure, inspect, or switch input methods
|
||||||
|
# omarchy:args=<status|cycle [back]|configure --seed|--defaults>
|
||||||
|
# omarchy:hidden=true
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ ${1:-} == "cycle" ]]; then
|
||||||
|
direction=${2:-next}
|
||||||
|
if [[ $direction != "next" && $direction != "back" ]]; then
|
||||||
|
echo "Usage: omarchy-input-method cycle [back]" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# The shell's reader retains a choice made while nothing has focus.
|
||||||
|
if [[ $(omarchy-shell shell cycleInput "$direction") != "ok" ]]; then
|
||||||
|
echo "The shell's input reader is not running" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
/usr/bin/python3 "$OMARCHY_PATH/default/input-methods/configure.py" "$@"
|
||||||
|
fi
|
||||||
@@ -92,6 +92,9 @@ phoenix)
|
|||||||
;;
|
;;
|
||||||
rust)
|
rust)
|
||||||
echo -e "Installing Rust...\n"
|
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
|
bash -c "$(curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs)" -- -y
|
||||||
;;
|
;;
|
||||||
java)
|
java)
|
||||||
|
|||||||
@@ -15,17 +15,15 @@ if gum confirm "Are you sure you want to restore all preinstalled web apps, TUI
|
|||||||
aether \
|
aether \
|
||||||
cliamp \
|
cliamp \
|
||||||
libreoffice-fresh \
|
libreoffice-fresh \
|
||||||
xournalpp \
|
|
||||||
pinta \
|
pinta \
|
||||||
obsidian \
|
obsidian \
|
||||||
obs-studio \
|
|
||||||
kdenlive \
|
|
||||||
moonlight-qt \
|
moonlight-qt \
|
||||||
omacut \
|
omacut \
|
||||||
monologue \
|
monologue \
|
||||||
omacalc \
|
omacalc \
|
||||||
omawrite \
|
omawrite \
|
||||||
hype; then
|
hype \
|
||||||
|
signed; then
|
||||||
echo -e "\nPreinstalls are still marked as removed. Fix the errors above and try again."
|
echo -e "\nPreinstalls are still marked as removed. Fix the errors above and try again."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -555,7 +555,7 @@ output_binding_records_uncached() {
|
|||||||
|
|
||||||
keybindings_cache_key() {
|
keybindings_cache_key() {
|
||||||
{
|
{
|
||||||
printf 'v14\n'
|
printf 'v15\n'
|
||||||
hyprctl devices 2>/dev/null | grep -F 'active keymap:'
|
hyprctl devices 2>/dev/null | grep -F 'active keymap:'
|
||||||
cat "$key_names_file" 2>/dev/null
|
cat "$key_names_file" 2>/dev/null
|
||||||
hyprctl binds 2>/dev/null
|
hyprctl binds 2>/dev/null
|
||||||
|
|||||||
+46
-4
@@ -1,9 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Pick one option from a menu
|
# omarchy:summary=Pick one or multiple options from a menu
|
||||||
# omarchy:group=menu
|
# omarchy:group=menu
|
||||||
# omarchy:name=select
|
# omarchy:name=select
|
||||||
# omarchy:args=prompt [option...] [-- menu args...]
|
# omarchy:args=prompt [option...] [-- --multiple --selected <value> --on-change <command-json> --change-key <key> --width <pixels> --maxheight <pixels>]
|
||||||
# omarchy:examples=omarchy menu select Format jpg png|omarchy-menu-select Resolution 4k 1080p 720p -- --width 400
|
# omarchy:examples=omarchy menu select Format jpg png|omarchy-menu-select Resolution 4k 1080p 720p -- --width 400
|
||||||
|
|
||||||
# An option may lead with an icon, as "<glyph><TAB><label>", and may trail a
|
# An option may lead with an icon, as "<glyph><TAB><label>", and may trail a
|
||||||
@@ -12,6 +12,12 @@
|
|||||||
# alone; an option with a subtext returns "<label><TAB><subtext>", so callers
|
# alone; an option with a subtext returns "<label><TAB><subtext>", so callers
|
||||||
# with same-named rows get the subtext back as the stable key.
|
# with same-named rows get the subtext back as the stable key.
|
||||||
|
|
||||||
|
# With --multiple, clicking/Enter toggles rows and Apply returns a JSON array.
|
||||||
|
# --on-change takes a JSON command array; each toggle runs it with the selection
|
||||||
|
# JSON appended, applies immediately, and needs no Apply row. A successful
|
||||||
|
# command may print a JSON array of actual selections to update the checkmarks.
|
||||||
|
# --selected takes a returned value (label plus any subtext), and can be repeated.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if (( $# < 1 )); then
|
if (( $# < 1 )); then
|
||||||
@@ -25,12 +31,43 @@ shift
|
|||||||
options=()
|
options=()
|
||||||
menu_width=""
|
menu_width=""
|
||||||
menu_maxheight=""
|
menu_maxheight=""
|
||||||
|
multiple=0
|
||||||
|
on_change="[]"
|
||||||
|
change_key=""
|
||||||
|
selected=()
|
||||||
|
|
||||||
while (( $# > 0 )); do
|
while (( $# > 0 )); do
|
||||||
if [[ $1 == "--" ]]; then
|
if [[ $1 == "--" ]]; then
|
||||||
shift
|
shift
|
||||||
while (( $# > 0 )); do
|
while (( $# > 0 )); do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
--on-change)
|
||||||
|
shift
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
echo "omarchy-menu-select: --on-change requires a JSON command array" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
on_change="$1"
|
||||||
|
;;
|
||||||
|
--change-key)
|
||||||
|
shift
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
echo "omarchy-menu-select: --change-key requires a value" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
change_key="$1"
|
||||||
|
;;
|
||||||
|
--multiple)
|
||||||
|
multiple=1
|
||||||
|
;;
|
||||||
|
--selected)
|
||||||
|
shift
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
echo "omarchy-menu-select: --selected requires a value" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
selected+=("$1")
|
||||||
|
;;
|
||||||
--width)
|
--width)
|
||||||
shift
|
shift
|
||||||
if (( $# == 0 )); then
|
if (( $# == 0 )); then
|
||||||
@@ -73,20 +110,25 @@ rm -f "$done_file"
|
|||||||
trap 'rm -f "$selection_file" "$done_file"' EXIT
|
trap 'rm -f "$selection_file" "$done_file"' EXIT
|
||||||
|
|
||||||
options_json=$(perl -MEncode=decode -MJSON::PP=encode_json -e 'print encode_json([map { decode("UTF-8", $_) } @ARGV])' "${options[@]}")
|
options_json=$(perl -MEncode=decode -MJSON::PP=encode_json -e 'print encode_json([map { decode("UTF-8", $_) } @ARGV])' "${options[@]}")
|
||||||
|
selected_json=$(perl -MEncode=decode -MJSON::PP=encode_json -e 'print encode_json([map { decode("UTF-8", $_) } @ARGV])' "${selected[@]}")
|
||||||
payload=$(perl -MEncode=decode -MJSON::PP=encode_json,decode_json -e '
|
payload=$(perl -MEncode=decode -MJSON::PP=encode_json,decode_json -e '
|
||||||
my $payload = {
|
my $payload = {
|
||||||
mode => "select",
|
mode => "select",
|
||||||
prompt => decode("UTF-8", $ARGV[0]),
|
prompt => decode("UTF-8", $ARGV[0]),
|
||||||
options => decode_json($ARGV[1]),
|
options => decode_json($ARGV[1]),
|
||||||
|
multiple => $ARGV[6] ? JSON::PP::true : JSON::PP::false,
|
||||||
|
selected => decode_json($ARGV[7]),
|
||||||
|
onChange => decode_json($ARGV[8]),
|
||||||
|
changeKey => $ARGV[9],
|
||||||
selectionFile => decode("UTF-8", $ARGV[2]),
|
selectionFile => decode("UTF-8", $ARGV[2]),
|
||||||
doneFile => decode("UTF-8", $ARGV[3])
|
doneFile => decode("UTF-8", $ARGV[3])
|
||||||
};
|
};
|
||||||
$payload->{width} = int($ARGV[4]) if length($ARGV[4] // "");
|
$payload->{width} = int($ARGV[4]) if length($ARGV[4] // "");
|
||||||
$payload->{maxHeight} = int($ARGV[5]) if length($ARGV[5] // "");
|
$payload->{maxHeight} = int($ARGV[5]) if length($ARGV[5] // "");
|
||||||
print encode_json($payload)
|
print encode_json($payload)
|
||||||
' "$prompt" "$options_json" "$selection_file" "$done_file" "$menu_width" "$menu_maxheight")
|
' "$prompt" "$options_json" "$selection_file" "$done_file" "$menu_width" "$menu_maxheight" "$multiple" "$selected_json" "$on_change" "$change_key")
|
||||||
|
|
||||||
omarchy-shell shell summon omarchy.menu "$payload" >/dev/null
|
omarchy-shell shell summon omarchy.menu "$payload" >/dev/null || exit 2
|
||||||
|
|
||||||
while [[ ! -e $done_file ]]; do
|
while [[ ! -e $done_file ]]; do
|
||||||
sleep 0.05
|
sleep 0.05
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ if [[ -z ${profile:-} ]] || ! profile_available "$profile"; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
powerprofilesctl set "$profile" || exit
|
powerprofilesctl set "$profile" || exit
|
||||||
|
omarchy-shell -q battery refreshPowerProfile >/dev/null 2>&1 || true
|
||||||
|
|
||||||
if [[ -n $requested_profile ]]; then
|
if [[ -n $requested_profile ]]; then
|
||||||
mkdir -p "$state_dir"
|
mkdir -p "$state_dir"
|
||||||
|
|||||||
@@ -187,11 +187,11 @@ tips=(
|
|||||||
"Super + Space opens the Omarchy menu for apps, settings, and more"
|
"Super + Space opens the Omarchy menu for apps, settings, and more"
|
||||||
"Super + K shows all the key bindings"
|
"Super + K shows all the key bindings"
|
||||||
"Super is the Windows or command key on your keyboard"
|
"Super is the Windows or command key on your keyboard"
|
||||||
"Use Xournal++ to sign PDFs"
|
"Sign PDFs and fill them in with Signed"
|
||||||
"Share files with phones and laptops using LocalSend"
|
"Share files with phones and laptops using LocalSend"
|
||||||
"Turn any website into an app with Install > Web App in the menu"
|
"Turn any website into an app with Install > Web App in the menu"
|
||||||
"Super + Return opens a terminal, Super + Shift + Return the browser"
|
"Super + Return opens a terminal, Super + Shift + Return the browser"
|
||||||
"Edit images with Pinta, videos with Kdenlive, docs with LibreOffice"
|
"Edit images with Pinta, trim videos with Omacut, docs with LibreOffice"
|
||||||
"Super + Ctrl + Print grabs text off the screen with OCR"
|
"Super + Ctrl + Print grabs text off the screen with OCR"
|
||||||
"Print takes a screenshot, Alt + Print records the screen"
|
"Print takes a screenshot, Alt + Print records the screen"
|
||||||
"Set a reminder with Super + Ctrl + R"
|
"Set a reminder with Super + Ctrl + R"
|
||||||
@@ -615,6 +615,8 @@ keyboard_form() {
|
|||||||
# re-key below. Confirm it so a stray Ctrl+C doesn't bounce the machine.
|
# re-key below. Confirm it so a stray Ctrl+C doesn't bounce the machine.
|
||||||
confirm_reboot && exec systemctl reboot
|
confirm_reboot && exec systemctl reboot
|
||||||
done
|
done
|
||||||
|
|
||||||
|
omarchy_persist_input_selection
|
||||||
}
|
}
|
||||||
|
|
||||||
confirm_reboot() {
|
confirm_reboot() {
|
||||||
|
|||||||
@@ -34,15 +34,13 @@ if gum confirm "Are you sure you want to remove all preinstalled web apps, TUI w
|
|||||||
aether \
|
aether \
|
||||||
cliamp \
|
cliamp \
|
||||||
libreoffice-fresh \
|
libreoffice-fresh \
|
||||||
xournalpp \
|
|
||||||
pinta \
|
pinta \
|
||||||
obsidian \
|
obsidian \
|
||||||
obs-studio \
|
|
||||||
kdenlive \
|
|
||||||
moonlight-qt \
|
moonlight-qt \
|
||||||
omacut \
|
omacut \
|
||||||
monologue \
|
monologue \
|
||||||
omacalc \
|
omacalc \
|
||||||
omawrite \
|
omawrite \
|
||||||
hype
|
hype \
|
||||||
|
signed
|
||||||
fi
|
fi
|
||||||
@@ -2,9 +2,30 @@
|
|||||||
|
|
||||||
# omarchy:summary=Restart the XCompose input method service (fcitx5) to apply new compose key settings.
|
# omarchy:summary=Restart the XCompose input method service (fcitx5) to apply new compose key settings.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
# A fcitx5 running outside the unit owns org.fcitx.Fcitx5, so the unit's
|
# A fcitx5 running outside the unit owns org.fcitx.Fcitx5, so the unit's
|
||||||
# instance would exit on arrival and the stale process would keep serving the
|
# instance would exit on arrival and the stale process would keep serving the
|
||||||
# old ~/.XCompose -- a restart that reports success and changes nothing.
|
# old ~/.XCompose -- a restart that reports success and changes nothing.
|
||||||
systemctl --user stop omarchy-fcitx5.service
|
systemctl --user stop omarchy-fcitx5.service
|
||||||
pkill -x fcitx5 >/dev/null 2>&1 || true
|
pkill -u "$UID" -x fcitx5 >/dev/null 2>&1 || true
|
||||||
|
for attempt in {1..50}; do
|
||||||
|
if ! pgrep -u "$UID" -x fcitx5 >/dev/null; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
if pgrep -u "$UID" -x fcitx5 >/dev/null; then
|
||||||
|
# An activation concurrent with stop may have restarted the unit.
|
||||||
|
systemctl --user stop omarchy-fcitx5.service
|
||||||
|
pkill -u "$UID" -x fcitx5 >/dev/null 2>&1 || true
|
||||||
|
for attempt in {1..50}; do
|
||||||
|
if ! pgrep -u "$UID" -x fcitx5 >/dev/null; then break; fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if pgrep -u "$UID" -x fcitx5 >/dev/null; then
|
||||||
|
echo "Fcitx did not stop; input service was not restarted." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
systemctl --user start omarchy-fcitx5.service
|
systemctl --user start omarchy-fcitx5.service
|
||||||
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Select input methods for Japanese, Korean, or Chinese typing
|
||||||
|
# omarchy:args=[mozc|hangul|pinyin|chewing]
|
||||||
|
# omarchy:examples=omarchy setup input | omarchy setup input mozc
|
||||||
|
# omarchy:requires-sudo=true
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if (( $# > 1 )); then
|
||||||
|
echo "Usage: omarchy setup input [mozc|hangul|pinyin|chewing]" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
method=${1:-}
|
||||||
|
if [[ -z $method ]]; then
|
||||||
|
/usr/bin/python3 "$OMARCHY_PATH/default/input-methods/typing_setup.py" input
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$method" in
|
||||||
|
mozc | hangul | pinyin | chewing) ;;
|
||||||
|
*) echo "Unknown input method: $method" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
mapfile -t packages < <(jq -r --arg method "$method" '.[$method].packages[]' "$OMARCHY_PATH/default/input-methods/presets.json")
|
||||||
|
omarchy-pkg-add "${packages[@]}"
|
||||||
|
/usr/bin/python3 "$OMARCHY_PATH/default/input-methods/typing_setup.py" add "$method"
|
||||||
|
echo "Input method added. Use Super+I or the Japanese/Korean input key to switch; existing custom Fcitx keys are preserved."
|
||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Select the keyboard layouts available for switching
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
/usr/bin/python3 "$OMARCHY_PATH/default/input-methods/typing_setup.py" keyboard
|
||||||
@@ -462,7 +462,34 @@ root_dispatch() {
|
|||||||
esac
|
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() {
|
show_enabled() {
|
||||||
|
schedule_expiry_refresh
|
||||||
if [[ $minutes == "permanent" ]]; then
|
if [[ $minutes == "permanent" ]]; then
|
||||||
echo "Passwordless sudo has been ENABLED permanently. Run this command again to disable it."
|
echo "Passwordless sudo has been ENABLED permanently. Run this command again to disable it."
|
||||||
else
|
else
|
||||||
@@ -542,12 +569,12 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if (( status == 0 )) && { (($# == 0)) || [[ $minutes == "--disable" ]]; }; then
|
if (( status == 0 )) && { (($# == 0)) || [[ $minutes == "--disable" ]]; }; then
|
||||||
/usr/bin/sudo -n -N -- "$INSTALLED_SELF" __disable "$uid"
|
change_grant -n -N -- "$INSTALLED_SELF" __disable "$uid"
|
||||||
echo "Passwordless sudo has been DISABLED. Sudo will require a password again."
|
echo "Passwordless sudo has been DISABLED. Sudo will require a password again."
|
||||||
elif (( status == STATUS_INACTIVE )) && [[ $minutes == "--disable" ]]; then
|
elif (( status == STATUS_INACTIVE )) && [[ $minutes == "--disable" ]]; then
|
||||||
echo "Passwordless sudo is already DISABLED."
|
echo "Passwordless sudo is already DISABLED."
|
||||||
elif (( status == 0 )) && [[ $minutes != "permanent" ]]; then
|
elif (( status == 0 )) && [[ $minutes != "permanent" ]]; then
|
||||||
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
|
change_grant -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
|
||||||
show_enabled
|
show_enabled
|
||||||
else
|
else
|
||||||
if (( status != 0 && status != STATUS_INACTIVE )); then
|
if (( status != 0 && status != STATUS_INACTIVE )); then
|
||||||
@@ -587,7 +614,7 @@ else
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if /usr/bin/gum confirm "Enable passwordless sudo $duration? This is a significant security risk!"; then
|
if /usr/bin/gum confirm "Enable passwordless sudo $duration? This is a significant security risk!"; then
|
||||||
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
|
change_grant -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
|
||||||
echo ""
|
echo ""
|
||||||
show_enabled
|
show_enabled
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -20,6 +20,6 @@ fi
|
|||||||
# Create symlink to the new background media
|
# Create symlink to the new background media
|
||||||
ln -nsf "$BACKGROUND" "$CURRENT_BACKGROUND_LINK"
|
ln -nsf "$BACKGROUND" "$CURRENT_BACKGROUND_LINK"
|
||||||
|
|
||||||
# Update the live shell background immediately when it is running. The
|
# Update the live shell background when it is running. Nothing watches the
|
||||||
# background plugin also polls this symlink, but IPC avoids the visible delay.
|
# symlink, so this is how the shell learns of the new background.
|
||||||
omarchy-shell -q background set "$BACKGROUND"
|
omarchy-shell -q background set "$BACKGROUND"
|
||||||
@@ -207,11 +207,14 @@ set_theme_background() {
|
|||||||
shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true
|
shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true
|
||||||
ln -nsf "$new_background" "$CURRENT_BACKGROUND_LINK"
|
ln -nsf "$new_background" "$CURRENT_BACKGROUND_LINK"
|
||||||
(
|
(
|
||||||
if ! omarchy-theme-bg-boot-intro --theme-switch "$new_background" "$theme_id" "$intro_prepared"; then
|
# Played or not, the shell gets the new still: it still holds the old
|
||||||
if [[ $(readlink -f "$CURRENT_BACKGROUND_LINK") == "$(readlink -f "$new_background")" &&
|
# theme's path, which the theme swap above deleted, and loads it again
|
||||||
$(stat -Lc '%d:%i' "$CURRENT_THEME_PATH" 2>/dev/null) == "$theme_id" ]]; then
|
# when OWE hands the desktop back after the intro. While the intro plays
|
||||||
shell_ipc background setInstant "$new_background" || true
|
# the shell's background is suspended under it, so the swap is unseen.
|
||||||
fi
|
omarchy-theme-bg-boot-intro --theme-switch "$new_background" "$theme_id" "$intro_prepared" || true
|
||||||
|
if [[ $(readlink -f "$CURRENT_BACKGROUND_LINK") == "$(readlink -f "$new_background")" &&
|
||||||
|
$(stat -Lc '%d:%i' "$CURRENT_THEME_PATH" 2>/dev/null) == "$theme_id" ]]; then
|
||||||
|
shell_ipc background setInstant "$new_background" || true
|
||||||
fi
|
fi
|
||||||
shell_ipc shell finishThemeIntro "$theme_id" || true
|
shell_ipc shell finishThemeIntro "$theme_id" || true
|
||||||
wait_for_theme_intro themeIntroStatus "$theme_id" "pending"
|
wait_for_theme_intro themeIntroStatus "$theme_id" "pending"
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Apply current Omarchy theme colors to running Foot terminals
|
# omarchy:summary=Apply current Omarchy theme colors and background alpha to running Foot terminals
|
||||||
# omarchy:hidden=true
|
# omarchy:hidden=true
|
||||||
|
|
||||||
colors_toml=$HOME/.local/state/omarchy/current/theme/colors.toml
|
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
|
if [[ ! -f $colors_toml ]] || ! pgrep -x foot >/dev/null; then
|
||||||
exit 0
|
exit 0
|
||||||
@@ -12,6 +13,16 @@ fi
|
|||||||
foot_osc=$(omarchy-theme-osc "$colors_toml")
|
foot_osc=$(omarchy-theme-osc "$colors_toml")
|
||||||
[[ -n $foot_osc ]] || exit 0
|
[[ -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 foot_pid in $(pgrep -x foot); do
|
||||||
for child_pid in $(pgrep -P "$foot_pid"); do
|
for child_pid in $(pgrep -P "$foot_pid"); do
|
||||||
tty=$(readlink "/proc/$child_pid/fd/1" 2>/dev/null)
|
tty=$(readlink "/proc/$child_pid/fd/1" 2>/dev/null)
|
||||||
|
|||||||
Executable
+122
@@ -0,0 +1,122 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Give terminals, btop, and GTK apps a see-through background while windows are glassy
|
||||||
|
# omarchy:args=[theme-dir]
|
||||||
|
# omarchy:hidden=true
|
||||||
|
|
||||||
|
# A Hyprland opacity rule fades a terminal's text along with its background,
|
||||||
|
# and foot turns compositor blur down unless its own config asks for it, so the
|
||||||
|
# glass belongs in each terminal's theme file, as in the glassy Starship
|
||||||
|
# (#14726). The lines sit between markers so leaving glassy takes them out
|
||||||
|
# again, and a theme that sets its own opacity keeps it.
|
||||||
|
|
||||||
|
THEME_DIR="${1:-$HOME/.local/state/omarchy/current/theme}"
|
||||||
|
TOGGLES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/toggles/hypr"
|
||||||
|
GLASSY=false
|
||||||
|
[[ -f $TOGGLES_DIR/window-glassy.lua ]] && GLASSY=true
|
||||||
|
status=0
|
||||||
|
|
||||||
|
# A literal for a sed address, whose delimiter is /.
|
||||||
|
regex() {
|
||||||
|
sed 's/[][\\.*^$/]/\\&/g' <<<"$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# A block added to a file that did not end in a newline has to supply one, so
|
||||||
|
# its begin marker says so, and taking the block out takes that newline back.
|
||||||
|
# Leaving glassy then gives the file back exactly as it was. Markers come as
|
||||||
|
# an opening and a closing part, so CSS can wrap them in a comment.
|
||||||
|
remove_block() {
|
||||||
|
local file="$1" open="$2" close="$3"
|
||||||
|
local begin end
|
||||||
|
begin=$(regex "$open omarchy-glass begin")
|
||||||
|
end=$(regex "$open omarchy-glass end$close")
|
||||||
|
[[ -f $file ]] && grep -qF "$open omarchy-glass begin" "$file" || return 0
|
||||||
|
|
||||||
|
local added_newline=false
|
||||||
|
grep -qxF "$open omarchy-glass begin after a line without a newline$close" "$file" && added_newline=true
|
||||||
|
sed -i "/^$begin/,/^$end\$/d" "$file" || return 1
|
||||||
|
if [[ $added_newline == "true" && -s $file && -z $(tail -c 1 "$file") ]]; then
|
||||||
|
truncate -s -1 "$file" || return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
append_block() {
|
||||||
|
local file="$1" open="$2" close="$3" lines="$4"
|
||||||
|
if [[ -s $file && -n $(tail -c 1 "$file") ]]; then
|
||||||
|
printf '\n%s\n%s\n%s\n' "$open omarchy-glass begin after a line without a newline$close" "$lines" "$open omarchy-glass end$close" >>"$file"
|
||||||
|
else
|
||||||
|
printf '%s\n%s\n%s\n' "$open omarchy-glass begin$close" "$lines" "$open omarchy-glass end$close" >>"$file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Each setting comes with a pattern for the theme setting it already, which
|
||||||
|
# keeps the theme's own; an empty pattern always adds it. Lines that belong in
|
||||||
|
# a section go in under its header. A file that already has the section gets
|
||||||
|
# them inside it, since TOML refuses a table declared twice; otherwise the
|
||||||
|
# header comes along with them.
|
||||||
|
glass() {
|
||||||
|
local file="$THEME_DIR/$1" section="$2"
|
||||||
|
shift 2
|
||||||
|
[[ -f $file ]] || return 0
|
||||||
|
|
||||||
|
remove_block "$file" "#" "" || return 1
|
||||||
|
[[ $GLASSY == "true" ]] || return 0
|
||||||
|
|
||||||
|
local lines=""
|
||||||
|
while (( $# >= 2 )); do
|
||||||
|
if [[ -z $1 ]] || ! grep -qE "$1" "$file"; then
|
||||||
|
lines+="${lines:+$'\n'}$2"
|
||||||
|
fi
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
[[ -n $lines ]] || return 0
|
||||||
|
|
||||||
|
# A header may carry trailing space or a comment.
|
||||||
|
local header="^$(regex "$section")[[:space:]]*(#.*)?\$"
|
||||||
|
if [[ -n $section ]] && grep -qE "$header" "$file"; then
|
||||||
|
local block
|
||||||
|
block=$(mktemp) || return 1
|
||||||
|
printf '# omarchy-glass begin\n%s\n# omarchy-glass end\n' "$lines" >"$block"
|
||||||
|
sed -i -E "0,/$header/{/$header/r $block
|
||||||
|
}" "$file"
|
||||||
|
local result=$?
|
||||||
|
rm -f "$block"
|
||||||
|
return $result
|
||||||
|
fi
|
||||||
|
|
||||||
|
append_block "$file" "#" "" "${section:+$section$'\n'}$lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Alacritty's own blur works only on macOS and KDE; Hyprland blurs it anyway.
|
||||||
|
glass alacritty.toml '[window]' \
|
||||||
|
'^[[:space:]]*opacity[[:space:]]*=' 'opacity = 0.65' || status=1
|
||||||
|
glass foot.ini '[colors-dark]' \
|
||||||
|
'^[[:space:]]*alpha[[:space:]]*=' 'alpha=0.65' \
|
||||||
|
'^[[:space:]]*blur[[:space:]]*=' 'blur=yes' || status=1
|
||||||
|
glass kitty.conf '' \
|
||||||
|
'^[[:space:]]*background_opacity[[:space:]]' 'background_opacity 0.65' || status=1
|
||||||
|
# btop paints its own background over the terminal's glass. An empty main_bg
|
||||||
|
# uses the terminal background; the last theme value wins, so removing the
|
||||||
|
# block restores the theme's colour without changing btop's user config.
|
||||||
|
glass btop.theme '' \
|
||||||
|
'^[[:space:]]*theme\[main_bg\][[:space:]]*=[[:space:]]*""' 'theme[main_bg]=""' || status=1
|
||||||
|
# GTK only asks Hyprland for blur under a CSS backdrop-filter, which Ghostty
|
||||||
|
# takes as custom CSS and reloads live; its background-blur speaks only KDE's
|
||||||
|
# blur protocol.
|
||||||
|
glass ghostty.conf '' \
|
||||||
|
'^[[:space:]]*background-opacity[[:space:]]*=' 'background-opacity = 0.65' \
|
||||||
|
'' "gtk-custom-css = $OMARCHY_PATH/default/gtk/glass.css" || status=1
|
||||||
|
|
||||||
|
# Other GTK apps, such as Nautilus, read the user's gtk.css at launch, so the
|
||||||
|
# same backdrop blur is imported there. It follows the mode alone, so a theme
|
||||||
|
# switch, which names the theme it is rendering, leaves it be.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
gtk_css="${XDG_CONFIG_HOME:-$HOME/.config}/gtk-4.0/gtk.css"
|
||||||
|
remove_block "$gtk_css" "/*" " */" || status=1
|
||||||
|
if [[ $GLASSY == "true" ]]; then
|
||||||
|
{ mkdir -p "${gtk_css%/*}" &&
|
||||||
|
append_block "$gtk_css" "/*" " */" "@import url(\"file://$OMARCHY_PATH/default/gtk/glass.css\");"; } || status=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit $status
|
||||||
@@ -451,3 +451,4 @@ if [[ -f $COLORS_FILE ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
apply_shell_section_overrides
|
apply_shell_section_overrides
|
||||||
|
omarchy-theme-set-glass "$NEXT_THEME_DIR"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Toggle animations, transparency and other effects that are slow without a GPU
|
# omarchy:summary=Toggle animations, blur and shadows, which are slow without a GPU
|
||||||
# omarchy:args=[toggle|on|off]
|
# omarchy:args=[toggle|on|off]
|
||||||
# omarchy:examples=omarchy toggle animations | omarchy toggle animations off | omarchy toggle animations on
|
# 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
|
esac
|
||||||
|
|
||||||
if omarchy-hyprland-toggle-enabled no-animations; then
|
if omarchy-hyprland-toggle-enabled no-animations; then
|
||||||
omarchy-notification-send -g "Animations disabled"
|
omarchy-notification-send -g "Animations disabled"
|
||||||
else
|
else
|
||||||
omarchy-notification-send -g "Animations enabled"
|
omarchy-notification-send -g "Animations enabled"
|
||||||
fi
|
fi
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Toggle bar visibility without killing the Omarchy shell
|
# omarchy:summary=Toggle bar visibility without killing the Omarchy shell (on hides the bar, off shows it)
|
||||||
# omarchy:args=[toggle|on|off]
|
# omarchy:args=[toggle|on|off]
|
||||||
# omarchy:examples=omarchy toggle bar | omarchy toggle bar off | omarchy toggle bar on
|
# omarchy:examples=omarchy toggle bar | omarchy toggle bar off | omarchy toggle bar on
|
||||||
|
|
||||||
|
|||||||
@@ -1807,6 +1807,8 @@ copy_missing_config_defaults() {
|
|||||||
while IFS= read -r -d '' source_path; do
|
while IFS= read -r -d '' source_path; do
|
||||||
rel="${source_path#$source_dir/}"
|
rel="${source_path#$source_dir/}"
|
||||||
is_retired_config_file "$rel" && continue
|
is_retired_config_file "$rel" && continue
|
||||||
|
# The input migration preserves working shortcuts in existing IME profiles.
|
||||||
|
[[ $rel == "fcitx5/config" ]] && continue
|
||||||
|
|
||||||
target_path="$target_dir/$rel"
|
target_path="$target_dir/$rel"
|
||||||
[[ -e $target_path || -L $target_path ]] && continue
|
[[ -e $target_path || -L $target_path ]] && continue
|
||||||
|
|||||||
@@ -234,6 +234,11 @@ if [[ -n $MIME_TYPES ]]; then
|
|||||||
printf 'MimeType=%s\n' "$(desktop_string_escape "$MIME_TYPES")" >>"$DESKTOP_FILE"
|
printf 'MimeType=%s\n' "$(desktop_string_escape "$MIME_TYPES")" >>"$DESKTOP_FILE"
|
||||||
fi
|
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"
|
chmod +x "$DESKTOP_FILE"
|
||||||
|
|
||||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
||||||
|
|||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/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'
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Keep Pinyin offline without prompting to enable cloud prediction.
|
||||||
|
FirstRun=False
|
||||||
|
CloudPinyinEnabled=False
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[TriggerKey]
|
||||||
|
0=Super+semicolon
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Allow Overriding System XKB Settings=False
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Omarchy binds Super+I. Leave Ctrl+Space to tmux/Herdr and other Super keys to the desktop.
|
||||||
|
[Hotkey/TriggerKeys]
|
||||||
|
0=Zenkaku_Hankaku
|
||||||
|
1=Hangul
|
||||||
|
|
||||||
|
[Hotkey/ActivateKeys]
|
||||||
|
0=Henkan
|
||||||
|
|
||||||
|
[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]
|
||||||
|
|
||||||
|
[Hotkey/EnumerateGroupBackwardKeys]
|
||||||
|
|
||||||
|
[Behavior]
|
||||||
|
ActiveByDefault=False
|
||||||
@@ -6,9 +6,12 @@
|
|||||||
name = "terminal"
|
name = "terminal"
|
||||||
|
|
||||||
[theme.custom]
|
[theme.custom]
|
||||||
# The active tab is drawn as panel_bg text on an accent background, so panel_bg
|
# The tab bar sits on the terminal's own background, like tmux's bg=default, so
|
||||||
# has to be dark for it to read - same colors as status-left's "#[fg=black,bg=blue]"
|
# it turns see-through with the rest of the terminal under Glassy windows. With
|
||||||
panel_bg = "black"
|
# 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"
|
||||||
|
|
||||||
[terminal]
|
[terminal]
|
||||||
# Matches -c "#{pane_current_path}" on every split, window, and session
|
# Matches -c "#{pane_current_path}" on every split, window, and session
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-- input = {
|
-- input = {
|
||||||
-- -- Use multiple keyboard layouts and switch between them with Left Alt + Right Alt.
|
-- -- Use multiple keyboard layouts and switch between them with Left Alt + Right Alt.
|
||||||
-- kb_layout = "us,dk,eu",
|
-- kb_layout = "us,dk,eu",
|
||||||
-- kb_options = "compose:caps,shift:both_capslock_cancel,grp:alts_toggle",
|
-- kb_options = "omarchy:shift_caps_compose,grp:alts_toggle",
|
||||||
--
|
--
|
||||||
-- -- Use a specific keyboard variant if needed (e.g. intl for international keyboards).
|
-- -- Use a specific keyboard variant if needed (e.g. intl for international keyboards).
|
||||||
-- kb_variant = "intl",
|
-- kb_variant = "intl",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"bar": {
|
"bar": {
|
||||||
"position": "top",
|
"position": "top",
|
||||||
"transparent": false,
|
"background": "opaque",
|
||||||
"centerAnchor": "omarchy.clock",
|
"centerAnchor": "omarchy.clock",
|
||||||
"layout": {
|
"layout": {
|
||||||
"left": [
|
"left": [
|
||||||
@@ -30,9 +30,6 @@
|
|||||||
{
|
{
|
||||||
"id": "omarchy.elsewhen"
|
"id": "omarchy.elsewhen"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "omarchy.keyboard-layout"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "omarchy.weather"
|
"id": "omarchy.weather"
|
||||||
},
|
},
|
||||||
@@ -41,6 +38,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"right": [
|
"right": [
|
||||||
|
{
|
||||||
|
"id": "omarchy.keyboard-layout"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "omarchy.tray"
|
"id": "omarchy.tray"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,268 +0,0 @@
|
|||||||
<?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 copyLastPageSettings=true size=595.275591x841.889764 backgroundType=lined backgroundColor=#ffffff "/>
|
|
||||||
<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>
|
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
name: omarchy
|
name: omarchy
|
||||||
description: >
|
description: >
|
||||||
REQUIRED for end-user customization of Linux desktop, window manager, or system config.
|
REQUIRED for end-user customization of Linux desktop, window manager, or system config.
|
||||||
Use when editing ~/.config/hypr/, ~/.config/omarchy/,
|
Use when editing ANY file in ~/.config/, ~/.config/hypr/, ~/.config/omarchy/,
|
||||||
~/.config/alacritty/, ~/.config/foot/, ~/.config/kitty/, or ~/.config/ghostty/.
|
~/.config/alacritty/, ~/.config/foot/, ~/.config/kitty/, or ~/.config/ghostty/.
|
||||||
Triggers: Hyprland, window rules, animations, keybindings, monitors, gaps, borders,
|
Triggers: Hyprland, window rules, window behavior, animations, keybindings, monitors, gaps, borders,
|
||||||
blur, opacity, omarchy-shell, bar, terminal config, themes, background,
|
blur, opacity, omarchy-shell, bar, widgets, terminal config, themes, fonts, background,
|
||||||
night light, idle, lock screen, screenshots, reminders, layer rules, workspace
|
night light, idle, lock screen, screenshots, screen recording, reminders, layer rules, workspace
|
||||||
settings, display config, and user-facing omarchy commands. Excludes Omarchy
|
settings, display config, appearance changes, and user-facing omarchy commands. Excludes Omarchy
|
||||||
source development through `omarchy dev link` workflows.
|
source development through `omarchy dev link` workflows.
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -18,24 +18,6 @@ Manage [Omarchy](https://omarchy.org/) Linux systems - a beautiful, fun, agentic
|
|||||||
This skill is for end-user customization on installed systems.
|
This skill is for end-user customization on installed systems.
|
||||||
It is not for contributing to Omarchy source code.
|
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
|
## Topic Guides
|
||||||
|
|
||||||
Deeper instructions for common areas live next to this file. Read the
|
Deeper instructions for common areas live next to this file. Read the
|
||||||
|
|||||||
@@ -17,6 +17,25 @@ changes. `idle.screensaver` and `idle.lock` are seconds since user idle began.
|
|||||||
|
|
||||||
**Commands:** `omarchy restart shell`, `omarchy refresh shell`
|
**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
|
## Bar Layout
|
||||||
|
|
||||||
Use the `omarchy bar` group to move and manage widgets:
|
Use the `omarchy bar` group to move and manage widgets:
|
||||||
|
|||||||
@@ -1,6 +1,298 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||||
<fontconfig>
|
<fontconfig>
|
||||||
|
<!-- CJK: fold the language tag onto one tag per Noto CJK variant -->
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="eq">
|
||||||
|
<string>zh</string>
|
||||||
|
</test>
|
||||||
|
<edit name="lang" mode="assign">
|
||||||
|
<string>zh-cn</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="eq">
|
||||||
|
<string>zh-hant</string>
|
||||||
|
</test>
|
||||||
|
<edit name="lang" mode="assign">
|
||||||
|
<string>zh-tw</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-hant-hk</string>
|
||||||
|
</test>
|
||||||
|
<edit name="lang" mode="assign">
|
||||||
|
<string>zh-hk</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-hant-mo</string>
|
||||||
|
</test>
|
||||||
|
<edit name="lang" mode="assign">
|
||||||
|
<string>zh-hk</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-hant</string>
|
||||||
|
</test>
|
||||||
|
<edit name="lang" mode="assign">
|
||||||
|
<string>zh-tw</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-hans</string>
|
||||||
|
</test>
|
||||||
|
<edit name="lang" mode="assign">
|
||||||
|
<string>zh-cn</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-sg</string>
|
||||||
|
</test>
|
||||||
|
<edit name="lang" mode="assign">
|
||||||
|
<string>zh-cn</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-mo</string>
|
||||||
|
</test>
|
||||||
|
<edit name="lang" mode="assign">
|
||||||
|
<string>zh-hk</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="eq">
|
||||||
|
<string>yue</string>
|
||||||
|
</test>
|
||||||
|
<edit name="lang" mode="assign">
|
||||||
|
<string>zh-hk</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>yue-hans</string>
|
||||||
|
</test>
|
||||||
|
<edit name="lang" mode="assign">
|
||||||
|
<string>zh-cn</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>yue-cn</string>
|
||||||
|
</test>
|
||||||
|
<edit name="lang" mode="assign">
|
||||||
|
<string>zh-cn</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>yue</string>
|
||||||
|
</test>
|
||||||
|
<edit name="lang" mode="assign">
|
||||||
|
<string>zh-hk</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<!-- Then append that variant after the generic family it falls back from -->
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-cn</string>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>sans-serif</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Sans CJK SC</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-cn</string>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>serif</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Serif CJK SC</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-cn</string>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>monospace</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Sans Mono CJK SC</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-hk</string>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>sans-serif</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Sans CJK HK</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-hk</string>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>serif</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Serif CJK HK</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-hk</string>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>monospace</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Sans Mono CJK HK</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-tw</string>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>sans-serif</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Sans CJK TC</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-tw</string>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>serif</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Serif CJK TC</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<string>zh-tw</string>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>monospace</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Sans Mono CJK TC</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<langset><string>ja</string></langset>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>sans-serif</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Sans CJK JP</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<langset><string>ja</string></langset>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>serif</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Serif CJK JP</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<langset><string>ja</string></langset>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>monospace</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Sans Mono CJK JP</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<langset><string>ko</string></langset>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>sans-serif</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Sans CJK KR</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<langset><string>ko</string></langset>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>serif</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Serif CJK KR</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<match target="pattern">
|
||||||
|
<test name="lang" qual="first" compare="contains">
|
||||||
|
<langset><string>ko</string></langset>
|
||||||
|
</test>
|
||||||
|
<test name="family">
|
||||||
|
<string>monospace</string>
|
||||||
|
</test>
|
||||||
|
<edit name="family" mode="append" binding="strong">
|
||||||
|
<string>Noto Sans Mono CJK KR</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
|
||||||
<match target="pattern">
|
<match target="pattern">
|
||||||
<test name="family" qual="any">
|
<test name="family" qual="any">
|
||||||
<string>sans-serif</string>
|
<string>sans-serif</string>
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/* 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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
-- Same terminal tag test as clipboard.lua.
|
||||||
|
local function active_window_is_terminal()
|
||||||
|
local window = hl.get_active_window()
|
||||||
|
if not window then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, tag in ipairs(window.tags or {}) do
|
||||||
|
if tag:gsub("%*$", "") == "terminal" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Keep the desktop shortcut independent of Fcitx's custom switching keys.
|
||||||
|
-- Read the live Fcitx group so setup works without a compositor reload.
|
||||||
|
o.bind("SUPER + I", "Switch input language", "omarchy-input-method cycle")
|
||||||
|
o.bind("SUPER + SHIFT + I", "Switch input language back", "omarchy-input-method cycle back")
|
||||||
|
|
||||||
|
-- Pass the tmux/Herdr prefix through and return to direct input, so the next
|
||||||
|
-- key reaches the multiplexer instead of the engine's composition.
|
||||||
|
o.bind("CTRL + SPACE", "Direct input for the terminal prefix", function()
|
||||||
|
if active_window_is_terminal() then
|
||||||
|
-- Wait for the local controller reply before dispatching the next key.
|
||||||
|
o.shell_succeeds("timeout 0.3 fcitx5-remote --check -c")
|
||||||
|
end
|
||||||
|
end, { non_consuming = true })
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
-- Defaults for Japanese (JIS) keyboards, which only apply when the installed
|
||||||
|
-- layout is jp. See default/hypr/keyboard.lua.
|
||||||
|
|
||||||
|
local keyboard = require("default.hypr.keyboard")
|
||||||
|
|
||||||
|
if not keyboard.jis() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Same down/up split as the universal clipboard shortcuts in clipboard.lua.
|
||||||
|
local function send_shortcut_once(mods, key)
|
||||||
|
hl.dispatch(hl.dsp.send_key_state({ mods = mods, key = key, state = "down" }))
|
||||||
|
|
||||||
|
hl.timer(function()
|
||||||
|
hl.dispatch(hl.dsp.send_key_state({ mods = mods, key = key, state = "up" }))
|
||||||
|
end, { timeout = 50, type = "oneshot" })
|
||||||
|
end
|
||||||
|
|
||||||
|
local function active_window_class()
|
||||||
|
local window = hl.get_active_window()
|
||||||
|
return ((window and window.class) or ""):lower()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- On JIS, + is Shift + ;, so apps see Ctrl + Shift + semicolon and never zoom
|
||||||
|
-- in. Hand them Ctrl + keypad plus instead, which Chromium, Electron apps,
|
||||||
|
-- VS Code, and Firefox read as zoom in. Obsidian only listens for Ctrl + ^,
|
||||||
|
-- which JIS types without Shift.
|
||||||
|
local zoom_in_overrides = {
|
||||||
|
obsidian = { "CTRL", "asciicircum" },
|
||||||
|
}
|
||||||
|
|
||||||
|
o.bind("CTRL + SHIFT + semicolon", "Zoom in (JIS + key)", function()
|
||||||
|
local class = active_window_class()
|
||||||
|
|
||||||
|
for pattern, chord in pairs(zoom_in_overrides) do
|
||||||
|
if class:find(pattern, 1, true) then
|
||||||
|
send_shortcut_once(chord[1], chord[2])
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
send_shortcut_once("CTRL", "KP_Add")
|
||||||
|
end)
|
||||||
@@ -11,7 +11,8 @@ 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 + 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 + 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 + Home", "Restore window width", "omarchy-hyprland-window-width restore")
|
||||||
o.bind("SUPER + L", "Toggle workspace layout", "omarchy-hyprland-workspace-layout-toggle")
|
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 + LEFT", "Focus on left window", hl.dsp.focus({ direction = "l" }))
|
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" }))
|
o.bind("SUPER + RIGHT", "Focus on right window", hl.dsp.focus({ direction = "r" }))
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
-- 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
|
||||||
@@ -281,7 +281,8 @@ function o.window(match, rules)
|
|||||||
hl.window_rule(rules)
|
hl.window_rule(rules)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Opt a window in to Omarchy's standard active/inactive transparency.
|
-- Opt a window in to Omarchy's standard active/inactive transparency. The tag
|
||||||
|
-- lets Appearance > Window Transparency turn these windows opaque or glassy.
|
||||||
function o.transparent_window(match, opacity)
|
function o.transparent_window(match, opacity)
|
||||||
o.window(match, { opacity = opacity or "0.985 0.96" })
|
o.window(match, { opacity = opacity or "0.985 0.96", tag = "+transparent-window" })
|
||||||
end
|
end
|
||||||
+16
-26
@@ -1,40 +1,19 @@
|
|||||||
-- https://wiki.hypr.land/Configuring/Basics/Variables/#input
|
-- https://wiki.hypr.land/Configuring/Basics/Variables/#input
|
||||||
|
|
||||||
local function read_vconsole()
|
local keyboard = require("default.hypr.keyboard")
|
||||||
local values = {}
|
|
||||||
local file = io.open("/etc/vconsole.conf", "r")
|
|
||||||
if not file then
|
|
||||||
return values
|
|
||||||
end
|
|
||||||
|
|
||||||
for line in file:lines() do
|
|
||||||
local key, value = line:match("^%s*([%w_]+)%s*=%s*(.-)%s*$")
|
|
||||||
if key and value then
|
|
||||||
value = value:gsub("%s+#.*$", "")
|
|
||||||
value = value:gsub('^"(.*)"$', "%1")
|
|
||||||
value = value:gsub("^'(.*)'$", "%1")
|
|
||||||
values[key] = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
file:close()
|
|
||||||
return values
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Layouts that can't type Latin letters. Keep in sync with the list in
|
-- Layouts that can't type Latin letters. Keep in sync with the list in
|
||||||
-- etc/mkinitcpio.conf.d/omarchy_hooks.conf.
|
-- etc/mkinitcpio.conf.d/omarchy_hooks.conf.
|
||||||
local non_latin_layouts =
|
local non_latin_layouts =
|
||||||
" af am ara bd bg by et ge gr il in iq ir kg kh kz la lk mk mm mn mv np rs ru sy th tj ua "
|
" af am ara bd bg by et ge gr il in iq ir kg kh kz la lk mk mm mn mv np rs ru sy th tj ua "
|
||||||
|
|
||||||
local vconsole = read_vconsole()
|
local vconsole = keyboard.selected()
|
||||||
|
|
||||||
local kb_layout = vconsole.XKBLAYOUT or "us"
|
local kb_layout = vconsole.XKBLAYOUT or "us"
|
||||||
local kb_variant = vconsole.XKBVARIANT or ""
|
local kb_variant = vconsole.XKBVARIANT or ""
|
||||||
-- CapsLock is the compose key, so Caps Lock itself has to live somewhere else.
|
-- Caps Lock is Caps Lock, and Shift + Caps Lock is the compose key. Stock XKB
|
||||||
-- Both Shifts together is the usual home for it, but it's easy to hit by
|
-- has no such option, so Omarchy defines it in /etc/xkb.
|
||||||
-- accident while typing. The _cancel variant sets Caps Lock the same way and
|
local kb_options = "omarchy:shift_caps_compose"
|
||||||
-- 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
|
-- 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
|
-- layout that's currently active, so Omarchy's Latin-keysym bindings (SUPER + W
|
||||||
@@ -47,6 +26,17 @@ if non_latin_layouts:find(" " .. kb_layout:match("^[^,]*") .. " ", 1, true) then
|
|||||||
kb_options = kb_options .. ",grp:alts_toggle"
|
kb_options = kb_options .. ",grp:alts_toggle"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Saved selections already include their Latin-leading layout. Keep the
|
||||||
|
-- installer's layout-switching shortcut when a non-Latin layout remains.
|
||||||
|
if not kb_options:find("grp:alts_toggle", 1, true) then
|
||||||
|
for layout in kb_layout:gmatch("[^,]+") do
|
||||||
|
if non_latin_layouts:find(" " .. layout .. " ", 1, true) then
|
||||||
|
kb_options = kb_options .. ",grp:alts_toggle"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
hl.config({
|
hl.config({
|
||||||
input = {
|
input = {
|
||||||
kb_layout = kb_layout,
|
kb_layout = kb_layout,
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
-- The keyboard layout picked at install time. The installer and first-boot
|
||||||
|
-- setup persist it to /etc/vconsole.conf, so Hyprland reads it from there
|
||||||
|
-- instead of rewriting every user's config.
|
||||||
|
|
||||||
|
local keyboard = {}
|
||||||
|
|
||||||
|
local function read_values(path)
|
||||||
|
local values = {}
|
||||||
|
local file = io.open(path, "r")
|
||||||
|
if not file then
|
||||||
|
return values
|
||||||
|
end
|
||||||
|
|
||||||
|
for line in file:lines() do
|
||||||
|
local key, value = line:match("^%s*([%w_]+)%s*=%s*(.-)%s*$")
|
||||||
|
if key and value then
|
||||||
|
value = value:gsub("%s+#.*$", "")
|
||||||
|
value = value:gsub('^"(.*)"$', "%1")
|
||||||
|
value = value:gsub("^'(.*)'$", "%1")
|
||||||
|
values[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
file:close()
|
||||||
|
return values
|
||||||
|
end
|
||||||
|
|
||||||
|
function keyboard.selected()
|
||||||
|
local paths = require("default.hypr.paths")
|
||||||
|
local values = read_values(paths.config_home .. "/omarchy/keyboard-layouts")
|
||||||
|
if values.XKBLAYOUT then return values end
|
||||||
|
return keyboard.installed()
|
||||||
|
end
|
||||||
|
|
||||||
|
function keyboard.vconsole()
|
||||||
|
return read_values("/etc/vconsole.conf")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Choices whose console keymap is plain US but whose desktop layout is not:
|
||||||
|
-- Korean keeps US letters, and Lao is non-Latin, so US still leads.
|
||||||
|
local desktop_overrides = { kr = true, la = true }
|
||||||
|
|
||||||
|
function keyboard.installed()
|
||||||
|
local values = keyboard.vconsole()
|
||||||
|
local override = read_values("/etc/omarchy/input-method").XKB_LAYOUT
|
||||||
|
if desktop_overrides[override]
|
||||||
|
and (values.XKBLAYOUT or "us") == "us"
|
||||||
|
and (values.XKBVARIANT or "") == "" then
|
||||||
|
values.XKBLAYOUT, values.XKBVARIANT = override, ""
|
||||||
|
end
|
||||||
|
return values
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Japanese (JIS) keyboards put +, ^, and the Henkan/Muhenkan keys where US
|
||||||
|
-- keyboards don't, so a few defaults only apply when that layout leads.
|
||||||
|
function keyboard.jis()
|
||||||
|
local layout = keyboard.selected().XKBLAYOUT or "us"
|
||||||
|
return layout:match("^[^,]*") == "jp"
|
||||||
|
end
|
||||||
|
|
||||||
|
return keyboard
|
||||||
@@ -14,7 +14,9 @@ hl.config({
|
|||||||
inactive_border = inactive_border_color,
|
inactive_border = inactive_border_color,
|
||||||
},
|
},
|
||||||
|
|
||||||
resize_on_border = false,
|
resize_on_border = true,
|
||||||
|
extend_border_grab_area = 8,
|
||||||
|
hover_icon_on_border = true,
|
||||||
allow_tearing = false,
|
allow_tearing = false,
|
||||||
layout = "dwindle",
|
layout = "dwindle",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ require("default.hypr.autostart")
|
|||||||
if _G.omarchy_default_bindings ~= false then
|
if _G.omarchy_default_bindings ~= false then
|
||||||
require("default.hypr.bindings.media")
|
require("default.hypr.bindings.media")
|
||||||
require("default.hypr.bindings.clipboard")
|
require("default.hypr.bindings.clipboard")
|
||||||
|
require("default.hypr.bindings.input-method")
|
||||||
|
require("default.hypr.bindings.jis")
|
||||||
require("default.hypr.bindings.tiling")
|
require("default.hypr.bindings.tiling")
|
||||||
require("default.hypr.bindings.utilities")
|
require("default.hypr.bindings.utilities")
|
||||||
require("default.hypr.bindings.dictation")
|
require("default.hypr.bindings.dictation")
|
||||||
|
|||||||
@@ -1,9 +1,38 @@
|
|||||||
|
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.
|
-- Hyprland polls cursor.invisible, so its first frames need a blank cursor too.
|
||||||
function omarchy_startup_cursor_restore(loaded)
|
function omarchy_startup_cursor_restore(loaded)
|
||||||
if not omarchy_startup_cursor_pending then return end
|
if not omarchy_startup_cursor_pending then return end
|
||||||
local cursor = omarchy_startup_cursor
|
local cursor = omarchy_startup_cursor
|
||||||
if loaded then
|
if loaded then
|
||||||
omarchy_startup_cursor_pending = false
|
omarchy_startup_cursor_pending = false
|
||||||
|
hl.env("OMARCHY_STARTUP_CURSOR_SESSION", "")
|
||||||
|
hl.env("OMARCHY_STARTUP_CURSOR_THEME", "")
|
||||||
hl.config({ cursor = cursor.config })
|
hl.config({ cursor = cursor.config })
|
||||||
if cursor.config.enable_hyprcursor and cursor.hyprcursor then
|
if cursor.config.enable_hyprcursor and cursor.hyprcursor then
|
||||||
hl.exec_cmd("hyprctl setcursor " .. o.shell_quote(cursor.hyprcursor) .. " " .. cursor.size)
|
hl.exec_cmd("hyprctl setcursor " .. o.shell_quote(cursor.hyprcursor) .. " " .. cursor.size)
|
||||||
@@ -11,15 +40,25 @@ function omarchy_startup_cursor_restore(loaded)
|
|||||||
elseif not cursor.restoring then
|
elseif not cursor.restoring then
|
||||||
-- Reload the normal Xcursor fallback before enabling Hyprcursor or GSettings.
|
-- Reload the normal Xcursor fallback before enabling Hyprcursor or GSettings.
|
||||||
cursor.restoring = true
|
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
|
hl.exec_cmd("hyprctl setcursor " .. o.shell_quote(cursor.xcursor) .. " " .. cursor.size
|
||||||
.. " && hyprctl eval 'omarchy_startup_cursor_restore(true)'")
|
.. " && hyprctl eval " .. o.shell_quote(complete))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
hl.on("config.reloaded", function()
|
hl.on("config.reloaded", function()
|
||||||
|
local recover = false
|
||||||
if omarchy_startup_cursor_pending == nil then
|
if omarchy_startup_cursor_pending == nil then
|
||||||
-- Config updates in an existing compositor must not hide its pointer.
|
-- An ordinary reload must not hide the pointer, but an interrupted reveal survives.
|
||||||
omarchy_startup_cursor_pending = #hl.get_monitors() == 0
|
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
|
||||||
if omarchy_startup_cursor_pending then
|
if omarchy_startup_cursor_pending then
|
||||||
local hyprcursor = hl.get_config("cursor.enable_hyprcursor")
|
local hyprcursor = hl.get_config("cursor.enable_hyprcursor")
|
||||||
omarchy_startup_cursor = {
|
omarchy_startup_cursor = {
|
||||||
@@ -30,17 +69,31 @@ hl.on("config.reloaded", function()
|
|||||||
},
|
},
|
||||||
hyprcursor = os.getenv("HYPRCURSOR_THEME"),
|
hyprcursor = os.getenv("HYPRCURSOR_THEME"),
|
||||||
size = tonumber((hyprcursor and os.getenv("HYPRCURSOR_SIZE")) or os.getenv("XCURSOR_SIZE")) or 24,
|
size = tonumber((hyprcursor and os.getenv("HYPRCURSOR_SIZE")) or os.getenv("XCURSOR_SIZE")) or 24,
|
||||||
path = os.getenv("XCURSOR_PATH") or "~/.local/share/icons:~/.icons:/usr/share/icons:/usr/share/pixmaps",
|
path = normal_path(),
|
||||||
xcursor = os.getenv("XCURSOR_THEME") or "default",
|
xcursor = normal_theme(interrupted),
|
||||||
}
|
}
|
||||||
if omarchy_startup_cursor.size <= 0 then omarchy_startup_cursor.size = 24 end
|
if omarchy_startup_cursor.size <= 0 then omarchy_startup_cursor.size = 24 end
|
||||||
hl.env("XCURSOR_PATH", os.getenv("OMARCHY_PATH") .. "/default/hypr/cursors:" .. omarchy_startup_cursor.path)
|
-- Compositor environment survives Lua reloads; a session id prevents stale inheritance.
|
||||||
hl.env("XCURSOR_THEME", "omarchy-startup")
|
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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if omarchy_startup_cursor_pending then
|
if omarchy_startup_cursor_pending then
|
||||||
-- Keep the temporary cursor private to the compositor, including GSettings.
|
-- Keep the temporary cursor private to the compositor, including GSettings.
|
||||||
hl.config({ cursor = { invisible = true, enable_hyprcursor = false, sync_gsettings_theme = false } })
|
hl.config({ cursor = { invisible = true, enable_hyprcursor = false, sync_gsettings_theme = false } })
|
||||||
hl.timer(function() omarchy_startup_cursor_restore() end, { timeout = 15000, type = "oneshot" })
|
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
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- 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,
|
||||||
|
})
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- Round window corners, whatever the theme sets. Matches Solitude's corners.
|
||||||
|
hl.config({
|
||||||
|
decoration = {
|
||||||
|
rounding = 6,
|
||||||
|
rounding_power = 3,
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- Square window corners, whatever the theme sets.
|
||||||
|
hl.config({
|
||||||
|
decoration = {
|
||||||
|
rounding = 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
-- Draw nothing just for show, for machines that render on the CPU, like a VM:
|
-- Draw nothing just for show, for machines that render on the CPU, like a VM:
|
||||||
-- no animations, blur or shadows, and opaque windows, so nothing behind a
|
-- no animations, blur or shadows. How see-through windows are is left to
|
||||||
-- window has to be blended into it.
|
-- Appearance > Window Transparency.
|
||||||
hl.config({
|
hl.config({
|
||||||
animations = {
|
animations = {
|
||||||
enabled = false,
|
enabled = false,
|
||||||
@@ -16,5 +16,3 @@ hl.config({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
o.window(".*", { opacity = "1 1" })
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
-- 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,
|
||||||
|
})
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Opaque windows, even those Omarchy makes transparent by default.
|
||||||
|
o.window({ tag = "transparent-window" }, { opacity = "1 1" })
|
||||||
@@ -3,6 +3,19 @@
|
|||||||
local paths = require("default.hypr.paths")
|
local paths = require("default.hypr.paths")
|
||||||
local require_all = require("default.hypr.require_all")
|
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"
|
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 })
|
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()
|
||||||
@@ -0,0 +1,385 @@
|
|||||||
|
-- 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
|
||||||
@@ -0,0 +1,332 @@
|
|||||||
|
"""Fcitx defaults for offline finalization and updates."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from contextlib import contextmanager
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(os.environ["OMARCHY_PATH"])
|
||||||
|
PRESETS = json.loads((ROOT / "default/input-methods/presets.json").read_text())
|
||||||
|
NON_LATIN = set("af am ara bd bg by et ge gr il in iq ir kg kh kz la lk mk mm mn mv np rs ru sy th tj ua".split())
|
||||||
|
CONTROLLER = ["busctl", "--user", "call", "org.fcitx.Fcitx5", "/controller", "org.fcitx.Fcitx.Controller1"]
|
||||||
|
|
||||||
|
|
||||||
|
def run(args, **kwargs):
|
||||||
|
return subprocess.run(args, check=True, text=True, capture_output=True, **kwargs).stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def read(path):
|
||||||
|
return path.read_text() if path.exists() else ""
|
||||||
|
|
||||||
|
|
||||||
|
def values(text):
|
||||||
|
result = {}
|
||||||
|
for line in text.splitlines():
|
||||||
|
match = re.match(r"^\s*([\w ]+)\s*=\s*(.*?)\s*(?:\s+#.*)?$", line)
|
||||||
|
if match:
|
||||||
|
result[match[1].strip()] = match[2].strip("\"'")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def sections(text):
|
||||||
|
result = {}
|
||||||
|
current = ""
|
||||||
|
for line in text.splitlines():
|
||||||
|
match = re.match(r"^\s*\[([^]]+)\]\s*$", line)
|
||||||
|
if match:
|
||||||
|
current = match[1]
|
||||||
|
result.setdefault(current, {})
|
||||||
|
elif current:
|
||||||
|
result[current].update(values(line))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def atomic_write(path, text):
|
||||||
|
# Follow a user's config symlink rather than replacing the link itself.
|
||||||
|
path = path.resolve()
|
||||||
|
if read(path) == text:
|
||||||
|
return False
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
mode = path.stat().st_mode & 0o777 if path.exists() else 0o644
|
||||||
|
fd, temporary = tempfile.mkstemp(prefix=".omarchy-input-", dir=path.parent)
|
||||||
|
try:
|
||||||
|
with os.fdopen(fd, "w") as file:
|
||||||
|
file.write(text)
|
||||||
|
os.chmod(temporary, mode)
|
||||||
|
os.replace(temporary, path)
|
||||||
|
finally:
|
||||||
|
if os.path.exists(temporary):
|
||||||
|
os.unlink(temporary)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def key_list(entries):
|
||||||
|
# Fcitx canonicalizes modifiers (e.g. Shift+Super instead of Super+Shift).
|
||||||
|
return {frozenset(value.split("+")) for value in entries.values()}
|
||||||
|
|
||||||
|
|
||||||
|
def without_ctrl_space(text):
|
||||||
|
# Ctrl+Space belongs to tmux and Herdr, and the terminal binding returns to
|
||||||
|
# direct input on it; a Fcitx toggle on the same key would undo that. Without
|
||||||
|
# a TriggerKeys list Fcitx falls back to its default, which has Ctrl+Space.
|
||||||
|
section = re.search(r"(?ms)^\[Hotkey/TriggerKeys\][ \t]*\n(.*?)(?=^\[|\Z)", text)
|
||||||
|
if not section:
|
||||||
|
return text.rstrip() + ("\n\n" if text.strip() else "") + "[Hotkey/TriggerKeys]\n0=Zenkaku_Hankaku\n1=Hangul\n"
|
||||||
|
keys = values(section[1])
|
||||||
|
kept = [keys[key] for key in sorted(keys, key=lambda key: int(key) if key.isdigit() else 0) if keys[key] != "Control+space"]
|
||||||
|
if len(kept) == len(keys):
|
||||||
|
return text
|
||||||
|
body = "".join(f"{index}={key}\n" for index, key in enumerate(kept)) + ("\n" if section[1].endswith("\n\n") else "")
|
||||||
|
return text[:section.start(1)] + body + text[section.end(1):]
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config_home, fresh=False):
|
||||||
|
changed = False
|
||||||
|
files = ["config", "conf/quickphrase.conf", "conf/wayland.conf", "conf/xcb.conf", "conf/pinyin.conf"]
|
||||||
|
for name in files:
|
||||||
|
target = config_home / "fcitx5" / name
|
||||||
|
original = read(target)
|
||||||
|
shipped = read(ROOT / "config/fcitx5" / name)
|
||||||
|
if name == "config" or name == "conf/quickphrase.conf":
|
||||||
|
if name == "config":
|
||||||
|
original = without_ctrl_space(original)
|
||||||
|
existing = sections(original)
|
||||||
|
additions = []
|
||||||
|
for section, entries in sections(shipped).items():
|
||||||
|
# Missing hotkey sections also represent a choice: Fcitx's defaults.
|
||||||
|
if not fresh and section.startswith("Hotkey/"):
|
||||||
|
continue
|
||||||
|
# Existing lists (including explicitly empty lists) are user choices.
|
||||||
|
# A fresh profile has no working IME shortcut to preserve.
|
||||||
|
stock_lists = {
|
||||||
|
"Hotkey/ActivateKeys": {"0": "Hangul_Hanja"},
|
||||||
|
"Hotkey/DeactivateKeys": {"0": "Hangul_Romaja"},
|
||||||
|
"Hotkey/EnumerateGroupForwardKeys": {"0": "Super+space"},
|
||||||
|
"Hotkey/EnumerateGroupBackwardKeys": {"0": "Super+Shift+space"},
|
||||||
|
"TriggerKey": {"0": "Super+grave", "1": "Super+semicolon"},
|
||||||
|
}
|
||||||
|
if fresh and section in existing and section in stock_lists and (key_list(existing[section]) == key_list(stock_lists[section]) or (not existing[section] and section in {"Hotkey/ActivateKeys", "Hotkey/DeactivateKeys"})):
|
||||||
|
original = re.sub(r"(?ms)^\[" + re.escape(section) + r"\]\s*\n.*?(?=^\[|\Z)", "", original)
|
||||||
|
existing.pop(section)
|
||||||
|
if section not in existing:
|
||||||
|
additions.append(f"[{section}]\n" + "".join(f"{key}={value}\n" for key, value in entries.items()))
|
||||||
|
if additions:
|
||||||
|
original = original.rstrip() + ("\n\n" if original.strip() else "") + "\n".join(additions)
|
||||||
|
changed |= atomic_write(target, original)
|
||||||
|
elif name == "conf/pinyin.conf":
|
||||||
|
# Suppress the first-use cloud prompt while preserving any explicit
|
||||||
|
# cloud-prediction preference and all other existing engine settings.
|
||||||
|
if not original:
|
||||||
|
updated = shipped
|
||||||
|
elif "FirstRun" in values(original):
|
||||||
|
updated = re.sub(r"(?m)^\s*FirstRun\s*=.*$", "FirstRun=False", original)
|
||||||
|
else:
|
||||||
|
updated = "FirstRun=False\n" + original
|
||||||
|
changed |= atomic_write(target, updated)
|
||||||
|
elif not target.exists() and not target.is_symlink():
|
||||||
|
changed |= atomic_write(target, shipped)
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
|
def activation_default():
|
||||||
|
data_home = Path(os.environ.get("XDG_DATA_HOME") or str(Path.home() / ".local/share"))
|
||||||
|
path = data_home / "dbus-1/services/org.fcitx.Fcitx5.service"
|
||||||
|
original = read(path)
|
||||||
|
# Repair the package's plain activation entry, but retain custom launchers.
|
||||||
|
packaged = values(original).get("Exec") in ("/usr/bin/fcitx5", "/usr/bin/fcitx5 -d")
|
||||||
|
if not original or (packaged and "SystemdService" not in values(original)):
|
||||||
|
return atomic_write(path, read(ROOT / "default/input-methods/org.fcitx.Fcitx5.service"))
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def transaction(config_home):
|
||||||
|
data_home = Path(os.environ.get("XDG_DATA_HOME") or str(Path.home() / ".local/share"))
|
||||||
|
paths = [config_home / "fcitx5" / name for name in ["profile", "config", "conf/quickphrase.conf", "conf/wayland.conf", "conf/xcb.conf", "conf/pinyin.conf"]]
|
||||||
|
paths += [config_home / "fontconfig/conf.d/50-omarchy-input-method.conf", data_home / "dbus-1/services/org.fcitx.Fcitx5.service"]
|
||||||
|
before = {path.resolve(): path.read_text() if path.exists() else None for path in paths}
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except BaseException:
|
||||||
|
for path, original in before.items():
|
||||||
|
if original is None:
|
||||||
|
path.unlink(missing_ok=True)
|
||||||
|
else:
|
||||||
|
atomic_write(path, original)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def selection():
|
||||||
|
vconsole = values(read(Path(os.environ.get("OMARCHY_VCONSOLE", "/etc/vconsole.conf"))))
|
||||||
|
preference = values(read(Path(os.environ.get("OMARCHY_INPUT_SELECTION", "/etc/omarchy/input-method"))))
|
||||||
|
layout = vconsole.get("XKBLAYOUT", "us").split(",")[0] or "us"
|
||||||
|
variant = vconsole.get("XKBVARIANT", "").split(",")[0]
|
||||||
|
if preference.get("XKB_LAYOUT") in ("kr", "la") and vconsole.get("XKBLAYOUT", "us") == "us" and not variant:
|
||||||
|
layout, variant = preference["XKB_LAYOUT"], ""
|
||||||
|
method = preference.get("INPUT_METHOD", "mozc" if layout == "jp" else "none")
|
||||||
|
if method not in PRESETS:
|
||||||
|
raise ValueError(f"Unknown installed input method: {method}")
|
||||||
|
if layout in NON_LATIN:
|
||||||
|
layout, variant = "us", ""
|
||||||
|
group_layout = layout + (f"-{variant}" if variant else "")
|
||||||
|
if not re.fullmatch(r"[A-Za-z0-9_-]+", group_layout):
|
||||||
|
raise ValueError("Invalid installed keyboard layout")
|
||||||
|
return method, group_layout
|
||||||
|
|
||||||
|
|
||||||
|
def stock_profile(text):
|
||||||
|
if not text.strip():
|
||||||
|
return True
|
||||||
|
groups = sections(text)
|
||||||
|
items = [data.get("Name", "") for key, data in groups.items() if re.fullmatch(r"Groups/\d+/Items/\d+", key)]
|
||||||
|
group = groups.get("Groups/0", {})
|
||||||
|
return (items == ["keyboard-us"]
|
||||||
|
and list(key for key in groups if re.fullmatch(r"Groups/\d+", key)) == ["Groups/0"]
|
||||||
|
and group.get("Name", "Default") == "Default"
|
||||||
|
and group.get("Default Layout", "us") == "us"
|
||||||
|
and not groups.get("Groups/0/Items/0", {}).get("Layout"))
|
||||||
|
|
||||||
|
|
||||||
|
def profile_text(layout, method):
|
||||||
|
default_im = method if method != "none" else f"keyboard-{layout}"
|
||||||
|
text = f"[Groups/0]\nName=Default\nDefault Layout={layout}\nDefaultIM={default_im}\n\n[Groups/0/Items/0]\nName=keyboard-{layout}\nLayout=\n"
|
||||||
|
if method != "none":
|
||||||
|
text += f"\n[Groups/0/Items/1]\nName={method}\nLayout=\n"
|
||||||
|
return text + "\n[GroupOrder]\n0=Default\n"
|
||||||
|
|
||||||
|
|
||||||
|
def font_default(config_home, method):
|
||||||
|
variant = PRESETS[method]["font"]
|
||||||
|
path = config_home / "fontconfig/conf.d/50-omarchy-input-method.conf"
|
||||||
|
if not variant or path.exists() or path.is_symlink():
|
||||||
|
return
|
||||||
|
# Only CJK fallback changes; retain the interface and terminal's Latin font.
|
||||||
|
rules = []
|
||||||
|
for families, face in [("sans-serif|Liberation Sans", "Sans"), ("serif|Liberation Serif", "Serif"), ("monospace|JetBrainsMono Nerd Font", "Sans Mono")]:
|
||||||
|
for family in families.split("|"):
|
||||||
|
rules.append(f'<alias><family>{family}</family><accept><family>Noto {face} CJK {variant}</family></accept></alias>')
|
||||||
|
atomic_write(path, '<?xml version="1.0"?>\n<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">\n<fontconfig>\n' + "\n".join(rules) + "\n</fontconfig>\n")
|
||||||
|
|
||||||
|
|
||||||
|
def live_group():
|
||||||
|
group = run(["fcitx5-remote", "-q"])
|
||||||
|
info = json.loads(run(CONTROLLER[:1] + ["--json=short"] + CONTROLLER[1:] + ["InputMethodGroupInfo", "s", group]))["data"]
|
||||||
|
layout, items = info[:2]
|
||||||
|
return group, layout, items
|
||||||
|
|
||||||
|
|
||||||
|
def live_set(group, layout, items):
|
||||||
|
args = [group, layout, str(len(items))]
|
||||||
|
for name, item_layout in items:
|
||||||
|
args.extend([name, item_layout])
|
||||||
|
run(CONTROLLER + ["SetInputMethodGroupInfo", "ssa(ss)"] + args)
|
||||||
|
run(CONTROLLER + ["Save"])
|
||||||
|
|
||||||
|
|
||||||
|
def reload_bus():
|
||||||
|
# Offline finalization and updates over SSH may have no session bus.
|
||||||
|
try:
|
||||||
|
run(["busctl", "--user", "call", "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ReloadConfig"])
|
||||||
|
except (OSError, subprocess.CalledProcessError, RuntimeError) as error:
|
||||||
|
print(f"Input activation reload deferred until login: {error}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def available_methods():
|
||||||
|
return json.loads(run(CONTROLLER[:1] + ["--json=short"] + CONTROLLER[1:] + ["AvailableInputMethods"]))["data"][0]
|
||||||
|
|
||||||
|
|
||||||
|
def wait_ready(method):
|
||||||
|
for attempt in range(100):
|
||||||
|
try:
|
||||||
|
available = available_methods()
|
||||||
|
if any(item[0] == method for item in available) and live_group()[2]:
|
||||||
|
return
|
||||||
|
except (OSError, ValueError, KeyError, IndexError, subprocess.CalledProcessError):
|
||||||
|
pass
|
||||||
|
time.sleep(0.1)
|
||||||
|
raise RuntimeError(f"Fcitx did not load the {method} engine after restart")
|
||||||
|
|
||||||
|
|
||||||
|
def configure(args):
|
||||||
|
config_home = Path(os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config"))
|
||||||
|
running = subprocess.run(["pgrep", "-u", str(os.getuid()), "-x", "fcitx5"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0
|
||||||
|
# Finalization can be retried after login. A live daemon owns its profile.
|
||||||
|
if args.seed and running:
|
||||||
|
return
|
||||||
|
with transaction(config_home):
|
||||||
|
configure_inner(args, config_home, running)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_inner(args, config_home, running):
|
||||||
|
method, layout = selection()
|
||||||
|
profile = config_home / "fcitx5/profile"
|
||||||
|
original = read(profile)
|
||||||
|
if args.seed:
|
||||||
|
if not stock_profile(original):
|
||||||
|
return
|
||||||
|
atomic_write(profile, profile_text(layout, method))
|
||||||
|
defaults(config_home, fresh=True)
|
||||||
|
activation_default()
|
||||||
|
font_default(config_home, method)
|
||||||
|
return
|
||||||
|
|
||||||
|
fresh = stock_profile(original)
|
||||||
|
changed = defaults(config_home, fresh=fresh)
|
||||||
|
if activation_default():
|
||||||
|
reload_bus()
|
||||||
|
# Upgrades can run through sudo/SSH without access to the live user's bus.
|
||||||
|
# Never overwrite its profile or restart input in that context.
|
||||||
|
if fresh:
|
||||||
|
if running:
|
||||||
|
try:
|
||||||
|
group, old_layout, items = live_group()
|
||||||
|
if old_layout == "us" and items == [["keyboard-us", ""]]:
|
||||||
|
new_items = [[f"keyboard-{layout}", ""]]
|
||||||
|
if method != "none":
|
||||||
|
if any(item[0] == method for item in available_methods()):
|
||||||
|
new_items.append([method, ""])
|
||||||
|
else:
|
||||||
|
print(f"Finish input setup after login: omarchy setup input {method}", file=sys.stderr)
|
||||||
|
live_set(group, layout, new_items)
|
||||||
|
if live_group()[1:] != (layout, new_items):
|
||||||
|
live_set(group, old_layout, items)
|
||||||
|
raise RuntimeError("Fcitx did not retain the installed keyboard settings")
|
||||||
|
except (OSError, ValueError, KeyError, IndexError, RuntimeError, subprocess.CalledProcessError) as error:
|
||||||
|
print(f"Input profile alignment deferred until login: {error}", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
atomic_write(profile, profile_text(layout, method))
|
||||||
|
if running and changed:
|
||||||
|
try:
|
||||||
|
run(CONTROLLER + ["ReloadConfig"])
|
||||||
|
except (OSError, subprocess.CalledProcessError) as error:
|
||||||
|
print(f"Input defaults reload deferred until login: {error}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
commands = parser.add_subparsers(dest="command", required=True)
|
||||||
|
commands.add_parser("status")
|
||||||
|
configure_parser = commands.add_parser("configure")
|
||||||
|
mode = configure_parser.add_mutually_exclusive_group(required=True)
|
||||||
|
mode.add_argument("--seed", action="store_true")
|
||||||
|
mode.add_argument("--defaults", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.command == "status":
|
||||||
|
method, layout = selection()
|
||||||
|
print(json.dumps({"installed_method": method, "layout": layout}))
|
||||||
|
else:
|
||||||
|
configure(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except (OSError, ValueError, RuntimeError, subprocess.CalledProcessError) as error:
|
||||||
|
raise SystemExit(f"Input method setup failed: {error}")
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
"""Live Fcitx state for the existing keyboard layout widget."""
|
||||||
|
|
||||||
|
from collections import Counter
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from gi.repository import Gio, GLib
|
||||||
|
|
||||||
|
|
||||||
|
BUS = "org.fcitx.Fcitx5"
|
||||||
|
INTERFACE = "org.fcitx.Fcitx.Controller1"
|
||||||
|
|
||||||
|
|
||||||
|
def call(bus, method, args=None):
|
||||||
|
# Reading the bar must neither activate a stopped service nor wait indefinitely.
|
||||||
|
return bus.call_sync(BUS, "/controller", INTERFACE, method, args, None,
|
||||||
|
Gio.DBusCallFlags.NO_AUTO_START, 1000, None).unpack()
|
||||||
|
|
||||||
|
|
||||||
|
def snapshot(bus):
|
||||||
|
group = call(bus, "CurrentInputMethodGroup")[0]
|
||||||
|
methods = list(dict.fromkeys(item[0] for item in
|
||||||
|
call(bus, "InputMethodGroupInfo", GLib.Variant("(s)", (group,)))[1]))
|
||||||
|
info = call(bus, "CurrentInputMethodInfo")
|
||||||
|
return {"methods": methods, "current": info[0], "name": info[1], "label": info[4], "language": info[5]}
|
||||||
|
|
||||||
|
|
||||||
|
def layout_keyboard(keyboards):
|
||||||
|
typed = [keyboard for keyboard in keyboards if not re.match(
|
||||||
|
r"^(hl-virtual-keyboard|power-button|sleep-button|lid-switch|video-bus)", keyboard.get("name", ""))]
|
||||||
|
typed = [item for item in typed if len(item.get("layout", "").split(",")) > 1]
|
||||||
|
if not typed:
|
||||||
|
return None
|
||||||
|
# Consumer controls also carry the seat layout. Its frequency keeps an
|
||||||
|
# individually configured keyboard from taking over the desktop shortcut.
|
||||||
|
counts = Counter(item.get("layout", "") for item in keyboards)
|
||||||
|
layout = max((item["layout"] for item in typed), key=lambda layout: counts[layout])
|
||||||
|
keyboard = max((item for item in typed if item["layout"] == layout),
|
||||||
|
key=lambda item: item.get("active_layout_index", 0))
|
||||||
|
return keyboard
|
||||||
|
|
||||||
|
|
||||||
|
def layout_switches(keyboards, index=None, step=1):
|
||||||
|
keyboard = layout_keyboard(keyboards)
|
||||||
|
if keyboard is None:
|
||||||
|
return []
|
||||||
|
layout = keyboard["layout"]
|
||||||
|
following = index if index is not None else (keyboard.get("active_layout_index", 0) + step) % len(layout.split(","))
|
||||||
|
return [["hyprctl", "switchxkblayout", item["name"], str(following)]
|
||||||
|
for item in keyboards if item.get("layout") == layout and item.get("name")]
|
||||||
|
|
||||||
|
|
||||||
|
def cycle_choice(state, keyboards, step=1):
|
||||||
|
methods = state.get("methods", [])
|
||||||
|
keyboard = layout_keyboard(keyboards)
|
||||||
|
primary = next((method for method in methods if method.startswith("keyboard-")), None)
|
||||||
|
if keyboard is None or primary is None:
|
||||||
|
current = state.get("current")
|
||||||
|
if len(methods) > 1 and current in methods:
|
||||||
|
return methods[(methods.index(current) + step) % len(methods)], None
|
||||||
|
return None, None
|
||||||
|
# One direct-input method represents all compositor layouts. Composition
|
||||||
|
# engines follow them in the user's configured order.
|
||||||
|
choices = []
|
||||||
|
for method in methods:
|
||||||
|
if method == primary:
|
||||||
|
choices.extend((method, index) for index in range(len(keyboard["layout"].split(","))))
|
||||||
|
else:
|
||||||
|
choices.append((method, None))
|
||||||
|
current = state.get("current") or primary
|
||||||
|
position = (current, keyboard.get("active_layout_index", 0) if current == primary else None)
|
||||||
|
if position not in choices:
|
||||||
|
return None, None
|
||||||
|
return choices[(choices.index(position) + step) % len(choices)]
|
||||||
|
|
||||||
|
|
||||||
|
class Indicator:
|
||||||
|
def __init__(self, bus):
|
||||||
|
self.bus = bus
|
||||||
|
self.pending = ""
|
||||||
|
self.last = ""
|
||||||
|
self.entries = {}
|
||||||
|
|
||||||
|
def select_next(self, step=1):
|
||||||
|
devices = subprocess.run(["hyprctl", "-j", "devices"], check=True, text=True, capture_output=True)
|
||||||
|
keyboards = json.loads(devices.stdout).get("keyboards", [])
|
||||||
|
try:
|
||||||
|
state = snapshot(self.bus)
|
||||||
|
except GLib.Error:
|
||||||
|
# Without Fcitx, Super+I still cycles the compositor layouts.
|
||||||
|
for command in layout_switches(keyboards, step=step):
|
||||||
|
subprocess.run(command, check=True, capture_output=True)
|
||||||
|
return
|
||||||
|
methods = state["methods"]
|
||||||
|
current = self.pending or state["current"] or self.last
|
||||||
|
state["current"] = current if current in methods else next(iter(methods), "")
|
||||||
|
following, index = cycle_choice(state, keyboards, step)
|
||||||
|
if following is None:
|
||||||
|
return
|
||||||
|
self.pending = following
|
||||||
|
for command in layout_switches(keyboards, index) if index is not None else []:
|
||||||
|
subprocess.run(command, check=True, capture_output=True)
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
state = snapshot(self.bus)
|
||||||
|
methods = state["methods"]
|
||||||
|
if self.pending not in methods:
|
||||||
|
self.pending = ""
|
||||||
|
if self.pending and state["current"]:
|
||||||
|
# Before the first application gains focus Fcitx has no input context.
|
||||||
|
# Retain the click until it has one, without changing startup defaults.
|
||||||
|
call(self.bus, "SetCurrentIM", GLib.Variant("(s)", (self.pending,)))
|
||||||
|
state = snapshot(self.bus)
|
||||||
|
if state["current"] == self.pending:
|
||||||
|
self.pending = ""
|
||||||
|
if state["current"]:
|
||||||
|
self.last = state["current"]
|
||||||
|
else:
|
||||||
|
chosen = self.pending or (self.last if self.last in methods else "") or next(iter(methods), "")
|
||||||
|
if chosen not in self.entries:
|
||||||
|
self.entries = {entry[0]: entry for entry in call(self.bus, "AvailableInputMethods")[0]}
|
||||||
|
entry = self.entries.get(chosen)
|
||||||
|
if entry:
|
||||||
|
state.update(current=entry[0], name=entry[1], label=entry[4], language=entry[5])
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def watch(bus):
|
||||||
|
previous = None
|
||||||
|
indicator = Indicator(bus)
|
||||||
|
loop = GLib.MainLoop()
|
||||||
|
buffer = ""
|
||||||
|
timer = 0
|
||||||
|
|
||||||
|
def refresh():
|
||||||
|
nonlocal previous
|
||||||
|
try:
|
||||||
|
state = indicator.refresh()
|
||||||
|
except GLib.Error:
|
||||||
|
indicator.pending = ""
|
||||||
|
indicator.last = ""
|
||||||
|
state = {"methods": [], "current": "", "name": "", "language": ""}
|
||||||
|
line = json.dumps(state, ensure_ascii=False)
|
||||||
|
if line != previous:
|
||||||
|
print(line, flush=True)
|
||||||
|
previous = line
|
||||||
|
|
||||||
|
def poll():
|
||||||
|
nonlocal timer
|
||||||
|
refresh()
|
||||||
|
timer = GLib.timeout_add(50 if indicator.pending else 500, poll)
|
||||||
|
return GLib.SOURCE_REMOVE
|
||||||
|
|
||||||
|
def command(source, condition):
|
||||||
|
nonlocal buffer, timer
|
||||||
|
data = os.read(sys.stdin.fileno(), 4096)
|
||||||
|
if not data:
|
||||||
|
loop.quit()
|
||||||
|
return GLib.SOURCE_REMOVE
|
||||||
|
buffer += data.decode()
|
||||||
|
while "\n" in buffer:
|
||||||
|
line, buffer = buffer.split("\n", 1)
|
||||||
|
if line in ("cycle", "cycle back"):
|
||||||
|
try:
|
||||||
|
indicator.select_next(-1 if line == "cycle back" else 1)
|
||||||
|
except (GLib.Error, OSError, ValueError, subprocess.CalledProcessError):
|
||||||
|
indicator.pending = ""
|
||||||
|
refresh()
|
||||||
|
GLib.source_remove(timer)
|
||||||
|
timer = GLib.timeout_add(50 if indicator.pending else 500, poll)
|
||||||
|
return GLib.SOURCE_CONTINUE
|
||||||
|
|
||||||
|
# Fcitx's controller has no current-method-changed signal. Keep one bus
|
||||||
|
# connection open instead of spawning a command on every poll. Polling also
|
||||||
|
# follows per-application input contexts and recovers after service restarts.
|
||||||
|
GLib.io_add_watch(sys.stdin, GLib.IO_IN | GLib.IO_HUP, command)
|
||||||
|
poll()
|
||||||
|
loop.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
watch(Gio.bus_get_sync(Gio.BusType.SESSION, None))
|
||||||
|
except (GLib.Error, OSError, ValueError, subprocess.CalledProcessError) as error:
|
||||||
|
raise SystemExit(str(error))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Installed per-user so D-Bus activation uses the supervised Omarchy unit.
|
||||||
|
[D-BUS Service]
|
||||||
|
Name=org.fcitx.Fcitx5
|
||||||
|
Exec=/usr/bin/fcitx5
|
||||||
|
SystemdService=omarchy-fcitx5.service
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"none": {"label": "Keyboard only", "packages": [], "font": ""},
|
||||||
|
"mozc": {"label": "Japanese (Mozc)", "packages": ["fcitx5-mozc"], "font": "JP"},
|
||||||
|
"hangul": {"label": "Korean (Hangul)", "packages": ["fcitx5-hangul"], "font": "KR"},
|
||||||
|
"pinyin": {"label": "Chinese (Simplified, Pinyin)", "packages": ["fcitx5-chinese-addons"], "font": "SC"},
|
||||||
|
"chewing": {"label": "Chinese (Traditional, Zhuyin)", "packages": ["fcitx5-chewing"], "font": "TC"}
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
"""Native menu selectors for keyboard layouts and active input methods."""
|
||||||
|
|
||||||
|
from collections import Counter
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import configure as setup
|
||||||
|
|
||||||
|
|
||||||
|
def keyboard_catalog():
|
||||||
|
# Read the same choices as the ISO and first-boot form, then convert console
|
||||||
|
# keymaps through systemd's mapping, just as localectl does during install.
|
||||||
|
choices = setup.run(["bash", "-c", 'source "$1"; printf "%s\\n" "$OMARCHY_KEYBOARD_LAYOUTS"',
|
||||||
|
"bash", str(setup.ROOT / "install/provisioning/setup-form.sh")])
|
||||||
|
mappings = {}
|
||||||
|
for line in Path("/usr/share/systemd/kbd-model-map").read_text().splitlines():
|
||||||
|
fields = line.split()
|
||||||
|
if not fields or fields[0].startswith("#"):
|
||||||
|
continue
|
||||||
|
layout = fields[1].split(",")[0]
|
||||||
|
variant = fields[3].split(",")[0]
|
||||||
|
mappings.setdefault(fields[0], layout + (":" + variant if variant != "-" else ""))
|
||||||
|
# The console names systemd's table lacks convert as keyboard_xkb_settings in
|
||||||
|
# omarchy-provision-owner does, so a choice types what it did at install.
|
||||||
|
mappings.update({"bg-cp1251": "bg:phonetic", "colemak": "us:colemak", "cz": "cz:qwerty",
|
||||||
|
"kyrgyz": "kg", "de_CH-latin1": "ch", "no-latin1": "no"})
|
||||||
|
catalog = {}
|
||||||
|
for choice in choices.splitlines():
|
||||||
|
label, keymap, *input_settings = choice.split("|")
|
||||||
|
layout = input_settings[1] if len(input_settings) > 1 else mappings.get(keymap, keymap)
|
||||||
|
# Composition engines belong in Input Methods, not as duplicate US layouts.
|
||||||
|
catalog.setdefault(layout, label)
|
||||||
|
return catalog
|
||||||
|
|
||||||
|
|
||||||
|
def keyboard_selection():
|
||||||
|
layouts = json.loads(setup.run(["hyprctl", "-j", "getoption", "input:kb_layout"]))["str"].split(",")
|
||||||
|
variants = json.loads(setup.run(["hyprctl", "-j", "getoption", "input:kb_variant"]))["str"].split(",")
|
||||||
|
return [layout + (":" + variants[index] if index < len(variants) and variants[index] else "")
|
||||||
|
for index, layout in enumerate(layouts)]
|
||||||
|
|
||||||
|
|
||||||
|
def picker_rows(catalog, selected):
|
||||||
|
# Current entries stay first, in switching order, followed by the catalog.
|
||||||
|
keys = list(dict.fromkeys(selected + sorted(catalog, key=lambda key: catalog[key])))
|
||||||
|
labels = {key: catalog.get(key, key) for key in keys}
|
||||||
|
counts = Counter(labels.values())
|
||||||
|
rows = {key: "\t" + labels[key] + (f" ({key})" if counts[labels[key]] > 1 else "") for key in keys}
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def choose(title, catalog, selected, kind, overrides=None):
|
||||||
|
catalog = {**catalog, **{key: catalog.get(key, key) for key in selected}}
|
||||||
|
rows = picker_rows(catalog, selected)
|
||||||
|
args = ["omarchy-menu-select", title] + list(rows.values())
|
||||||
|
on_change = ["/usr/bin/python3", str(setup.ROOT / "default/input-methods/typing_setup.py"), "apply", kind, json.dumps({"catalog": catalog, "overrides": overrides or {}})]
|
||||||
|
args += ["--", "--multiple", "--width", "620", "--maxheight", "650", "--on-change", json.dumps(on_change), "--change-key", "typing:" + kind]
|
||||||
|
for key in selected:
|
||||||
|
args += ["--selected", rows[key][1:]]
|
||||||
|
result = subprocess.run(args, text=True, capture_output=True)
|
||||||
|
if result.returncode == 1:
|
||||||
|
return None
|
||||||
|
if result.returncode:
|
||||||
|
raise RuntimeError(result.stderr.strip() or "The selection menu could not open")
|
||||||
|
|
||||||
|
|
||||||
|
def input_catalog(items):
|
||||||
|
available = {item[0]: item[1] for item in setup.available_methods()}
|
||||||
|
catalog = {key: preset["label"] for key, preset in setup.PRESETS.items() if key != "none"}
|
||||||
|
for name, layout in items:
|
||||||
|
if not name.startswith("keyboard-"):
|
||||||
|
catalog.setdefault(name, available.get(name, name))
|
||||||
|
return catalog, available
|
||||||
|
|
||||||
|
|
||||||
|
def input_items(items, selected, overrides=None):
|
||||||
|
if not any(item[0].startswith("keyboard-") for item in items):
|
||||||
|
raise RuntimeError("The current input group has no keyboard input")
|
||||||
|
# Retained entries keep their switching order; new engines follow them.
|
||||||
|
kept = [item for item in items if item[0].startswith("keyboard-") or item[0] in selected]
|
||||||
|
current = dict(items)
|
||||||
|
return kept + [[name, (overrides or {}).get(name, "")] for name in selected if name not in current]
|
||||||
|
|
||||||
|
|
||||||
|
def set_inputs(group, layout, before, after):
|
||||||
|
if before == after:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
# Removing the active engine must return typing to the keyboard.
|
||||||
|
setup.run(["fcitx5-remote", "-c"])
|
||||||
|
setup.live_set(group, layout, after)
|
||||||
|
if setup.live_group() != (group, layout, after):
|
||||||
|
raise RuntimeError("Fcitx did not retain the input selection")
|
||||||
|
except BaseException:
|
||||||
|
setup.live_set(group, layout, before)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def configure_inputs():
|
||||||
|
group, layout, items = setup.live_group()
|
||||||
|
catalog, available = input_catalog(items)
|
||||||
|
current = [name for name, override in items if not name.startswith("keyboard-")]
|
||||||
|
choose("Input Methods", catalog, current, "input", dict(items))
|
||||||
|
|
||||||
|
|
||||||
|
def load_engines(names, catalog):
|
||||||
|
packages = sorted({package for name in names for package in setup.PRESETS.get(name, {}).get("packages", [])})
|
||||||
|
absent = [package for package in packages
|
||||||
|
if subprocess.run(["omarchy-pkg-present", package], capture_output=True).returncode]
|
||||||
|
if absent or any(name not in setup.PRESETS for name in names):
|
||||||
|
labels = ", ".join(catalog.get(name, name) for name in names)
|
||||||
|
raise RuntimeError(f"Install {' '.join(absent) or 'its engine'} to use {labels}")
|
||||||
|
# Fcitx discovers engines only at startup, so one installed while it was
|
||||||
|
# running (by an update, say) needs a restart before it can be selected.
|
||||||
|
setup.run(["omarchy-restart-xcompose"])
|
||||||
|
for name in names:
|
||||||
|
setup.wait_ready(name)
|
||||||
|
|
||||||
|
|
||||||
|
def save_inputs(selected, overrides=None):
|
||||||
|
group, layout, items = setup.live_group()
|
||||||
|
catalog, available = input_catalog(items)
|
||||||
|
missing = [name for name in selected if name not in available]
|
||||||
|
if missing:
|
||||||
|
load_engines(missing, catalog)
|
||||||
|
group, layout, items = setup.live_group()
|
||||||
|
set_inputs(group, layout, items, input_items(items, selected, overrides))
|
||||||
|
# Only a saved engine claims the CJK font fallback, and a failed font write
|
||||||
|
# returns the group so the menu's restored checks stay true.
|
||||||
|
config_home = Path(os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config"))
|
||||||
|
try:
|
||||||
|
for name in selected:
|
||||||
|
if name in setup.PRESETS:
|
||||||
|
setup.font_default(config_home, name)
|
||||||
|
except BaseException:
|
||||||
|
setup.live_set(group, layout, items)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def add_input(method):
|
||||||
|
group, layout, items = setup.live_group()
|
||||||
|
current = [name for name, override in items if not name.startswith("keyboard-")]
|
||||||
|
if method not in current:
|
||||||
|
save_inputs(current + [method])
|
||||||
|
|
||||||
|
|
||||||
|
def keyboard_values(selected):
|
||||||
|
if not selected:
|
||||||
|
raise ValueError("Select at least one keyboard layout")
|
||||||
|
parts = [value.split(":", 1) for value in selected]
|
||||||
|
# Preserve the installer's Latin-leading rule for desktop keybindings.
|
||||||
|
if parts[0][0] in setup.NON_LATIN:
|
||||||
|
parts = [["us"]] + [part for part in parts if part != ["us"]]
|
||||||
|
layouts = ",".join(part[0] for part in parts)
|
||||||
|
variants = ",".join(part[1] if len(part) > 1 else "" for part in parts)
|
||||||
|
return layouts, variants
|
||||||
|
|
||||||
|
|
||||||
|
def save_keyboard(selected):
|
||||||
|
layouts, variants = keyboard_values(selected)
|
||||||
|
config_home = Path(os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config"))
|
||||||
|
path = config_home / "omarchy/keyboard-layouts"
|
||||||
|
original = setup.read(path) if path.exists() else None
|
||||||
|
group, old_layout, before = setup.live_group()
|
||||||
|
group_changed = False
|
||||||
|
previous_errors = setup.run(["hyprctl", "configerrors"])
|
||||||
|
try:
|
||||||
|
setup.atomic_write(path, f"XKBLAYOUT={layouts}\nXKBVARIANT={variants}\n")
|
||||||
|
setup.run(["hyprctl", "reload"])
|
||||||
|
errors = setup.run(["hyprctl", "configerrors"])
|
||||||
|
if errors and errors != previous_errors:
|
||||||
|
raise RuntimeError(errors)
|
||||||
|
expected = [layout + (":" + variant if variant else "")
|
||||||
|
for layout, variant in zip(layouts.split(","), variants.split(","))]
|
||||||
|
if keyboard_selection() != expected:
|
||||||
|
raise RuntimeError("Your personal keyboard layout override takes precedence over this selection")
|
||||||
|
first_layout = layouts.split(",")[0]
|
||||||
|
first_variant = variants.split(",")[0]
|
||||||
|
group_layout = first_layout + ("-" + first_variant if first_variant else "")
|
||||||
|
primary = next((item[0] for item in before if item[0].startswith("keyboard-")), None)
|
||||||
|
if primary is None:
|
||||||
|
raise RuntimeError("The current input group has no keyboard input")
|
||||||
|
keyboard = "keyboard-" + group_layout
|
||||||
|
after = [[keyboard, ""]] + [item for item in before if item[0] not in (primary, keyboard)]
|
||||||
|
group_changed = True
|
||||||
|
setup.live_set(group, group_layout, after)
|
||||||
|
if setup.live_group() != (group, group_layout, after):
|
||||||
|
raise RuntimeError("Fcitx did not retain the keyboard selection")
|
||||||
|
except BaseException:
|
||||||
|
if original is None:
|
||||||
|
path.resolve().unlink(missing_ok=True)
|
||||||
|
else:
|
||||||
|
setup.atomic_write(path, original)
|
||||||
|
try:
|
||||||
|
setup.run(["hyprctl", "reload"])
|
||||||
|
finally:
|
||||||
|
if group_changed:
|
||||||
|
setup.live_set(group, old_layout, before)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def configure_keyboard():
|
||||||
|
current = keyboard_selection()
|
||||||
|
catalog = keyboard_catalog()
|
||||||
|
choose("Keyboard Layouts", catalog, current, "keyboard")
|
||||||
|
|
||||||
|
|
||||||
|
def apply_selection(kind, context, values):
|
||||||
|
catalog = context["catalog"]
|
||||||
|
rows = picker_rows(catalog, [])
|
||||||
|
valid = {row[1:]: key for key, row in rows.items()}
|
||||||
|
if not isinstance(values, list) or any(not isinstance(value, str) or value not in valid for value in values):
|
||||||
|
raise ValueError("The menu returned an invalid selection")
|
||||||
|
selected = list(dict.fromkeys(valid[value] for value in values))
|
||||||
|
if kind == "input":
|
||||||
|
save_inputs(selected, context.get("overrides"))
|
||||||
|
else:
|
||||||
|
save_keyboard(selected)
|
||||||
|
selected = keyboard_selection()
|
||||||
|
print(json.dumps([rows[key][1:] for key in selected]))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if sys.argv[1:2] == ["add"] and len(sys.argv) == 3:
|
||||||
|
try:
|
||||||
|
add_input(sys.argv[2])
|
||||||
|
except (OSError, ValueError, RuntimeError, subprocess.CalledProcessError) as error:
|
||||||
|
raise SystemExit(f"Input method could not be added: {error}")
|
||||||
|
return
|
||||||
|
applying = len(sys.argv) == 5 and sys.argv[1] == "apply"
|
||||||
|
kind = sys.argv[2] if applying else sys.argv[1] if len(sys.argv) == 2 else ""
|
||||||
|
if kind not in ("input", "keyboard"):
|
||||||
|
raise ValueError("Choose keyboard or input setup")
|
||||||
|
title = "Input methods" if kind == "input" else "Keyboard layouts"
|
||||||
|
try:
|
||||||
|
if applying:
|
||||||
|
apply_selection(kind, json.loads(sys.argv[3]), json.loads(sys.argv[4]))
|
||||||
|
elif kind == "input":
|
||||||
|
configure_inputs()
|
||||||
|
else:
|
||||||
|
configure_keyboard()
|
||||||
|
except (OSError, ValueError, RuntimeError, subprocess.CalledProcessError) as error:
|
||||||
|
subprocess.run(["omarchy-notification-send", title + " could not be updated", str(error)], check=True)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -53,7 +53,60 @@
|
|||||||
"learn.community": {"icon":"","label":"Community","action":"omarchy-launch-discord-community"},
|
"learn.community": {"icon":"","label":"Community","action":"omarchy-launch-discord-community"},
|
||||||
|
|
||||||
// Trigger
|
// 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.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.reminder": {"icon":"","label":"Reminder","aliases":["reminder"]},
|
||||||
"trigger.capture": {"icon":"","label":"Capture","aliases":["capture","screenshot","screenrecord","screen-record","screenrecording"]},
|
"trigger.capture": {"icon":"","label":"Capture","aliases":["capture","screenshot","screenrecord","screen-record","screenrecording"]},
|
||||||
"trigger.capture.screenshot": {"icon":"","label":"Screenshot","action":"omarchy-capture-screenshot"},
|
"trigger.capture.screenshot": {"icon":"","label":"Screenshot","action":"omarchy-capture-screenshot"},
|
||||||
@@ -68,7 +121,6 @@
|
|||||||
"trigger.capture.screenrecord.webcam": {"icon":"","label":"With desktop + microphone audio + webcam","when":"omarchy-hw-webcam","action":"omarchy-capture-screenrecording-with-webcam"},
|
"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.transcode": {"icon":"","label":"Transcode","action":"omarchy-transcode"},
|
||||||
"trigger.share": {"icon":"","label":"Share","aliases":["share"]},
|
"trigger.share": {"icon":"","label":"Share","aliases":["share"]},
|
||||||
"trigger.toggle": {"icon":"","label":"Toggle","aliases":["toggle","toggles"]},
|
|
||||||
"trigger.hardware": {"icon":"","label":"Hardware","aliases":["hardware","hw"]},
|
"trigger.hardware": {"icon":"","label":"Hardware","aliases":["hardware","hw"]},
|
||||||
"trigger.tests": {"icon":"","label":"Speed Test"},
|
"trigger.tests": {"icon":"","label":"Speed Test"},
|
||||||
"trigger.hardware.laptop-display": {"icon":"","label":"Laptop Display","when":"omarchy-hw-laptop","action":"omarchy-hyprland-monitor-internal toggle"},
|
"trigger.hardware.laptop-display": {"icon":"","label":"Laptop Display","when":"omarchy-hw-laptop","action":"omarchy-hyprland-monitor-internal toggle"},
|
||||||
@@ -87,18 +139,6 @@
|
|||||||
"trigger.share.file": {"icon":"","label":"File","action":"omarchy-menu-share file"},
|
"trigger.share.file": {"icon":"","label":"File","action":"omarchy-menu-share file"},
|
||||||
"trigger.share.folder": {"icon":"","label":"Folder","action":"omarchy-menu-share folder"},
|
"trigger.share.folder": {"icon":"","label":"Folder","action":"omarchy-menu-share folder"},
|
||||||
"trigger.share.receive": {"icon":"","label":"Receive","action":"uwsm-app -- localsend"},
|
"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.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"},
|
"trigger.tests.disk-speedtest": {"icon":"","label":"Disk Speed Test","action":"omarchy-shell shell summon omarchy.disk-speedtest"},
|
||||||
|
|
||||||
@@ -107,16 +147,9 @@
|
|||||||
"style.background": {"icon":"","label":"Background","aliases":["background","wallpaper"],"action":"background=$(omarchy-theme-bg-switcher); [[ -n $background ]] && omarchy-theme-bg-set \"$background\""},
|
"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.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.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.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/looknfeel.lua\""},
|
||||||
"style.screensaver": {"icon":"","label":"Screensaver"},
|
"style.screensaver": {"icon":"","label":"Screensaver"},
|
||||||
"style.about": {"icon":"","label":"About"},
|
"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.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.image": {"icon":"","label":"Set From Image","action":"omarchy-branding-about image"},
|
||||||
"style.about.default": {"icon":"","label":"Restore Default","action":"omarchy-branding-about reset"},
|
"style.about.default": {"icon":"","label":"Restore Default","action":"omarchy-branding-about reset"},
|
||||||
@@ -128,6 +161,9 @@
|
|||||||
"setup.monitors": {"icon":"","label":"Monitors","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/monitors.lua\""},
|
"setup.monitors": {"icon":"","label":"Monitors","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/monitors.lua\""},
|
||||||
"setup.keybindings": {"icon":"","label":"Keybindings","when":"[[ -f ~/.config/hypr/bindings.lua ]]","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/bindings.lua\""},
|
"setup.keybindings": {"icon":"","label":"Keybindings","when":"[[ -f ~/.config/hypr/bindings.lua ]]","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/bindings.lua\""},
|
||||||
"setup.input": {"icon":"","label":"Input","when":"[[ -f ~/.config/hypr/input.lua ]]","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/input.lua\""},
|
"setup.input": {"icon":"","label":"Input","when":"[[ -f ~/.config/hypr/input.lua ]]","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/input.lua\""},
|
||||||
|
"setup.typing": {"icon":"","label":"Typing"},
|
||||||
|
"setup.typing.keyboard": {"icon":"","label":"Keyboard Layouts","action":"omarchy-setup-keyboard"},
|
||||||
|
"setup.typing.input": {"icon":"","label":"Input Methods","action":"omarchy-setup-input"},
|
||||||
"setup.network": {"icon":"","label":"Network","aliases":["network"]},
|
"setup.network": {"icon":"","label":"Network","aliases":["network"]},
|
||||||
"setup.network.dns": {"icon":"","label":"DNS","aliases":["dns"]},
|
"setup.network.dns": {"icon":"","label":"DNS","aliases":["dns"]},
|
||||||
"setup.network.dns.dhcp": {"icon":"","label":"DHCP","checked":"[[ \"$(omarchy-dns)\" == \"DHCP\" ]]","action":"omarchy-dns DHCP"},
|
"setup.network.dns.dhcp": {"icon":"","label":"DHCP","checked":"[[ \"$(omarchy-dns)\" == \"DHCP\" ]]","action":"omarchy-dns DHCP"},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Fcitx5 input method (XCompose sequences)
|
Description=Fcitx5 input methods and XCompose sequences
|
||||||
# fcitx5 turns the CapsLock compose sequences in ~/.XCompose into text for
|
# fcitx5 turns the Shift + CapsLock compose sequences in ~/.XCompose into text for
|
||||||
# Wayland clients.
|
# Wayland clients.
|
||||||
#
|
#
|
||||||
# Wait for the compositor: fcitx5 needs WAYLAND_DISPLAY and DISPLAY, which uwsm
|
# Wait for the compositor: fcitx5 needs WAYLAND_DISPLAY and DISPLAY, which uwsm
|
||||||
@@ -19,7 +19,8 @@ PartOf=graphical-session.target
|
|||||||
ConditionEnvironment=WAYLAND_DISPLAY
|
ConditionEnvironment=WAYLAND_DISPLAY
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=dbus
|
||||||
|
BusName=org.fcitx.Fcitx5
|
||||||
# notificationitem duplicates the tray entry omarchy already renders itself.
|
# notificationitem duplicates the tray entry omarchy already renders itself.
|
||||||
ExecStart=/usr/bin/fcitx5 --disable notificationitem
|
ExecStart=/usr/bin/fcitx5 --disable notificationitem
|
||||||
# always, not on-failure: fcitx5 exits 0 when it detects another instance owning
|
# always, not on-failure: fcitx5 exits 0 when it detects another instance owning
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
[colors-dark]
|
[colors-dark]
|
||||||
foreground={{ foreground_strip }}
|
foreground={{ foreground_strip }}
|
||||||
background={{ background_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-foreground={{ selection_foreground_strip }}
|
||||||
selection-background={{ selection_background_strip }}
|
selection-background={{ selection_background_strip }}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ selection_background {{ selection_background }}
|
|||||||
cursor {{ bright_foreground }}
|
cursor {{ bright_foreground }}
|
||||||
cursor_text_color {{ background }}
|
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_border_color {{ accent }}
|
||||||
active_tab_background {{ accent }}
|
active_tab_background {{ accent }}
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ config/** ──► omarchy-settings /etc/skel/.config/
|
|||||||
/usr/share/omarchy/config/** (resync source)
|
/usr/share/omarchy/config/** (resync source)
|
||||||
etc/fastfetch/config.jsonc ──► omarchy-settings /etc/fastfetch/config.jsonc
|
etc/fastfetch/config.jsonc ──► omarchy-settings /etc/fastfetch/config.jsonc
|
||||||
etc/xdg/kitty/kitty.conf ──► omarchy-settings /etc/xdg/kitty/kitty.conf
|
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/
|
applications/*.desktop ──► omarchy-settings /etc/skel/.local/share/applications/
|
||||||
/usr/share/omarchy/applications/
|
/usr/share/omarchy/applications/
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Input methods
|
||||||
|
|
||||||
|
The install keyboard picker selects a console keymap plus an optional Fcitx engine. `install/provisioning/setup-form.sh` is shared by the ISO configurator and deferred first-boot owner setup. Its rows are `label|console keymap|input method|desktop XKB override`; the last two fields are optional, so ordinary keyboard choices keep their existing meaning.
|
||||||
|
|
||||||
|
The ISO carries the extra fields in `omarchy_install.input_method` and `omarchy_install.input_xkb_layout`. Both installation paths persist them in `/etc/omarchy/input-method` as `INPUT_METHOD` and `XKB_LAYOUT`. This file is parsed as data, never sourced. Korean and Lao have a real US console keymap and a `kr` or `la` desktop override (Lao is non-Latin, so US still leads on the desktop); Japanese uses `jp106`, or US when the US-keyboard choice is selected. Console and disk-unlock input remain ordinary keymaps.
|
||||||
|
|
||||||
|
`install/user/input-method.sh` calls `omarchy-input-method configure --seed` during user finalization, before graphical login. The standard engines are in the base package set so deferred provisioning and factory resets do not need network access to configure typing. `default/input-methods/presets.json` is the engine catalog used by the post-install selector.
|
||||||
|
|
||||||
|
`default/input-methods/configure.py` owns the shared configuration behavior. Offline seeding replaces only a missing profile or Fcitx's untouched single `keyboard-us` guess. The group layout matches Hyprland's effective leading layout and variant; non-Latin layouts retain the US-leading behavior used for desktop bindings. The selected composition engine is the group's `DefaultIM`, with `ActiveByDefault=False` so initial typing remains Latin. Existing multilingual profiles and explicitly chosen keyboard layouts are preserved.
|
||||||
|
|
||||||
|
`omarchy setup input <engine>` installs the selected engine's packages, then appends it through the same path as the Input Methods picker. Selecting an engine whose package is installed but which the running Fcitx has not discovered (one an update installed, say) restarts the supervised Fcitx unit once and waits for it; an engine whose package is missing is refused with the package name. Changes go through `InputMethodGroupInfo`, `SetInputMethodGroupInfo`, and `Save`, keeping the group name, layout, existing methods in their switching order, and their layout overrides. There is no parallel unsupervised daemon. A per-user D-Bus activation file delegates `org.fcitx.Fcitx5` to `omarchy-fcitx5.service`.
|
||||||
|
|
||||||
|
`Setup > Typing > Keyboard Layouts` and `Input Methods` use the native menu's multi-select mode, with current entries checked and changes applied immediately. `default/input-methods/typing_setup.py` uses the installer’s keyboard choices, converted to desktop layouts through systemd’s keymap table, and reads the current input group. Input selection replaces only the non-keyboard group items and retains selected engines' layout overrides; deselection does not uninstall packages. The standard composition engines are already shipped in the base package set. Closing a picker keeps changes already applied. Failed updates leave its checked state unchanged.
|
||||||
|
|
||||||
|
Keyboard selections are persisted as data in `~/.config/omarchy/keyboard-layouts`; `default.hypr.keyboard.selected()` reads them before falling back to installation defaults. Personal Hyprland overrides still take precedence. A save reloads and validates the compositor, aligns the Fcitx group's primary keyboard with the leading layout/variant, and restores the previous configuration and group on failure. Non-Latin selections keep the installer's Latin-leading rule for desktop shortcuts. System console, disk-unlock, and greeter layouts are unchanged.
|
||||||
|
|
||||||
|
Shortcut defaults reserve Ctrl+Space for Tmux/Herdr, Super+Space for the menu, Super+Shift+Space for the bar, and Super+grave for the console. Hyprland owns Super+I to cycle all selected compositor layouts followed by the configured Fcitx input methods, independently of Fcitx switching keys. With keyboard layouts alone, the same shortcut cycles the compositor layouts, keeping matching devices in sync even without the bar widget. The widget is enabled in the default right-hand bar layout and appears only when more than one input method or keyboard layout is configured. Fcitx handles Zenkaku/Hankaku and Hangul; Henkan activates and Muhenkan deactivates. Ctrl+Space is never a Fcitx toggle: `configure --defaults` removes it from any TriggerKeys list, keeping the user's other keys, and writes Omarchy's list where none exists, since Fcitx's built-in default includes it. Other existing lists, including empty ones, are preserved, and stock generated lists are repaired only while the user still has the single-keyboard default profile. A terminal Ctrl+Space returns to direct input before the following key, with a 300 ms timeout and no D-Bus activation. Wayland and XCB addons do not override the compositor's XKB settings.
|
||||||
|
|
||||||
|
The migration installs the standard engines and applies conservative defaults. It aligns an untouched Fcitx keyboard profile with the install layout, using the live controller when Fcitx is running. It leaves existing multilingual profiles and working trigger keys alone. Atomic file writes follow configuration symlinks, preserve file modes, and roll back on a failed seed or migration so a retry can complete. Migration bus errors defer profile alignment to the next login and do not restart input. If a pre-update daemon has not discovered a new engine, the migration aligns only its keyboard and prints the setup command.
|
||||||
|
|
||||||
|
Input selection does not change the system locale, repository region, desktop language, or Latin interface font. A new language setup seeds a CJK fallback preference once; additional languages do not overwrite it.
|
||||||
|
|
||||||
|
Focused checks live in `test/shell.d/input-method-test.sh`, `setup-form-test.sh`, `hyprland-keyboard-layout-test.sh`, and `hyprland-jis-bindings-test.sh`. `test/acceptance.d/input-methods-test.sh` exercises real composition in a disposable installed VM and restores the original user configuration. The ISO repository's keyboard tests cover transport and target persistence. Browser input must be verified against the shipped browser versions; the language selector is not a place to guess flags from older guides.
|
||||||
|
|
||||||
|
Default key values are checked against [Fcitx global configuration](https://github.com/fcitx/fcitx5/blob/master/src/lib/fcitx/globalconfig.cpp).
|
||||||
@@ -165,3 +165,10 @@ renders under the label, filters with it, and comes back as
|
|||||||
`label\tsubtext` so callers with same-named rows get a stable key. This is
|
`label\tsubtext` so callers with same-named rows get a stable key. This is
|
||||||
how the pickers behind menu actions (`omarchy-menu-plugin`,
|
how the pickers behind menu actions (`omarchy-menu-plugin`,
|
||||||
`omarchy-menu-timezone`, ...) present lists without owning any UI.
|
`omarchy-menu-timezone`, ...) present lists without owning any UI.
|
||||||
|
|
||||||
|
|
||||||
|
Pass `-- --multiple` to `omarchy-menu-select` for a multi-select request. Repeat `--selected <value>` to preselect returned values (labels plus any subtext, without icons). Clicking or pressing Enter toggles an option without closing the menu; Apply returns a JSON array in option order, including `[]` when all choices are deselected. Cancellation still exits 1. Selections survive filtering.
|
||||||
|
|
||||||
|
With `--on-change <JSON command array>`, each toggle runs that command with the proposed selection JSON appended as its final argument, and no Apply row is shown. The checkbox changes only after the command succeeds; a failed command retains the previous selection. Changes persist when the menu closes. The typing selectors use this mode.
|
||||||
|
|
||||||
|
For immediate multi-select requests, `--change-key <key>` identifies the setting being changed across picker openings. The menu keeps queued selections for that key when reopened, so a new toggle cannot discard a change still being saved. Without a key, the command array identifies the setting.
|
||||||
@@ -94,8 +94,8 @@ You can still install by hand: drop a plugin into
|
|||||||
`~/.config/omarchy/plugins/<id>/`, run `omarchy-shell shell rescanPlugins`, then
|
`~/.config/omarchy/plugins/<id>/`, run `omarchy-shell shell rescanPlugins`, then
|
||||||
`omarchy plugin enable <id>`. A bar widget starts in its declared default
|
`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
|
section; enabling a full bar replaces the one in use. `omarchy bar` drives the
|
||||||
bar from the CLI — `use | reset | defaults | position | transparent | put |
|
bar from the CLI — `use | reset | defaults | position | background | put |
|
||||||
move | set`, with placement flags such as `--section` and `--index`.
|
move | set | get`, with placement flags such as `--section` and `--index`.
|
||||||
The lower-level IPC methods remain available through `omarchy-shell shell ...`.
|
The lower-level IPC methods remain available through `omarchy-shell shell ...`.
|
||||||
|
|
||||||
## Elsewhen
|
## Elsewhen
|
||||||
@@ -121,7 +121,8 @@ or `omarchy.power`. There is no `bar` target.
|
|||||||
| `rescanPlugins` | re-walk plugin dirs and hot-reload plugin code |
|
| `rescanPlugins` | re-walk plugin dirs and hot-reload plugin code |
|
||||||
| `reloadConfig` | reload shell.json |
|
| `reloadConfig` | reload shell.json |
|
||||||
| `applyTheme <colorsB64> <shellB64>` | push theme colors + shell.toml |
|
| `applyTheme <colorsB64> <shellB64>` | push theme colors + shell.toml |
|
||||||
| `toggleBarTransparency` | flip the bar background between solid and transparent |
|
| `toggleBarTransparency` | cycle the bar background from clear to glassy to opaque |
|
||||||
|
| `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`) |
|
| `setPluginEnabled <id> <"true"\|…>` | flip enabled bit (`ok` / `unknown`) |
|
||||||
| `enablePlugin <id> <placementJson>` | enable and place in one mutation |
|
| `enablePlugin <id> <placementJson>` | enable and place in one mutation |
|
||||||
| `putBarWidget <id> <placementJson>` | place a widget only where absent (`omarchy bar put`) |
|
| `putBarWidget <id> <placementJson>` | place a widget only where absent (`omarchy bar put`) |
|
||||||
@@ -147,7 +148,7 @@ string on a miss.
|
|||||||
"bar": {
|
"bar": {
|
||||||
"id": "omarchy.bar",
|
"id": "omarchy.bar",
|
||||||
"position": "top",
|
"position": "top",
|
||||||
"transparent": false,
|
"background": "opaque",
|
||||||
"centerAnchor": "omarchy.clock",
|
"centerAnchor": "omarchy.clock",
|
||||||
"layout": {
|
"layout": {
|
||||||
"left": [ { "id": "omarchy.menu" } ],
|
"left": [ { "id": "omarchy.menu" } ],
|
||||||
|
|||||||
@@ -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`.
|
`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 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.
|
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.
|
||||||
|
|
||||||
## Grant lifecycle
|
## Grant lifecycle
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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.
|
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. Toggle > 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. Appearance > 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).
|
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).
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// 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)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 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() ]
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -15,7 +15,6 @@ bolt
|
|||||||
brightnessctl
|
brightnessctl
|
||||||
btop
|
btop
|
||||||
chromium
|
chromium
|
||||||
clang
|
|
||||||
cliamp
|
cliamp
|
||||||
cups
|
cups
|
||||||
cups-filters
|
cups-filters
|
||||||
@@ -34,7 +33,11 @@ eza
|
|||||||
fakeroot
|
fakeroot
|
||||||
fastfetch
|
fastfetch
|
||||||
fcitx5
|
fcitx5
|
||||||
|
fcitx5-chewing
|
||||||
|
fcitx5-chinese-addons
|
||||||
fcitx5-gtk
|
fcitx5-gtk
|
||||||
|
fcitx5-hangul
|
||||||
|
fcitx5-mozc
|
||||||
fcitx5-qt
|
fcitx5-qt
|
||||||
fd
|
fd
|
||||||
ffmpeg
|
ffmpeg
|
||||||
@@ -66,7 +69,6 @@ inotify-tools
|
|||||||
inxi
|
inxi
|
||||||
networkmanager
|
networkmanager
|
||||||
jq
|
jq
|
||||||
kdenlive
|
|
||||||
kernel-modules-hook
|
kernel-modules-hook
|
||||||
lazygit
|
lazygit
|
||||||
less
|
less
|
||||||
@@ -74,7 +76,6 @@ libsecret
|
|||||||
libvips
|
libvips
|
||||||
libyaml
|
libyaml
|
||||||
libreoffice-fresh
|
libreoffice-fresh
|
||||||
llvm
|
|
||||||
localsend
|
localsend
|
||||||
lua51
|
lua51
|
||||||
luarocks
|
luarocks
|
||||||
@@ -93,12 +94,12 @@ noto-fonts-cjk
|
|||||||
noto-fonts-emoji
|
noto-fonts-emoji
|
||||||
nss-mdns
|
nss-mdns
|
||||||
nvim
|
nvim
|
||||||
obs-studio
|
|
||||||
obsidian
|
obsidian
|
||||||
omacalc
|
omacalc
|
||||||
omacut
|
omacut
|
||||||
omawrite
|
omawrite
|
||||||
omarchy-nvim
|
omarchy-nvim
|
||||||
|
omarchy-hyprland-titlebars
|
||||||
omasnap
|
omasnap
|
||||||
# OWE owns desktop video backgrounds and feeds the lock screen its frames.
|
# OWE owns desktop video backgrounds and feeds the lock screen its frames.
|
||||||
owe
|
owe
|
||||||
@@ -125,6 +126,7 @@ quickshell
|
|||||||
ripgrep
|
ripgrep
|
||||||
ruby
|
ruby
|
||||||
sddm
|
sddm
|
||||||
|
signed
|
||||||
slurp
|
slurp
|
||||||
socat
|
socat
|
||||||
starship
|
starship
|
||||||
@@ -157,7 +159,6 @@ woff2-font-awesome
|
|||||||
xdg-desktop-portal-gtk
|
xdg-desktop-portal-gtk
|
||||||
xdg-desktop-portal-hyprland
|
xdg-desktop-portal-hyprland
|
||||||
xdg-terminal-exec
|
xdg-terminal-exec
|
||||||
xournalpp
|
|
||||||
yaru-icon-theme
|
yaru-icon-theme
|
||||||
yay
|
yay
|
||||||
yt-dlp
|
yt-dlp
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
# so it never reaches the shell as SIGINT. Act on the status, never on a trap.
|
# so it never reaches the shell as SIGINT. Act on the status, never on a trap.
|
||||||
#
|
#
|
||||||
# Callers supply `notice <message> <seconds>` for validation feedback, and set
|
# Callers supply `notice <message> <seconds>` for validation feedback, and set
|
||||||
# the variables these prompts write: keyboard, keyboard_label, username,
|
# the variables these prompts write: keyboard, keyboard_label,
|
||||||
|
# keyboard_input_method, keyboard_xkb_layout, username,
|
||||||
# password, password_confirmation, full_name, email_address, hostname, timezone.
|
# password, password_confirmation, full_name, email_address, hostname, timezone.
|
||||||
|
|
||||||
OMARCHY_FORM_BACK=1
|
OMARCHY_FORM_BACK=1
|
||||||
@@ -36,6 +37,8 @@ English (US, Colemak)|colemak
|
|||||||
Belarusian|by
|
Belarusian|by
|
||||||
Belgian|be-latin1
|
Belgian|be-latin1
|
||||||
Bulgarian|bg-cp1251
|
Bulgarian|bg-cp1251
|
||||||
|
Chinese (Simplified, Pinyin)|us|pinyin
|
||||||
|
Chinese (Traditional, Zhuyin)|us|chewing
|
||||||
Croatian|croat
|
Croatian|croat
|
||||||
Czech|cz
|
Czech|cz
|
||||||
Danish|dk-latin1
|
Danish|dk-latin1
|
||||||
@@ -54,10 +57,12 @@ Hungarian|hu
|
|||||||
Icelandic|is-latin1
|
Icelandic|is-latin1
|
||||||
Irish|ie
|
Irish|ie
|
||||||
Italian|it
|
Italian|it
|
||||||
Japanese|jp106
|
Japanese|jp106|mozc
|
||||||
|
Japanese (US keyboard)|us|mozc
|
||||||
Kazakh|kazakh
|
Kazakh|kazakh
|
||||||
|
Korean|us|hangul|kr
|
||||||
Kyrgyz|kyrgyz
|
Kyrgyz|kyrgyz
|
||||||
Lao|la-latin1
|
Lao|us||la
|
||||||
Latvian|lv
|
Latvian|lv
|
||||||
Lithuanian|lt
|
Lithuanian|lt
|
||||||
Macedonian|mk-utf
|
Macedonian|mk-utf
|
||||||
@@ -96,8 +101,22 @@ omarchy_prompt_keyboard() {
|
|||||||
gum choose --height 10 --selected "English (US)" --header "Select keyboard layout") && status=0 || status=$?
|
gum choose --height 10 --selected "English (US)" --header "Select keyboard layout") && status=0 || status=$?
|
||||||
((status == 0)) || return $status
|
((status == 0)) || return $status
|
||||||
|
|
||||||
keyboard_label="$choice"
|
local selection
|
||||||
keyboard=$(printf '%s\n' "$OMARCHY_KEYBOARD_LAYOUTS" | awk -F'|' -v c="$choice" '$1==c{print $2; exit}')
|
selection=$(printf '%s\n' "$OMARCHY_KEYBOARD_LAYOUTS" | awk -F'|' -v c="$choice" '$1==c{print; exit}')
|
||||||
|
[[ -n $selection ]] || return 1
|
||||||
|
IFS='|' read -r keyboard_label keyboard keyboard_input_method keyboard_xkb_layout <<< "$selection"
|
||||||
|
keyboard_input_method=${keyboard_input_method:-none}
|
||||||
|
keyboard_xkb_layout=${keyboard_xkb_layout:-}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Root-side persistence. Console keymaps remain real keymaps; the input
|
||||||
|
# preference survives separately for every user of the installed system.
|
||||||
|
omarchy_persist_input_selection() {
|
||||||
|
local target_root=${1:-} method=${keyboard_input_method:-none} layout=${keyboard_xkb_layout:-}
|
||||||
|
case "$method" in none | mozc | hangul | pinyin | chewing) ;; *) return 1 ;; esac
|
||||||
|
case "$layout" in "" | kr | la) ;; *) return 1 ;; esac
|
||||||
|
mkdir -p "$target_root/etc/omarchy"
|
||||||
|
printf 'INPUT_METHOD=%s\nXKB_LAYOUT=%s\n' "$method" "$layout" > "$target_root/etc/omarchy/input-method"
|
||||||
}
|
}
|
||||||
|
|
||||||
omarchy_prompt_username() {
|
omarchy_prompt_username() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ run_logged "$OMARCHY_INSTALL/user/theme.sh"
|
|||||||
run_logged "$OMARCHY_INSTALL/user/chromium.sh"
|
run_logged "$OMARCHY_INSTALL/user/chromium.sh"
|
||||||
run_logged "$OMARCHY_INSTALL/user/git.sh"
|
run_logged "$OMARCHY_INSTALL/user/git.sh"
|
||||||
run_logged "$OMARCHY_INSTALL/user/xcompose.sh"
|
run_logged "$OMARCHY_INSTALL/user/xcompose.sh"
|
||||||
|
run_logged "$OMARCHY_INSTALL/user/input-method.sh"
|
||||||
run_logged "$OMARCHY_INSTALL/user/mise-work.sh"
|
run_logged "$OMARCHY_INSTALL/user/mise-work.sh"
|
||||||
|
|
||||||
run_logged "$OMARCHY_INSTALL/user/hardware/asus/fix-audio-mixer.sh"
|
run_logged "$OMARCHY_INSTALL/user/hardware/asus/fix-audio-mixer.sh"
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
omarchy-input-method configure --defaults || echo "Input method defaults deferred; run omarchy setup input after login." >&2
|
||||||
|
|
||||||
systemctl --user daemon-reload
|
systemctl --user daemon-reload
|
||||||
|
|
||||||
# One at a time: given a list, systemctl enables none of it when it cannot find
|
# One at a time: given a list, systemctl enables none of it when it cannot find
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
# A VM usually renders on the CPU, where animations and transparency cost
|
# A VM usually renders on the CPU, where animations and transparency cost
|
||||||
# every frame, so start it without them. omarchy-toggle-animations brings them
|
# every frame, so start it without them and with opaque windows.
|
||||||
# back. This runs before any Hyprland session exists, so place the flag
|
# omarchy-toggle-animations and Appearance > Window Transparency bring them
|
||||||
# directly rather than through omarchy-hyprland-toggle, which reloads Hyprland.
|
# back. This runs before any Hyprland session exists, so place the flags
|
||||||
|
# directly rather than through the commands, which reload Hyprland.
|
||||||
if omarchy-hw-vm; then
|
if omarchy-hw-vm; then
|
||||||
echo "Detected a virtual machine. Turning off animations and transparency."
|
echo "Detected a virtual machine. Turning off animations and transparency."
|
||||||
mkdir -p "$HOME/.local/state/omarchy/toggles/hypr"
|
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/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
|
fi
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
omarchy-input-method configure --seed
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Set default XCompose that is triggered with CapsLock
|
# Set default XCompose that is triggered with Shift + CapsLock
|
||||||
tee ~/.XCompose >/dev/null <<EOF
|
tee ~/.XCompose >/dev/null <<EOF
|
||||||
# Run omarchy-restart-xcompose to apply changes
|
# Run omarchy-restart-xcompose to apply changes
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Welcome to Omarchy!
|
# 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 Kdenlive to OBS Studio. 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 [Hype](https://github.com/omacom/hype) to [Monologue](https://github.com/omacom/monologue). 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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ 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`.
|
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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).
|
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.
|
||||||
|
|
||||||
If you wish to use the scrolling layout as the default, you can set that in `~/.config/hypr/looknfeel.lua`:
|
If you wish to use the scrolling layout as the default, you can set that in `~/.config/hypr/looknfeel.lua`:
|
||||||
|
|
||||||
@@ -48,6 +50,12 @@ 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
|
### 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.
|
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.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ It's also the one piece of the desktop that's always on screen, so it's worth kn
|
|||||||
|
|
||||||
## What's on it by default
|
## What's on it by default
|
||||||
|
|
||||||
The bar has three sections. On the left sits the Omarchy logo (the menu launcher) and the workspace indicators. In the center you get the status indicators, the clock, the keyboard layout, the weather, and an Omarchy update badge. On the right: the system tray, agents, bluetooth, network, audio, display, and power.
|
The bar has three sections. On the left sits the Omarchy logo (the menu launcher) and the workspace indicators. In the center you get the status indicators, the clock, Elsewhen (a world clock), the weather, and an Omarchy update badge. On the right: the keyboard layout (when more than one is configured), the system tray, agents, bluetooth, network, audio, display, and power.
|
||||||
|
|
||||||
A few of those only show up when they have something to say. The keyboard layout appears only if you've configured more than one layout. The update badge appears only when there's an Omarchy update waiting. And the agents icon appears the first time Omarchy finds AI coding usage on the machine (see [AI](17-ai.md)).
|
A few of those only show up when they have something to say. The keyboard layout appears only if you've configured more than one layout. The update badge appears only when there's an Omarchy update waiting. And the agents icon appears the first time Omarchy finds AI coding usage on the machine (see [AI](17-ai.md)).
|
||||||
|
|
||||||
@@ -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.
|
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 toggle transparency. 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 cycle the bar background from clear to glassy to opaque. And drag any widget to reorder it or throw it into another section.
|
||||||
|
|
||||||
If you'd rather pick from a menu, **Style → Menu Bar** has both position and transparency.
|
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**.
|
||||||
|
|
||||||
The same things have commands, which is what you want for a [dotfiles](31-dotfiles.md) setup:
|
The same things have commands, which is what you want for a [dotfiles](31-dotfiles.md) setup:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
omarchy bar position bottom
|
omarchy bar position bottom
|
||||||
omarchy bar transparent toggle
|
omarchy bar background glassy # opaque, glassy, none, or cycle
|
||||||
omarchy bar move omarchy.clock --section center --index 0
|
omarchy bar move omarchy.clock --section center --index 0
|
||||||
omarchy bar set omarchy.clock format "HH:mm"
|
omarchy bar set omarchy.clock format "HH:mm"
|
||||||
omarchy bar defaults # back to the shipped layout
|
omarchy bar defaults # back to the shipped layout
|
||||||
@@ -104,7 +104,7 @@ omarchy plugin disable omarchy.weather
|
|||||||
|
|
||||||
## Hiding the bar
|
## 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 → Toggle → Menu 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**.
|
||||||
|
|
||||||
## The config file
|
## The config file
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ All of it is stored in `~/.config/omarchy/shell.json`, under the `bar` key. Here
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"bar": {
|
"bar": {
|
||||||
"position": "top",
|
"position": "top",
|
||||||
"transparent": false,
|
"background": "opaque",
|
||||||
"centerAnchor": "omarchy.clock",
|
"centerAnchor": "omarchy.clock",
|
||||||
"layout": {
|
"layout": {
|
||||||
"left": [{ "id": "omarchy.menu" }, { "id": "omarchy.workspaces" }],
|
"left": [{ "id": "omarchy.menu" }, { "id": "omarchy.workspaces" }],
|
||||||
|
|||||||
+28
-27
@@ -15,7 +15,8 @@ You can see all the main keyboard bindings with `Super + K` (Tmux bindings with
|
|||||||
| `Super + T` | Toggle window between tiling/floating |
|
| `Super + T` | Toggle window between tiling/floating |
|
||||||
| `Super + J` | Toggle window position (horizontal/vertical) |
|
| `Super + J` | Toggle window position (horizontal/vertical) |
|
||||||
| `Super + O` | Toggle popping window into sticky'n'floating |
|
| `Super + O` | Toggle popping window into sticky'n'floating |
|
||||||
| `Super + L` | Toggle between dwindle and scrolling layout |
|
| `Super + L` | Cycle through the dwindle, scrolling, and floating layouts |
|
||||||
|
| `Super + Alt + L` | Cycle through the layouts in reverse |
|
||||||
| `Super + P` | Toggle pseudo window style (natural v stretch) |
|
| `Super + P` | Toggle pseudo window style (natural v stretch) |
|
||||||
| `Super + F` | Go full screen |
|
| `Super + F` | Go full screen |
|
||||||
| `Super + Alt + F` | Go full width |
|
| `Super + Alt + F` | Go full width |
|
||||||
@@ -339,36 +340,36 @@ You can use `Super + Ctrl + E` to show a complete emoji picker that'll put the s
|
|||||||
|
|
||||||
| Hotkey | EM | Clue |
|
| Hotkey | EM | Clue |
|
||||||
| ------------ | -- | ---------- |
|
| ------------ | -- | ---------- |
|
||||||
| `CapsLock M S` | 😄 | smile |
|
| `Shift + CapsLock M S` | 😄 | smile |
|
||||||
| `CapsLock M C` | 😂 | cry |
|
| `Shift + CapsLock M C` | 😂 | cry |
|
||||||
| `CapsLock M L` | 😍 | love |
|
| `Shift + CapsLock M L` | 😍 | love |
|
||||||
| `CapsLock M V` | ✌️ | victory |
|
| `Shift + CapsLock M V` | ✌️ | victory |
|
||||||
| `CapsLock M H` | ❤️ | heart |
|
| `Shift + CapsLock M H` | ❤️ | heart |
|
||||||
| `CapsLock M Y` | 👍 | yes |
|
| `Shift + CapsLock M Y` | 👍 | yes |
|
||||||
| `CapsLock M N` | 👎 | no |
|
| `Shift + CapsLock M N` | 👎 | no |
|
||||||
| `CapsLock M F` | 🖕 | fuck |
|
| `Shift + CapsLock M F` | 🖕 | fuck |
|
||||||
| `CapsLock M W` | 🤞 | wish |
|
| `Shift + CapsLock M W` | 🤞 | wish |
|
||||||
| `CapsLock M R` | 🤘 | rock |
|
| `Shift + CapsLock M R` | 🤘 | rock |
|
||||||
| `CapsLock M K` | 😘 | kiss |
|
| `Shift + CapsLock M K` | 😘 | kiss |
|
||||||
| `CapsLock M E` | 🙄 | eyeroll |
|
| `Shift + CapsLock M E` | 🙄 | eyeroll |
|
||||||
| `CapsLock M I` | 😉 | wink |
|
| `Shift + CapsLock M I` | 😉 | wink |
|
||||||
| `CapsLock M P` | 🙏 | pray |
|
| `Shift + CapsLock M P` | 🙏 | pray |
|
||||||
| `CapsLock M D` | 🤤 | drool |
|
| `Shift + CapsLock M D` | 🤤 | drool |
|
||||||
| `CapsLock M M` | 💰 | money |
|
| `Shift + CapsLock M M` | 💰 | money |
|
||||||
| `CapsLock M X` | 🎉 | xellebrate |
|
| `Shift + CapsLock M X` | 🎉 | xellebrate |
|
||||||
| `CapsLock M 1` | 💯 | 100% |
|
| `Shift + CapsLock M 1` | 💯 | 100% |
|
||||||
| `CapsLock M T` | 🥂 | toast |
|
| `Shift + CapsLock M T` | 🥂 | toast |
|
||||||
| `CapsLock M O` |👌 | ok |
|
| `Shift + CapsLock M O` |👌 | ok |
|
||||||
| `CapsLock M G` |👋 | greeting |
|
| `Shift + CapsLock M G` |👋 | greeting |
|
||||||
| `CapsLock M A` |💪 | arm |
|
| `Shift + CapsLock M A` |💪 | arm |
|
||||||
| `CapsLock M B` |🤯 | blowing |
|
| `Shift + CapsLock M B` |🤯 | blowing |
|
||||||
|
|
||||||
## Quick Completions
|
## Quick Completions
|
||||||
|
|
||||||
| Hotkey | Completion |
|
| Hotkey | Completion |
|
||||||
| ------------ | ---------- |
|
| ------------ | ---------- |
|
||||||
| `CapsLock Space Space` | — (mdash) |
|
| `Shift + CapsLock Space Space` | — (mdash) |
|
||||||
| `CapsLock Space N` | Your name (as entered on setup) |
|
| `Shift + CapsLock Space N` | Your name (as entered on setup) |
|
||||||
| `CapsLock Space E` | Your email (as entered on setup) |
|
| `Shift + 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.
|
You can add more of your own by editing `~/.XCompose`, then running `omarchy-restart-xcompose` in the terminal to get the changes picked up.
|
||||||
@@ -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
|
### 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.
|
`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_.
|
||||||
|
|
||||||
From the terminal, the same switches are `omarchy toggle <thing>`. Run `omarchy toggle` on its own to see the whole group.
|
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.
|
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.
|
||||||
|
|
||||||
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`).
|
_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`).
|
||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
||||||
|
|||||||
+6
-12
@@ -69,6 +69,12 @@ 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.
|
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
|
||||||
|
|
||||||
[Omacalc](https://github.com/omacom-io/omacalc) is Omarchy's own dead-simple calculator, which opens in a floating window.
|
[Omacalc](https://github.com/omacom-io/omacalc) is Omarchy's own dead-simple calculator, which opens in a floating window.
|
||||||
@@ -87,18 +93,6 @@ 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.
|
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
|
||||||
|
|
||||||
[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.
|
[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.
|
||||||
|
|||||||
Loaded 100 of 223 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user