diff --git a/bin/omarchy-bar b/bin/omarchy-bar index d8d99deb..8ab968c5 100755 --- a/bin/omarchy-bar +++ b/bin/omarchy-bar @@ -2,7 +2,7 @@ # omarchy:summary=Configure the bar and its widget layout # omarchy:group=bar -# omarchy:args=use | reset | defaults | position [top|bottom|left|right] | transparent | put [placement] | move [placement] | set [--json] [placement] | get +# omarchy:args=use | reset | defaults | position [top|bottom|left|right] | background [opaque|glassy|none|cycle] | put [placement] | move [placement] | set [--json] [placement] | get # omarchy:examples=omarchy bar use local.neon-bar | omarchy bar put omarchy.keyboard-layout --after omarchy.clock | omarchy bar move omarchy.clock --section center --index 0 | omarchy bar set omarchy.clock format HH:mm set -euo pipefail @@ -18,7 +18,11 @@ Usage: omarchy bar [args...] defaults Restore the default bar and service widgets position [top|bottom|left|right] Bar position; prints the current one when given none - transparent Bar transparency + background [opaque|glassy|none|cycle] + Bar background: glassy shows what is + behind the bar blurred, cycle steps + none, glassy, opaque; prints the + current one when given none put [placement] Put a widget on the bar, leaving one that is already there where it is move [placement] Move a widget within or between sections @@ -194,6 +198,7 @@ cmd_defaults() { " \ --slurpfile defaults "$DEFAULTS_FILE" \ --argjson widgets "$optional_widgets" + sync_glass || fail "could not change the blur behind the bar" echo "Restored the default Omarchy bar" } @@ -210,18 +215,75 @@ cmd_position() { echo "Bar position set to $position" } +# The bar background is opaque, glassy, or none. A shell.json from before it +# had one says transparent instead, which is none, and setting a background +# drops it. +current_background() { + jq -r '.bar.background // (if .bar.transparent == true then "none" else "opaque" end)' "$(source_file)" +} + +cmd_background() { + if (( $# == 0 )); then + current_background + return + fi + + local background="$1" + (( $# == 1 )) || fail "background takes a single value" + [[ $background =~ ^(opaque|glassy|none|cycle)$ ]] || fail "background must be opaque, glassy, none, or cycle" + + if [[ $background == "cycle" ]]; then + # Each step changes the bar by one degree: clear, then glassy, then opaque. + case $(current_background) in + none) background="glassy" ;; + glassy) background="opaque" ;; + *) background="none" ;; + esac + fi + + local previous + previous=$(current_background) + commit "$NORMALIZE | .bar.background = \$background | del(.bar.transparent)" --arg background "$background" + if ! sync_glass; then + commit "$NORMALIZE | .bar.background = \$background | del(.bar.transparent)" --arg background "$previous" + fail "could not change the blur behind the bar" + fi + echo "Bar background set to $background" +} + +# The older on/off form of the background, kept for scripts and dotfiles: +# transparent is none, and not transparent is opaque. cmd_transparent() { local transparent="${1:-}" [[ -n $transparent ]] || fail "transparent is required" (( $# == 1 )) || fail "transparent takes a single value" [[ $transparent =~ ^(true|false|toggle)$ ]] || fail "transparent must be true, false, or toggle" if [[ $transparent == "toggle" ]]; then - commit "$NORMALIZE | .bar.transparent = (.bar.transparent != true)" - echo "Bar transparency toggled" - else - commit "$NORMALIZE | .bar.transparent = \$transparent" --argjson transparent "$transparent" - echo "Bar transparency set to $transparent" + [[ $(current_background) == "none" ]] && transparent="false" || transparent="true" fi + if [[ $transparent == "true" ]]; then + cmd_background none + else + cmd_background opaque + fi +} + +# Hyprland blurs behind the bar only while the bar-glassy toggle is on, so it +# follows the bar background, reloading Hyprland only when it changes. The +# toggle sits where Hyprland reads it, under XDG_STATE_HOME. +sync_glass() { + local dir="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/toggles/hypr" + local flag="$dir/bar-glassy.lua" + + if [[ $(current_background) == "glassy" ]]; then + [[ ! -f $flag ]] || return 0 + mkdir -p "$dir" && cp "$OMARCHY_PATH/default/hypr/toggles/bar-glassy.lua" "$flag" || return 1 + else + [[ -f $flag ]] || return 0 + rm -f "$flag" || return 1 + fi + + hyprctl reload >/dev/null 2>&1 || true } bar_widget_default_section() { @@ -406,6 +468,14 @@ case "$command" in transparent) cmd_transparent "$@" ;; + background) + cmd_background "$@" + ;; + # Internal: the shell runs this when it loads a bar background, so one set + # by editing shell.json gets its blur too. + sync-glass) + sync_glass || fail "could not change the blur behind the bar" + ;; put) cmd_put "$@" ;; diff --git a/bin/omarchy-hyprland-corners b/bin/omarchy-hyprland-corners index 0d73ed56..ed35d168 100755 --- a/bin/omarchy-hyprland-corners +++ b/bin/omarchy-hyprland-corners @@ -34,7 +34,7 @@ if [[ $1 != "theme" ]]; then mkdir -p "$TOGGLES_DIR" && cp "$OMARCHY_PATH/default/hypr/toggles/corners-$1.lua" "$TOGGLES_DIR/corners-$1.lua" || exit 1 fi for mode in sharp rounded; do - [[ $mode == "$1" ]] || rm -f "$TOGGLES_DIR/corners-$mode.lua" + [[ $mode == "$1" ]] || rm -f "$TOGGLES_DIR/corners-$mode.lua" || exit 1 done hyprctl reload >/dev/null || exit 1 diff --git a/bin/omarchy-hyprland-window-transparency b/bin/omarchy-hyprland-window-transparency new file mode 100755 index 00000000..e99c4b5c --- /dev/null +++ b/bin/omarchy-hyprland-window-transparency @@ -0,0 +1,70 @@ +#!/bin/bash + +# omarchy:summary=Show or set how see-through windows are: opaque, transparent, or glassy +# omarchy:args=[opaque|transparent|glassy] +# omarchy:examples=omarchy hyprland window transparency | omarchy hyprland window transparency glassy + +TOGGLES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/toggles/hypr" + +# Opaque and glassy are toggle files loaded after every theme: opaque makes the +# windows Omarchy makes transparent solid, and glassy blurs what is behind any +# see-through window and gives terminals a see-through background. Transparent, +# the default, removes both. +current_mode() { + if [[ -f $TOGGLES_DIR/window-opaque.lua ]]; then + echo "opaque" + elif [[ -f $TOGGLES_DIR/window-glassy.lua ]]; then + echo "glassy" + else + echo "transparent" + fi +} + +# Save the new choice before dropping the old one, so a failed save keeps it. +save_mode() { + if [[ $1 != "transparent" ]]; then + mkdir -p "$TOGGLES_DIR" && cp "$OMARCHY_PATH/default/hypr/toggles/window-$1.lua" "$TOGGLES_DIR/window-$1.lua" || return 1 + fi + local mode + for mode in opaque glassy; do + [[ $mode == "$1" ]] || rm -f "$TOGGLES_DIR/window-$mode.lua" || return 1 + done +} + +if (( $# == 0 )); then + current_mode + exit 0 +fi + +case $1 in + opaque) LABEL="Opaque windows" ;; + transparent) LABEL="Transparent windows" ;; + glassy) LABEL="Glassy windows" ;; + *) + echo "Usage: omarchy-hyprland-window-transparency [opaque|transparent|glassy]" >&2 + exit 1 + ;; +esac + +# A theme switch renders the next theme's terminal files from the saved choice +# and then replaces the current theme, so the choice and the glass written into +# the current theme are changed under its lock, or the switch could drop them. +exec 9>"${XDG_RUNTIME_DIR:-/tmp}/omarchy-theme-set.lock" +flock 9 + +# A choice whose glass cannot be written is taken back, so the saved choice +# is always the one on screen. +previous=$(current_mode) +save_mode "$1" || exit 1 +if ! omarchy-theme-set-glass; then + save_mode "$previous" + omarchy-theme-set-glass + exit 1 +fi +flock -u 9 + +hyprctl reload >/dev/null || exit 1 +omarchy-restart-terminal +omarchy-theme-set-foot +omarchy-restart-btop +omarchy-notification-send -g 󰂵 "$LABEL" diff --git a/bin/omarchy-theme-set-foot b/bin/omarchy-theme-set-foot index 1a8ef035..5be806e6 100755 --- a/bin/omarchy-theme-set-foot +++ b/bin/omarchy-theme-set-foot @@ -1,9 +1,10 @@ #!/bin/bash -# omarchy:summary=Apply current Omarchy theme colors to running Foot terminals +# omarchy:summary=Apply current Omarchy theme colors and background alpha to running Foot terminals # omarchy:hidden=true colors_toml=$HOME/.local/state/omarchy/current/theme/colors.toml +foot_ini=$HOME/.local/state/omarchy/current/theme/foot.ini if [[ ! -f $colors_toml ]] || ! pgrep -x foot >/dev/null; then exit 0 @@ -12,6 +13,16 @@ fi foot_osc=$(omarchy-theme-osc "$colors_toml") [[ -n $foot_osc ]] || exit 0 +# foot takes a background alpha as an urxvt-style [percent] prefix, so a theme +# going see-through or back, as with Glassy windows, reaches running terminals. +# The last alpha in the theme's foot.ini wins, as it does when foot reads it. +alpha=$(sed -n 's/^[[:space:]]*alpha[[:space:]]*=[[:space:]]*\([0-9.]*\).*/\1/p' "$foot_ini" 2>/dev/null | tail -n 1) +background=$(omarchy-theme-color --file "$colors_toml" background 2>/dev/null) +if [[ -n $background ]]; then + percent=$(awk -v alpha="${alpha:-1}" 'BEGIN { printf "%d", alpha * 100 + 0.5 }') + foot_osc+=$(printf '\033]11;[%d]%s\007' "$percent" "$background") +fi + for foot_pid in $(pgrep -x foot); do for child_pid in $(pgrep -P "$foot_pid"); do tty=$(readlink "/proc/$child_pid/fd/1" 2>/dev/null) diff --git a/bin/omarchy-theme-set-glass b/bin/omarchy-theme-set-glass new file mode 100755 index 00000000..b1620cd6 --- /dev/null +++ b/bin/omarchy-theme-set-glass @@ -0,0 +1,122 @@ +#!/bin/bash + +# omarchy:summary=Give terminals, btop, and GTK apps a see-through background while windows are glassy +# omarchy:args=[theme-dir] +# omarchy:hidden=true + +# A Hyprland opacity rule fades a terminal's text along with its background, +# and foot turns compositor blur down unless its own config asks for it, so the +# glass belongs in each terminal's theme file, as in the glassy Starship +# (#14726). The lines sit between markers so leaving glassy takes them out +# again, and a theme that sets its own opacity keeps it. + +THEME_DIR="${1:-$HOME/.local/state/omarchy/current/theme}" +TOGGLES_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/toggles/hypr" +GLASSY=false +[[ -f $TOGGLES_DIR/window-glassy.lua ]] && GLASSY=true +status=0 + +# A literal for a sed address, whose delimiter is /. +regex() { + sed 's/[][\\.*^$/]/\\&/g' <<<"$1" +} + +# A block added to a file that did not end in a newline has to supply one, so +# its begin marker says so, and taking the block out takes that newline back. +# Leaving glassy then gives the file back exactly as it was. Markers come as +# an opening and a closing part, so CSS can wrap them in a comment. +remove_block() { + local file="$1" open="$2" close="$3" + local begin end + begin=$(regex "$open omarchy-glass begin") + end=$(regex "$open omarchy-glass end$close") + [[ -f $file ]] && grep -qF "$open omarchy-glass begin" "$file" || return 0 + + local added_newline=false + grep -qxF "$open omarchy-glass begin after a line without a newline$close" "$file" && added_newline=true + sed -i "/^$begin/,/^$end\$/d" "$file" || return 1 + if [[ $added_newline == "true" && -s $file && -z $(tail -c 1 "$file") ]]; then + truncate -s -1 "$file" || return 1 + fi +} + +append_block() { + local file="$1" open="$2" close="$3" lines="$4" + if [[ -s $file && -n $(tail -c 1 "$file") ]]; then + printf '\n%s\n%s\n%s\n' "$open omarchy-glass begin after a line without a newline$close" "$lines" "$open omarchy-glass end$close" >>"$file" + else + printf '%s\n%s\n%s\n' "$open omarchy-glass begin$close" "$lines" "$open omarchy-glass end$close" >>"$file" + fi +} + +# Each setting comes with a pattern for the theme setting it already, which +# keeps the theme's own; an empty pattern always adds it. Lines that belong in +# a section go in under its header. A file that already has the section gets +# them inside it, since TOML refuses a table declared twice; otherwise the +# header comes along with them. +glass() { + local file="$THEME_DIR/$1" section="$2" + shift 2 + [[ -f $file ]] || return 0 + + remove_block "$file" "#" "" || return 1 + [[ $GLASSY == "true" ]] || return 0 + + local lines="" + while (( $# >= 2 )); do + if [[ -z $1 ]] || ! grep -qE "$1" "$file"; then + lines+="${lines:+$'\n'}$2" + fi + shift 2 + done + [[ -n $lines ]] || return 0 + + # A header may carry trailing space or a comment. + local header="^$(regex "$section")[[:space:]]*(#.*)?\$" + if [[ -n $section ]] && grep -qE "$header" "$file"; then + local block + block=$(mktemp) || return 1 + printf '# omarchy-glass begin\n%s\n# omarchy-glass end\n' "$lines" >"$block" + sed -i -E "0,/$header/{/$header/r $block +}" "$file" + local result=$? + rm -f "$block" + return $result + fi + + append_block "$file" "#" "" "${section:+$section$'\n'}$lines" +} + +# Alacritty's own blur works only on macOS and KDE; Hyprland blurs it anyway. +glass alacritty.toml '[window]' \ + '^[[:space:]]*opacity[[:space:]]*=' 'opacity = 0.65' || status=1 +glass foot.ini '[colors-dark]' \ + '^[[:space:]]*alpha[[:space:]]*=' 'alpha=0.65' \ + '^[[:space:]]*blur[[:space:]]*=' 'blur=yes' || status=1 +glass kitty.conf '' \ + '^[[:space:]]*background_opacity[[:space:]]' 'background_opacity 0.65' || status=1 +# btop paints its own background over the terminal's glass. An empty main_bg +# uses the terminal background; the last theme value wins, so removing the +# block restores the theme's colour without changing btop's user config. +glass btop.theme '' \ + '^[[:space:]]*theme\[main_bg\][[:space:]]*=[[:space:]]*""' 'theme[main_bg]=""' || status=1 +# GTK only asks Hyprland for blur under a CSS backdrop-filter, which Ghostty +# takes as custom CSS and reloads live; its background-blur speaks only KDE's +# blur protocol. +glass ghostty.conf '' \ + '^[[:space:]]*background-opacity[[:space:]]*=' 'background-opacity = 0.65' \ + '' "gtk-custom-css = $OMARCHY_PATH/default/gtk/glass.css" || status=1 + +# Other GTK apps, such as Nautilus, read the user's gtk.css at launch, so the +# same backdrop blur is imported there. It follows the mode alone, so a theme +# switch, which names the theme it is rendering, leaves it be. +if (( $# == 0 )); then + gtk_css="${XDG_CONFIG_HOME:-$HOME/.config}/gtk-4.0/gtk.css" + remove_block "$gtk_css" "/*" " */" || status=1 + if [[ $GLASSY == "true" ]]; then + { mkdir -p "${gtk_css%/*}" && + append_block "$gtk_css" "/*" " */" "@import url(\"file://$OMARCHY_PATH/default/gtk/glass.css\");"; } || status=1 + fi +fi + +exit $status diff --git a/bin/omarchy-theme-set-templates b/bin/omarchy-theme-set-templates index fdac2a47..be745e6f 100755 --- a/bin/omarchy-theme-set-templates +++ b/bin/omarchy-theme-set-templates @@ -451,3 +451,4 @@ if [[ -f $COLORS_FILE ]]; then fi apply_shell_section_overrides +omarchy-theme-set-glass "$NEXT_THEME_DIR" diff --git a/bin/omarchy-toggle-animations b/bin/omarchy-toggle-animations index e3005b51..70db0166 100755 --- a/bin/omarchy-toggle-animations +++ b/bin/omarchy-toggle-animations @@ -1,6 +1,6 @@ #!/bin/bash -# omarchy:summary=Toggle animations, transparency and other effects that are slow without a GPU +# omarchy:summary=Toggle animations, blur and shadows, which are slow without a GPU # omarchy:args=[toggle|on|off] # omarchy:examples=omarchy toggle animations | omarchy toggle animations off | omarchy toggle animations on diff --git a/config/omarchy/shell.json b/config/omarchy/shell.json index faccf599..97951e01 100644 --- a/config/omarchy/shell.json +++ b/config/omarchy/shell.json @@ -6,7 +6,7 @@ }, "bar": { "position": "top", - "transparent": false, + "background": "opaque", "centerAnchor": "omarchy.clock", "layout": { "left": [ diff --git a/default/gtk/glass.css b/default/gtk/glass.css new file mode 100644 index 00000000..4c2c2191 --- /dev/null +++ b/default/gtk/glass.css @@ -0,0 +1,7 @@ +/* GTK asks the compositor to blur behind a see-through window only where a + backdrop-filter blur covers it, so Glassy windows load this into GTK apps, + such as Ghostty and Nautilus. The compositor's own blur settings decide + how it looks. */ +window { + backdrop-filter: blur(20px); +} diff --git a/default/hypr/glass.lua b/default/hypr/glass.lua new file mode 100644 index 00000000..eb5cb0db --- /dev/null +++ b/default/hypr/glass.lua @@ -0,0 +1,22 @@ +-- Frosted glass from the glassy Starship (#14726), shared by Glassy windows and +-- the Glassy bar: whatever is behind a see-through surface is blurred. +return function() + hl.config({ + decoration = { + blur = { + enabled = true, + size = 4, + passes = 2, + new_optimizations = true, + xray = false, + contrast = 1.0, + brightness = 0.75, + noise = 0.01, + vibrancy = 0.35, + vibrancy_darkness = 0.35, + popups = true, + special = true, + }, + }, + }) +end diff --git a/default/hypr/helpers.lua b/default/hypr/helpers.lua index bd5e109f..15985e41 100644 --- a/default/hypr/helpers.lua +++ b/default/hypr/helpers.lua @@ -281,7 +281,8 @@ function o.window(match, rules) hl.window_rule(rules) end --- Opt a window in to Omarchy's standard active/inactive transparency. +-- Opt a window in to Omarchy's standard active/inactive transparency. The tag +-- lets Appearance > Window Transparency turn these windows opaque or glassy. function o.transparent_window(match, opacity) - o.window(match, { opacity = opacity or "0.985 0.96" }) + o.window(match, { opacity = opacity or "0.985 0.96", tag = "+transparent-window" }) end diff --git a/default/hypr/toggles/bar-glassy.lua b/default/hypr/toggles/bar-glassy.lua new file mode 100644 index 00000000..a7ed7e0a --- /dev/null +++ b/default/hypr/toggles/bar-glassy.lua @@ -0,0 +1,9 @@ +-- The Glassy bar: the shell draws the bar see-through, and Hyprland blurs what +-- is behind it. +require("default.hypr.glass")() + +hl.layer_rule({ + match = { namespace = "^omarchy-bar$" }, + blur = true, + ignore_alpha = 0.3, +}) diff --git a/default/hypr/toggles/no-animations.lua b/default/hypr/toggles/no-animations.lua index 504b3b13..2e1c78ce 100644 --- a/default/hypr/toggles/no-animations.lua +++ b/default/hypr/toggles/no-animations.lua @@ -1,6 +1,6 @@ -- Draw nothing just for show, for machines that render on the CPU, like a VM: --- no animations, blur or shadows, and opaque windows, so nothing behind a --- window has to be blended into it. +-- no animations, blur or shadows. How see-through windows are is left to +-- Appearance > Window Transparency. hl.config({ animations = { enabled = false, @@ -16,5 +16,3 @@ hl.config({ }, }, }) - -o.window(".*", { opacity = "1 1" }) diff --git a/default/hypr/toggles/window-glassy.lua b/default/hypr/toggles/window-glassy.lua new file mode 100644 index 00000000..2af11ccf --- /dev/null +++ b/default/hypr/toggles/window-glassy.lua @@ -0,0 +1,14 @@ +-- Frosted glass from the glassy Starship (#14726): whatever is behind a +-- see-through window or shell surface is blurred. Terminals get the see-through +-- background itself from omarchy-theme-set-glass, which keeps their text crisp. +require("default.hypr.glass")() + +-- GTK apps fade as a whole, since GTK draws their backgrounds solid, and get +-- their blur from the backdrop-filter Glassy adds to the user's gtk.css. +o.window("org\\.gnome\\.Nautilus", { opacity = "0.75 0.75" }) + +hl.layer_rule({ + match = { namespace = "^(omarchy-bar|omarchy-menu|omarchy-notifications|omarchy-osd|omarchy-polkit|omarchy-reminders|omarchy-clipboard|omarchy-emojis|omarchy-image-selector|omarchy-keyboard-panel|omarchy-network-qr)$" }, + blur = true, + ignore_alpha = 0.3, +}) diff --git a/default/hypr/toggles/window-opaque.lua b/default/hypr/toggles/window-opaque.lua new file mode 100644 index 00000000..031c2c4e --- /dev/null +++ b/default/hypr/toggles/window-opaque.lua @@ -0,0 +1,2 @@ +-- Opaque windows, even those Omarchy makes transparent by default. +o.window({ tag = "transparent-window" }, { opacity = "1 1" }) diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc index 364c99c5..c1ced898 100644 --- a/default/omarchy/omarchy-menu.jsonc +++ b/default/omarchy/omarchy-menu.jsonc @@ -59,9 +59,11 @@ "trigger.appearance.top-bar.hide": {"icon":"󰈉","label":"Hide Bar","when":"! omarchy-toggle-enabled bar-off","action":"omarchy-toggle-bar"}, "trigger.appearance.top-bar.show": {"icon":"󰈈","label":"Show Bar","when":"omarchy-toggle-enabled bar-off","action":"omarchy-toggle-bar"}, "trigger.appearance.top-bar.position": {"icon":"","label":"Position Bar"}, - "trigger.appearance.top-bar.transparent": {"icon":"󰂵","label":"Transparent Bar","when":"! jq -e .bar.transparent ~/.config/omarchy/shell.json >/dev/null 2>&1","action":"omarchy-bar transparent true"}, - "trigger.appearance.top-bar.opaque": {"icon":"󰂵","label":"Opaque Bar","when":"jq -e .bar.transparent ~/.config/omarchy/shell.json >/dev/null 2>&1","action":"omarchy-bar transparent false"}, - "trigger.appearance.top-bar.indicators": {"icon":"󰋽","label":"Indicators"}, + "trigger.appearance.top-bar.background": {"icon":"󰂵","label":"Bar Background"}, + "trigger.appearance.top-bar.background.opaque": {"icon":"󰝤","label":"Opaque Bar","checked":"[[ $(omarchy-bar background) == \"opaque\" ]]","action":"omarchy-bar background opaque"}, + "trigger.appearance.top-bar.background.glassy": {"icon":"󰂵","label":"Glassy Bar","checked":"[[ $(omarchy-bar background) == \"glassy\" ]]","action":"omarchy-bar background glassy"}, + "trigger.appearance.top-bar.background.clear": {"icon":"󰝣","label":"Clear Bar","checked":"[[ $(omarchy-bar background) == \"none\" ]]","action":"omarchy-bar background none"}, + "trigger.appearance.top-bar.indicators": {"icon":"󰋽","label":"Bar Indicators"}, "trigger.appearance.top-bar.indicators.battery-percentage": {"icon":"󰁹","label":"Battery Percentage","when":"omarchy-hw-laptop","action":"omarchy-shell omarchy.power togglePercentage"}, "trigger.appearance.top-bar.indicators.clock": {"icon":"󰥔","label":"Clock"}, "trigger.appearance.top-bar.indicators.clock.sunday-24h": {"icon":"󰥔","label":"Sunday 14:30","when":"[[ $(omarchy-bar position) != \"left\" && $(omarchy-bar position) != \"right\" ]]","checked":"[[ $(omarchy-bar get omarchy.clock format) == \"dddd HH:mm\" ]]","action":"omarchy-bar set omarchy.clock format \"dddd HH:mm\""}, @@ -87,6 +89,10 @@ "trigger.appearance.window-corners.theme": {"icon":"󰏘","label":"Theme Corners","checked":"[[ $(omarchy-hyprland-corners) == \"theme\" ]]","action":"omarchy-hyprland-corners theme"}, "trigger.appearance.window-corners.sharp": {"icon":"󰝣","label":"Sharp Corners","checked":"[[ $(omarchy-hyprland-corners) == \"sharp\" ]]","action":"omarchy-hyprland-corners sharp"}, "trigger.appearance.window-corners.rounded": {"icon":"󰘇","label":"Rounded Corners","checked":"[[ $(omarchy-hyprland-corners) == \"rounded\" ]]","action":"omarchy-hyprland-corners rounded"}, + "trigger.appearance.window-transparency": {"icon":"󰂵","label":"Window Transparency"}, + "trigger.appearance.window-transparency.opaque": {"icon":"󰝤","label":"Opaque Windows","checked":"[[ $(omarchy-hyprland-window-transparency) == \"opaque\" ]]","action":"omarchy-hyprland-window-transparency opaque"}, + "trigger.appearance.window-transparency.transparent": {"icon":"󰝣","label":"Transparent Windows","checked":"[[ $(omarchy-hyprland-window-transparency) == \"transparent\" ]]","action":"omarchy-hyprland-window-transparency transparent"}, + "trigger.appearance.window-transparency.glassy": {"icon":"󰂵","label":"Glassy Windows","checked":"[[ $(omarchy-hyprland-window-transparency) == \"glassy\" ]]","action":"omarchy-hyprland-window-transparency glassy"}, "trigger.appearance.one-window-ratio": {"icon":"","label":"1-Window Ratio","action":"omarchy-hyprland-window-single-square-aspect-toggle"}, "trigger.appearance.workspace-layout": {"icon":"󱂬","label":"Workspace Layout","title":"Default Workspace Layout"}, "trigger.appearance.workspace-layout.dwindle": {"icon":"󱓻","label":"Tiling","checked":"[[ $(omarchy-hyprland-workspace-layout-default) == \"dwindle\" ]]","action":"omarchy-hyprland-workspace-layout-default dwindle"}, diff --git a/default/themed/foot.ini.tpl b/default/themed/foot.ini.tpl index a3764b20..3642fbbc 100644 --- a/default/themed/foot.ini.tpl +++ b/default/themed/foot.ini.tpl @@ -1,6 +1,9 @@ [colors-dark] foreground={{ foreground_strip }} background={{ background_strip }} +# Blur only applies while the background is see-through, as with Glassy windows, +# and foot reads it only at launch, so it is on from the start. +blur=yes selection-foreground={{ selection_foreground_strip }} selection-background={{ selection_background_strip }} diff --git a/default/themed/kitty.conf.tpl b/default/themed/kitty.conf.tpl index e94c1278..9380ab5d 100644 --- a/default/themed/kitty.conf.tpl +++ b/default/themed/kitty.conf.tpl @@ -6,6 +6,10 @@ selection_background {{ selection_background }} cursor {{ bright_foreground }} cursor_text_color {{ background }} +# Lets a reload change the background opacity, as Glassy windows do, since +# kitty only allows that when this is on from launch. +dynamic_background_opacity yes + active_border_color {{ accent }} active_tab_background {{ accent }} diff --git a/docs/omarchy-shell.md b/docs/omarchy-shell.md index 8a220a8d..bd1ae438 100644 --- a/docs/omarchy-shell.md +++ b/docs/omarchy-shell.md @@ -94,8 +94,8 @@ You can still install by hand: drop a plugin into `~/.config/omarchy/plugins//`, run `omarchy-shell shell rescanPlugins`, then `omarchy plugin enable `. A bar widget starts in its declared default section; enabling a full bar replaces the one in use. `omarchy bar` drives the -bar from the CLI — `use | reset | defaults | position | transparent | put | -move | set`, with placement flags such as `--section` and `--index`. +bar from the CLI — `use | reset | defaults | position | background | put | +move | set | get`, with placement flags such as `--section` and `--index`. The lower-level IPC methods remain available through `omarchy-shell shell ...`. ## Elsewhen @@ -121,7 +121,7 @@ or `omarchy.power`. There is no `bar` target. | `rescanPlugins` | re-walk plugin dirs and hot-reload plugin code | | `reloadConfig` | reload shell.json | | `applyTheme ` | push theme colors + shell.toml | -| `toggleBarTransparency` | flip the bar background between solid and transparent | +| `toggleBarTransparency` | cycle the bar background from clear to glassy to opaque | | `cycleInput ` | switch to the next or previous input method or layout (Super+I, Super+Shift+I); `not running` without the input reader | | `setPluginEnabled <"true"\|…>` | flip enabled bit (`ok` / `unknown`) | | `enablePlugin ` | enable and place in one mutation | @@ -148,7 +148,7 @@ string on a miss. "bar": { "id": "omarchy.bar", "position": "top", - "transparent": false, + "background": "opaque", "centerAnchor": "omarchy.clock", "layout": { "left": [ { "id": "omarchy.menu" } ], diff --git a/install/user/hardware/vm-no-animations.sh b/install/user/hardware/vm-no-animations.sh index 428fa969..412fbd36 100644 --- a/install/user/hardware/vm-no-animations.sh +++ b/install/user/hardware/vm-no-animations.sh @@ -1,9 +1,11 @@ # A VM usually renders on the CPU, where animations and transparency cost -# every frame, so start it without them. omarchy-toggle-animations brings them -# back. This runs before any Hyprland session exists, so place the flag -# directly rather than through omarchy-hyprland-toggle, which reloads Hyprland. +# every frame, so start it without them and with opaque windows. +# omarchy-toggle-animations and Appearance > Window Transparency bring them +# back. This runs before any Hyprland session exists, so place the flags +# directly rather than through the commands, which reload Hyprland. if omarchy-hw-vm; then echo "Detected a virtual machine. Turning off animations and transparency." mkdir -p "$HOME/.local/state/omarchy/toggles/hypr" cp "$OMARCHY_PATH/default/hypr/toggles/no-animations.lua" "$HOME/.local/state/omarchy/toggles/hypr/" + cp "$OMARCHY_PATH/default/hypr/toggles/window-opaque.lua" "$HOME/.local/state/omarchy/toggles/hypr/" fi diff --git a/manual/05-the-top-bar.md b/manual/05-the-top-bar.md index 2c203ab4..bac80f9b 100644 --- a/manual/05-the-top-bar.md +++ b/manual/05-the-top-bar.md @@ -81,15 +81,15 @@ If you'd rather they were always visible, set `alwaysShow` to `true` on the widg The bar configures itself. You don't have to open a config file to move things. -Grab an empty patch of the bar around the center and drag it toward another screen edge, and the bar moves there — left, right, top, or bottom all work, and every widget adapts (vertical bars fall back to compact icon-only forms). A click-and-hold starts the same drag. Double-left-click that same empty space to toggle transparency. And drag any widget to reorder it or throw it into another section. +Grab an empty patch of the bar around the center and drag it toward another screen edge, and the bar moves there — left, right, top, or bottom all work, and every widget adapts (vertical bars fall back to compact icon-only forms). A click-and-hold starts the same drag. Double-left-click that same empty space to cycle the bar background from clear to glassy to opaque. And drag any widget to reorder it or throw it into another section. -If you'd rather pick from a menu, **Style → Menu Bar** has both position and transparency. +If you'd rather pick from a menu, **Trigger → Appearance → Menu Bar** has the position, and **Bar Background** picks how the bar is drawn: **Opaque Bar**, **Glassy Bar** (see-through, with what's behind it blurred), or **Clear Bar**. The same things have commands, which is what you want for a [dotfiles](31-dotfiles.md) setup: ```bash omarchy bar position bottom -omarchy bar transparent toggle +omarchy bar background glassy # opaque, glassy, none, or cycle omarchy bar move omarchy.clock --section center --index 0 omarchy bar set omarchy.clock format "HH:mm" omarchy bar defaults # back to the shipped layout @@ -104,7 +104,7 @@ omarchy plugin disable omarchy.weather ## Hiding the bar -`Super + Shift + Space` toggles the bar off and back on without killing the shell — panels and hotkeys keep working, you just get the pixels back. It's also in the menu under **Trigger → Toggle → Menu Bar**. +`Super + Shift + Space` toggles the bar off and back on without killing the shell — panels and hotkeys keep working, you just get the pixels back. It's also in the menu under **Trigger → Appearance → Menu Bar → Hide Bar**. ## The config file @@ -115,7 +115,7 @@ All of it is stored in `~/.config/omarchy/shell.json`, under the `bar` key. Here "version": 1, "bar": { "position": "top", - "transparent": false, + "background": "opaque", "centerAnchor": "omarchy.clock", "layout": { "left": [{ "id": "omarchy.menu" }, { "id": "omarchy.workspaces" }], diff --git a/migrations/1791705142.sh b/migrations/1791705142.sh new file mode 100644 index 00000000..ef4e641c --- /dev/null +++ b/migrations/1791705142.sh @@ -0,0 +1,20 @@ +echo "Keep windows opaque where animations are turned off" + +# Turning animations off used to make windows opaque too. It no longer touches +# transparency, so drop that rule from the saved flag, where it would keep +# windows opaque whatever Appearance > Window Transparency says, and carry it +# over as Window Transparency > Opaque for anyone who has not picked one yet. +# omarchy-hyprland-toggle saved flags under ~/.local/state whatever +# XDG_STATE_HOME said, while Hyprland reads them from XDG_STATE_HOME, so both +# are fixed. +for state_home in "$HOME/.local/state" "${XDG_STATE_HOME:-}"; do + [[ -n $state_home ]] || continue + toggles_dir="$state_home/omarchy/toggles/hypr" + [[ -f $toggles_dir/no-animations.lua ]] || continue + + sed -i '/^o\.window("\.\*", { opacity = "1 1" })$/d' "$toggles_dir/no-animations.lua" + + if [[ ! -f $toggles_dir/window-opaque.lua && ! -f $toggles_dir/window-glassy.lua ]]; then + cp "$OMARCHY_PATH/default/hypr/toggles/window-opaque.lua" "$toggles_dir/" + fi +done diff --git a/shell/plugins/bar/Bar.qml b/shell/plugins/bar/Bar.qml index 392a7c44..9cb72ec7 100644 --- a/shell/plugins/bar/Bar.qml +++ b/shell/plugins/bar/Bar.qml @@ -45,7 +45,7 @@ Item { property string omarchyConfigDir: home + "/.config/omarchy" property var fallbackBarConfig: ({ position: "top", - transparent: false, + background: "opaque", centerAnchor: "omarchy.clock", layout: { left: [], center: [], right: [] } }) @@ -54,6 +54,12 @@ Item { property bool requestedTransparent: false property bool useTransparentForeground: false property bool transparent: false + // A see-through bar that Hyprland blurs behind (the bar-glassy toggle), so + // its theme background stays faintly. A transparent bar wins over it. + property bool glassy: false + // The look the blur behind the bar was last synced for. A background set by + // editing shell.json skips the CLI, so the bar syncs Hyprland's blur itself. + property string glassSyncedLook: "" property bool centerSectionHovered: false // One bar surface exists per monitor and each reports into this count, so a // pointer crossing from one monitor's bar to another's stays counted however @@ -79,6 +85,7 @@ Item { property color barForeground: useTransparentForeground ? transparentForeground : themeForeground property bool foregroundAnimationEnabled: true property color background: Commons.Color.bar.background + readonly property color glassBackground: Qt.rgba(background.r, background.g, background.b, Math.min(background.a, 0.35)) property color urgent: Commons.Color.bar.active Behavior on barForeground { enabled: root.foregroundAnimationEnabled; ColorAnimation { duration: Style.duration(420); easing.type: Easing.OutCubic } } @@ -651,7 +658,18 @@ Item { var config = Util.isPlainObject(barConfig) ? barConfig : fallbackBarConfig position = normalizePosition(config.position) - setRequestedTransparency(config.transparent === true) + // opaque, glassy or none; a config from before background says + // transparent instead. + var look = config.background || (config.transparent === true ? "none" : "opaque") + setRequestedTransparency(look === "none") + // A bar going clear waits for its text color before dropping its + // background, so a glassy one stays glassy until then rather than + // flashing opaque. Once clear, transparent wins over a stale glassy. + if (look !== "none") glassy = look === "glassy" + if (look !== glassSyncedLook) { + glassSyncedLook = look + Util.execDetached("omarchy-bar sync-glass") + } centerAnchor = Util.canonicalWidgetId(config.centerAnchor || "") // layoutEntries feeds plain JS arrays to the module Repeaters, and QML @@ -913,16 +931,10 @@ Item { Util.execDetached(command) } - function toggleTransparency() { - var nextTransparent = !(root.requestedTransparent === true) - if (root.shell && typeof root.shell.mutateShellConfig === "function") { - root.shell.mutateShellConfig(function(config) { - if (!Util.isPlainObject(config.bar)) config.bar = {} - config.bar.transparent = nextTransparent - }) - } else { - root.setRequestedTransparency(nextTransparent) - } + // Steps the bar background from clear to glassy to opaque and around. Through + // omarchy-bar, which keeps Hyprland's blur behind a glassy bar in step. + function cycleBackground() { + Util.execDetached("omarchy-bar background cycle") } function rawLayoutSection(config, region) { @@ -1357,7 +1369,7 @@ Item { implicitWidth: root.vertical ? thickness : 0 implicitHeight: root.vertical ? 0 : thickness - color: root.transparent ? "transparent" : root.background + color: root.transparent ? "transparent" : (root.glassy ? root.glassBackground : root.background) surfaceFormat.opaque: false WlrLayershell.namespace: "omarchy-bar" WlrLayershell.layer: WlrLayer.Top @@ -1832,7 +1844,7 @@ Item { return } if (mouse.button === Qt.LeftButton) { - root.toggleTransparency() + root.cycleBackground() mouse.accepted = true } } diff --git a/shell/plugins/bar/README.md b/shell/plugins/bar/README.md index 255c4bc6..300648bc 100644 --- a/shell/plugins/bar/README.md +++ b/shell/plugins/bar/README.md @@ -16,7 +16,7 @@ the shell for its whole session. The bar config lives under the `bar:` key of [`~/.config/omarchy/shell.json`](../../../docs/omarchy-shell.md#shelljson). Out of the box the shell uses [`config/omarchy/shell.json`](../../../config/omarchy/shell.json). Once you customize anything via the bar gestures, `omarchy bar ...`, or by editing shell.json directly, your file is canonical — there is no deep-merge. -The bar is configured directly on the bar itself: drag empty bar space (or click-and-hold) to move the bar to another screen edge, double-left-click empty center-bar space to toggle transparency, and drag widgets to reorder them. The `omarchy bar position`, `omarchy bar transparent`, `omarchy bar move`, and `omarchy bar set` commands do the same from scripts. Enable or disable widgets with `omarchy plugin enable` and `omarchy plugin disable` (widget ids come from `omarchy plugin list`). +The bar is configured directly on the bar itself: drag empty bar space (or click-and-hold) to move the bar to another screen edge, double-left-click empty center-bar space to cycle the bar background from clear to glassy to opaque, and drag widgets to reorder them. The `omarchy bar position`, `omarchy bar background`, `omarchy bar move`, and `omarchy bar set` commands do the same from scripts. Enable or disable widgets with `omarchy plugin enable` and `omarchy plugin disable` (widget ids come from `omarchy plugin list`). Example `shell.json` (bar subtree only shown): @@ -25,7 +25,7 @@ Example `shell.json` (bar subtree only shown): "version": 1, "bar": { "position": "top", - "transparent": false, + "background": "opaque", "centerAnchor": "omarchy.clock", "layout": { "left": [ diff --git a/shell/plugins/menu/MenuModel.js b/shell/plugins/menu/MenuModel.js index f2dbbeb7..588e48bb 100644 --- a/shell/plugins/menu/MenuModel.js +++ b/shell/plugins/menu/MenuModel.js @@ -393,6 +393,7 @@ function displayRow(items, itemOrder, checkedResults, disabledResults, entry, de // cached while one expression runs lives in that subshell only, so a lazy // memo never survives to the expression after it. var GUARD_READERS = [ + "omarchy-bar background", "omarchy-bar get omarchy.clock format", "omarchy-bar get omarchy.clock verticalFormat", "omarchy-bar position", @@ -404,6 +405,7 @@ var GUARD_READERS = [ "omarchy-default-dictation", "omarchy-dns", "omarchy-hyprland-corners", + "omarchy-hyprland-window-transparency", "omarchy-hyprland-workspace-layout-default" ] diff --git a/shell/shell.qml b/shell/shell.qml index 19ad7fcf..03bdc22f 100644 --- a/shell/shell.qml +++ b/shell/shell.qml @@ -47,7 +47,7 @@ ShellRoot { }, bar: { position: "top", - transparent: false, + background: "opaque", centerAnchor: "omarchy.clock", layout: { left: [{ id: "omarchy.menu" }, { id: "omarchy.workspaces" }], @@ -1794,9 +1794,11 @@ ShellRoot { return "ok" } + // Named for the two-state toggle it once was; it now cycles the bar + // background from clear to glassy to opaque and around. function toggleBarTransparency(): string { - if (shell.bar && typeof shell.bar.toggleTransparency === "function") { - shell.bar.toggleTransparency() + if (shell.bar && typeof shell.bar.cycleBackground === "function") { + shell.bar.cycleBackground() return "ok" } return "no-bar" diff --git a/test/shell.d/bar-test.sh b/test/shell.d/bar-test.sh index 79b7f9a4..ce72e70f 100644 --- a/test/shell.d/bar-test.sh +++ b/test/shell.d/bar-test.sh @@ -59,7 +59,7 @@ const bar = requireFromRoot('shell/plugins/bar/BarModel.js') const barSource = fs.readFileSync(root + '/shell/plugins/bar/Bar.qml', 'utf8') const shellSource = fs.readFileSync(root + '/shell/shell.qml', 'utf8') -assert(/function toggleBarTransparency\(\): string \{[\s\S]*?shell\.bar\.toggleTransparency\(\)/.test(shellSource), 'shell exposes the bar transparency toggle over IPC') +assert(/function toggleBarTransparency\(\): string \{[\s\S]*?shell\.bar\.cycleBackground\(\)/.test(shellSource), 'shell exposes the bar background cycle over IPC') // put tolerates a placement target the bar does not carry, so the IPC call // must reach the registry's put rather than route back through enable. diff --git a/test/shell.d/config-test.sh b/test/shell.d/config-test.sh index ae07af9a..4d644ff6 100755 --- a/test/shell.d/config-test.sh +++ b/test/shell.d/config-test.sh @@ -255,6 +255,9 @@ pass "Hyprland bootstrap reloads cached Omarchy config modules" TMPDIR=$(mktemp -d) mkdir -p "$TMPDIR/home/.config/omarchy" +# The bar keeps its blur toggle where Hyprland reads it, under XDG_STATE_HOME, +# so that points into the test home too. +export XDG_STATE_HOME="$TMPDIR/home/.local/state" ipc_mock_bin="$TMPDIR/ipc-mock" mkdir -p "$ipc_mock_bin" @@ -335,16 +338,97 @@ pass "shell config sets bar position" HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar transparent true jq -e ' - .bar.transparent == true and + .bar.background == "none" and .bar.position == "bottom" and .plugins == [] ' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null pass "shell config sets bar transparency" HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar transparent toggle -jq -e '.bar.transparent == false' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null +jq -e '.bar.background == "opaque"' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null pass "shell config toggles bar transparency" +# The bar background is opaque, glassy or none. A glassy bar needs Hyprland's +# blur, so the bar-glassy toggle follows it. +printf '#!/bin/bash\nprintf "%%s\\n" "$*" >>"%s/hyprctl.log"\n' "$TMPDIR" >"$ipc_mock_bin/hyprctl" +chmod +x "$ipc_mock_bin/hyprctl" +glass_toggle="$TMPDIR/home/.local/state/omarchy/toggles/hypr/bar-glassy.lua" +bar_background() { HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar background "$@"; } + +bar_background opaque >/dev/null +[[ $(bar_background) == "opaque" ]] || fail "an opaque bar background is reported" +bar_background glassy >/dev/null +jq -e '.bar.background == "glassy"' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null || + fail "shell config sets a glassy bar" +[[ $(bar_background) == "glassy" ]] || fail "a glassy bar background is reported" +[[ -f $glass_toggle ]] || fail "a glassy bar turns on Hyprland's blur behind it" +grep -qx 'reload' "$TMPDIR/hyprctl.log" || fail "a glassy bar reloads Hyprland" +bar_background none >/dev/null +jq -e '.bar.background == "none"' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null || + fail "a bar without a background is stored as none" +[[ ! -f $glass_toggle ]] || fail "a bar without a background turns Hyprland's blur off again" +pass "shell config sets the bar background, and Hyprland's blur follows a glassy one" + +seen=() +for _ in 1 2 3; do + bar_background cycle >/dev/null + seen+=("$(bar_background)") +done +[[ ${seen[*]} == "glassy opaque none" ]] || fail "the bar background cycles from clear to glassy to opaque and around" "${seen[*]}" +if bar_background frosted 2>/dev/null; then + fail "an unknown bar background is refused" +fi +: >"$TMPDIR/hyprctl.log" +bar_background opaque >/dev/null +[[ ! -s $TMPDIR/hyprctl.log ]] || fail "Hyprland reloads only when the bar's glass changes" +pass "the bar background cycles through its looks and refuses unknown ones" + +# transparent stays for scripts written against it: true is none, false opaque. +bar_background glassy >/dev/null +HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar transparent true >/dev/null +[[ $(bar_background) == "none" ]] || fail "transparent true leaves the bar without a background" +HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar transparent toggle >/dev/null +[[ $(bar_background) == "opaque" ]] || fail "toggling transparency off makes the bar opaque" +# A shell.json from before background says transparent, which reads as none +# and is dropped once a background is set. +jq '.bar.transparent = true | del(.bar.background)' "$TMPDIR/home/.config/omarchy/shell.json" >"$TMPDIR/legacy.json" && + mv "$TMPDIR/legacy.json" "$TMPDIR/home/.config/omarchy/shell.json" +[[ $(bar_background) == "none" ]] || fail "an older transparent bar reads as no background" +bar_background glassy >/dev/null +jq -e '.bar.background == "glassy" and (.bar | has("transparent") | not)' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null || + fail "setting a background drops the older transparent flag" +pass "the older transparent form still sets and reads as the bar background" + +# Restoring the default bar also restores its opaque background, and the blur +# behind a glassy one goes with it. +bar_background glassy >/dev/null +[[ -f $glass_toggle ]] || fail "a glassy bar has its blur on before defaults" +HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar defaults >/dev/null +[[ $(bar_background) == "opaque" && ! -f $glass_toggle ]] || fail "omarchy bar defaults turns a glassy bar's blur off" +pass "restoring the default bar turns a glassy bar's blur off" + +# A background set by editing shell.json gets its blur when the shell syncs it. +jq '.bar.background = "glassy"' "$TMPDIR/home/.config/omarchy/shell.json" >"$TMPDIR/edited.json" && + mv "$TMPDIR/edited.json" "$TMPDIR/home/.config/omarchy/shell.json" +HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar sync-glass +[[ -f $glass_toggle ]] || fail "syncing a hand-edited glassy bar turns its blur on" +jq '.bar.background = "opaque"' "$TMPDIR/home/.config/omarchy/shell.json" >"$TMPDIR/edited.json" && + mv "$TMPDIR/edited.json" "$TMPDIR/home/.config/omarchy/shell.json" +HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar sync-glass +[[ ! -f $glass_toggle ]] || fail "syncing a hand-edited opaque bar turns its blur off" +pass "a bar background edited into shell.json gets its blur synced" + +# A glassy bar whose blur can't be turned on keeps the background it had. +toggles_dir="${glass_toggle%/*}" +chmod a-w "$toggles_dir" +if bar_background glassy >/dev/null 2>&1; then + chmod u+w "$toggles_dir" + fail "a bar whose blur can't be turned on reports failure" +fi +chmod u+w "$toggles_dir" +[[ $(bar_background) == "opaque" && ! -f $glass_toggle ]] || fail "a bar whose blur can't be turned on keeps its old background" "$(bar_background)" +pass "a bar whose blur can't change keeps its old background" + HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar set omarchy.bluetooth enabled false --json grep -Fqx 'shell setBarWidget omarchy.bluetooth enabled false {}' \ "$TMPDIR/home/.local/state/omarchy/shell-ipc-calls" diff --git a/test/shell.d/hyprland-window-transparency-test.sh b/test/shell.d/hyprland-window-transparency-test.sh new file mode 100755 index 00000000..d3ab5415 --- /dev/null +++ b/test/shell.d/hyprland-window-transparency-test.sh @@ -0,0 +1,230 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh" + +tmpdir=$(mktemp -d) +trap 'rm -rf "$tmpdir"' EXIT + +stub_dir="$tmpdir/bin" +home_dir="$tmpdir/home" +state_home="$tmpdir/state" +toggles_dir="$state_home/omarchy/toggles/hypr" +mkdir -p "$stub_dir" "$home_dir" + +for command in hyprctl omarchy-notification-send omarchy-restart-terminal omarchy-theme-set-foot omarchy-restart-btop; do + printf '#!/bin/bash\nprintf "%%s\\n" "%s $*" >>"%s/commands.log"\n' "$command" "$tmpdir" >"$stub_dir/$command" +done +chmod +x "$stub_dir"/* + +run() { + HOME="$home_dir" XDG_RUNTIME_DIR="$tmpdir" XDG_STATE_HOME="$state_home" XDG_CONFIG_HOME="$home_dir/.config" OMARCHY_PATH="$ROOT" PATH="$stub_dir:$ROOT/bin:$PATH" "$@" +} + +[[ $(run omarchy-hyprland-window-transparency) == "transparent" ]] || fail "windows start out transparent" +pass "windows start out transparent" + +run omarchy-hyprland-window-transparency glassy +[[ $(run omarchy-hyprland-window-transparency) == "glassy" ]] || fail "glassy windows are reported" +cmp -s "$ROOT/default/hypr/toggles/window-glassy.lua" "$toggles_dir/window-glassy.lua" || fail "glassy windows are loaded after the theme" +grep -qx 'hyprctl reload' "$tmpdir/commands.log" || fail "new transparency reloads Hyprland" +grep -q '^omarchy-restart-btop ' "$tmpdir/commands.log" || fail "new transparency reloads running btop windows" +pass "glassy windows override the theme" + +run omarchy-hyprland-window-transparency opaque +[[ $(run omarchy-hyprland-window-transparency) == "opaque" ]] || fail "opaque windows are reported" +[[ ! -e $toggles_dir/window-glassy.lua ]] || fail "opaque windows replace glassy ones" +pass "opaque windows replace glassy ones" + +run omarchy-hyprland-window-transparency transparent +[[ $(run omarchy-hyprland-window-transparency) == "transparent" ]] || fail "transparent windows are reported" +[[ ! -e $toggles_dir/window-opaque.lua ]] || fail "transparent windows drop the override" +pass "transparent windows drop the override" + +if run omarchy-hyprland-window-transparency frosted 2>/dev/null; then + fail "unknown transparency is refused" +fi +pass "unknown transparency is refused" + +# A failed save keeps the transparency already chosen. +run omarchy-hyprland-window-transparency glassy +chmod a-w "$toggles_dir" +if run omarchy-hyprland-window-transparency opaque 2>/dev/null; then + chmod u+w "$toggles_dir" + fail "a failed save is reported" +fi +chmod u+w "$toggles_dir" +[[ $(run omarchy-hyprland-window-transparency) == "glassy" ]] || fail "a failed save keeps the transparency already chosen" +run omarchy-hyprland-window-transparency transparent +pass "a failed save keeps the transparency already chosen" + +# GTK apps only ask Hyprland for blur under a CSS backdrop-filter, so glassy +# imports one into the user's gtk.css, leaving the rest of it alone. +gtk_css="$home_dir/.config/gtk-4.0/gtk.css" +mkdir -p "${gtk_css%/*}" +printf 'label { color: red; }\n' >"$gtk_css" +run omarchy-hyprland-window-transparency glassy +grep -qxF "@import url(\"file://$ROOT/default/gtk/glass.css\");" "$gtk_css" || fail "glassy GTK apps import the backdrop blur" +grep -qx 'label { color: red; }' "$gtk_css" || fail "glassy keeps the user's own GTK CSS" +run omarchy-hyprland-window-transparency glassy +(( $(grep -c 'omarchy-glass begin' "$gtk_css") == 1 )) || fail "glassy GTK apps import the backdrop blur once" +run omarchy-hyprland-window-transparency transparent +[[ $(<"$gtk_css") == 'label { color: red; }' ]] || fail "leaving glassy restores the user's GTK CSS" +grep -qF 'backdrop-filter: blur(' "$ROOT/default/gtk/glass.css" || fail "the GTK glass is a backdrop blur" +printf 'label { color: red; }' >"$gtk_css" +run omarchy-hyprland-window-transparency glassy +run omarchy-hyprland-window-transparency transparent +[[ $(od -An -c "$gtk_css" | tr -d ' \n') == 'label{color:red;}' ]] || fail "leaving glassy restores GTK CSS without a final newline" + +# A GTK config that cannot be written fails the change rather than announcing it. +printf 'label { color: red; }\n' >"$gtk_css" +chmod a-w "$gtk_css" +: >"$tmpdir/commands.log" +if run omarchy-hyprland-window-transparency glassy 2>/dev/null; then + chmod u+w "$gtk_css" + fail "a GTK config that cannot be written fails the change" +fi +chmod u+w "$gtk_css" +! grep -q '^omarchy-notification-send' "$tmpdir/commands.log" || fail "a failed change is not announced" +[[ $(run omarchy-hyprland-window-transparency) == "transparent" ]] || fail "a failed change keeps the previous choice" +run omarchy-hyprland-window-transparency transparent +pass "glassy GTK apps get a backdrop blur, and leaving glassy takes it out" + +# Opaque reaches the windows Omarchy makes transparent through their tag. +grep -qF 'tag = "+transparent-window"' "$ROOT/default/hypr/helpers.lua" || fail "transparent windows carry a tag" +grep -qF 'o.window({ tag = "transparent-window" }, { opacity = "1 1" })' "$ROOT/default/hypr/toggles/window-opaque.lua" || + fail "opaque windows apply to the tagged windows" +pass "opaque windows apply to the windows Omarchy makes transparent" + +# Terminals turn compositor blur down and fade their text with the window, so +# glassy gives each theme terminal file a see-through, blurred background. +theme_dir="$tmpdir/theme" +mkdir -p "$theme_dir" +printf '[colors.primary]\nbackground = "#000000"\n' >"$theme_dir/alacritty.toml" +printf '[colors-dark]\nbackground=000000\n' >"$theme_dir/foot.ini" +printf 'background #000000\n' >"$theme_dir/kitty.conf" +printf 'background = #000000\nbackground-opacity = 0.9\n' >"$theme_dir/ghostty.conf" +printf 'theme[main_bg]="#102030"\ntheme[main_fg]="#ffffff"\n' >"$theme_dir/btop.theme" +cp -r "$theme_dir" "$tmpdir/theme-before" + +run omarchy-hyprland-window-transparency glassy +run omarchy-theme-set-glass "$theme_dir" +run omarchy-theme-set-glass "$theme_dir" +grep -qx 'opacity = 0.65' "$theme_dir/alacritty.toml" || fail "glassy alacritty is see-through" +grep -qx 'alpha=0.65' "$theme_dir/foot.ini" && grep -qx 'blur=yes' "$theme_dir/foot.ini" || fail "glassy foot is see-through and asks for blur" +grep -qx 'background_opacity 0.65' "$theme_dir/kitty.conf" || fail "glassy kitty is see-through" +(( $(grep -c 'omarchy-glass begin' "$theme_dir/foot.ini") == 1 )) || fail "glassy terminals get the glass once" +[[ $(grep '^theme\[main_bg\]=' "$theme_dir/btop.theme" | tail -1) == 'theme[main_bg]=""' ]] || fail "glassy btop uses the terminal background" +(( $(grep -c 'omarchy-glass begin' "$theme_dir/btop.theme") == 1 )) || fail "glassy btop gets the glass once" +pass "btop follows the glassy terminal background" +(( $(grep -c 'background-opacity' "$theme_dir/ghostty.conf") == 1 )) && + grep -qx 'background-opacity = 0.9' "$theme_dir/ghostty.conf" || fail "a theme's own terminal opacity is kept" +grep -qx "gtk-custom-css = $ROOT/default/gtk/glass.css" "$theme_dir/ghostty.conf" || + fail "a ghostty theme with its own opacity still gets the backdrop blur" +# A foot theme with its own alpha keeps it but still asks for blur, as +# Hyprland skips blur for foot unless it does. +own_alpha_dir="$tmpdir/own-alpha-theme" +mkdir -p "$own_alpha_dir" +printf '[colors-dark]\nbackground=000000\nalpha=0.8\n' >"$own_alpha_dir/foot.ini" +run omarchy-theme-set-glass "$own_alpha_dir" +(( $(grep -c '^alpha=' "$own_alpha_dir/foot.ini") == 1 )) && grep -qx 'alpha=0.8' "$own_alpha_dir/foot.ini" && + grep -qx 'blur=yes' "$own_alpha_dir/foot.ini" || fail "a foot theme with its own alpha still asks for blur" +if command -v foot >/dev/null; then + foot --check-config -c "$own_alpha_dir/foot.ini" || fail "a foot theme with its own alpha stays valid" +fi +pass "a theme's own terminal opacity is kept, and its blur is still added" +if command -v foot >/dev/null; then + foot --check-config -c "$theme_dir/foot.ini" || fail "glassy foot config is valid" +fi +# A window table of the theme's own takes the glass in, as TOML refuses a +# table declared twice. +window_dir="$tmpdir/window-theme" +mkdir -p "$window_dir" +printf '[window] # mine\npadding = { x = 4, y = 4 }\nblur = false\n\n[colors.primary]\nbackground = "#000000"\n' >"$window_dir/alacritty.toml" +cp "$window_dir/alacritty.toml" "$tmpdir/window-before.toml" +run omarchy-theme-set-glass "$window_dir" +run omarchy-theme-set-glass "$window_dir" +(( $(grep -c '^\[window\]' "$window_dir/alacritty.toml") == 1 )) || fail "glassy alacritty keeps a single window table" +if command -v python3 >/dev/null; then + python3 -c 'import sys, tomllib; c = tomllib.load(open(sys.argv[1], "rb")); assert c["window"]["opacity"] == 0.65 and c["window"]["padding"]["x"] == 4' "$window_dir/alacritty.toml" || + fail "glassy alacritty with a window table of its own stays valid TOML" +fi +HOME="$home_dir" XDG_STATE_HOME="$tmpdir/no-glass" OMARCHY_PATH="$ROOT" PATH="$stub_dir:$ROOT/bin:$PATH" omarchy-theme-set-glass "$window_dir" +cmp -s "$tmpdir/window-before.toml" "$window_dir/alacritty.toml" || fail "leaving glassy restores an alacritty window table" +pass "glassy alacritty joins a window table the theme already has" + +# A file without a final newline gets one with the glass, and loses it again +# when the glass comes out, so leaving glassy restores it byte for byte. +bare_dir="$tmpdir/bare-theme" +mkdir -p "$bare_dir" +printf 'background #000000' >"$bare_dir/kitty.conf" +cp "$bare_dir/kitty.conf" "$tmpdir/bare-before.conf" +run omarchy-theme-set-glass "$bare_dir" +grep -qx 'background_opacity 0.65' "$bare_dir/kitty.conf" || fail "glassy reaches a file without a final newline" +grep -qx 'background #000000' "$bare_dir/kitty.conf" || fail "glassy ends the line before its block" +HOME="$home_dir" XDG_STATE_HOME="$tmpdir/no-glass" OMARCHY_PATH="$ROOT" PATH="$stub_dir:$ROOT/bin:$PATH" omarchy-theme-set-glass "$bare_dir" +cmp -s "$tmpdir/bare-before.conf" "$bare_dir/kitty.conf" || fail "leaving glassy restores a file without a final newline" +pass "leaving glassy restores a file without a final newline byte for byte" + +btop_dir="$tmpdir/transparent-btop-theme" +mkdir -p "$btop_dir" +printf 'theme[main_bg]=""\n' >"$btop_dir/btop.theme" +cp "$btop_dir/btop.theme" "$tmpdir/btop-before.theme" +run omarchy-theme-set-glass "$btop_dir" +cmp -s "$tmpdir/btop-before.theme" "$btop_dir/btop.theme" || fail "btop themes already using the terminal background are kept" +pass "btop themes already using the terminal background are kept" + +ghostty_dir="$tmpdir/ghostty-theme" +mkdir -p "$ghostty_dir" +printf 'background = #000000\n' >"$ghostty_dir/ghostty.conf" +run omarchy-theme-set-glass "$ghostty_dir" +grep -qx 'background-opacity = 0.65' "$ghostty_dir/ghostty.conf" && + grep -qx "gtk-custom-css = $ROOT/default/gtk/glass.css" "$ghostty_dir/ghostty.conf" || + fail "glassy ghostty is see-through and loads the GTK backdrop blur" +pass "glassy terminals get a see-through, blurred background" + +run omarchy-hyprland-window-transparency transparent +run omarchy-theme-set-glass "$theme_dir" +diff -r "$tmpdir/theme-before" "$theme_dir" || fail "leaving glassy restores the terminal files" +pass "leaving glassy restores the terminal files" + +# Running foot terminals take the theme's background alpha over OSC, so a +# change of transparency reaches them without a restart. script stands in for +# foot: its child sits on a real pty, and whatever reaches the pty lands in the +# typescript. +if command -v script >/dev/null; then + foot_home="$tmpdir/foot-home" + foot_theme="$foot_home/.local/state/omarchy/current/theme" + mkdir -p "$foot_theme" "$tmpdir/foot-bin" + printf 'background = "#102030"\nforeground = "#ffffff"\n' >"$foot_theme/colors.toml" + printf '[colors-dark]\nbackground=102030\n' >"$foot_theme/foot.ini" + + script -qfc "sleep 30" "$tmpdir/typescript" >/dev/null 2>&1 & + script_pid=$! + for _ in {1..50}; do pgrep -P "$script_pid" >/dev/null && break; sleep 0.1; done + + # Only the foot lookup is faked; finding its shell goes to the real pgrep. + real_pgrep=$(command -v pgrep) + printf '#!/bin/bash\nif [[ $1 == "-x" && $2 == "foot" ]]; then echo %s; else exec %s "$@"; fi\n' "$script_pid" "$real_pgrep" >"$tmpdir/foot-bin/pgrep" + chmod +x "$tmpdir/foot-bin/pgrep" + + set_foot() { + HOME="$foot_home" PATH="$tmpdir/foot-bin:$ROOT/bin:$PATH" omarchy-theme-set-foot + sleep 0.3 + } + + set_foot + grep -qF $'\e]11;[100]#102030' "$tmpdir/typescript" || fail "running foot terminals stay opaque without glass" + printf '# omarchy-glass begin\n[colors-dark]\nalpha=0.65\nblur=yes\n# omarchy-glass end\n' >>"$foot_theme/foot.ini" + set_foot + grep -qF $'\e]11;[65]#102030' "$tmpdir/typescript" || fail "running foot terminals turn glassy" + kill "$script_pid" 2>/dev/null || true + pass "running foot terminals take the theme's background alpha" +else + skip "script is not installed" +fi + +grep -qx 'blur=yes' "$ROOT/default/themed/foot.ini.tpl" || fail "foot starts ready to blur" +grep -qx 'dynamic_background_opacity yes' "$ROOT/default/themed/kitty.conf.tpl" || fail "kitty starts ready to change opacity" +pass "foot and kitty start ready for glass, since they read that only at launch" diff --git a/test/shell.d/menu-test.sh b/test/shell.d/menu-test.sh index 55488054..5ff8d58a 100644 --- a/test/shell.d/menu-test.sh +++ b/test/shell.d/menu-test.sh @@ -365,7 +365,7 @@ assert( ) assertDeepEqual( defaultItems.filter(item => item.parent === 'trigger.appearance.top-bar').map(item => item.label), - ['Hide Bar', 'Show Bar', 'Position Bar', 'Transparent Bar', 'Opaque Bar', 'Indicators'], + ['Hide Bar', 'Show Bar', 'Position Bar', 'Bar Background', 'Bar Indicators'], 'menu gathers the Menu Bar under Trigger > Appearance' ) assert( @@ -384,16 +384,43 @@ assert( ['top', 'bottom', 'left', 'right'].every(position => defaultById[`trigger.appearance.top-bar.position.${position}`].action === `omarchy-bar position ${position}`), 'menu lists all Menu Bar positions under Position' ) -assert( - defaultById['trigger.appearance.top-bar.transparent'].action === 'omarchy-bar transparent true' - && defaultById['trigger.appearance.top-bar.opaque'].action === 'omarchy-bar transparent false' - && defaultById['trigger.appearance.top-bar.transparent'].when === '! ' + defaultById['trigger.appearance.top-bar.opaque'].when, - 'menu offers Transparent Bar while the bar is opaque and Opaque Bar while it is transparent' +assertDeepEqual( + defaultItems.filter(item => item.parent === 'trigger.appearance.top-bar.background').map(item => [item.label, item.action]), + [ + ['Opaque Bar', 'omarchy-bar background opaque'], + ['Glassy Bar', 'omarchy-bar background glassy'], + ['Clear Bar', 'omarchy-bar background none'] + ], + 'menu picks the bar background from Menu Bar > Bar Background' ) +// Exactly the bar's current background is checked. Ask bash, against a +// shell.json in each state, which rows it checks. +{ + const os = require('os') + const { execFileSync } = require('child_process') + const home = fs.mkdtempSync(path.join(os.tmpdir(), 'menu-bar-background-')) + fs.mkdirSync(path.join(home, '.config/omarchy'), { recursive: true }) + const checked = bar => { + fs.writeFileSync(path.join(home, '.config/omarchy/shell.json'), JSON.stringify({ bar })) + return ['opaque', 'glassy', 'clear'].filter(look => { + try { + execFileSync('bash', ['-c', defaultById[`trigger.appearance.top-bar.background.${look}`].checked], { env: { ...process.env, HOME: home, OMARCHY_PATH: root, PATH: `${root}/bin:${process.env.PATH}` } }) + return true + } catch (e) { + return false + } + }) + } + assertDeepEqual(checked({}), ['opaque'], 'menu checks Opaque Bar while the bar is opaque') + assertDeepEqual(checked({ background: 'glassy' }), ['glassy'], 'menu checks Glassy Bar while the bar is glassy') + assertDeepEqual(checked({ background: 'none' }), ['clear'], 'menu checks Clear Bar while the bar has no background') + assertDeepEqual(checked({ transparent: true }), ['clear'], 'menu checks Clear Bar for a bar made transparent before backgrounds') + fs.rmSync(home, { recursive: true, force: true }) +} assertDeepEqual( defaultItems.filter(item => item.parent === 'trigger.appearance.top-bar.indicators').map(item => item.label), ['Battery Percentage', 'Clock'], - 'menu gathers bar indicator options under Menu Bar > Indicators' + 'menu gathers bar indicator options under Menu Bar > Bar Indicators' ) const clockModel = requireFromRoot('shell/plugins/panels/clock/Model.js') const clockRows = defaultItems.filter(item => item.parent === 'trigger.appearance.top-bar.indicators.clock') @@ -417,8 +444,17 @@ assertDeepEqual( [['Theme Corners', 'omarchy-hyprland-corners theme'], ['Sharp Corners', 'omarchy-hyprland-corners sharp'], ['Rounded Corners', 'omarchy-hyprland-corners rounded']], 'menu picks window corners from Appearance > Window Corners' ) +assertDeepEqual( + defaultItems.filter(item => item.parent === 'trigger.appearance.window-transparency').map(item => [item.label, item.action]), + [ + ['Opaque Windows', 'omarchy-hyprland-window-transparency opaque'], + ['Transparent Windows', 'omarchy-hyprland-window-transparency transparent'], + ['Glassy Windows', 'omarchy-hyprland-window-transparency glassy'] + ], + 'menu picks window transparency from Appearance > Window Transparency' +) const triggerIds = triggerItems.map(item => item.id) -const appearanceLabels = ['Animations', 'Menu Bar', 'Window Gaps', 'Window Corners', '1-Window Ratio', 'Workspace Layout', 'Herdr Theme Sync'] +const appearanceLabels = ['Animations', 'Menu Bar', 'Window Gaps', 'Window Corners', 'Window Transparency', '1-Window Ratio', 'Workspace Layout', 'Herdr Theme Sync'] assertEqual(triggerIds.indexOf('trigger.toggle'), triggerIds.indexOf('trigger.appearance') + 1, 'menu places Toggle right after Appearance') assertDeepEqual( defaultItems.filter(item => item.parent === 'trigger.appearance').map(item => item.label), diff --git a/test/shell.d/theme-templates-test.sh b/test/shell.d/theme-templates-test.sh index e52b89ea..bcd57d7f 100644 --- a/test/shell.d/theme-templates-test.sh +++ b/test/shell.d/theme-templates-test.sh @@ -41,7 +41,7 @@ printf 'no newline {{ accent }}' >"$themed/tail.txt.tpl" # Function arguments may be separated by any whitespace, tabs included. printf '{{ mix\tbackground accent 30%% }} {{\tmix_rgb red\tblue 50%%\t}} {{ gradient_start\thyprland_active_border accent }}\n' >"$themed/tabs.txt.tpl" -HOME="$home" OMARCHY_PATH="$ROOT" PATH="$ROOT/bin:$PATH" "$ROOT/bin/omarchy-theme-set-templates" +HOME="$home" XDG_STATE_HOME="$home/.local/state" OMARCHY_PATH="$ROOT" PATH="$ROOT/bin:$PATH" "$ROOT/bin/omarchy-theme-set-templates" # Expected values were produced by the sed-based renderer this replaced, so # every theme keeps rendering byte for byte the same. diff --git a/test/shell.d/toggle-test.sh b/test/shell.d/toggle-test.sh index a4169209..edf78f80 100644 --- a/test/shell.d/toggle-test.sh +++ b/test/shell.d/toggle-test.sh @@ -106,8 +106,30 @@ for virt in vm none; do HOME="$test_home" OMARCHY_PATH="$ROOT" bash "$ROOT/install/user/hardware/vm-no-animations.sh" >/dev/null if [[ $virt == "vm" ]]; then [[ -f $animations_flag ]] || fail "a VM install turns animations off" + [[ -f ${animations_flag%/*}/window-opaque.lua ]] || fail "a VM install starts with opaque windows" else [[ ! -f $animations_flag ]] || fail "a machine that is not a VM keeps its animations" + [[ ! -f ${animations_flag%/*}/window-opaque.lua ]] || fail "a machine that is not a VM keeps transparent windows" fi done -pass "a VM install starts without animations" +pass "a VM install starts without animations and with opaque windows" + +# Animations off no longer makes windows opaque. The migration takes that rule +# out of a saved flag, keeping anything else in it, and keeps those windows +# opaque through Window Transparency instead. +migration_home=$(mktemp -d) +migration_flags="$migration_home/.local/state/omarchy/toggles/hypr" +mkdir -p "$migration_flags" +printf 'hl.config({ animations = { enabled = false } })\n\no.window(".*", { opacity = "1 1" })\n-- mine\n' >"$migration_flags/no-animations.lua" +for _ in 1 2; do + HOME="$migration_home" XDG_STATE_HOME="" OMARCHY_PATH="$ROOT" bash -euo pipefail "$ROOT/migrations/1791705142.sh" >/dev/null +done +! grep -q 'opacity' "$migration_flags/no-animations.lua" || fail "the migration drops the opacity rule from a saved animations flag" +grep -qx -- '-- mine' "$migration_flags/no-animations.lua" || fail "the migration keeps the rest of a saved animations flag" +[[ -f $migration_flags/window-opaque.lua ]] || fail "the migration keeps windows opaque where animations are off" +rm -f "$migration_flags/window-opaque.lua" +printf 'x\n' >"$migration_flags/window-glassy.lua" +HOME="$migration_home" XDG_STATE_HOME="" OMARCHY_PATH="$ROOT" bash -euo pipefail "$ROOT/migrations/1791705142.sh" >/dev/null +[[ ! -f $migration_flags/window-opaque.lua ]] || fail "the migration leaves a chosen transparency alone" +rm -rf "$migration_home" +pass "the migration moves opaque windows from the animations flag to Window Transparency"