Merge upstream quattro for the Qt 6.12 palette fix (#14553)

Qt 6.12 shadows the bare Color singleton, so the bar and every Color
role read undefined on fresh installs. Keep our I18n text in the four
conflicting files, qualify #14433's notification button color, give the
new dictation menu rows and notifications Chinese, and drop table
entries for the removed Voxtype and FIDO2 texts.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-09 01:24:30 -04:00
commit fcd681c099
239 files changed
+3996 -616

No files matched your search

+4
View File
@@ -49,3 +49,7 @@ unicode characters. Agent file-editing tools can strip multi-byte codepoints
in some positions — do **not** rewrite widget files wholesale through those
tools. For glyph fixes, make a targeted edit with the surrounding context, or
use a Python script that inserts codepoints via `chr(0xXXXXX)`.
## Palette references
Import `qs.Commons as Commons` and use `Commons.Color` for the palette singleton, including `Connections.target`. Qt 6.12 introduces a `Color` type in `QtQuick` that shadows unqualified palette references. This applies to first-party components and third-party plugin QML alike. Keep the unqualified `qs.Commons` import when the file also uses `Style`, `Util`, or `Border`.
+1
View File
@@ -46,6 +46,7 @@ GROUP_DESCRIPTIONS[finalize]="Finalize user setup"
GROUP_DESCRIPTIONS[default]="Default application selection"
GROUP_DESCRIPTIONS[dev]="Omarchy development tools"
GROUP_DESCRIPTIONS[disk]="Disk performance helpers"
GROUP_DESCRIPTIONS[dictation]="Dictation recording"
GROUP_DESCRIPTIONS[display]="Display and text scaling"
GROUP_DESCRIPTIONS[dns]="DNS resolver configuration"
GROUP_DESCRIPTIONS[drive]="Drive selection and encryption"
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
# omarchy:summary=Get or set the default dictation backend
# omarchy:args=[backend]
# omarchy:examples=omarchy default dictation | omarchy default dictation superwhisper
if (($# > 1)); then
echo "Usage: omarchy default dictation [backend]" >&2
exit 1
fi
if (($# == 0)); then
config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
if [[ ! -s $config ]]; then
echo "No dictation backend configured. Select one in Setup > Defaults > Dictation." >&2
exit 1
fi
read -r backend < "$config"
else
backend=$1
fi
if [[ ! $backend =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
echo "Invalid dictation backend: $backend" >&2
exit 1
fi
if (($# == 0)); then
echo "$backend"
else
installer="omarchy-install-dictation-$backend"
if omarchy-cmd-missing "$installer"; then
echo "No installer available for dictation backend: $backend" >&2
exit 1
fi
omarchy-launch-floating-terminal-with-presentation "$installer"
fi
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# omarchy:summary=Record dictation with the configured backend
# omarchy:args=<start|stop|toggle>
# omarchy:examples=omarchy dictation start | omarchy dictation toggle
if (($# != 1)) || [[ $1 != "start" && $1 != "stop" && $1 != "toggle" ]]; then
echo "Usage: omarchy dictation <start|stop|toggle>" >&2
exit 1
fi
config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
mkdir -p "${config%/*}"
exec 9> "$config.lock"
if ! flock -w 5 9; then
echo "Dictation is busy; try again." >&2
exit 1
fi
backend=$(omarchy-default-dictation) || exit 1
adapter="omarchy-dictation-$backend"
if omarchy-cmd-missing "$adapter"; then
echo "The selected dictation backend ($backend) is not installed." >&2
exit 1
fi
"$adapter" "$1" 9>&-
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# omarchy:summary=Record dictation using Superwhisper
# omarchy:args=<start|stop|toggle>
case "${1:-}" in
start) superwhisper start ;;
stop)
if omarchy-cmd-missing superwhisper || ! systemctl --user is-active --quiet superwhisper.service; then
exit 0
else
superwhisper stop
fi
;;
toggle) superwhisper record ;;
*) echo "Usage: omarchy dictation superwhisper <start|stop|toggle>" >&2; exit 1 ;;
esac
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# omarchy:hidden=true
# omarchy:summary=Save a configured dictation backend after stopping the previous backend
# omarchy:args=<backend>
set -e
if (($# != 1)) || [[ ! $1 =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
echo "Usage: omarchy-dictation-use <backend>" >&2
exit 1
fi
config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
mkdir -p "${config%/*}"
exec 9> "$config.lock"
if ! flock -w 5 9; then
echo "Dictation is busy; try again." >&2
exit 1
fi
previous=$(omarchy-default-dictation 2>/dev/null) || previous=""
if [[ -n $previous && $previous != "$1" ]] && omarchy-cmd-present "omarchy-dictation-$previous"; then
if ! "omarchy-dictation-$previous" stop 9>&-; then
echo "Could not stop $previous; keeping the existing dictation backend." >&2
exit 1
fi
fi
temporary=$(mktemp "$config.XXXXXX")
trap 'rm -f "$temporary"' EXIT
printf '%s\n' "$1" > "$temporary"
mv -f "$temporary" "$config"
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
# omarchy:summary=Record dictation using Voxtype
# omarchy:args=<start|stop|toggle>
case "${1:-}" in
start|toggle) voxtype record "$1" ;;
stop)
if omarchy-cmd-missing voxtype || [[ $(voxtype status) == "stopped" ]]; then
exit 0
else
voxtype record stop
fi
;;
*) echo "Usage: omarchy dictation voxtype <start|stop|toggle>" >&2; exit 1 ;;
esac
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
# omarchy:summary=Install and configure Superwhisper dictation
# omarchy:requires-sudo=true
set -e
omarchy-pkg-add superwhisper-bin
bash /usr/share/superwhisper/setup-user
# Existing profiles may still use Right Alt, which conflicts with Omarchy.
# Keep a recording shortcut active while moving away from that binding.
shortcuts=$(superwhisper shortcuts show)
if [[ $(jq -r '.shortcuts.hold' <<< "$shortcuts") == "RightAlt" ]]; then
if [[ -z $(jq -r '.shortcuts.toggle' <<< "$shortcuts") ]]; then
superwhisper shortcuts set hold "RightSuper" >/dev/null
else
superwhisper shortcuts set hold none >/dev/null
fi
fi
# Superwhisper requires a native recording shortcut; keep it separate from
# Omarchy's push-to-talk keys.
superwhisper shortcuts set toggle "Alt+Space" >/dev/null
superwhisper shortcuts set hold none >/dev/null
omarchy-dictation-use superwhisper
hyprctl reload >/dev/null
omarchy-notification-send -g  "$(omarchy-text "Superwhisper Dictation Ready")" "$(omarchy-text "Hold Right Alt or F9 to dictate (or toggle with Super + Ctrl + X).")" -t 10000
superwhisper settings
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# omarchy:summary=Install and configure Voxtype dictation
# omarchy:requires-sudo=true
set -e
if omarchy-cmd-missing voxtype; then
if gum confirm "Install Voxtype + AI model (~150MB) to enable dictation?"; then
omarchy-pkg-add wtype voxtype-bin
else
exit 0
fi
fi
config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
mkdir -p "$config_home/voxtype"
if [[ ! -e $config_home/voxtype/config.toml ]]; then
cp "$OMARCHY_PATH/default/voxtype/config.toml" "$config_home/voxtype/"
if omarchy-hw-vulkan; then
voxtype setup gpu --enable || true
fi
fi
# Setup reuses the configured model and downloads it only when missing.
# Retry these steps even when an earlier attempt already installed the binary.
voxtype setup --download --no-post-install
voxtype setup systemd
systemctl --user enable --now voxtype.service
omarchy-dictation-use voxtype
hyprctl reload >/dev/null
omarchy-notification-send -g  "$(omarchy-text "Voxtype Dictation Ready")" "$(omarchy-text "Hold Right Alt or F9 to dictate (or toggle with Super + Ctrl + X).")" -t 10000
+3 -1
View File
@@ -15,7 +15,9 @@
# against a half-written tree, and that failed reload leaves a second engine
# generation behind that turns the next restart's IPC kill into a crash.
run_shell() {
QS_DISABLE_FILE_WATCHER=1 QS_NO_RELOAD_POPUP=1 \
local startup_background
startup_background=$(readlink -f "$HOME/.local/state/omarchy/current/background" 2>/dev/null)
OMARCHY_STARTUP_BACKGROUND="$startup_background" QS_DISABLE_FILE_WATCHER=1 QS_NO_RELOAD_POPUP=1 \
systemd-cat -t omarchy-shell -- quickshell -n -p "$OMARCHY_PATH/shell" &
shell_pid=$!
-2
View File
@@ -68,8 +68,6 @@ run_first_run_step() {
fi
}
run_first_run_step "install Voxtype post-update hook" \
omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/install-voxtype.hook"
run_first_run_step "install fingerprint setup post-update hook" \
omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/setup-fingerprint.hook"
run_first_run_step "install agent setup post-update hook" \
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
# omarchy:summary=Remove Superwhisper dictation and its desktop integration
# omarchy:requires-sudo=true
set -e
config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
systemctl --user disable --now superwhisper.service 2>/dev/null || true
omarchy-shell -q shell setPluginEnabled superwhisper.panel false >/dev/null
omarchy-pkg-drop superwhisper-bin
systemctl --user daemon-reload
# Remove the package setup's links, preserving independently installed copies
# and the user's preferences, history, and models.
unlink_package_link() {
if [[ -L $1 && $(readlink "$1") == "$2" ]]; then
rm "$1"
fi
}
unlink_package_link "$HOME/.local/bin/superwhisper" /usr/bin/superwhisper
unlink_package_link "$data_home/superwhisper/app/current" /opt/superwhisper
unlink_package_link "$config_home/omarchy/plugins/superwhisper-panel" /opt/superwhisper/assets/omarchy-plugin/superwhisper-panel
for agent in .agents .claude .codex .pi/agent; do
unlink_package_link "$HOME/$agent/skills/superwhisper" /opt/superwhisper/assets/agent-skill/superwhisper
done
backend_config="$config_home/omarchy/defaults/dictation"
if [[ -f $backend_config && $(< "$backend_config") == "superwhisper" ]]; then
rm "$backend_config"
fi
omarchy-shell -q shell rescanPlugins
hyprctl reload >/dev/null
if systemctl --user -q is-active omarchy-fcitx5.service; then
systemctl --user restart omarchy-fcitx5.service
fi
@@ -18,6 +18,10 @@ if omarchy-cmd-present voxtype; then
omarchy-pkg-drop voxtype-bin
rm -rf ~/.config/voxtype
rm -rf ~/.local/share/voxtype
backend_config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
if [[ -f $backend_config && $(< "$backend_config") == "voxtype" ]]; then
rm "$backend_config"
fi
hyprctl reload >/dev/null
else
echo "Voxtype was not installed."
+201
View File
@@ -0,0 +1,201 @@
#!/bin/bash
# omarchy:summary=Play the current background's matching theme intro
# omarchy:hidden=true
set -euo pipefail
state_home="$HOME/.local/state"
current_dir="$state_home/omarchy/current"
current_background_link="$current_dir/background"
marker="$state_home/omarchy/background-intro.session-id"
toggle="$state_home/omarchy/toggles/background-intros-off"
video_extensions=(mp4 m4v mov webm mkv avi)
resolve_intro() {
local background
local stem
local extension
local intro
background=$(readlink -f "${1:-$current_background_link}" 2>/dev/null) || return 1
[[ -f $background ]] || return 1
case ${background,,} in
*.jpg | *.jpeg | *.png | *.bmp | *.webp) ;;
*) return 1 ;;
esac
stem=${background##*/}
stem=${stem%.*}
for extension in "${video_extensions[@]}"; do
intro="${background%/*}/intros/$stem.$extension"
if [[ -f $intro ]]; then
printf '%s\n' "$intro"
return 0
fi
done
return 1
}
# Preparation is speculative: the selected staged clip is compared again by OWE.
prepare_theme_intro() {
local theme="$1" background remembered name source intro
local -a names=()
[[ -n $theme && $theme != .* && $theme != */* ]] || return 0
[[ ! -f $toggle ]] && ! animations_off || return 0
owe warm >/dev/null 2>&1 || return 0
[[ $(cat "$current_dir/theme.name" 2>/dev/null) != "$theme" ]] || return 0
remembered=$(cat "$state_home/omarchy/theme-backgrounds/$theme" 2>/dev/null || true)
if [[ ${remembered%/*} == "$current_dir/theme/backgrounds" ]]; then
name=${remembered##*/}
elif [[ -f $remembered ]]; then
background="$remembered"
else
mapfile -d '' -t names < <(
find -L "$HOME/.config/omarchy/backgrounds/$theme" -maxdepth 1 -type f -print0 2>/dev/null | sort -z
)
if (( ${#names[@]} > 0 )); then
background=${names[0]}
else
mapfile -d '' -t names < <(
find -L "$OMARCHY_PATH/themes/$theme/backgrounds" "$HOME/.config/omarchy/themes/$theme/backgrounds" -maxdepth 1 -type f \
\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.bmp' -o -iname '*.webp' \) -printf '%f\0' 2>/dev/null | sort -zu
)
(( ${#names[@]} > 0 )) || return 0
name=${names[0]}
fi
fi
if [[ -n ${name:-} ]]; then
for source in "$HOME/.config/omarchy/themes/$theme" "$OMARCHY_PATH/themes/$theme"; do
if [[ -f $source/backgrounds/$name ]]; then
background="$source/backgrounds/$name"
break
fi
done
fi
[[ -n ${background:-} ]] || return 0
intro=$(resolve_intro "$background" || true)
[[ -z $intro ]] || owe intro-prepare "$intro" >/dev/null 2>&1 || true
}
animations_off() {
[[ $(hyprctl -j getoption animations:enabled 2>/dev/null | jq -r '.bool' 2>/dev/null) == "false" ]]
}
theme_switch=false
intro_prepared=false
expected_background=""
expected_theme=""
case ${1:-} in
"") ;;
--prepare-theme)
prepare_theme_intro "${2:-}"
exit 0
;;
--resolve-only)
resolve_intro "${2:-}" || true
exit 0
;;
--resolve-startup)
if [[ ! -f $toggle ]] && ! animations_off; then
resolve_intro || true
fi
exit 0
;;
--theme-switch)
theme_switch=true
expected_background=$(readlink -f "${2:-}" 2>/dev/null) || exit 1
expected_theme=${3:-}
intro_prepared=${4:-false}
[[ -n $expected_theme ]] || exit 1
;;
*)
echo "Usage: omarchy-theme-bg-boot-intro [--resolve-only [background]|--resolve-startup|--prepare-theme theme|--theme-switch background theme-id [prepared]]" >&2
exit 1
;;
esac
if [[ $theme_switch == false ]]; then
mkdir -p "$(dirname "$marker")"
exec 9>"$state_home/omarchy/background-intro.lock"
flock 9
session_id=${OMARCHY_SESSION_ID:-${HYPRLAND_INSTANCE_SIGNATURE:-}}
[[ -n $session_id ]] || exit 0
if [[ -f $marker && $(<"$marker") == "$session_id" ]]; then
exit 0
fi
marker_staged=$(mktemp "$state_home/omarchy/.background-intro.session-id.XXXXXX")
trap 'rm -f "$marker_staged"' EXIT
printf '%s\n' "$session_id" >"$marker_staged"
mv -f "$marker_staged" "$marker"
trap - EXIT
exec 9>&-
fi
selection_current() {
[[ $(readlink -f "$current_background_link") == "$expected_background" &&
$(stat -Lc '%d:%i' "$current_dir/theme" 2>/dev/null) == "$expected_theme" ]]
}
if [[ $theme_switch == true ]] && ! selection_current; then
exit 0
fi
if [[ -f $toggle ]] || animations_off; then
[[ $theme_switch == false ]] || exit 1
exit 0
fi
intro=$(resolve_intro || true)
if [[ -z $intro ]]; then
[[ $theme_switch == false ]] || exit 1
exit 0
fi
if [[ $theme_switch == true && $intro_prepared != "true" ]]; then
owe refresh >/dev/null || exit 1
elif [[ $theme_switch == false ]]; then
deadline=$((SECONDS + 5))
while ! timeout 1 owe status >/dev/null 2>&1; do
if (( SECONDS >= deadline )); then
exit 0
fi
sleep 0.2
done
(( SECONDS < deadline )) || exit 0
fi
# The asynchronous outgoing cover must reach the compositor before playback
# can hide the shell background. Older shells have no readiness method.
if [[ $theme_switch == true ]]; then
deadline=$((SECONDS + 3))
while true; do
cover_status=$(omarchy-shell shell themeIntroCoverStatus "$expected_theme" 2>/dev/null || true)
case $cover_status in
loading)
(( SECONDS < deadline )) || exit 1
sleep 0.01
;;
error) exit 1 ;;
superseded) exit 0 ;;
*) break ;;
esac
done
fi
if [[ $theme_switch == true ]] && ! selection_current; then
exit 0
fi
# A selection or setting changed while OWE was starting no longer permits this intro.
if [[ ! -f $toggle && $intro == "$(resolve_intro || true)" ]] && ! animations_off; then
if [[ $theme_switch == true && $intro_prepared == "true" ]]; then
owe intro --start first-frame --refresh "$intro"
else
owe intro --start first-frame "$intro"
fi
elif [[ $theme_switch == true ]]; then
exit 1
fi
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
# omarchy:summary=Enable or disable background intros
# omarchy:args=<enable|disable|toggle|status>
# omarchy:examples=omarchy theme bg intro toggle | omarchy theme bg intro disable
set -euo pipefail
toggle="$HOME/.local/state/omarchy/toggles/background-intros-off"
case ${1:-} in
enable)
rm -f "$toggle"
omarchy-notification-send "$(omarchy-text "Background Intros")" "$(omarchy-text "Enabled")"
;;
disable)
mkdir -p "$(dirname "$toggle")"
touch "$toggle"
omarchy-notification-send "$(omarchy-text "Background Intros")" "$(omarchy-text "Disabled")"
;;
toggle)
if [[ -f $toggle ]]; then
omarchy-theme-bg-intro enable
else
omarchy-theme-bg-intro disable
fi
;;
status)
[[ ! -f $toggle ]]
;;
*)
echo "Usage: omarchy-theme-bg-intro <enable|disable|toggle|status>" >&2
exit 1
;;
esac
+84 -6
View File
@@ -50,6 +50,13 @@ shell_ipc() {
timeout 2 omarchy-shell "$@" >/dev/null 2>&1
}
wait_for_theme_intro() {
local method="$1" token="$2" pending="$3" deadline=$((SECONDS + 3))
while (( SECONDS < deadline )) && [[ $(omarchy-shell shell "$method" "$token" 2>/dev/null) == "$pending" ]]; do
sleep 0.02
done
}
is_video_path() {
[[ ${1,,} =~ \.(mp4|m4v|mov|webm|mkv|avi)$ ]]
}
@@ -70,10 +77,18 @@ snapshot_background_path() {
}
snapshot_current_background() {
local current_background
local current_background snapshot
current_background=$(readlink -f "$CURRENT_BACKGROUND_LINK" 2>/dev/null || true)
snapshot_background_path "$current_background" "previous"
# An interrupted intro is still moving; its final still would flash as a
# cover. Capture the frame already playing instead, when OWE supports it.
mkdir -p "$BACKGROUND_TRANSITION_CACHE"
snapshot="$BACKGROUND_TRANSITION_CACHE/previous-$$.ppm"
if owe render "$(jq -nc --arg path "$snapshot" '{cmd: "snapshot", path: $path}')" >/dev/null 2>&1; then
echo "$snapshot"
else
current_background=$(readlink -f "$CURRENT_BACKGROUND_LINK" 2>/dev/null || true)
snapshot_background_path "$current_background" "previous"
fi
}
theme_background_state_file() {
@@ -171,7 +186,7 @@ set_theme_background_link() {
}
set_theme_background() {
local new_background new_background_snapshot
local new_background new_background_snapshot theme_id intro_prepared=false
if [[ -z $CHOSEN_THEME_BACKGROUND || ! -f $CHOSEN_THEME_BACKGROUND ]]; then
if ! choose_theme_background; then
@@ -182,6 +197,37 @@ set_theme_background() {
fi
new_background="$CHOSEN_THEME_BACKGROUND"
if [[ -n $THEME_BACKGROUND_INTRO ]]; then
if [[ -n $INTRO_PREPARE_PID ]] && wait "$INTRO_PREPARE_PID"; then
intro_prepared=true
fi
theme_id=$(stat -Lc '%d:%i' "$CURRENT_THEME_PATH")
THEME_INTRO_TOKEN="$theme_id"
shell_ipc shell prepareThemeIntro "$OLD_BACKGROUND_SNAPSHOT" "$theme_id" "$colors_payload" "$shell_payload" || \
shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true
ln -nsf "$new_background" "$CURRENT_BACKGROUND_LINK"
(
if ! omarchy-theme-bg-boot-intro --theme-switch "$new_background" "$theme_id" "$intro_prepared"; then
if [[ $(readlink -f "$CURRENT_BACKGROUND_LINK") == "$(readlink -f "$new_background")" &&
$(stat -Lc '%d:%i' "$CURRENT_THEME_PATH" 2>/dev/null) == "$theme_id" ]]; then
shell_ipc background setInstant "$new_background" || true
fi
fi
shell_ipc shell finishThemeIntro "$theme_id" || true
wait_for_theme_intro themeIntroStatus "$theme_id" "pending"
# Reloading Hyprland interrupts video presentation. Wait for playback,
# then serialize with theme selection so an old intro cannot reload
# the compositor during the next theme's video.
(
flock 9
if [[ $(stat -Lc '%d:%i' "$CURRENT_THEME_PATH" 2>/dev/null) == "$theme_id" ]]; then
omarchy-restart-hyprctl
fi
) 9>"$THEME_SET_LOCK"
[[ -z $OLD_BACKGROUND_SNAPSHOT ]] || rm -f "$OLD_BACKGROUND_SNAPSHOT"
) 9>&- &
return
fi
new_background_snapshot=""
if [[ -n $PREPARED_BACKGROUND_SNAPSHOT && $PREPARED_BACKGROUND == "$new_background" ]]; then
new_background_snapshot="$PREPARED_BACKGROUND_SNAPSHOT"
@@ -337,6 +383,12 @@ fi
exec 9>"$THEME_SET_LOCK"
flock 9
# A current video frame only represents the screen after its opening fade.
# Finish that short reveal before capturing the next outgoing cover.
if [[ $THEME_HEADLESS != "1" && $OMARCHY_THEME_SKIP_BACKGROUND != "1" ]]; then
wait_for_theme_intro themeIntroStatus "$(stat -Lc '%d:%i' "$CURRENT_THEME_PATH" 2>/dev/null)" "pending"
fi
PREVIOUS_THEME_NAME=$(cat "$CURRENT_THEME_NAME_PATH" 2>/dev/null || true)
remember_current_theme_background
@@ -365,11 +417,24 @@ OLD_BACKGROUND_SNAPSHOT=""
PREPARED_BACKGROUND=""
PREPARED_BACKGROUND_SNAPSHOT=""
BACKGROUND_TRANSITION_SNAPSHOTS=true
THEME_BACKGROUND_INTRO=""
INTRO_PREPARE_PID=""
THEME_INTRO_TOKEN=""
if [[ $THEME_HEADLESS != "1" && $OMARCHY_THEME_SKIP_BACKGROUND != "1" ]]; then
# Resolve the staged choice while the old theme still exists. Video changes
# switch directly to the durable path in QML, so neither side needs a copy.
if choose_staged_theme_background; then
if background_transition_uses_snapshots "$CHOSEN_THEME_BACKGROUND"; then
if [[ $PREVIOUS_THEME_NAME != "$THEME_NAME" && ! -f $HOME/.local/state/omarchy/toggles/background-intros-off ]] &&
[[ $(hyprctl -j getoption animations:enabled 2>/dev/null | jq -r '.bool') == "true" ]]; then
THEME_BACKGROUND_INTRO=$(omarchy-theme-bg-boot-intro --resolve-only "$STAGED_THEME_BACKGROUND")
fi
if [[ -n $THEME_BACKGROUND_INTRO ]]; then
OLD_BACKGROUND_SNAPSHOT=$(snapshot_current_background)
# OWE retains the opened clip across the staged directory's rename.
owe intro-prepare "$THEME_BACKGROUND_INTRO" >/dev/null 2>&1 9>&- &
INTRO_PREPARE_PID=$!
shell_ipc shell prepareThemeIntro "$OLD_BACKGROUND_SNAPSHOT" "$(stat -Lc '%d:%i' "$NEXT_THEME_PATH")" "" "" || true
elif background_transition_uses_snapshots "$CHOSEN_THEME_BACKGROUND"; then
OLD_BACKGROUND_SNAPSHOT=$(snapshot_current_background)
# Hand the shell the next background now, so it decodes while the
@@ -389,6 +454,12 @@ fi
# Generate dynamic configs
omarchy-theme-set-templates
# Replacing the theme's still can cancel an active OWE intro even before the
# background link changes. Present its cover before replacing either path.
if [[ -n $THEME_BACKGROUND_INTRO ]]; then
wait_for_theme_intro themeIntroCoverStatus "$(stat -Lc '%d:%i' "$NEXT_THEME_PATH")" "loading"
fi
# Swap next theme in as current
rm -rf "$CURRENT_THEME_PATH"
mv "$NEXT_THEME_PATH" "$CURRENT_THEME_PATH"
@@ -418,7 +489,6 @@ flock -u 9
post_theme_commands=(
omarchy-restart-terminal
omarchy-restart-hyprctl
omarchy-restart-btop
omarchy-restart-opencode
omarchy-restart-helix
@@ -437,6 +507,14 @@ post_theme_commands=(
)
if [[ $THEME_HEADLESS != "1" ]]; then
# Let the wallpaper and bar finish their reveal before app retints compete
# with decoding and the shell's frame rendering. An intro reloads Hyprland
# after playback; ordinary theme changes reload it with the app retints.
if [[ -n $THEME_INTRO_TOKEN ]]; then
wait_for_theme_intro themeIntroStatus "$THEME_INTRO_TOKEN" "pending"
else
post_theme_commands+=(omarchy-restart-hyprctl)
fi
run_parallel "${post_theme_commands[@]}"
# Call hook on theme set
+7 -2
View File
@@ -29,6 +29,8 @@ if [[ -n $OMARCHY_THEME_SYNC_FROM ]] || ! omarchy-toggle-enabled herdr-theme-syn
fi
theme=$(cat "$HOME/.local/state/omarchy/current/theme.name" 2>/dev/null)
control_dir="$XDG_RUNTIME_DIR/omarchy-theme-sync"
mkdir -m 700 -p "$control_dir"
# Runs on the remote machine under a login shell, so OMARCHY_PATH and PATH are set there.
# SSH carries no desktop session, so the environment comes from the live Hyprland instance.
@@ -66,10 +68,13 @@ read -r -d '' REMOTE_SCRIPT <<'EOF'
} </dev/null
EOF
# One machine at a time, so an SSH agent like 1Password asks for approval at most once.
# Reuse each authenticated connection for ten idle minutes, so an SSH agent like
# 1Password does not need to approve every theme change. Connect one machine at a time.
# SSH joins its arguments into a shell command, so the theme travels on stdin as a quoted assignment.
for target in $targets; do
result=$(printf 'theme=%q from=%q\n%s\n' "$theme" "$(hostname)" "$REMOTE_SCRIPT" |
timeout 60 ssh -o BatchMode=yes -o ConnectTimeout=5 "$target" bash -l -s 2>&1 | tail -n 1)
timeout 60 ssh -o BatchMode=yes -o ConnectTimeout=5 \
-o ControlMaster=auto -o ControlPersist=600 -o "ControlPath=$control_dir/%C" \
"$target" bash -l -s 2>&1 | tail -n 1)
echo "$(date '+%F %T') $target: ${result:-unreachable}"
done
-26
View File
@@ -1,26 +0,0 @@
#!/bin/bash
# omarchy:summary=Install and configure Voxtype dictation
# omarchy:requires-sudo=true
set -e
# Install voxtype and configure it for use.
if gum confirm "Install Voxtype + AI model (~150MB) to enable dictation?"; then
omarchy-pkg-add wtype voxtype-bin
# Setup voxtype
mkdir -p ~/.config/voxtype
cp "$OMARCHY_PATH/default/voxtype/config.toml" ~/.config/voxtype/
voxtype setup --download --no-post-install
if omarchy-hw-vulkan; then
voxtype setup gpu --enable || true
fi
voxtype setup systemd
hyprctl reload >/dev/null
omarchy-restart-shell
omarchy-notification-send -g  "$(omarchy-text "Voxtype Dictation Ready")" "$(omarchy-text "Hold F9 to dictate (or toggle with Super + Ctrl + X).")" -t 10000
fi
-13
View File
@@ -1,13 +0,0 @@
#!/bin/bash
# omarchy:summary=Stream voxtype --follow status as bar-friendly JSON
if omarchy-cmd-missing voxtype; then
echo '{"alt": "", "class": "idle", "tooltip": ""}'
exit 0
fi
# Keep the status follower as the direct child of the bar. Quickshell
# clean up their direct child when they restart; a shell pipeline/coproc would
# leave `voxtype status --follow` orphaned under the user systemd instance.
exec setpriv --pdeathsig TERM voxtype status --follow --extended --format json
+1 -1
View File
@@ -1,5 +1,5 @@
--ozone-platform=wayland
--ozone-platform-hint=wayland
--password-store=gnome-libsecret
--enable-features=TouchpadOverscrollHistoryNavigation
--enable-features=TouchpadOverscrollHistoryNavigation,OverlayScrollbar
--load-extension=/usr/share/omarchy/default/chromium/extensions/copy-url,/usr/share/omarchy/default/chromium/extensions/yt-dlp,/usr/share/omarchy/default/chromium/extensions/whatsapp-slim
+1
View File
@@ -0,0 +1 @@
superwhisper
@@ -11,7 +11,7 @@
#
# To put it into use, remove .sample from this file name.
# Example: add an Include line above [core].
# Example: add an Include line above the first repository, [omarchy].
# Maintain the repo entries in /etc/pacman.d/custom-repos.conf yourself.
CONF=/etc/pacman.conf
@@ -21,4 +21,4 @@ MARKER="Include = $SNIPPET"
[[ -r $SNIPPET ]] || exit 0
grep -qxF "$MARKER" "$CONF" && exit 0
sudo sed -i "0,/^\[core\]/s||$MARKER\n\n[core]|" "$CONF"
sudo sed -i "0,/^\[omarchy\]/s||$MARKER\n\n[omarchy]|" "$CONF"
+8 -4
View File
@@ -6,12 +6,16 @@ hl.layer_rule({ match = { namespace = "omarchy-bar" }, no_anim = true, animation
-- Launcher, image selector, emojis, clipboard overlays, the OSD, reminders, the
-- Wi-Fi QR code and keyboard-driven panels should pop without compositor layer
-- animations. The overlays stay mapped between opens and grow from a parked
-- 1x1 when shown, which Hyprland would otherwise animate as a slide in from
-- the corner. Panels keep their own QML opacity transition for normal
-- open/close, and skip it for panel handoff.
-- animations. Overlays open as soon as their scale is ready, without a
-- compositor fade or slide. Panels keep their own QML opacity transition
-- for normal open/close, and skip it for panel handoff.
hl.layer_rule({ match = { namespace = "^(omarchy-menu|omarchy-image-selector|omarchy-emojis|omarchy-clipboard|omarchy-keyboard-panel|omarchy-osd|omarchy-reminders|omarchy-network-qr)$" }, no_anim = true, animation = "none" })
-- A boot intro hands the desktop between OWE's video layer and the shell's
-- background layer. The media fades itself, so a compositor fade on either
-- layer only dips the screen toward the empty desktop during the handoff.
hl.layer_rule({ match = { namespace = "^(omarchy-background|owe-background)$" }, no_anim = true, animation = "none" })
-- Dev gallery is the main shell workbench; open it maximized like
-- SUPER+ALT+F so component previews have the whole workspace.
o.window({ class = "^org.quickshell$", title = "^Omarchy shell – dev gallery$" }, { maximize = true })
+8
View File
@@ -1,4 +1,12 @@
require("default.hypr.startup-cursor")
hl.on("hyprland.start", function()
-- Applications inherit the user's cursor; only the compositor starts blank.
if omarchy_startup_cursor_pending then
hl.env("XCURSOR_PATH", omarchy_startup_cursor.path)
hl.env("XCURSOR_THEME", omarchy_startup_cursor.xcursor)
end
-- Slow app launch fix -- set systemd vars before starting session services.
hl.exec_cmd("systemctl --user import-environment $(env | cut -d'=' -f 1)")
hl.exec_cmd("dbus-update-activation-environment --systemd --all")
+9
View File
@@ -0,0 +1,9 @@
if o.shell_succeeds("omarchy-default-dictation") then
o.bind("SUPER + CTRL + X", "Toggle dictation", "omarchy-dictation toggle")
o.bind("F9", "Start dictation (push-to-talk)", "omarchy-dictation start")
o.bind("F9", "Stop dictation (push-to-talk)", "omarchy-dictation stop", { release = true })
-- A modifier's mask changes between its press and release. Match the keysym
-- independently of that mask; AltGr layouts use a different keysym.
o.bind("ALT + Alt_R", "Start dictation (push-to-talk)", "omarchy-dictation start", { ignore_mods = true })
o.bind("ALT + Alt_R", "Stop dictation (push-to-talk)", "omarchy-dictation stop", { release = true, ignore_mods = true })
end
-5
View File
@@ -1,5 +0,0 @@
if o.cmd_present("voxtype") then
o.bind("SUPER + CTRL + X", "Toggle dictation", "voxtype record toggle")
o.bind("F9", "Start dictation (push-to-talk)", "voxtype record start")
o.bind("F9", "Stop dictation (push-to-talk)", "voxtype record stop", { release = true })
end
+24
View File
@@ -0,0 +1,24 @@
-- Backends may supply their own desktop integration without editing the
-- user's Hyprland config. Recording bindings remain owned by Omarchy.
local paths = require("default.hypr.paths")
local pipe = io.popen("omarchy-default-dictation 2>/dev/null", "r")
if not pipe then
return
end
local backend = pipe:read("*l")
pipe:close()
if not backend or not backend:match("^[%w%-]+$") or o.cmd_missing("omarchy-dictation-" .. backend) then
return
end
local integration = paths.config_home .. "/" .. backend .. "/shortcuts.lua"
local file = io.open(integration, "r")
if file then
file:close()
local ok, error = pcall(dofile, integration)
if not ok then
print("Could not load dictation backend " .. backend .. ": " .. tostring(error))
end
end
+1
View File
@@ -104,6 +104,7 @@ hl.config({
misc = {
disable_hyprland_logo = true,
disable_splash_rendering = true,
background_color = "rgb(000000)",
disable_scale_notification = true,
focus_on_activate = true,
anr_missed_pings = 3,
+2 -1
View File
@@ -12,7 +12,7 @@ if _G.omarchy_default_bindings ~= false then
require("default.hypr.bindings.jis")
require("default.hypr.bindings.tiling")
require("default.hypr.bindings.utilities")
require("default.hypr.bindings.voxtype")
require("default.hypr.bindings.dictation")
require_optional.module("default.hypr.bindings.applications")
end
require("default.hypr.envs")
@@ -20,6 +20,7 @@ require("default.hypr.looknfeel")
require("default.hypr.qconsole")
require("default.hypr.input")
require("default.hypr.windows")
require("default.hypr.dictation-backend")
-- Current theme overrides.
require_optional.module("omarchy.current.theme.hyprland")
+46
View File
@@ -0,0 +1,46 @@
-- Hyprland polls cursor.invisible, so its first frames need a blank cursor too.
function omarchy_startup_cursor_restore(loaded)
if not omarchy_startup_cursor_pending then return end
local cursor = omarchy_startup_cursor
if loaded then
omarchy_startup_cursor_pending = false
hl.config({ cursor = cursor.config })
if cursor.config.enable_hyprcursor and cursor.hyprcursor then
hl.exec_cmd("hyprctl setcursor " .. o.shell_quote(cursor.hyprcursor) .. " " .. cursor.size)
end
elseif not cursor.restoring then
-- Reload the normal Xcursor fallback before enabling Hyprcursor or GSettings.
cursor.restoring = true
hl.exec_cmd("hyprctl setcursor " .. o.shell_quote(cursor.xcursor) .. " " .. cursor.size
.. " && hyprctl eval 'omarchy_startup_cursor_restore(true)'")
end
end
hl.on("config.reloaded", function()
if omarchy_startup_cursor_pending == nil then
-- Config updates in an existing compositor must not hide its pointer.
omarchy_startup_cursor_pending = #hl.get_monitors() == 0
if omarchy_startup_cursor_pending then
local hyprcursor = hl.get_config("cursor.enable_hyprcursor")
omarchy_startup_cursor = {
config = {
invisible = hl.get_config("cursor.invisible"),
enable_hyprcursor = hyprcursor,
sync_gsettings_theme = hl.get_config("cursor.sync_gsettings_theme"),
},
hyprcursor = os.getenv("HYPRCURSOR_THEME"),
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",
xcursor = os.getenv("XCURSOR_THEME") or "default",
}
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)
hl.env("XCURSOR_THEME", "omarchy-startup")
end
end
if omarchy_startup_cursor_pending then
-- Keep the temporary cursor private to the compositor, including GSettings.
hl.config({ cursor = { invisible = true, enable_hyprcursor = false, sync_gsettings_theme = false } })
hl.timer(function() omarchy_startup_cursor_restore() end, { timeout = 15000, type = "oneshot" })
end
end)
+6 -2
View File
@@ -163,6 +163,9 @@
"setup.default.browser.edge": {"icon":"󰇩","label":"Edge","checked":"[[ \"$(omarchy-default-browser)\" == \"edge\" ]]","action":"omarchy-default-browser edge"},
"setup.default.browser.firefox": {"icon":"","label":"Firefox","checked":"[[ \"$(omarchy-default-browser)\" == \"firefox\" ]]","action":"omarchy-default-browser firefox"},
"setup.default.browser.zen": {"icon":"󰖟","label":"Zen","checked":"[[ \"$(omarchy-default-browser)\" == \"zen\" ]]","action":"omarchy-default-browser zen"},
"setup.default.dictation": {"icon":"","label":"Dictation","title":"Default Dictation"},
"setup.default.dictation.superwhisper": {"icon":"","label":"Superwhisper","checked":"[[ \"$(omarchy-default-dictation)\" == \"superwhisper\" ]]","action":"omarchy-default-dictation superwhisper"},
"setup.default.dictation.voxtype": {"icon":"","label":"Voxtype","checked":"[[ \"$(omarchy-default-dictation)\" == \"voxtype\" ]]","action":"omarchy-default-dictation voxtype"},
"setup.default.terminal": {"icon":"","label":"Terminal","title":"Default Terminal"},
"setup.default.terminal.alacritty": {"icon":"","label":"Alacritty","checked":"[[ \"$(omarchy-default-terminal)\" == \"alacritty\" ]]","action":"omarchy-default-terminal alacritty"},
"setup.default.terminal.foot": {"icon":"","label":"Foot","checked":"[[ \"$(omarchy-default-terminal)\" == \"foot\" ]]","action":"omarchy-default-terminal foot"},
@@ -267,7 +270,6 @@
"install.terminal.kitty": {"icon":"","label":"Kitty","disabled":"omarchy-pkg-present kitty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal kitty'"},
"install.ai.chatgpt": {"icon":"","iconFont":"omarchy","label":"ChatGPT Desktop","disabled":"omarchy-pkg-present openai-codex-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-chatgpt"},
"install.ai.claude": {"icon":"","iconFont":"omarchy","label":"Claude Desktop","disabled":"omarchy-pkg-present claude-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-claude"},
"install.ai.dictation": {"icon":"","label":"Dictation","disabled":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install"},
"install.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","disabled":"omarchy-pkg-present grok-bot","action":"omarchy-install-and-launch 'Grok Bot' grok-bot grok-bot"},
"install.ai.hermes": {"icon":"","iconFont":"omarchy","label":"Hermes Desktop","disabled":"omarchy-pkg-present hermes-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-hermes"},
"install.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","disabled":"omarchy-pkg-present lmstudio-bin","action":"omarchy-install-app 'LM Studio' lmstudio-bin"},
@@ -311,6 +313,7 @@
// Remove
"remove.package": {"icon":"󰣇","label":"Package","action":"xdg-terminal-exec --app-id=org.omarchy.terminal omarchy-pkg-remove"},
"remove.ai": {"icon":"󱚤","label":"AI","title":"Remove"},
"remove.dictation": {"icon":"","label":"Dictation","when":"omarchy-pkg-present voxtype-bin || omarchy-pkg-present superwhisper-bin"},
"remove.service": {"icon":"","label":"Services","title":"Remove"},
"remove.development": {"icon":"󰵮","label":"Development","title":"Remove"},
"remove.theme": {"icon":"󰸌","label":"Theme","action":"omarchy-theme-remove"},
@@ -338,7 +341,8 @@
"remove.service.ssh-agent": {"icon":"󰣀","label":"SSH Agent","when":"systemctl --user -q is-enabled gcr-ssh-agent.socket","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-service-ssh-agent"},
"remove.ai.chatgpt": {"icon":"","iconFont":"omarchy","label":"ChatGPT Desktop","when":"omarchy-pkg-present openai-codex-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-chatgpt"},
"remove.ai.claude": {"icon":"","iconFont":"omarchy","label":"Claude Desktop","when":"omarchy-pkg-present claude-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-claude"},
"remove.ai.dictation": {"icon":"","label":"Dictation","when":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-remove"},
"remove.dictation.superwhisper": {"icon":"","label":"Superwhisper","when":"omarchy-pkg-present superwhisper-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-dictation-superwhisper"},
"remove.dictation.voxtype": {"icon":"","label":"Voxtype","when":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-dictation-voxtype"},
"remove.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","when":"omarchy-pkg-present grok-bot","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-grok-bot"},
"remove.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","when":"omarchy-pkg-present lmstudio-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-lm-studio"},
"remove.ai.ollama": {"icon":"","iconFont":"omarchy","label":"Ollama","when":"omarchy-pkg-present ollama","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-ollama"},
+2 -2
View File
@@ -95,6 +95,7 @@
"setup.network.qr": {"label":"二维码"},
"setup.default": {"label":"默认程序"},
"setup.default.agent": {"title":"默认 Agent"},
"setup.default.dictation": {"label":"语音听写","title":"默认语音听写"},
"setup.default.browser": {"label":"浏览器","title":"默认浏览器"},
"setup.default.terminal": {"label":"终端","title":"默认终端"},
"setup.default.editor": {"label":"编辑器","title":"默认编辑器"},
@@ -125,7 +126,6 @@
"install.terminal": {"label":"终端"},
"install.preinstalls": {"label":"预装应用"},
"install.service.chromium-account": {"label":"Chromium 账号"},
"install.ai.dictation": {"label":"语音听写"},
"install.gaming.xbox-cloud": {"label":"Xbox 云游戏"},
"install.gaming.xbox-controllers": {"label":"Xbox 手柄"},
"install.gaming.retro-launcher": {"label":"RetroArch 启动器"},
@@ -142,7 +142,7 @@
"remove.security": {"label":"安全","title":"移除"},
"remove.security.fingerprint": {"label":"指纹"},
"remove.security.sudoless-docker": {"label":"免 sudo Docker"},
"remove.ai.dictation": {"label":"语音听写"},
"remove.dictation": {"label":"语音听写"},
"remove.gaming.xbox-cloud": {"label":"Xbox 云游戏"},
"remove.gaming.xbox-controllers": {"label":"Xbox 手柄 (󰂯)"},
"remove.development.javascript": {"title":"移除"},
+3 -3
View File
@@ -16,6 +16,9 @@ SigLevel = Required DatabaseOptional
LocalFileSigLevel = Optional
# pacman searches repositories in the order defined here
[omarchy]
Server = https://pkgs.omarchy.org/edge/$arch
[core]
Include = /etc/pacman.d/mirrorlist
@@ -25,9 +28,6 @@ Include = /etc/pacman.d/mirrorlist
[multilib]
Include = /etc/pacman.d/mirrorlist
[omarchy]
Server = https://pkgs.omarchy.org/edge/$arch
# Repositories for debug symbol packages.
#
# These upstream debug packages will only match core/extra/multilib packages from the Omarchy edge
+3 -3
View File
@@ -16,6 +16,9 @@ SigLevel = Required DatabaseOptional
LocalFileSigLevel = Optional
# pacman searches repositories in the order defined here
[omarchy]
Server = https://pkgs.omarchy.org/rc/$arch
[core]
Include = /etc/pacman.d/mirrorlist
@@ -24,6 +27,3 @@ Include = /etc/pacman.d/mirrorlist
[multilib]
Include = /etc/pacman.d/mirrorlist
[omarchy]
Server = https://pkgs.omarchy.org/rc/$arch
+3 -3
View File
@@ -16,6 +16,9 @@ SigLevel = Required DatabaseOptional
LocalFileSigLevel = Optional
# pacman searches repositories in the order defined here
[omarchy]
Server = https://pkgs.omarchy.org/stable/$arch
[core]
Include = /etc/pacman.d/mirrorlist
@@ -24,6 +27,3 @@ Include = /etc/pacman.d/mirrorlist
[multilib]
Include = /etc/pacman.d/mirrorlist
[omarchy]
Server = https://pkgs.omarchy.org/stable/$arch
+1
View File
@@ -53,6 +53,7 @@ hl.config({
misc = {
disable_hyprland_logo = true,
disable_splash_rendering = true,
background_color = "rgb(000000)",
force_default_wallpaper = 0,
},
+1 -1
View File
@@ -11,7 +11,7 @@
state_file = "auto"
[hotkey]
# Hotkey is configured in Hyprland. Default is Super + Ctrl + X
# Hotkeys are configured in Hyprland: hold Right Alt or F9, or toggle with Super + Ctrl + X.
enabled = false
[audio]
+2 -2
View File
@@ -267,8 +267,8 @@ first runs `omarchy-provision-user || true` so finalize catches up if it
never ran, then handles the steps that need a running graphical session
and/or a working user systemd instance:
- `omarchy-hook-install post-update` for the three shipped hooks
(`install-voxtype.hook`, `setup-fingerprint.hook`, `setup-agent.hook`).
- `omarchy-hook-install post-update` for the two shipped hooks
(`setup-fingerprint.hook`, `setup-agent.hook`).
- `install/user/first-run/enable-user-units.sh` — daemon-reload, then
`systemctl --user enable --now` the shipped user units (`bt-agent`,
`omarchy-sleep-lock`, `omarchy-recover-internal-monitor`,
+21 -7
View File
@@ -189,6 +189,20 @@ overrides like `omarchy display text size` survive theme switches.
## Theme tokens
Shell and plugin QML must qualify the palette singleton because Qt 6.12 introduces its own `Color` type. Import `qs.Commons` with an alias and use `Commons.Color` for palette properties and signal connections:
```qml
import QtQuick
import qs.Commons
import qs.Commons as Commons
Rectangle {
color: Commons.Color.background
}
```
The unqualified import can remain for `Style`, `Util`, and `Border`. Existing third-party plugins that use bare `Color` must make the same change; fixing the host shell does not change a plugin's import scope.
See [`theming.md`](theming.md) for the full theme/template workflow,
including generated `*.tpl` files, gradient helpers, and shell border syntax.
@@ -200,17 +214,17 @@ or override a single section with `shell.<section>.toml`, merged in by
`omarchy-theme-set-templates` (see [`theming.md`](theming.md)).
`colors.toml` uses `foreground` and `background` for the foundational
text/background palette, exposed to QML as `Color.foreground` and
`Color.background`.
text/background palette, exposed to QML as `Commons.Color.foreground` and
`Commons.Color.background`.
The shell exposes these tokens to QML via three singletons in
`qs.Commons`:
- `Color` — palette (`foreground`, `background`, `accent`, `urgent`)
and per-surface roles (`Color.bar.*`, `Color.popups.*`,
`Color.tooltip.*`, `Color.notifications.*`, `Color.menu.*`,
`Color.polkit.*`, `Color.lock.*`, `Color.imagePicker.*`). Clipboard
and emojis share `Color.menu.*`; the `[launcher]` section is consumed
and per-surface roles (`Commons.Color.bar.*`, `Commons.Color.popups.*`,
`Commons.Color.tooltip.*`, `Commons.Color.notifications.*`, `Commons.Color.menu.*`,
`Commons.Color.polkit.*`, `Commons.Color.lock.*`, `Commons.Color.imagePicker.*`). Clipboard
and emojis share `Commons.Color.menu.*`; the `[launcher]` section is consumed
by the launcher outside shell QML.
- `Style` — structural tokens (`cornerRadius`), shared interactive
state tokens/helpers, spacing (`Style.spacing.*` / `Style.space(px)`),
@@ -218,7 +232,7 @@ The shell exposes these tokens to QML via three singletons in
(`Style.bar.sizeHorizontal` / `Style.bar.sizeVertical`).
- `Border` — border-spec helpers for QML surfaces. Use with
`BorderSurface` from `qs.Ui` when a border should honor shell theme
gradients or per-side widths. `Color.<section>.border` is only the
gradients or per-side widths. `Commons.Color.<section>.border` is only the
flat-color fallback for code that cannot render a real border.
### Interactive states
+10 -10
View File
@@ -6,12 +6,11 @@ Omarchy themes live under `themes/<name>/` in the source tree (installed at
`colors.toml`; Omarchy generates the active theme files from
`default/themed/*.tpl` when `omarchy-theme-set <name>` runs.
Beyond `colors.toml` and hand-written config overrides, a first-party theme can
ship `backgrounds/` (users overlay their own via
`~/.config/omarchy/backgrounds/<name>/`; the active image is the
`~/.local/state/omarchy/current/background` symlink), `preview.png` and
`preview-unlock.png` for the theme switcher, `icons.theme`, `keyboard.rgb`,
`unlock.png`, and a `light.mode` marker file.
Beyond `colors.toml` and hand-written config overrides, a first-party theme can ship `backgrounds/` (users overlay their own via `~/.config/omarchy/backgrounds/<name>/`; the active image is the `~/.local/state/omarchy/current/background` symlink), `preview.png` and `preview-unlock.png` for the theme switcher, `icons.theme`, `keyboard.rgb`, `unlock.png`, and a `light.mode` marker file.
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.
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).
@@ -198,7 +197,7 @@ The filename decides the target section, so the `[lock]` header is optional.
The running shell reads `shell.toml` into two QML singletons:
- `Color` for palette and surface roles like `Color.menu.border`.
- `Color` for palette and surface roles like `Commons.Color.menu.border`.
- `Style` for controls, spacing, font scale, corner radius, and bar sizing.
### Borders
@@ -327,11 +326,12 @@ Plugin and shell QML should use `BorderSurface` for theme-aware borders:
```qml
import qs.Commons
import qs.Commons as Commons
import qs.Ui
BorderSurface {
color: Color.popups.background
borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, 2)
color: Commons.Color.popups.background
borderSpec: Border.surfaceSpec("popups", "border", Commons.Color.popups.border, 2)
padding: Style.spacing.popupPadding
Item {
@@ -348,7 +348,7 @@ Use `Border.surfaceSpec(section, token, fallbackColor, fallbackWidth, alphaKey)`
for shell theme tokens (the optional `alphaKey` names the alpha token, e.g.
`"border-alpha"`), `Border.controlSpec(state, foreground, accent, urgent)` for
shared controls, and `Border.flat(color, width)` for a deliberate local border
that should not be overridden by the active theme. `Color.<section>.border` is the
that should not be overridden by the active theme. `Commons.Color.<section>.border` is the
flat first-stop color for consumers that cannot render full border specs.
## Hyprland templates
@@ -0,0 +1,4 @@
[Service]
# Let start-hyprland stop the compositor before killing its shell clients.
# Sending SIGTERM to the whole group removes the wallpaper during teardown.
KillMode=mixed
+1
View File
@@ -131,6 +131,7 @@ sddm
slurp
socat
starship
superwhisper-bin
sushi
system-config-printer
tesseract
@@ -1,9 +0,0 @@
#!/bin/bash
set -e
if omarchy-done ensure voxtype-install-invitation; then
omarchy-notification-send -u critical -g  "$(omarchy-text "Install Dictation with Voxtype")" \
"$(omarchy-text "Click to install voice dictation for Omarchy.")" \
--exec omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install
fi
+7 -2
View File
@@ -12,7 +12,12 @@ case ${OMARCHY_SETUP_CONTEXT:-runtime} in
esac
if [[ -n $NODE_PACKAGE_DIR ]]; then
NODE_TARBALL=$(find "$NODE_PACKAGE_DIR" -name "node-v*-linux-x64.tar.gz" -type f 2>/dev/null | head -n1)
# The ISO bundles the tarball for its own architecture.
case $(uname -m) in
aarch64) NODE_PLATFORM=linux-arm64 ;;
*) NODE_PLATFORM=linux-x64 ;;
esac
NODE_TARBALL=$(find "$NODE_PACKAGE_DIR" -name "node-v*-$NODE_PLATFORM.tar.gz" -type f 2>/dev/null | head -n1)
if [[ -z $NODE_TARBALL ]]; then
if [[ ${OMARCHY_SETUP_CONTEXT:-} == "provision-owner" ]]; then
# A factory snapshot predating the bundled tarball may not have it staged.
@@ -24,7 +29,7 @@ if [[ -n $NODE_PACKAGE_DIR ]]; then
exit 1
fi
else
NODE_VERSION=$(basename "$NODE_TARBALL" | sed 's/node-v\(.*\)-linux-x64.tar.gz/\1/')
NODE_VERSION=$(basename "$NODE_TARBALL" | sed "s/node-v\(.*\)-$NODE_PLATFORM.tar.gz/\1/")
NODE_INSTALL_DIR="$HOME/.local/share/mise/installs/node/$NODE_VERSION"
mkdir -p "$NODE_INSTALL_DIR"
+2 -2
View File
@@ -150,8 +150,8 @@ Usually on Linux, you need `Ctrl + Shift + C/V` to copy'n'paste in the terminal
| `Super + Alt + ]` | Make webcam overlay larger while recording |
| `Alt + Shift + L` | Copy current URL from webapp or Chromium |
| `Alt + Shift + D` | Download the video on the current page to `~/Videos` |
| `Super + Ctrl + X` | Start/stop dictation (requires _Install > AI > Dictation_) |
| `F9` | Push-to-talk dictation (requires _Install > AI > Dictation_) |
| `Super + Ctrl + X` | Start/stop dictation (requires _Setup > Defaults > Dictation_) |
| `Right Alt` / `F9` | Push-to-talk dictation (requires Voxtype or Superwhisper) |
With screenrecordings, the hotkey first asks which audio you want, then starts recording. Hit it again to stop. See [screenshots and recording](12-screenshots-recording.md) for the details.
+10 -2
View File
@@ -10,6 +10,14 @@ This is very helpful for grabbing addresses out of image footers or phone number
### Dictation
Omarchy offers AI dictation via [Voxtype](https://voxtype.io/). You install it via _Install > AI > Dictation_ through the Omarchy menu. By default, it'll load a base English model that takes up 150MB. But you can tweak which model you'd like to use by running `voxtype setup model` in the terminal. And you can tweak all the settings via `~/.config/voxtype/config.toml`.
On fresh installs, [Superwhisper](https://superwhisper.com/) is the default dictation backend, using cloud processing without offline model downloads. Choose Superwhisper or [Voxtype](https://voxtype.io/) through _Setup > Defaults > Dictation_ in the Omarchy menu. Selecting a backend installs it if needed and makes it the default for dictation. Voxtype loads a base English model that takes up 150MB; change the model with `voxtype setup model` and other settings through `~/.config/voxtype/config.toml`. Superwhisper opens its settings to choose cloud or local processing, and OPR keeps the application updated.
Once installed, you dictate by holding down `F9` or by toggling with `Super + Ctrl + X`, and the dictated text will appear in the focused input area.
You dictate by holding down `Right Alt` or `F9`, or by toggling with `Super + Ctrl + X`, and the dictated text will appear in the focused input area. These shortcuts use the selected backend. Right Alt also starts dictation when used in a modifier chord; use Left Alt for those chords. On keyboard layouts where Right Alt is AltGr, use `F9` to keep AltGr available for typing.
The same shortcuts work with either backend. Select your backend through Setup > Defaults > Dictation. The backend installer configures it and saves the selection. Run `omarchy default dictation` to see which one is selected. The selection is saved in `~/.config/omarchy/defaults/dictation`. Fresh installs select Superwhisper automatically.
Omarchy loads the selected backend's desktop integration automatically. No Superwhisper configuration is needed in `~/.config/hypr/`.
Custom bindings and scripts can use `omarchy dictation start`, `omarchy dictation stop`, and `omarchy dictation toggle`. The selected backend handles the recording and transcription. Selecting Superwhisper disables its native hold shortcut and sets its native toggle to `Alt + Space`, keeping it separate from Omarchy's recording keys. Superwhisper requires at least one native recording shortcut, and its cancellation shortcut stays available.
Additional backends can integrate without changing Omarchy. Provide an executable named `omarchy-dictation-<backend>` on PATH that accepts `start`, `stop`, and `toggle` and returns promptly, with a nonzero status on failure. Its installer configures the provider and calls `omarchy-dictation-use <backend>` after successful setup. This stops the previous backend and saves the selection atomically; `stop` must succeed when the provider is idle or unavailable. Backend names use lowercase letters, digits, and hyphens. Provide `omarchy-install-dictation-<backend>` for setup; `omarchy default dictation <backend>` launches it. Add a Defaults menu entry through an Omarchy menu extension to call that selector. Optional desktop integration can live in `${XDG_CONFIG_HOME:-$HOME/.config}/<backend>/shortcuts.lua`.
+4
View File
@@ -6,6 +6,10 @@ You can do this most easily by going to _Install > Style > Background_ in the Om
Backgrounds can be videos as well as stills. Drop an `mp4`, `m4v`, `mov`, `webm`, `mkv`, or `avi` file in the same folder and it appears alongside the images. Videos are played by the OWE wallpaper engine. It decodes the video once for all monitors and plays its sound through the default audio output, and it stops playback whenever nothing can see it. The lock screen draws the same decode, muted, through OWE. A video wallpaper still costs far more power than a still one.
Themes can include a short intro for a still background. For `backgrounds/road.webp`, the matching video lives at `backgrounds/intros/road.mp4`. It plays automatically at boot and when switching to a theme with that background selected, then returns to the still. Intro videos stay out of the background picker and never loop.
Turning off _Toggle > Animations_ also turns off background intros. Omarchy does this by default in a virtual machine. The `omarchy theme bg intro toggle` command can toggle intros separately. Refreshing a theme or choosing another background within the same theme does not play an intro.
You can find a huge collection of cool curated backgrounds on https://github.com/dharmx/walls.
Video backgrounds keep a cached still on the lock screen while playback is paused or unavailable. Animated GIFs play on the desktop and show a still frame on the lock screen.
+26
View File
@@ -0,0 +1,26 @@
echo "Re-stage the active theme with packaged intro videos"
theme_name_path="$HOME/.local/state/omarchy/current/theme.name"
[[ -s $theme_name_path ]] || exit 0
theme_name=$(<"$theme_name_path")
[[ $theme_name =~ ^[[:alnum:]_][[:alnum:].+_-]*$ ]] || exit 0
[[ -d $OMARCHY_PATH/themes/$theme_name/backgrounds/intros ]] || exit 0
omarchy-theme-refresh
state_dir="$HOME/.local/state/omarchy"
marker="$state_dir/background-intro.session-id"
session_id=${OMARCHY_SESSION_ID:-${HYPRLAND_INSTANCE_SIGNATURE:-}}
if [[ -z $session_id ]]; then
hypr_dir=$(find "${XDG_RUNTIME_DIR:-/run/user/$UID}/hypr" -mindepth 1 -maxdepth 1 -type d -printf '%T@ %p\n' 2>/dev/null | sort -n | tail -n 1 | cut -d' ' -f2- || true)
session_id=${hypr_dir##*/}
fi
[[ -n $session_id ]] || exit 0
mkdir -p "$state_dir"
marker_staged=$(mktemp "$state_dir/.background-intro.session-id.XXXXXX")
trap 'rm -f "$marker_staged"' EXIT
printf '%s\n' "$session_id" >"$marker_staged"
mv -f "$marker_staged" "$marker"
trap - EXIT
+15
View File
@@ -0,0 +1,15 @@
echo "Enable overlay scrollbars in Chromium-based browsers"
for flags_file in "$HOME"/.config/{chromium,chrome,microsoft-edge-stable,brave,brave-origin}-flags.conf; do
[[ -f $flags_file ]] || continue
grep -Eq -- '^--enable-features=([^,]+,)*OverlayScrollbar([,:]|$)' "$flags_file" && continue
if grep -q -- '^--enable-features=' "$flags_file"; then
sed -i --follow-symlinks \
-e '/^--enable-features=/ s/$/,OverlayScrollbar/' \
-e '/^--enable-features=/ s/=,/=/' "$flags_file"
else
[[ -n $(tail -c1 "$flags_file") ]] && echo >>"$flags_file"
echo '--enable-features=OverlayScrollbar' >>"$flags_file"
fi
done
+28
View File
@@ -0,0 +1,28 @@
echo "Prioritize the Omarchy package repository over Arch repositories"
# Move the existing section rather than replacing pacman.conf so channel URLs,
# custom repositories, and administrator options survive the migration.
pacman_config=$(mktemp)
trap 'rm -f "$pacman_config"' EXIT
awk '
/^[[:space:]]*\[[^]]+\]/ {
section = $0
sub(/^[[:space:]]*\[/, "", section)
sub(/\].*$/, "", section)
if (section != "options") repositories = 1
}
!repositories { prefix = prefix $0 "\n"; next }
section == "omarchy" { omarchy = omarchy $0 "\n"; next }
{ others = others $0 "\n" }
END { printf "%s%s%s", prefix, omarchy, others }
' /etc/pacman.conf >"$pacman_config"
if ! cmp -s /etc/pacman.conf "$pacman_config"; then
sudo cp -a /etc/pacman.conf /etc/pacman.conf.bak
sudo install -m 644 -o root -g root "$pacman_config" /etc/pacman.conf
fi
# Refresh databases and upgrade against the new repository priority. Run this
# even when the config is already ordered so a failed upgrade can be retried.
omarchy-update-system-pkgs
+9
View File
@@ -0,0 +1,9 @@
echo "Preserve Voxtype as the default for existing dictation users"
backend_config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
if [[ ! -e $backend_config ]] && omarchy-pkg-present voxtype-bin; then
mkdir -p "${backend_config%/*}"
temporary=$(mktemp "$backend_config.XXXXXX")
printf '%s\n' voxtype > "$temporary"
mv -f "$temporary" "$backend_config"
fi
+7 -6
View File
@@ -1,5 +1,6 @@
pragma Singleton
import QtQuick
import qs.Commons as Commons
import "BorderGeometry.js" as Geometry
// Central border-spec factory for shell surfaces and controls. A spec carries
@@ -22,7 +23,7 @@ QtObject {
}
function value(section, key) {
var v = Color.shellValues[section + "." + key]
var v = Commons.Color.shellValues[section + "." + key]
return (v === undefined || v === null) ? "" : v
}
@@ -39,7 +40,7 @@ QtObject {
var seen = {}
while (s.match(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/) && !seen[s]) {
seen[s] = true
var next = Color.shellValues[s]
var next = Commons.Color.shellValues[s]
if (next === undefined || next === null || String(next).length === 0) break
s = String(next).replace(/^\s+|\s+$/g, "")
}
@@ -68,10 +69,10 @@ QtObject {
var s = String(color || "").replace(/^\s+|\s+$/g, "")
var role = s.toLowerCase()
if (role === "foreground" || role === "text") return cssColor(Color.foreground, a)
if (role === "accent") return cssColor(Color.accent, a)
if (role === "urgent") return cssColor(Color.urgent, a)
if (role === "background") return cssColor(Color.background, a)
if (role === "foreground" || role === "text") return cssColor(Commons.Color.foreground, a)
if (role === "accent") return cssColor(Commons.Color.accent, a)
if (role === "urgent") return cssColor(Commons.Color.urgent, a)
if (role === "background") return cssColor(Commons.Color.background, a)
if (role === "transparent") return "transparent"
return Geometry.canonicalColor(s, a)
}
+6 -6
View File
@@ -396,7 +396,6 @@ var table = {
"Pin": "固定",
"Show": "显示",
"Hide": "隐藏",
"Dictate": "语音输入",
"Allow Notifications": "允许通知",
"Silence Notifications": "静默通知",
"Day Light": "日间光线",
@@ -434,11 +433,9 @@ var table = {
"Download failed": "下载失败",
"Editing config file": "正在编辑配置文件",
"Extended mode restored": "已恢复扩展模式",
"FIDO2 authfile needs attention": "FIDO2 认证文件需要处理",
"Failed to start Windows VM": "无法启动 Windows 虚拟机",
"Herdr theme sync disabled": "Herdr 主题同步已关闭",
"Herdr theme sync enabled": "Herdr 主题同步已开启",
"Hold F9 to dictate (or toggle with Super + Ctrl + X).": "按住 F9 语音输入(或用 Super + Ctrl + X 切换)。",
"Laptop display disabled": "笔记本屏幕已关闭",
"Laptop display enabled": "笔记本屏幕已开启",
"Logging out": "正在注销",
@@ -514,8 +511,6 @@ var table = {
"Enable sudo and unlocking with your fingerprint.": "用指纹进行 sudo 和解锁。",
"Set your default agent": "设置默认 AI 代理",
"Let your favorite agent help with Omarchy.": "让你喜欢的 AI 代理帮你使用 Omarchy。",
"Install Dictation with Voxtype": "安装 Voxtype 语音输入",
"Click to install voice dictation for Omarchy.": "点击为 Omarchy 安装语音输入。",
"All %1 accounts are over %2%": "所有 %1 账户都超过 %2%",
"Staying on %1. %2 resets in %3.": "继续使用 %1。%2 将在 %3 后重置。",
"Staying on %1.": "继续使用 %1。",
@@ -616,5 +611,10 @@ var table = {
"Log out now to switch the language?": "立即注销以切换语言?",
"Log back in to switch the language.": "重新登录后切换语言。",
"Log out now to switch the developer mirrors?": "立即注销以切换开发工具镜像?",
"Log back in to switch the developer mirrors.": "重新登录后切换开发工具镜像。"
"Log back in to switch the developer mirrors.": "重新登录后切换开发工具镜像。",
"Superwhisper Dictation Ready": "Superwhisper 语音输入就绪",
"Hold Right Alt or F9 to dictate (or toggle with Super + Ctrl + X).": "按住右 Alt 或 F9 说话(或用 Super + Ctrl + X 切换)。",
"Background Intros": "背景开场动画",
"Enabled": "已开启",
"Disabled": "已关闭"
}
+22 -21
View File
@@ -1,5 +1,6 @@
pragma Singleton
import QtQuick
import qs.Commons as Commons
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
@@ -114,27 +115,27 @@ QtObject {
}
function resolveStateColor(token, foreground, accent, urgent, fallback) {
var fb = fallback || foreground || Color.foreground
var fb = fallback || foreground || Commons.Color.foreground
var s = String(token || "").replace(/^\s+|\s+$/g, "")
var role = s.toLowerCase()
if (role === "foreground" || role === "text") return foreground || Color.foreground
if (role === "accent") return accent || Color.accent
if (role === "urgent") return urgent || Color.urgent
if (role === "background") return Color.background
if (role === "foreground" || role === "text") return foreground || Commons.Color.foreground
if (role === "accent") return accent || Commons.Color.accent
if (role === "urgent") return urgent || Commons.Color.urgent
if (role === "background") return Commons.Color.background
if (role === "transparent") return Qt.rgba(0, 0, 0, 0)
return colorFromHex(s, fb)
}
function normalStateColor(foreground, accent, urgent) {
return resolveStateColor(normalColorToken, foreground, accent, urgent, foreground || Color.foreground)
return resolveStateColor(normalColorToken, foreground, accent, urgent, foreground || Commons.Color.foreground)
}
function hoverStateColor(foreground, accent, urgent) {
return resolveStateColor(hoverColorToken, foreground, accent, urgent, foreground || Color.foreground)
return resolveStateColor(hoverColorToken, foreground, accent, urgent, foreground || Commons.Color.foreground)
}
function selectedStateColor(foreground, accent, urgent) {
return resolveStateColor(selectedColorToken, foreground, accent, urgent, foreground || Color.foreground)
return resolveStateColor(selectedColorToken, foreground, accent, urgent, foreground || Commons.Color.foreground)
}
function pressedStateColor(foreground, accent, urgent) {
@@ -149,7 +150,7 @@ QtObject {
}
function selectionStateColor(foreground, accent, urgent) {
return resolveStateColor(selectionColorToken, foreground, accent, urgent, foreground || Color.foreground)
return resolveStateColor(selectionColorToken, foreground, accent, urgent, foreground || Commons.Color.foreground)
}
function normalFillFor(foreground, accent, urgent) { return Util.alpha(normalStateColor(foreground, accent, urgent), normalFillAlpha) }
@@ -187,17 +188,17 @@ QtObject {
}
// Convenience colors resolved against the foundational palette.
readonly property color normalFill: normalFillFor(Color.foreground, Color.accent, Color.urgent)
readonly property color hoverFill: hoverFillFor(Color.foreground, Color.accent, Color.urgent)
readonly property color selectedFill: selectedFillFor(Color.foreground, Color.accent, Color.urgent)
readonly property color pressedFill: pressedFillFor(Color.foreground, Color.accent, Color.urgent)
readonly property color focusFillColor: focusFillFor(Color.foreground, Color.accent, Color.urgent)
readonly property color normalBorderColor: normalBorderFor(Color.foreground, Color.accent, Color.urgent)
readonly property color hoverBorderColor: hoverBorderFor(Color.foreground, Color.accent, Color.urgent)
readonly property color selectedBorderColor: selectedBorderFor(Color.foreground, Color.accent, Color.urgent)
readonly property color focusBorderColor: focusBorderFor(Color.foreground, Color.accent, Color.urgent)
readonly property color selectedAccentFill: Util.alpha(Color.accent, selectedFillAlpha)
readonly property color selectionFill: selectionFillFor(Color.foreground, Color.accent, Color.urgent)
readonly property color normalFill: normalFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color hoverFill: hoverFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color selectedFill: selectedFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color pressedFill: pressedFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color focusFillColor: focusFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color normalBorderColor: normalBorderFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color hoverBorderColor: hoverBorderFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color selectedBorderColor: selectedBorderFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color focusBorderColor: focusBorderFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
readonly property color selectedAccentFill: Util.alpha(Commons.Color.accent, selectedFillAlpha)
readonly property color selectionFill: selectionFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
// ---------------------------------------------------------- spacing
//
@@ -390,7 +391,7 @@ QtObject {
}
// Pull typography, bar dimensions, state tokens, and spacing out of the
// shell.toml dict that Color already parsed. Called by Color.loadShell so
// shell.toml dict that Color already parsed. Called by Commons.Color.loadShell so
// a single parse pass feeds both singletons.
function applyShellValues(values) {
var fontOut = {}
+7 -6
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Commons as Commons
// The button. One component for every clickable thing in the kit.
// States compose independently and are applied in priority order:
@@ -32,9 +33,9 @@ BorderSurface {
property bool bordered: false
// Colors. Defaults track the theme; per-instance overrides are honored.
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property color background: "transparent"
property color accent: Color.accent
property color accent: Commons.Color.accent
// Sizing.
property string fontFamily: Style.font.family
@@ -54,9 +55,9 @@ BorderSurface {
// Tooltip palette. Auto-rendered if tooltipText is set. Defaults pull
// from [tooltip] in shell.toml; override per-instance only when a button
// intentionally wants a tooltip that diverges from the theme.
property color tooltipBackground: Color.tooltip.background
property color tooltipForeground: Color.tooltip.text
property color tooltipBorder: Color.tooltip.border
property color tooltipBackground: Commons.Color.tooltip.background
property color tooltipForeground: Commons.Color.tooltip.text
property color tooltipBorder: Commons.Color.tooltip.border
signal clicked()
signal rightClicked()
@@ -77,7 +78,7 @@ BorderSurface {
readonly property bool hot: mouseArea.containsMouse || hasCursor
readonly property bool _showFocusRing: focusable && activeFocus
readonly property color _selectedColor: Style.selectedStateColor(root.foreground, root.accent)
readonly property var _tooltipBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", root.tooltipBorder, Color.tooltip.border, Math.max(1, Style.normalBorderWidth))
readonly property var _tooltipBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", root.tooltipBorder, Commons.Color.tooltip.border, Math.max(1, Style.normalBorderWidth))
readonly property var _focusBorderSpec: Border.controlSpec("focus", root.foreground, root.accent)
readonly property var _hoverBorderSpec: Border.controlSpec("hover-cursor", root.foreground, root.accent)
readonly property var _selectedBorderSpec: Border.controlSpec("selected", root.foreground, root.accent)
+4 -3
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// Mutually-exclusive row of Buttons — the form-style "pick one of N"
// pattern (bar position top/right/bottom/left, theme preset chips, etc.).
@@ -25,9 +26,9 @@ Row {
property var options: []
property string value: ""
property color foreground: Color.foreground
property color background: Color.background
property color accent: Color.accent
property color foreground: Commons.Color.foreground
property color background: Commons.Color.background
property color accent: Commons.Color.accent
property string fontFamily: Style.font.family
property real fontSize: Style.font.body
property bool focusable: true
+9 -8
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
Item {
id: root
@@ -9,11 +10,11 @@ Item {
property string cancelText: I18n.tr("Cancel")
property string confirmText: I18n.tr("Confirm")
property int selectedIndex: 1
property color background: Color.background
property color foreground: Color.foreground
property color scrim: Util.alpha(Color.background, 0.7)
property color selectedBackground: Util.alpha(Color.foreground, 0.08)
property color selectedText: Color.accent
property color background: Commons.Color.background
property color foreground: Commons.Color.foreground
property color scrim: Util.alpha(Commons.Color.background, 0.7)
property color selectedBackground: Util.alpha(Commons.Color.foreground, 0.08)
property color selectedText: Commons.Color.accent
property string fontFamily: Style.font.family
property int cornerRadius: Style.cornerRadius
@@ -98,10 +99,10 @@ Item {
width: Style.space(88)
height: Style.space(34)
color: selected
? (destructive ? Util.alpha(Color.urgent, 0.22) : root.selectedBackground)
? (destructive ? Util.alpha(Commons.Color.urgent, 0.22) : root.selectedBackground)
: "transparent"
borderSpec: Border.flat(destructive
? (selected ? Color.urgent : Util.alpha(Color.urgent, 0.56))
? (selected ? Commons.Color.urgent : Util.alpha(Commons.Color.urgent, 0.56))
: (selected ? root.selectedText : Util.alpha(root.foreground, 0.38)), Style.normalBorderWidth)
radius: 0
@@ -109,7 +110,7 @@ Item {
textFormat: Text.PlainText
anchors.centerIn: parent
text: modelData
color: destructive ? (selected ? Color.urgent : root.foreground) : (selected ? root.selectedText : root.foreground)
color: destructive ? (selected ? Commons.Color.urgent : root.foreground) : (selected ? root.selectedText : root.foreground)
font.family: root.fontFamily
font.pixelSize: Style.font.caption
}
+3 -2
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// Shared visual chrome for keyboard-and-mouse-navigable items inside a panel.
// Contract: items must NOT read `containsMouse` for color/border. Mouse
@@ -19,8 +20,8 @@ BorderSurface {
property bool outline: false
property bool bordered: false
property color foreground: Color.foreground
property color accent: Color.accent
property color foreground: Commons.Color.foreground
property color accent: Commons.Color.accent
property color fill: Style.hoverFillFor(foreground, accent)
property color currentFill: Style.selectedFillFor(foreground, accent)
+8 -7
View File
@@ -1,10 +1,11 @@
import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Commons as Commons
// Themed single-select dropdown. Trigger row paints with the kit's focus
// chrome; the popup anchors below and uses Color.popups.background +
// Color.popups.border so it reads as a panel surface rather than the
// chrome; the popup anchors below and uses Commons.Color.popups.background +
// Commons.Color.popups.border so it reads as a panel surface rather than the
// platform-native ComboBox look.
//
// `options` accepts either a plain string[] or an array of
@@ -22,11 +23,11 @@ Item {
property string value: ""
property var options: []
property color foreground: Color.popups.text
property color background: Color.popups.background
property color popupBorder: Color.popups.border
property color accent: Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Color.popups.border, Style.normalBorderWidth)
property color foreground: Commons.Color.popups.text
property color background: Commons.Color.popups.background
property color popupBorder: Commons.Color.popups.border
property color accent: Commons.Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Commons.Color.popups.border, Style.normalBorderWidth)
property string fontFamily: Style.font.family
property int rowHeight: Style.spacing.controlHeight
property int popupRowHeight: Style.spacing.popupRowHeight
+3 -2
View File
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Commons as Commons
// Layer-shell popup attached to a bar widget icon, designed for
// click-driven AND keyboard-driven panels (e.g. SUPER+CTRL+W summon).
@@ -44,7 +45,7 @@ PanelWindow {
property int padding: Style.spacing.popupPadding
property int contentWidth: Style.space(280)
property int contentHeight: Style.space(200)
property var borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2)))
property var borderSpec: Border.surfaceSpec("popups", "border", Commons.Color.popups.border, Math.max(1, Style.space(2)))
property bool centerOnBar: false
property bool open: false
property int gap: Style.gapsOut // distance between bar edge and panel
@@ -382,7 +383,7 @@ PanelWindow {
y: root.cardOrigin.y
width: root.contentWidth
height: root.contentHeight
color: Color.popups.background
color: Commons.Color.popups.background
borderSpec: root.borderSpec
padding: root.padding
radius: Style.cornerRadius
+6 -5
View File
@@ -3,6 +3,7 @@ import QtQuick.Controls as QQC
import QtQuick.Window
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
// Searchable multi-select dropdown. Trigger shape matches Dropdown /
// SearchableDropdown; the popup shows a search field, an optional refresh
@@ -33,11 +34,11 @@ Item {
property string triggerLabel: ""
property bool showLabel: true
property color foreground: Color.popups.text
property color background: Color.popups.background
property color popupBorder: Color.popups.border
property color accent: Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Color.popups.border, Style.normalBorderWidth)
property color foreground: Commons.Color.popups.text
property color background: Commons.Color.popups.background
property color popupBorder: Commons.Color.popups.border
property color accent: Commons.Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Commons.Color.popups.border, Style.normalBorderWidth)
property string fontFamily: Style.font.family
property int rowHeight: Style.spacing.controlHeight
property int popupRowHeight: Style.spacing.popupRowHeight
+3 -2
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls as QQC
import qs.Commons
import qs.Commons as Commons
Column {
id: root
@@ -10,8 +11,8 @@ Column {
property int from: 0
property int to: 100
property int stepSize: 1
property color foreground: Color.foreground
property color accent: Color.accent
property color foreground: Commons.Color.foreground
property color accent: Commons.Color.accent
property string fontFamily: Style.font.family
property real fontSize: Style.font.body
property real fieldWidth: Style.spacing.numberFieldWidth
+2 -1
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
Item {
id: root
@@ -7,7 +8,7 @@ Item {
property string text: ""
property string fontFamily: Style.font.family
property real fontSize: Style.font.body
property color color: Color.foreground
property color color: Commons.Color.foreground
property bool debugBounds: false
readonly property int renderedFontSize: Math.max(1, Math.round(fontSize))
+57 -24
View File
@@ -3,12 +3,9 @@ import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
// A fullscreen overlay whose surface outlives each open. A fresh surface draws
// its first frames before the compositor sends its fractional scale, so an
// overlay mapped per open flashed blurry until the scale arrived. Closed, the
// surface parks as a 1x1, input-less, content-less layer below windows: small
// enough to cost nothing, off the overlay layer so it never blocks direct
// scanout for fullscreen apps. Opening only resizes and raises it.
// Map a fresh fullscreen surface on each open. Keep its first frames
// transparent until Qt receives the output's scale, so it opens sharp without
// retaining a parked surface that can be closed when a monitor disconnects.
PanelWindow {
id: window
@@ -17,11 +14,31 @@ PanelWindow {
property int shownLayer: WlrLayer.Overlay
property int shownKeyboardFocus: WlrKeyboardFocus.Exclusive
// The surface no longer lands on the focused output by being mapped there,
// so it follows the focused monitor each time it is shown. Unset until the
// first show lets the compositor choose.
property var targetScreen: null
property Region emptyRegion: Region {}
readonly property var targetMonitor: {
var monitors = Hyprland.monitors.values
for (var i = 0; i < monitors.length; i++) {
if (targetScreen && monitors[i].name === targetScreen.name) return monitors[i]
}
return null
}
// Wait for fullscreen geometry as well as scale so no smaller opening frame
// is stretched across the output. Fractional scale uses units of 1/120;
// compare in those units to tolerate floating point rounding.
readonly property bool contentReady: shown && backingWindowVisible && !!targetScreen
&& width === targetScreen.width && height === targetScreen.height
&& !!targetMonitor && Math.round(devicePixelRatio * 120) === Math.round(targetMonitor.scale * 120)
property bool contentRevealed: false
onContentReadyChanged: {
contentRevealed = false
// Resize and scale notifications arrive while Qt is still updating the
// window. Reveal on the next turn, once its content layout has caught up.
if (contentReady) Qt.callLater(function() {
if (window.contentReady) window.contentRevealed = true
})
}
function focusedScreen() {
var monitor = Hyprland.focusedMonitor
@@ -29,28 +46,44 @@ PanelWindow {
for (var i = 0; i < Quickshell.screens.length; i++) {
if (Quickshell.screens[i].name === name) return Quickshell.screens[i]
}
return null
return Quickshell.screens.length > 0 ? Quickshell.screens[0] : null
}
onShownChanged: if (shown) targetScreen = focusedScreen() || targetScreen
// Live monitor scaling does not emit a Hyprland monitor event, so its cached
// scale can lag behind Qt. Refresh on opening and on scale changes while open.
onShownChanged: {
targetScreen = shown ? focusedScreen() : null
if (shown) Hyprland.refreshMonitors()
}
onDevicePixelRatioChanged: if (shown) Hyprland.refreshMonitors()
visible: true
Connections {
target: Quickshell
function onScreensChanged() {
if (window.shown && Quickshell.screens.indexOf(window.targetScreen) < 0) {
window.targetScreen = null
// Let the removed output finish closing its old window before mapping
// a replacement, rather than recreating it during Qt's teardown.
Qt.callLater(function() {
if (window.shown && !window.targetScreen) window.targetScreen = window.focusedScreen()
})
}
}
}
visible: shown && !!targetScreen && !!targetMonitor
screen: targetScreen
anchors { top: true; left: true; bottom: shown; right: shown }
implicitWidth: 1
implicitHeight: 1
mask: shown ? null : emptyRegion
anchors { top: true; left: true; bottom: true; right: true }
color: "transparent"
exclusionMode: ExclusionMode.Ignore
WlrLayershell.layer: shown ? shownLayer : WlrLayer.Bottom
WlrLayershell.keyboardFocus: shown ? shownKeyboardFocus : WlrKeyboardFocus.None
WlrLayershell.layer: shownLayer
WlrLayershell.keyboardFocus: shownKeyboardFocus
// Draw nothing until the surface has actually grown. A frame drawn while it
// is still 1x1 holds only the scrim's color, which the compositor would
// stretch across the whole screen until the fullscreen frame arrives.
// Opacity preserves keyboard handling while the scale arrives, including
// search keystrokes and Escape pressed immediately after opening.
Binding {
target: window.contentItem
property: "visible"
value: window.shown && window.width > 1 && window.height > 1
property: "opacity"
value: window.contentRevealed ? 1 : 0
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
import QtQuick
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
// Base item for plugin popup widgets. Many first-party plugins expose a bar
// button plus a popup from one QML entry point; this base owns the shared
@@ -19,7 +20,7 @@ Item {
property bool popoutSwitchClosing: false
readonly property bool opened: panelController.open
readonly property color barForeground: bar ? bar.barForeground : Color.foreground
readonly property color barForeground: bar ? bar.barForeground : Commons.Color.foreground
function open() { panelController.show() }
function close() { panelController.hide() }
+3 -2
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// Small (22×22 by default) icon button used at the right edge of panel rows
// for inline actions — forget network, confirm passphrase, unpair device,
@@ -29,7 +30,7 @@ BorderSurface {
property string iconText: ""
property string tooltipText: ""
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property color hoverColor: foreground
property string fontFamily: Style.font.family
property real fontSize: Style.font.icon
@@ -57,7 +58,7 @@ BorderSurface {
? Border.controlSpec("focus", hoverColor, hoverColor)
: (_hot && bordered
? Border.controlSpec("hover-cursor", hoverColor, hoverColor)
: (bordered ? Border.controlSpec("normal", foreground, Color.accent) : Border.none()))
: (bordered ? Border.controlSpec("normal", foreground, Commons.Color.accent) : Border.none()))
color: _showFocusRing
? Style.focusFillFor(hoverColor, hoverColor)
+3 -2
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
Item {
id: root
@@ -8,7 +9,7 @@ Item {
property string title: ""
property string meta: ""
property string detail: ""
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property string fontFamily: Style.font.family
property real iconSize: Style.font.display
property real iconOpacity: 1.0
@@ -71,7 +72,7 @@ Item {
implicitHeight: detailText.implicitHeight + Style.space(4)
anchors.verticalCenter: parent.verticalCenter
color: "transparent"
borderSpec: Border.controlSpec("normal", root.foreground, Color.accent)
borderSpec: Border.controlSpec("normal", root.foreground, Commons.Color.accent)
radius: Style.cornerRadius
Text {
+2 -1
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// Small-caps-style label that introduces a panel section ("DNS provider",
// "Wi-Fi networks", "Output device", "Paired devices"). Sits between a
@@ -7,7 +8,7 @@ import qs.Commons
Text {
id: root
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property string fontFamily: Style.font.family
property real fontSize: Style.font.caption
+2 -1
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// 1px horizontal divider for panel sections. The alpha-on-foreground tint
// keeps the rule legible against the panel background without competing
@@ -7,7 +8,7 @@ import qs.Commons
Rectangle {
id: root
property color foreground: Color.foreground
property color foreground: Commons.Color.foreground
property real strength: 0.12
width: parent ? parent.width : implicitWidth
+5 -4
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
Item {
id: root
@@ -10,9 +11,9 @@ Item {
property real maximum: 1
property real step: 0.05
property bool integer: false
property color trackColor: bar ? Style.selectedFillFor(bar.foreground, Color.accent) : "#333"
property color fillColor: bar ? bar.foreground : Color.foreground
property color knobColor: bar ? bar.foreground : Color.foreground
property color trackColor: bar ? Style.selectedFillFor(bar.foreground, Commons.Color.accent) : "#333"
property color fillColor: bar ? bar.foreground : Commons.Color.foreground
property color knobColor: bar ? bar.foreground : Commons.Color.foreground
property bool dragging: false
property real trackHeight: Math.max(4, Math.round(Style.spacing.controlHeight * 0.11))
property real knobSize: Math.max(14, Math.round(Style.spacing.controlHeight * 0.38))
@@ -23,7 +24,7 @@ Item {
// crossing the track shows). Purely visual — snapping is the caller's job via
// `integer`/`step` or an index-based value. Default 0 leaves the track plain.
property int tickCount: 0
property color tickColor: bar ? bar.background : Color.background
property color tickColor: bar ? bar.background : Commons.Color.background
onValueChanged: if (!dragging) liveValue = value
+6 -5
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Commons as Commons
// Styled wrapper around Qt Quick Controls ToolTip. Drop-in: declare inside
// the hovered item and bind `visible` to the hover state, e.g.
@@ -9,7 +10,7 @@ import qs.Commons
// text: "Forget network"
// }
//
// Defaults pull from [tooltip] in shell.toml via Color.tooltip.*. Override
// Defaults pull from [tooltip] in shell.toml via Commons.Color.tooltip.*. Override
// the panel* properties per-instance only when you need a tooltip that
// intentionally diverges from the theme.
//
@@ -18,13 +19,13 @@ import qs.Commons
ToolTip {
id: root
property color panelForeground: Color.tooltip.text
property color panelBackground: Color.tooltip.background
property color panelBorder: Color.tooltip.border
property color panelForeground: Commons.Color.tooltip.text
property color panelBackground: Commons.Color.tooltip.background
property color panelBorder: Commons.Color.tooltip.border
property string fontFamily: Style.font.family
property real fontSize: Style.font.bodySmall
readonly property var panelBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", panelBorder, Color.tooltip.border, Style.normalBorderWidth)
readonly property var panelBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", panelBorder, Commons.Color.tooltip.border, Style.normalBorderWidth)
delay: 400
padding: 0
+4 -3
View File
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Hyprland
import qs.Commons
import qs.Commons as Commons
PopupWindow {
id: root
@@ -13,8 +14,8 @@ PopupWindow {
property int padding: Style.spacing.popupPadding
property int contentWidth: Style.space(280)
property int contentHeight: Style.space(200)
property color borderColor: Color.popups.border
property var borderSpec: Border.localOrSurfaceSpec("popups", "border", borderColor, Color.popups.border, Math.max(1, Style.space(2)))
property color borderColor: Commons.Color.popups.border
property var borderSpec: Border.localOrSurfaceSpec("popups", "border", borderColor, Commons.Color.popups.border, Math.max(1, Style.space(2)))
property bool open: false
property bool centerOnBar: false
// "click" — uses HyprlandFocusGrab so clicking outside dismisses the popup.
@@ -150,7 +151,7 @@ PopupWindow {
BorderSurface {
id: card
anchors.fill: parent
color: Color.popups.background
color: Commons.Color.popups.background
borderSpec: root.borderSpec
padding: root.padding
radius: Style.cornerRadius
+6 -5
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls as QQC
import qs.Commons
import qs.Commons as Commons
// Searchable single-select dropdown. Same trigger shape as Dropdown, but
// the popup leads with an embedded TextField that filters the option
@@ -25,11 +26,11 @@ Item {
property string emptyText: I18n.tr("No matches")
property string triggerLabel: ""
property color foreground: Color.popups.text
property color background: Color.popups.background
property color popupBorder: Color.popups.border
property color accent: Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Color.popups.border, Style.normalBorderWidth)
property color foreground: Commons.Color.popups.text
property color background: Commons.Color.popups.background
property color popupBorder: Commons.Color.popups.border
property color accent: Commons.Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Commons.Color.popups.border, Style.normalBorderWidth)
property string fontFamily: Style.font.family
property int rowHeight: Style.spacing.controlHeight
property int popupRowHeight: Style.spacing.popupRowHeight
+5 -4
View File
@@ -4,6 +4,7 @@ import QtQuick.Shapes
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Commons as Commons
import qs.Ui
// Centered speed test overlay shared by the network and disk speed tests. No
@@ -288,7 +289,7 @@ PanelWindow {
// round caps would leave a stray dot at the foot of the scale.
ShapePath {
strokeWidth: dial.arcWidth * 3
strokeColor: dial.arcVisible ? Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.18) : "transparent"
strokeColor: dial.arcVisible ? Qt.rgba(Commons.Color.accent.r, Commons.Color.accent.g, Commons.Color.accent.b, 0.18) : "transparent"
fillColor: "transparent"
capStyle: ShapePath.RoundCap
@@ -305,7 +306,7 @@ PanelWindow {
// Value: fills behind the needle.
ShapePath {
strokeWidth: dial.arcWidth
strokeColor: dial.arcVisible ? Color.accent : "transparent"
strokeColor: dial.arcVisible ? Commons.Color.accent : "transparent"
fillColor: "transparent"
capStyle: ShapePath.RoundCap
@@ -356,8 +357,8 @@ PanelWindow {
radius: width / 2
gradient: Gradient {
GradientStop { position: 0.0; color: Color.accent }
GradientStop { position: 0.55; color: Color.accent }
GradientStop { position: 0.0; color: Commons.Color.accent }
GradientStop { position: 0.55; color: Commons.Color.accent }
GradientStop { position: 1.0; color: "transparent" }
}
}
+3 -2
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Commons as Commons
// Single-line text input with the kit's focus + selection styling. Inherits
// from Qt Quick Controls TextField so the underlying type's API (text,
@@ -18,8 +19,8 @@ import qs.Commons
TextField {
id: root
property color foreground: Color.foreground
property color accent: Color.accent
property color foreground: Commons.Color.foreground
property color accent: Commons.Color.accent
property color selectionTint: Style.selectionFillFor(foreground, accent)
property bool password: false
property real horizontalPadding: Style.spacing.controlPaddingX
+3 -2
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// Labeled toggle row: title + optional description on the left, a
// `ToggleSwitch` on the right. Clicking anywhere on the row emits `clicked()`;
@@ -28,8 +29,8 @@ BorderSurface {
// Override per-instance if a caller wants the opposite.
property bool rounded: Style.cornerRadius > 0
property color foreground: Color.foreground
property color accent: Color.accent
property color foreground: Commons.Color.foreground
property color accent: Commons.Color.accent
property string fontFamily: Style.font.family
property real titleSize: Style.font.subtitle
property real descriptionSize: Style.font.caption
+3 -2
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
// Bare on/off switch: a track with a sliding knob and no label. This is the
// switch `Toggle` parks at the end of its labeled row, factored out so panel
@@ -40,8 +41,8 @@ Item {
property bool cursorRing: interactive
property int cursorPad: Style.space(6)
property bool rounded: Style.cornerRadius > 0
property color foreground: Color.foreground
property color accent: Color.accent
property color foreground: Commons.Color.foreground
property color accent: Commons.Color.accent
signal toggled()
signal hovered(bool isHovered)
+3 -2
View File
@@ -1,5 +1,6 @@
import QtQuick
import qs.Commons
import qs.Commons as Commons
Item {
id: root
@@ -8,8 +9,8 @@ Item {
property string text: ""
property string fontFamily: bar ? bar.fontFamily : Style.font.family
property real fontSize: Style.font.body
property color foreground: bar ? bar.barForeground : Color.foreground
property color activeColor: bar ? bar.urgent : Color.urgent
property color foreground: bar ? bar.barForeground : Commons.Color.foreground
property color activeColor: bar ? bar.urgent : Commons.Color.urgent
property bool active: false
property real horizontalMargin: 8.5
property real verticalPadding: 6
+17 -16
View File
@@ -3,6 +3,7 @@ import QtQuick.Controls
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
import qs.Ui
Panel {
@@ -11,11 +12,11 @@ Panel {
ipcTarget: "omarchy.agents"
manageIpc: false
readonly property color foreground: bar ? bar.foreground : Color.foreground
readonly property color urgent: bar ? bar.urgent : Color.urgent
readonly property color foreground: bar ? bar.foreground : Commons.Color.foreground
readonly property color urgent: bar ? bar.urgent : Commons.Color.urgent
readonly property color dim: Qt.darker(foreground, 1.55)
readonly property color surface: Color.popups.background
readonly property color track: Style.selectedFillFor(foreground, Color.accent)
readonly property color surface: Commons.Color.popups.background
readonly property color track: Style.selectedFillFor(foreground, Commons.Color.accent)
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
// Every subscription on one page, limits first: the question this panel
@@ -725,7 +726,7 @@ Panel {
function iconCandidatesForProvider(p, surfaceColor) {
if (!p) return []
var candidates = []
if (colorLuminance(surfaceColor || Color.background) >= 0.5)
if (colorLuminance(surfaceColor || Commons.Color.background) >= 0.5)
candidates.push(Qt.resolvedUrl("assets/" + p.providerId + "-light.svg"))
candidates.push(Qt.resolvedUrl("assets/" + p.providerId + ".svg"))
return candidates
@@ -1116,7 +1117,7 @@ Panel {
anchors.horizontalCenter: parent.horizontalCenter
textFormat: Text.PlainText
text: choice.modelData.providerName
color: choice.hasCursor ? Color.accent : root.foreground
color: choice.hasCursor ? Commons.Color.accent : root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.body
font.bold: true
@@ -1196,7 +1197,7 @@ Panel {
Text {
textFormat: Text.PlainText
text: root.addCode
color: Color.accent
color: Commons.Color.accent
font.family: root.fontFamily
font.pixelSize: Style.font.display
font.bold: true
@@ -1538,7 +1539,7 @@ Panel {
property bool hasCursor: false
implicitHeight: tileBody.implicitHeight + Style.space(20)
radius: Style.cornerRadius
color: hasCursor ? root.alpha(Color.accent, 0.14) : root.alpha(root.foreground, 0.05)
color: hasCursor ? root.alpha(Commons.Color.accent, 0.14) : root.alpha(root.foreground, 0.05)
onHasCursorChanged: if (hasCursor) root.revealItem(tile)
Row {
@@ -1550,7 +1551,7 @@ Panel {
anchors.verticalCenter: parent.verticalCenter
textFormat: Text.PlainText
text: tile.glyph
color: Color.accent
color: Commons.Color.accent
font.family: root.fontFamily
font.pixelSize: Style.font.heading
}
@@ -1559,7 +1560,7 @@ Panel {
anchors.verticalCenter: parent.verticalCenter
textFormat: Text.PlainText
text: tile.title
color: tile.hasCursor ? Color.accent : root.foreground
color: tile.hasCursor ? Commons.Color.accent : root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.body
font.bold: true
@@ -1593,16 +1594,16 @@ Panel {
implicitHeight: implicitWidth
radius: Style.cornerRadius
// The cursor needs more than a shade deeper to read on a tinted square.
color: root.alpha(Color.accent, hasCursor ? 0.3 : 0.12)
color: root.alpha(Commons.Color.accent, hasCursor ? 0.3 : 0.12)
border.width: hasCursor ? Math.max(1, Style.hoverBorderWidth) : 0
border.color: Color.accent
border.color: Commons.Color.accent
onHasCursorChanged: if (hasCursor) root.revealItem(heroButton)
Text {
anchors.centerIn: parent
textFormat: Text.PlainText
text: heroButton.glyph
color: Color.accent
color: Commons.Color.accent
font.family: root.fontFamily
font.pixelSize: Style.font.heading
}
@@ -1634,7 +1635,7 @@ Panel {
readonly property bool hot: linkMouse.containsMouse || picked
onPickedChanged: if (picked) root.revealItem(link)
textFormat: Text.PlainText
color: current ? Color.accent : (hot ? root.foreground : idleColor)
color: current ? Commons.Color.accent : (hot ? root.foreground : idleColor)
font.family: root.fontFamily
font.pixelSize: Style.font.caption
font.bold: current
@@ -1715,7 +1716,7 @@ Panel {
anchors.verticalCenter: parent.verticalCenter
width: Math.min(implicitWidth, parent.width * 0.6)
text: head.label
color: head.picked ? Color.accent : root.foreground
color: head.picked ? Commons.Color.accent : root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.body
font.bold: head.isActive
@@ -1821,7 +1822,7 @@ Panel {
anchors.right: parent.right
textFormat: Text.PlainText
text: I18n.tr("ACTIVE")
color: Color.accent
color: Commons.Color.accent
font.family: root.fontFamily
font.pixelSize: Style.font.caption
font.bold: true
+33 -6
View File
@@ -5,12 +5,14 @@ import QtQuick
import QtQuick.Effects
import QtQuick.Shapes
import qs.Commons
import qs.Commons as Commons
import qs.Ui
Item {
id: root
property var shell: null
property bool suspended: false
readonly property string home: Quickshell.env("HOME")
readonly property string stateHome: home + "/.local/state"
readonly property string currentBackgroundLink: stateHome + "/omarchy/current/background"
@@ -37,11 +39,20 @@ Item {
property var sizeQueue: []
property bool finishingTransition: false
property int backgroundVersion: 0
property int reloadVersion: 0
property int revealStartedVersion: -1
property int pendingThemeVersion: -1
property string pendingColorsRaw: ""
property string pendingShellRaw: ""
property real revealProgress: 1
readonly property bool ready: {
if (isVideo(displayedBackground)) return true
if (backgrounds.instances.length === 0) return false
for (var panel of backgrounds.instances) {
if (!panel.backgroundReady) return false
}
return true
}
function isVideo(path) {
return Util.isVideoPath(path)
@@ -56,7 +67,8 @@ Item {
}
function setBackground(path, instant) {
transitionBackground("", path, path, instant, false)
if (instant) reloadVersion += 1
transitionBackground("", path, path, instant, instant)
}
function transitionBackground(fromPath, path, finalPath, instant, force) {
@@ -106,10 +118,10 @@ Item {
// pending; the latest theme payload should still apply.
if (pendingThemeVersion < 0) return
pendingThemeFallbackTimer.stop()
Color.loadColors(pendingColorsRaw)
// Color.loadShell also refreshes Style so the type scale flips with the
Commons.Color.loadColors(pendingColorsRaw)
// Commons.Color.loadShell also refreshes Style so the type scale flips with the
// background reveal instead of waiting for a separate reload path.
Color.loadShell(pendingShellRaw)
Commons.Color.loadShell(pendingShellRaw)
Style.scheduleRefresh()
pendingThemeVersion = -1
pendingColorsRaw = ""
@@ -223,6 +235,10 @@ Item {
root.setBackground(path, true)
}
function setSuspended(value: string): void {
root.suspended = value === "true"
}
function transition(fromPath: string, path: string): void {
root.transitionBackground(fromPath, path, path, false, false)
}
@@ -259,7 +275,7 @@ Item {
from: 0
to: 1
duration: Style.duration(420)
easing.type: Easing.InOutCubic
easing.type: Easing.OutCubic
onFinished: {
if (root.incomingBackground) {
root.displayedBackground = root.currentBackground || root.incomingBackground
@@ -272,6 +288,7 @@ Item {
Component.onCompleted: refreshBackground()
Variants {
id: backgrounds
model: Quickshell.screens
PanelWindow {
@@ -279,7 +296,7 @@ Item {
required property var modelData
screen: modelData
visible: !remapGuard.remapping
visible: !remapGuard.remapping && !root.suspended
anchors { top: true; bottom: true; left: true; right: true }
ScreenMoveRemap {
@@ -294,6 +311,13 @@ Item {
updatesEnabled: true
property bool maskReady: false
property int readyFrames: 0
readonly property bool backgroundReady: base.ready && readyFrames >= 2
FrameAnimation {
running: base.ready && panel.readyFrames < 2
onTriggered: panel.readyFrames += 1
}
// Decode the wallpaper at the size this screen can show, not the size
// it was shipped at. With PreserveAspectCrop Qt takes sourceSize as the
@@ -337,9 +361,12 @@ Item {
id: base
anchors.fill: parent
path: root.displayedBackground
version: root.reloadVersion
cached: true
constrainDecode: true
decodeSize: panel.decodeSize(root.displayedBackground)
onReadyChanged: {
panel.readyFrames = 0
if (ready && root.finishingTransition) {
root.incomingBackground = ""
root.oldBackground = ""
+14 -13
View File
@@ -5,6 +5,7 @@ import Quickshell.Wayland
import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "BarModel.js" as BarModel
@@ -71,18 +72,18 @@ Item {
property string fontFamily: Style.font.family
// Bound to the central Color singleton so the bar tracks shell.toml's
// [bar] section. Property names kept for the rest of this file's bindings.
property color themeForeground: Color.bar.text
property color themeContrastForeground: Color.background
property color transparentForeground: Color.bar.text
property color themeForeground: Commons.Color.bar.text
property color themeContrastForeground: Commons.Color.background
property color transparentForeground: Commons.Color.bar.text
property color foreground: themeForeground
property color barForeground: useTransparentForeground ? transparentForeground : themeForeground
property bool foregroundAnimationEnabled: true
property color background: Color.bar.background
property color urgent: Color.bar.active
property color background: Commons.Color.bar.background
property color urgent: Commons.Color.bar.active
Behavior on barForeground { enabled: root.foregroundAnimationEnabled; ColorAnimation { duration: Style.duration(420); easing.type: Easing.InOutCubic } }
Behavior on background { ColorAnimation { duration: Style.duration(420); easing.type: Easing.InOutCubic } }
Behavior on urgent { ColorAnimation { duration: Style.duration(420); easing.type: Easing.InOutCubic } }
Behavior on barForeground { enabled: root.foregroundAnimationEnabled; ColorAnimation { duration: Style.duration(420); easing.type: Easing.OutCubic } }
Behavior on background { ColorAnimation { duration: Style.duration(420); easing.type: Easing.OutCubic } }
Behavior on urgent { ColorAnimation { duration: Style.duration(420); easing.type: Easing.OutCubic } }
property var tooltipTarget: null
property var pendingTooltipTarget: null
property string tooltipText: ""
@@ -1342,8 +1343,8 @@ Item {
id: tooltipBubble
implicitWidth: tooltipLabel.implicitWidth + 20
implicitHeight: tooltipLabel.implicitHeight + 14
color: Color.tooltip.background
borderSpec: Border.surfaceSpec("tooltip", "border", Color.tooltip.border, 1)
color: Commons.Color.tooltip.background
borderSpec: Border.surfaceSpec("tooltip", "border", Commons.Color.tooltip.border, 1)
radius: Style.cornerRadius
Text {
@@ -1351,7 +1352,7 @@ Item {
textFormat: Text.PlainText
anchors.centerIn: parent
text: root.tooltipText
color: Color.tooltip.text
color: Commons.Color.tooltip.text
font.family: root.fontFamily
font.pixelSize: Style.font.body
horizontalAlignment: Text.AlignHCenter
@@ -1470,7 +1471,7 @@ Item {
y: targetRect ? Math.round(targetRect.y) : 0
width: targetRect ? targetRect.width : 0
height: targetRect ? targetRect.height : 0
color: Color.accent
color: Commons.Color.accent
radius: Math.min(width, height) / 2
}
}
@@ -1905,7 +1906,7 @@ Item {
visible: opacity > 0
opacity: slot.panelOpen && !slot.dragSource ? 0.9 : 0
color: Color.accent
color: Commons.Color.accent
radius: Math.min(width, height) / 2
width: root.vertical ? Style.space(2) : slot.panelIndicatorExtent
height: root.vertical ? slot.panelIndicatorExtent : Style.space(2)
@@ -1,39 +0,0 @@
import QtQuick
import Quickshell.Io
import qs.Commons
import qs.Ui
BarIndicator {
id: root
property string state: "idle"
property string icon: ""
active: state === "recording"
activeText: icon
inactiveText: "󰍬"
activeTooltipText: state
inactiveTooltipText: I18n.tr("Dictate")
function update(raw) {
var data = extractData(raw)
state = String(data.alt || data.class || "idle")
if (state === "recording") icon = "󰍬"
else if (state === "transcribing") icon = "󰔟"
else icon = ""
}
Process {
command: ["bash", "-c", "omarchy-voxtype-status"]
running: true
stdout: SplitParser {
onRead: function(data) { root.update(data) }
}
}
onPressed: function() {
if (!root.bar) return
root.bar.run("omarchy-voxtype-config")
}
}
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Commons as Commons
import qs.Ui
BarIndicator {
@@ -16,7 +17,7 @@ BarIndicator {
activeTooltipText: I18n.tr("Disable Passwordless Sudo")
inactiveTooltipText: I18n.tr("Passwordless Sudo")
useActiveColor: true
activeColor: Color.urgent
activeColor: Commons.Color.urgent
function refresh() {
if (!root.bar) return
+2 -1
View File
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Commons as Commons
import qs.Ui
BarWidget {
@@ -34,7 +35,7 @@ BarWidget {
anchors.left: parent.left
width: parent.width
text: root.title
color: root.bar ? root.bar.barForeground : Color.foreground
color: root.bar ? root.bar.barForeground : Commons.Color.foreground
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.body
elide: Text.ElideRight
@@ -31,11 +31,6 @@
"label": "Passwordless sudo",
"description": "Warning while passwordless root access is enabled"
},
{
"value": "Dictation",
"label": "Dictation",
"description": "Voice typing status"
},
{
"value": "ScreenRecording",
"label": "Screen recording",
+1 -1
View File
@@ -8,7 +8,7 @@ BarWidget {
id: root
moduleName: "omarchy.indicators"
readonly property var defaultIndicatorEntries: [ "PasswordlessSudo", "Dictation", "ScreenRecording", "Reminder", "NightLight", "Dnd", "StayAwake" ]
readonly property var defaultIndicatorEntries: [ "PasswordlessSudo", "ScreenRecording", "Reminder", "NightLight", "Dnd", "StayAwake" ]
readonly property var indicatorEntries: indicatorEntriesFromSettings(settings)
property var activeIndicatorIds: []
property var indicatorActiveStates: ({})
+4 -3
View File
@@ -4,6 +4,7 @@ import QtQuick.Controls
import QtQuick.Effects
import Quickshell.Services.SystemTray
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "TrayModel.js" as TrayModel
@@ -16,7 +17,7 @@ BarWidget {
property bool trayMenuOpen: false
property var activeTrayItem: null
property var activeTrayAnchor: null
readonly property color foreground: bar ? bar.foreground : Color.foreground
readonly property color foreground: bar ? bar.foreground : Commons.Color.foreground
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
readonly property var pinnedIds: settings.pinned instanceof Array ? settings.pinned : []
readonly property var hiddenIds: settings.hidden instanceof Array ? settings.hidden : []
@@ -620,7 +621,7 @@ BarWidget {
anchors.rightMargin: Style.space(10)
anchors.verticalCenter: parent.verticalCenter
height: 1
color: Color.popups.border
color: Commons.Color.popups.border
opacity: 0.45
}
}
@@ -674,7 +675,7 @@ BarWidget {
anchors.rightMargin: Style.space(10)
anchors.verticalCenter: parent.verticalCenter
height: 1
color: Color.popups.border
color: Commons.Color.popups.border
opacity: 0.45
}
+7 -6
View File
@@ -3,6 +3,7 @@ import Quickshell.Io
import Quickshell.Wayland
import QtQuick
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "ClipboardHistory.js" as ClipboardHistory
@@ -22,13 +23,13 @@ Item {
// Shares the [menu] surface tokens — themes that style the menu also
// style the clipboard. Selected-row colors composed in the
// singleton so consumers drop them straight into Rectangle bindings.
property color background: Color.menu.background
property color foreground: Color.menu.text
property color border: Color.menu.border
property color background: Commons.Color.menu.background
property color foreground: Commons.Color.menu.text
property color border: Commons.Color.menu.border
property var borderSpec: Border.surfaceSpec("menu", "border", border, Math.max(1, Style.space(2)))
property color scrim: Color.menu.scrim
property color selectedBackground: Color.menu.selectedBackground
property color selectedText: Color.menu.selectedText
property color scrim: Commons.Color.menu.scrim
property color selectedBackground: Commons.Color.menu.selectedBackground
property color selectedText: Commons.Color.menu.selectedText
readonly property int cornerRadius: Style.cornerRadius
property string fontFamily: Style.font.menuFamily
property int contentMargin: Style.spacing.panelPadding
+5 -4
View File
@@ -3,6 +3,7 @@ import QtQuick.Controls
import Quickshell
import qs.Ui
import qs.Commons
import qs.Commons as Commons
// Visual reference + live playground for omarchy-shell's common UI
// components. Summon with `omarchy dev ui-preview`, or directly via:
@@ -66,10 +67,10 @@ Item {
property var shell: null
// ---- theme --------------------------------------------------------------
readonly property color foreground: Color.foreground
readonly property color background: Color.background
readonly property color accent: Color.accent
readonly property color urgent: Color.urgent
readonly property color foreground: Commons.Color.foreground
readonly property color background: Commons.Color.background
readonly property color accent: Commons.Color.accent
readonly property color urgent: Commons.Color.urgent
readonly property string fontFamily: "monospace"
// Fake `bar` for components that take a whole bar object (e.g. Slider).
+7 -6
View File
@@ -3,6 +3,7 @@ import Quickshell.Io
import Quickshell.Wayland
import QtQuick
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "EmojiSearch.js" as EmojiSearch
@@ -23,13 +24,13 @@ Item {
// Shares the [menu] surface tokens — themes that style the menu also
// style emojis. Selected-cell colors composed in the
// singleton so consumers drop them straight into Rectangle bindings.
property color background: Color.menu.background
property color foreground: Color.menu.text
property color border: Color.menu.border
property color background: Commons.Color.menu.background
property color foreground: Commons.Color.menu.text
property color border: Commons.Color.menu.border
property var borderSpec: Border.surfaceSpec("menu", "border", border, Math.max(1, Style.space(2)))
property color scrim: Color.menu.scrim
property color selectedBackground: Color.menu.selectedBackground
property color selectedText: Color.menu.selectedText
property color scrim: Commons.Color.menu.scrim
property color selectedBackground: Commons.Color.menu.selectedBackground
property color selectedText: Commons.Color.menu.selectedText
readonly property int cornerRadius: Style.cornerRadius
property string fontFamily: Style.font.menuFamily
property int contentMargin: Style.spacing.panelPadding
+30 -7
View File
@@ -5,6 +5,7 @@ import QtQuick
import QtQuick.Effects
import QtQuick.Shapes
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "ImagePickerModel.js" as ImagePickerModel
@@ -37,14 +38,14 @@ Item {
property string themeRows: ""
property bool themeMode: false
property bool themeOpenPending: false
// Bound to the central [image-picker] section in shell.toml via Color.qml.
// Bound to the central [image-picker] section in shell.toml via Commons.Color.qml.
// `dimColor` tints unselected slices and text outlines on top of the scrim;
// it intentionally tracks the foundational background, not a surface role.
property color dimColor: Color.background
property color foreground: Color.imagePicker.text
property color scrim: Color.imagePicker.scrim
property color selectedBorder: Color.imagePicker.selectedBorder
property color unselectedBorder: Color.imagePicker.unselectedBorder
property color dimColor: Commons.Color.background
property color foreground: Commons.Color.imagePicker.text
property color scrim: Commons.Color.imagePicker.scrim
property color selectedBorder: Commons.Color.imagePicker.selectedBorder
property color unselectedBorder: Commons.Color.imagePicker.unselectedBorder
property int expandedWidth: 768
property int expandedHeight: 475
property int sliceWidth: 108
@@ -274,7 +275,29 @@ Item {
property var imageArray: []
readonly property var matchingImageIndices: ImagePickerModel.matchingIndices(imageArray, filterText)
onMatchingImageIndicesChanged: updateVisibleItems()
onSelectedIndexChanged: updateVisibleItems()
onSelectedIndexChanged: {
updateVisibleItems()
introPrepareTimer.restart()
}
onThemeModeChanged: if (themeMode) introPrepareTimer.restart()
Timer {
id: introPrepareTimer
interval: 75
onTriggered: {
if (root.opened && root.themeMode && !introPrepare.running) {
introPrepare.theme = root.nameForPath(root.currentPath())
introPrepare.command = ["omarchy-theme-bg-boot-intro", "--prepare-theme", root.nameForPath(root.currentPath())]
introPrepare.running = true
}
}
}
Process {
id: introPrepare
property string theme: ""
onExited: if (root.opened && root.themeMode && theme !== root.nameForPath(root.currentPath())) introPrepareTimer.restart()
}
function updateVisibleItems() {
ImagePickerModel.syncWindow(visibleImages, ImagePickerModel.visibleWindow(matchingImageIndices, selectedIndex, previewRadius))
+12 -11
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Effects
import qs.Commons
import qs.Commons as Commons
import qs.Ui
Item {
@@ -42,8 +43,8 @@ Item {
readonly property bool showPasswordCursor: inputEnabled && !authenticatingPassword && failureMessage.length === 0
readonly property bool errorState: failureMessage.length > 0
readonly property var inputBorderSpec: errorState
? Border.surfaceSpec("lock", "border-error", Color.lock.borderError, root.outlineThickness, "border-alpha")
: Border.surfaceSpec("lock", "border-active", Color.lock.borderActive, root.outlineThickness, "border-alpha")
? Border.surfaceSpec("lock", "border-error", Commons.Color.lock.borderError, root.outlineThickness, "border-alpha")
: Border.surfaceSpec("lock", "border-active", Commons.Color.lock.borderActive, root.outlineThickness, "border-alpha")
readonly property bool video: Util.isVideoPath(root.backgroundPath)
readonly property bool feedActive: root.video && root.loadBackground && !root.displaysBlank && !root.powerSaverActive
@@ -97,7 +98,7 @@ Item {
Rectangle {
anchors.fill: parent
color: Color.background
color: Commons.Color.background
BackgroundMedia {
id: wallpaper
@@ -156,7 +157,7 @@ Item {
width: root.fieldWidth
height: root.fieldHeight
anchors.centerIn: parent
color: Color.lock.background
color: Commons.Color.lock.background
borderSpec: root.inputBorderSpec
radius: Style.cornerRadius
clip: true
@@ -179,16 +180,16 @@ Item {
echoMode: TextInput.Password
passwordCharacter: "\u25CF"
passwordMaskDelay: 0
color: Color.lock.text
selectionColor: Color.lock.selection
selectedTextColor: Color.lock.text
color: Commons.Color.lock.text
selectionColor: Commons.Color.lock.selection
selectedTextColor: Commons.Color.lock.text
font.family: Style.font.family
font.pixelSize: text.length > 0 ? Math.max(1, Math.floor(root.passwordDotFontSize * root.passwordDotScale)) : root.fieldFontSize
font.letterSpacing: text.length > 0 ? root.passwordDotLetterSpacing * root.passwordDotScale : 0
cursorVisible: activeFocus && root.showPasswordCursor && text.length > 0
cursorDelegate: Rectangle {
width: 2
color: Color.lock.text
color: Commons.Color.lock.text
visible: passwordInput.cursorVisible
}
@@ -224,7 +225,7 @@ Item {
anchors.fill: passwordInput
text: root.authenticatingPassword ? I18n.tr("Checking…") : (root.failureMessage.length > 0 ? root.failureMessage : root.placeholderText)
visible: passwordInput.text.length === 0
color: root.authenticatingPassword ? Color.lock.text : (root.failureMessage.length > 0 ? Color.lock.textError : Color.lock.placeholder)
color: root.authenticatingPassword ? Commons.Color.lock.text : (root.failureMessage.length > 0 ? Commons.Color.lock.textError : Commons.Color.lock.placeholder)
font.family: Style.font.family
font.pixelSize: root.fieldFontSize
font.italic: !root.authenticatingPassword && root.failureMessage.length > 0
@@ -247,7 +248,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
visible: root.fingerprintConfigured
text: root.fingerprintUnavailable ? "󰺱" : "󰈷"
color: root.fingerprintUnavailable ? Color.lock.textError : Color.lock.placeholder
color: root.fingerprintUnavailable ? Commons.Color.lock.textError : Commons.Color.lock.placeholder
font.family: Style.font.family
font.pixelSize: Math.round(root.fieldFontSize * 1.1)
horizontalAlignment: Text.AlignHCenter
@@ -266,7 +267,7 @@ Item {
anchors.horizontalCenter: inputField.horizontalCenter
visible: root.fingerprintConfigured && root.fingerprintUnavailable
text: I18n.tr("Fingerprint reader unavailable")
color: Color.lock.textError
color: Commons.Color.lock.textError
font.family: Style.font.family
font.pixelSize: Style.font.heading
font.italic: true
+2 -1
View File
@@ -4,6 +4,7 @@ import Quickshell.Io
import Quickshell.Services.Pam
import Quickshell.Wayland
import qs.Commons
import qs.Commons as Commons
import "FingerprintModel.js" as FingerprintModel
Item {
@@ -467,7 +468,7 @@ Item {
WlSessionLockSurface {
id: lockSurface
color: Color.background
color: Commons.Color.background
LockView {
id: lockView
+11 -10
View File
@@ -3,6 +3,7 @@ import Quickshell.Io
import Quickshell.Wayland
import QtQuick
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "MenuModel.js" as MenuModel
@@ -84,17 +85,17 @@ Item {
property bool deleteConfirmOpen: false
property var deleteTarget: null
onOpenedChanged: if (!opened) { deleteConfirmOpen = false; deleteTarget = null }
// Bound to the central [menu] section in shell.toml via Color.qml.
// Bound to the central [menu] section in shell.toml via Commons.Color.qml.
// Each color already includes its alpha companion (composed in the
// singleton), so consumers can drop them straight into a Rectangle.
property color background: Color.menu.background
property color foreground: Color.menu.text
property color border: Color.menu.border
property color background: Commons.Color.menu.background
property color foreground: Commons.Color.menu.text
property color border: Commons.Color.menu.border
property var borderSpec: Border.surfaceSpec("menu", "border", border, Math.max(1, Style.space(2)))
property color scrim: Color.menu.scrim
property color selectedBackground: Color.menu.selectedBackground
property color selectedText: Color.menu.selectedText
property color selectedBorder: Color.menu.selectedBorder
property color scrim: Commons.Color.menu.scrim
property color selectedBackground: Commons.Color.menu.selectedBackground
property color selectedText: Commons.Color.menu.selectedText
property color selectedBorder: Commons.Color.menu.selectedBorder
property var selectedBorderSpec: Border.surfaceSpec("menu", "selected-border", selectedBorder, 0)
readonly property real rowReservedBorderLeft: Border.left(selectedBorderSpec)
readonly property real rowReservedBorderRight: Border.right(selectedBorderSpec)
@@ -1088,7 +1089,7 @@ Item {
// then on the card grows and shrinks downward instead of re-centering
// on every resize, which made the menu jump around. The rows height is
// frozen at the same moment, so the starting menu also caps how tall the
// card may grow from there. Closing unfreezes both.
// card may grow from there. Closing or changing screens unfreezes both.
property int cardTop: -1
property int maxRowsHeight: -1
readonly property int centeredTop: Math.max(Style.gapsOut, Math.round((height - root.cardHeight) / 2))
@@ -1099,8 +1100,8 @@ Item {
maxRowsHeight = root.visibleRowsHeight
}
}
// The surface stays mapped between opens, so closing is shown going false.
onShownChanged: if (!shown) { cardTop = -1; maxRowsHeight = -1 }
onTargetScreenChanged: { cardTop = -1; maxRowsHeight = -1 }
Rectangle {
anchors.fill: parent
+1
View File
@@ -413,6 +413,7 @@ var GUARD_READERS = [
"omarchy-default-browser",
"omarchy-default-editor",
"omarchy-default-terminal",
"omarchy-default-dictation",
"omarchy-dns",
"omarchy-region",
"omarchy-language"
@@ -6,6 +6,7 @@ import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "../NotificationLogic.js" as NotificationLogic
@@ -48,10 +49,10 @@ BorderSurface {
readonly property string sanitizedBody: sanitizeBody(body)
readonly property string styledBody: NotificationLogic.styledBody(body, app, appIcon)
readonly property color dimColor: Qt.darker(Color.notifications.text, 1.4)
readonly property color bodyColor: Qt.darker(Color.notifications.text, 1.15)
readonly property color accentColor: urgency === 2 ? Color.urgent : (urgency === 0 ? dimColor : Color.notifications.countdown)
readonly property var cardBorderSpec: Border.surfaceSpec("notifications", "border", Color.notifications.border, Math.max(1, Style.space(2)))
readonly property color dimColor: Qt.darker(Commons.Color.notifications.text, 1.4)
readonly property color bodyColor: Qt.darker(Commons.Color.notifications.text, 1.15)
readonly property color accentColor: urgency === 2 ? Commons.Color.urgent : (urgency === 0 ? dimColor : Commons.Color.notifications.countdown)
readonly property var cardBorderSpec: Border.surfaceSpec("notifications", "border", Commons.Color.notifications.border, Math.max(1, Style.space(2)))
function sanitizeBody(s) {
return NotificationLogic.sanitizeBody(s, app, appIcon)
@@ -70,7 +71,7 @@ BorderSurface {
// doesn't push content under the bottom edge.
implicitHeight: mainColumn.implicitHeight + borderTop + borderBottom
radius: cornerRadius
color: Color.notifications.background
color: Commons.Color.notifications.background
borderSpec: cardBorderSpec
clip: true
@@ -139,7 +140,7 @@ BorderSurface {
anchors.centerIn: parent
visible: root.hasGlyph && smallIconImage.status !== Image.Ready
text: root.glyph
color: Color.notifications.text
color: Commons.Color.notifications.text
font.family: root.fontFamily
font.pixelSize: Style.font.displayLarge
}
@@ -150,7 +151,7 @@ BorderSurface {
Layout.alignment: Qt.AlignVCenter
visible: root.compactGlyph
text: root.glyph
color: Color.notifications.text
color: Commons.Color.notifications.text
font.family: root.fontFamily
font.pixelSize: Style.font.icon
}
@@ -172,7 +173,7 @@ BorderSurface {
visible: root.summary.length > 0
text: root.summary
font.family: "Liberation Sans"
color: Color.notifications.text
color: Commons.Color.notifications.text
font.pixelSize: Style.font.title
font.bold: true
wrapMode: Text.WordWrap
@@ -210,7 +211,7 @@ BorderSurface {
required property var modelData
text: modelData.text
bordered: true
foreground: Color.notifications.text
foreground: Commons.Color.notifications.text
fontFamily: root.fontFamily
onClicked: root.actionClicked(modelData.identifier)
}
@@ -235,7 +236,7 @@ BorderSurface {
Text {
anchors.centerIn: parent
text: "✕"
color: closeArea.containsMouse ? Color.notifications.text : root.dimColor
color: closeArea.containsMouse ? Commons.Color.notifications.text : root.dimColor
font.pixelSize: Math.round(Style.font.caption * 1.44)
}
+7 -6
View File
@@ -3,6 +3,7 @@ import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import qs.Commons
import qs.Commons as Commons
import qs.Ui
import "OsdModel.js" as OsdModel
@@ -139,8 +140,8 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.space(67)
color: Util.alpha(Color.background, 0.97)
borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2)))
color: Util.alpha(Commons.Color.background, 0.97)
borderSpec: Border.surfaceSpec("popups", "border", Commons.Color.popups.border, Math.max(1, Style.space(2)))
radius: Style.cornerRadius
opacity: root.opened ? 1 : 0
@@ -162,7 +163,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
text: root.icon
font: iconMetrics.font
color: Color.popups.text
color: Commons.Color.popups.text
}
}
Rectangle {
@@ -170,11 +171,11 @@ Item {
width: root.barWidth
height: Math.max(Style.space(6), Style.spacing.sm)
anchors.verticalCenter: parent.verticalCenter
color: Util.alpha(Color.popups.text, 0.45)
color: Util.alpha(Commons.Color.popups.text, 0.45)
Rectangle {
height: parent.height
width: parent.width * (root.hasProgress ? root.value / root.maxValue : 0)
color: Color.accent
color: Commons.Color.accent
Behavior on width {
enabled: root.opened
@@ -192,7 +193,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
text: root.message
font: messageMetrics.font
color: Color.popups.text
color: Commons.Color.popups.text
elide: Text.ElideRight
maximumLineCount: 1
}
+3 -2
View File
@@ -6,6 +6,7 @@ import Quickshell.Services.Mpris
import Quickshell.Services.Pipewire
import qs.Ui
import qs.Commons
import qs.Commons as Commons
import "Model.js" as Model
Panel {
@@ -179,10 +180,10 @@ Panel {
readonly property string toggleHint: anyAudible ? I18n.tr("Mute") : I18n.tr("Unmute")
readonly property color hoverFill: bar
? Style.hoverFillFor(bar.foreground, Color.accent)
? Style.hoverFillFor(bar.foreground, Commons.Color.accent)
: "transparent"
readonly property color selectedFill: bar
? Style.selectedFillFor(bar.foreground, Color.accent)
? Style.selectedFillFor(bar.foreground, Commons.Color.accent)
: "transparent"
function sectionCount(section) {
+3 -2
View File
@@ -6,6 +6,7 @@ import Quickshell.Bluetooth
import Quickshell.Services.Pipewire
import qs.Ui
import qs.Commons
import qs.Commons as Commons
import "Model.js" as Model
Panel {
@@ -104,10 +105,10 @@ Panel {
readonly property string toggleHint: root.adapter && root.adapter.enabled ? I18n.tr("Turn Bluetooth off") : I18n.tr("Turn Bluetooth on")
readonly property color hoverFill: bar
? Style.hoverFillFor(bar.foreground, Color.accent)
? Style.hoverFillFor(bar.foreground, Commons.Color.accent)
: "transparent"
readonly property color selectedFill: bar
? Style.selectedFillFor(bar.foreground, Color.accent)
? Style.selectedFillFor(bar.foreground, Commons.Color.accent)
: "transparent"
function sectionCount(section) {
Loaded 100 of 239 files, more files were not shown because too many files have changed in this diff. Show more