* Pick window transparency from Appearance > Window Transparency Transparent, the default, keeps today's windows. Opaque turns the windows Omarchy makes transparent solid. Glassy is the frosted glass Starship (#14726): its blur behind every see-through window and shell surface, and a see-through, blurred background in the theme's terminal files, since foot turns compositor blur down and a Hyprland opacity rule fades text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Leave window transparency to Appearance when animations are off Turning animations off no longer makes windows opaque. VM installs start with opaque windows instead, and a migration keeps them opaque for anyone who already has animations off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add a Glassy bar background, under Menu Bar > Bar Background Menu Bar > Bar Background picks Opaque Bar, Glassy Bar or Clear Bar, and Indicators becomes Bar Indicators to match. Glassy keeps the theme background faintly and Hyprland blurs what is behind it, with the same blur as Glassy windows. omarchy bar background opaque|glassy|none|cycle sets it and prints it when given nothing. omarchy bar transparent true|false|toggle stays for compatibility, as none and opaque. Double-clicking the bar cycles through the three backgrounds the same way, from clear to glassy to opaque. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Stop when a saved corner choice can't be removed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Make btop follow glassy windows --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
498 lines
17 KiB
Bash
Executable File
498 lines
17 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Configure the bar and its widget layout
|
|
# omarchy:group=bar
|
|
# omarchy:args=use <id> | reset | defaults | position [top|bottom|left|right] | background [opaque|glassy|none|cycle] | put <id> [placement] | move <id> [placement] | set <id> <key> <value> [--json] [placement] | get <id> <key>
|
|
# omarchy: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
|
|
|
|
source omarchy-shell-config
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
Usage: omarchy bar <command> [args...]
|
|
|
|
use <id> Use a bar option as the active bar
|
|
reset Return to the built-in Omarchy bar
|
|
defaults Restore the default bar and service widgets
|
|
position [top|bottom|left|right] Bar position; prints the current one
|
|
when given none
|
|
background [opaque|glassy|none|cycle]
|
|
Bar background: glassy shows what is
|
|
behind the bar blurred, cycle steps
|
|
none, glassy, opaque; prints the
|
|
current one when given none
|
|
put <id> [placement] Put a widget on the bar, leaving one
|
|
that is already there where it is
|
|
move <id> [placement] Move a widget within or between sections
|
|
set <id> <key> <value> [--json] [placement]
|
|
Set a per-widget option
|
|
get <id> <key> Print a per-widget option
|
|
|
|
Placement:
|
|
--section <left|center|right> Target section
|
|
--index <n> Target index
|
|
--before <id> Insert before a widget
|
|
--after <id> Insert after a widget
|
|
--from-section <section> Source section
|
|
--from-index <n> Source index
|
|
|
|
Enable and disable widgets with 'omarchy plugin enable' and
|
|
'omarchy plugin disable'.
|
|
|
|
'put' places a widget the way 'plugin enable' does, but leaves one that is
|
|
already on the bar where it is, and falls back to the widget's usual spot when
|
|
--before / --after names a widget the bar does not carry.
|
|
|
|
Examples:
|
|
omarchy bar use local.neon-bar
|
|
omarchy bar put omarchy.keyboard-layout --after omarchy.clock
|
|
omarchy bar move omarchy.media left
|
|
omarchy bar move omarchy.clock --section center --index 0
|
|
omarchy bar set omarchy.clock format HH:mm
|
|
USAGE
|
|
}
|
|
|
|
# ------------------------------------------------------------------ validation
|
|
|
|
bar_option_exists() {
|
|
omarchy-plugin-catalog | jq -e --arg id "$1" '
|
|
any(.[]; (.kinds | index("bar")) and .barPath != null and .id == $id)
|
|
' >/dev/null
|
|
}
|
|
|
|
validate_section() {
|
|
[[ $1 =~ ^(left|center|right)$ ]] || fail "section must be left, center, or right"
|
|
}
|
|
|
|
validate_index() {
|
|
[[ $1 =~ ^[0-9]+$ ]] || fail "index must be a non-negative integer"
|
|
}
|
|
|
|
# --------------------------------------------------------------------- placement
|
|
|
|
PLACEMENT_SECTION=""
|
|
PLACEMENT_INDEX=""
|
|
PLACEMENT_BEFORE=""
|
|
PLACEMENT_AFTER=""
|
|
PLACEMENT_FROM_SECTION=""
|
|
PLACEMENT_FROM_INDEX=""
|
|
|
|
parse_placement() {
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--section)
|
|
PLACEMENT_SECTION="${2:-}"
|
|
validate_section "$PLACEMENT_SECTION"
|
|
shift 2
|
|
;;
|
|
--index)
|
|
PLACEMENT_INDEX="${2:-}"
|
|
validate_index "$PLACEMENT_INDEX"
|
|
shift 2
|
|
;;
|
|
--before)
|
|
PLACEMENT_BEFORE="${2:-}"
|
|
[[ -n $PLACEMENT_BEFORE ]] || fail "--before requires a widget id"
|
|
shift 2
|
|
;;
|
|
--after)
|
|
PLACEMENT_AFTER="${2:-}"
|
|
[[ -n $PLACEMENT_AFTER ]] || fail "--after requires a widget id"
|
|
shift 2
|
|
;;
|
|
--from-section)
|
|
PLACEMENT_FROM_SECTION="${2:-}"
|
|
validate_section "$PLACEMENT_FROM_SECTION"
|
|
shift 2
|
|
;;
|
|
--from-index)
|
|
PLACEMENT_FROM_INDEX="${2:-}"
|
|
validate_index "$PLACEMENT_FROM_INDEX"
|
|
shift 2
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
fail "unknown option: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
[[ -z $PLACEMENT_BEFORE || -z $PLACEMENT_AFTER ]] || fail "use only one of --before or --after"
|
|
}
|
|
|
|
placement_json() {
|
|
jq -cn \
|
|
--arg section "$PLACEMENT_SECTION" \
|
|
--arg index "$PLACEMENT_INDEX" \
|
|
--arg before "$PLACEMENT_BEFORE" \
|
|
--arg after "$PLACEMENT_AFTER" \
|
|
--arg fromSection "$PLACEMENT_FROM_SECTION" \
|
|
--arg fromIndex "$PLACEMENT_FROM_INDEX" '
|
|
{}
|
|
+ (if $section == "" then {} else {section: $section} end)
|
|
+ (if $index == "" then {} else {index: ($index | tonumber)} end)
|
|
+ (if $before == "" then {} else {before: $before} end)
|
|
+ (if $after == "" then {} else {after: $after} end)
|
|
+ (if $fromSection == "" then {} else {fromSection: $fromSection} end)
|
|
+ (if $fromIndex == "" then {} else {fromIndex: ($fromIndex | tonumber)} end)
|
|
'
|
|
}
|
|
|
|
# -------------------------------------------------------------------- commands
|
|
|
|
cmd_use() {
|
|
local plugin="${1:-}"
|
|
[[ -n $plugin ]] || fail "bar option id is required"
|
|
(( $# == 1 )) || fail "use takes a single bar option id"
|
|
if [[ $plugin == "default" || $plugin == "built-in" ]]; then
|
|
plugin="omarchy.bar"
|
|
fi
|
|
bar_option_exists "$plugin" || fail "$plugin is not a known bar option; run 'omarchy plugin list'"
|
|
|
|
if [[ $plugin == "omarchy.bar" ]]; then
|
|
commit "$NORMALIZE | del(.bar.id)"
|
|
else
|
|
commit "$NORMALIZE | .bar.id = \$plugin" --arg plugin "$plugin"
|
|
fi
|
|
echo "Using $plugin as the active bar"
|
|
}
|
|
|
|
cmd_defaults() {
|
|
(( $# == 0 )) || fail "defaults does not take arguments"
|
|
|
|
local optional_widgets="[]"
|
|
local service widget
|
|
for service in dropbox tailscale; do
|
|
if "omarchy-installed-service-$service"; then
|
|
widget=$(jq -cn \
|
|
--arg id "omarchy.$service" \
|
|
--arg section "$(bar_widget_default_section "omarchy.$service")" \
|
|
'{id: $id, section: $section}')
|
|
optional_widgets=$(jq -c --argjson widget "$widget" '. + [$widget]' <<<"$optional_widgets")
|
|
fi
|
|
done
|
|
|
|
# This remains one file mutation so it also works during the headless
|
|
# Quattro upgrade and cannot race the shell's in-memory config.
|
|
commit "$NORMALIZE
|
|
| .bar = \$defaults[0].bar
|
|
| def entry_id: if type == \"object\" then (.id // \"\" | tostring) else tostring end;
|
|
def anchor_for(\$section): { left: \"omarchy.workspaces\", center: \"omarchy.weather\", right: \"omarchy.tray\" }[\$section];
|
|
reduce \$widgets[] as \$widget (.;
|
|
.bar.layout.left = (.bar.layout.left | map(select(entry_id != \$widget.id)))
|
|
| .bar.layout.center = (.bar.layout.center | map(select(entry_id != \$widget.id)))
|
|
| .bar.layout.right = (.bar.layout.right | map(select(entry_id != \$widget.id)))
|
|
| (.bar.layout[\$widget.section] | map(entry_id) | index(anchor_for(\$widget.section))) as \$anchor
|
|
| (\$anchor | if . == null then (.bar.layout[\$widget.section] | length) else . + 1 end) as \$index
|
|
| .bar.layout[\$widget.section] = (
|
|
.bar.layout[\$widget.section][0:\$index]
|
|
+ [{id: \$widget.id}]
|
|
+ .bar.layout[\$widget.section][\$index:]
|
|
)
|
|
)
|
|
" \
|
|
--slurpfile defaults "$DEFAULTS_FILE" \
|
|
--argjson widgets "$optional_widgets"
|
|
sync_glass || fail "could not change the blur behind the bar"
|
|
echo "Restored the default Omarchy bar"
|
|
}
|
|
|
|
cmd_position() {
|
|
if (( $# == 0 )); then
|
|
jq -r '.bar.position // "top"' "$(source_file)"
|
|
return
|
|
fi
|
|
|
|
local position="$1"
|
|
(( $# == 1 )) || fail "position takes a single value"
|
|
[[ $position =~ ^(top|bottom|left|right)$ ]] || fail "position must be top, bottom, left, or right"
|
|
commit "$NORMALIZE | .bar.position = \$position" --arg position "$position"
|
|
echo "Bar position set to $position"
|
|
}
|
|
|
|
# The bar background is opaque, glassy, or none. A shell.json from before it
|
|
# had one says transparent instead, which is none, and setting a background
|
|
# drops it.
|
|
current_background() {
|
|
jq -r '.bar.background // (if .bar.transparent == true then "none" else "opaque" end)' "$(source_file)"
|
|
}
|
|
|
|
cmd_background() {
|
|
if (( $# == 0 )); then
|
|
current_background
|
|
return
|
|
fi
|
|
|
|
local background="$1"
|
|
(( $# == 1 )) || fail "background takes a single value"
|
|
[[ $background =~ ^(opaque|glassy|none|cycle)$ ]] || fail "background must be opaque, glassy, none, or cycle"
|
|
|
|
if [[ $background == "cycle" ]]; then
|
|
# Each step changes the bar by one degree: clear, then glassy, then opaque.
|
|
case $(current_background) in
|
|
none) background="glassy" ;;
|
|
glassy) background="opaque" ;;
|
|
*) background="none" ;;
|
|
esac
|
|
fi
|
|
|
|
local previous
|
|
previous=$(current_background)
|
|
commit "$NORMALIZE | .bar.background = \$background | del(.bar.transparent)" --arg background "$background"
|
|
if ! sync_glass; then
|
|
commit "$NORMALIZE | .bar.background = \$background | del(.bar.transparent)" --arg background "$previous"
|
|
fail "could not change the blur behind the bar"
|
|
fi
|
|
echo "Bar background set to $background"
|
|
}
|
|
|
|
# The older on/off form of the background, kept for scripts and dotfiles:
|
|
# transparent is none, and not transparent is opaque.
|
|
cmd_transparent() {
|
|
local transparent="${1:-}"
|
|
[[ -n $transparent ]] || fail "transparent is required"
|
|
(( $# == 1 )) || fail "transparent takes a single value"
|
|
[[ $transparent =~ ^(true|false|toggle)$ ]] || fail "transparent must be true, false, or toggle"
|
|
if [[ $transparent == "toggle" ]]; then
|
|
[[ $(current_background) == "none" ]] && transparent="false" || transparent="true"
|
|
fi
|
|
if [[ $transparent == "true" ]]; then
|
|
cmd_background none
|
|
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() {
|
|
local catalog
|
|
catalog=$(omarchy-plugin-catalog 2>/dev/null) || {
|
|
echo "center"
|
|
return 0
|
|
}
|
|
jq -r --arg id "$1" '
|
|
map(select(.id == $id))[0].barWidget.defaultSection // "center"
|
|
| if IN("left", "center", "right") then . else "center" end
|
|
' <<<"$catalog"
|
|
}
|
|
|
|
# Asks the shell to place a widget, waiting out one still coming up. Answers 0
|
|
# with the reply in PUT_RESULT, 1 when there was no shell to ask. Only an
|
|
# absent shell is carried on from: omarchy-migrate marks a 0 return as done.
|
|
PUT_RESULT=""
|
|
SHELL_ANSWERED=0
|
|
ask_to_put() {
|
|
local id="$1" placement="$2" attempt absent=0
|
|
for (( attempt = 0; attempt < ${OMARCHY_SHELL_READY_ATTEMPTS:-50}; attempt++ )); do
|
|
if PUT_RESULT=$(omarchy-shell shell putBarWidget "$id" "$placement" 2>&1); then
|
|
SHELL_ANSWERED=1
|
|
[[ $PUT_RESULT == "not ready" ]] || return 0
|
|
elif [[ $PUT_RESULT == *"not ready"* ]]; then
|
|
SHELL_ANSWERED=1
|
|
elif [[ $PUT_RESULT == *"is not running"* ]]; then
|
|
# Answered once and now gone: it stopped mid-request.
|
|
if (( SHELL_ANSWERED )); then
|
|
fail "omarchy-shell did not become ready; $id was not put on the bar"
|
|
fi
|
|
# A shell being spawned has no socket yet, and nothing says a launch is
|
|
# under way, so give one a few seconds to turn up.
|
|
if (( ++absent >= ${OMARCHY_SHELL_ABSENT_ATTEMPTS:-30} )); then
|
|
echo "omarchy-shell is not running; $id was not put on the bar" >&2
|
|
return 1
|
|
fi
|
|
else
|
|
fail "could not put $id on the bar: $PUT_RESULT"
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
fail "omarchy-shell did not become ready; $id was not put on the bar"
|
|
}
|
|
|
|
# Placement lives in the shell, which owns the config it has in memory. Putting
|
|
# a widget therefore asks the shell rather than editing the file behind it.
|
|
cmd_put() {
|
|
local id="${1:-}"
|
|
[[ -n $id ]] || fail "put requires a widget id"
|
|
shift
|
|
|
|
local positional_section=""
|
|
if (( $# > 0 )) && [[ $1 != --* ]]; then
|
|
positional_section="$1"
|
|
validate_section "$positional_section"
|
|
shift
|
|
fi
|
|
|
|
parse_placement "$@"
|
|
[[ -z $positional_section || -z $PLACEMENT_SECTION ]] ||
|
|
fail "specify a section positionally or with --section, not both"
|
|
[[ -z $positional_section ]] || PLACEMENT_SECTION="$positional_section"
|
|
[[ -z $PLACEMENT_FROM_SECTION && -z $PLACEMENT_FROM_INDEX ]] ||
|
|
fail "put does not accept --from-section or --from-index"
|
|
|
|
local placement
|
|
placement=$(placement_json)
|
|
ask_to_put "$id" "$placement" || return 0
|
|
|
|
# An update runs migrations before it restarts the shell, so this one can
|
|
# predate the fallback. Ask it again without the neighbour it cannot find.
|
|
if [[ $PUT_RESULT == "could not find target widget"* ]]; then
|
|
PLACEMENT_BEFORE=""
|
|
PLACEMENT_AFTER=""
|
|
ask_to_put "$id" "$(placement_json)" || return 0
|
|
fi
|
|
|
|
[[ $PUT_RESULT != "unknown" ]] || fail "$id is not a known widget; run 'omarchy plugin list'"
|
|
[[ $PUT_RESULT == "ok" ]] || fail "$PUT_RESULT"
|
|
# Says nothing about whether it had to be placed: a widget already on the bar
|
|
# is left where it is, and both outcomes are the same answer to the caller.
|
|
echo "$id is on the bar"
|
|
}
|
|
|
|
cmd_move() {
|
|
local id="${1:-}"
|
|
[[ -n $id ]] || fail "move requires a widget id"
|
|
shift
|
|
|
|
local positional_section=""
|
|
if (( $# > 0 )) && [[ $1 != --* ]]; then
|
|
positional_section="$1"
|
|
validate_section "$positional_section"
|
|
shift
|
|
fi
|
|
|
|
parse_placement "$@"
|
|
[[ -z $positional_section || -z $PLACEMENT_SECTION ]] ||
|
|
fail "specify a section positionally or with --section, not both"
|
|
[[ -z $positional_section || -z $PLACEMENT_INDEX ]] ||
|
|
fail "specify a section positionally or use --index, not both"
|
|
[[ -z $positional_section || -z $PLACEMENT_BEFORE ]] ||
|
|
fail "specify a section positionally or use --before, not both"
|
|
[[ -z $positional_section || -z $PLACEMENT_AFTER ]] ||
|
|
fail "specify a section positionally or use --after, not both"
|
|
[[ -z $positional_section ]] || PLACEMENT_SECTION="$positional_section"
|
|
|
|
local result
|
|
result=$(omarchy-shell shell moveBarWidget "$id" "$(placement_json)")
|
|
[[ $result == "ok" ]] || fail "$result"
|
|
echo "Moved $id"
|
|
}
|
|
|
|
cmd_set() {
|
|
local id="${1:-}"
|
|
local key="${2:-}"
|
|
local value="${3:-}"
|
|
[[ -n $id ]] || fail "set requires a widget id"
|
|
[[ -n $key ]] || fail "set requires a setting key"
|
|
(( $# >= 3 )) || fail "set requires a value"
|
|
shift 3
|
|
|
|
local value_is_json="false"
|
|
if (( $# > 0 )) && [[ $1 == "--json" ]]; then
|
|
value_is_json="true"
|
|
shift
|
|
fi
|
|
parse_placement "$@"
|
|
[[ -z $PLACEMENT_BEFORE && -z $PLACEMENT_AFTER ]] ||
|
|
fail "set does not accept --before or --after"
|
|
|
|
local value_json
|
|
if [[ $value_is_json == "true" ]]; then
|
|
value_json=$(jq -cn --argjson value "$value" '$value') ||
|
|
fail "invalid JSON value: $value"
|
|
else
|
|
value_json=$(jq -cn --arg value "$value" '$value')
|
|
fi
|
|
|
|
local result
|
|
result=$(omarchy-shell shell setBarWidget "$id" "$key" "$value_json" "$(placement_json)")
|
|
[[ $result == "ok" ]] || fail "$result"
|
|
echo "Set $key on $id"
|
|
}
|
|
|
|
# Prints the option from the first instance of the widget on the bar, raw for a
|
|
# string and as JSON otherwise, and fails when no instance sets it.
|
|
cmd_get() {
|
|
local id="${1:-}"
|
|
local key="${2:-}"
|
|
[[ -n $id ]] || fail "get requires a widget id"
|
|
[[ -n $key ]] || fail "get requires a setting key"
|
|
(( $# == 2 )) || fail "get takes a widget id and a setting key"
|
|
|
|
jq -e -r --arg id "$id" --arg key "$key" '
|
|
first(.bar.layout[]?[]? | select(.id == $id) | .[$key] | select(. != null))
|
|
| if type == "string" then . else tojson end
|
|
' "$(source_file)" 2>/dev/null || exit 1
|
|
}
|
|
|
|
# --------------------------------------------------------------------- dispatch
|
|
|
|
command="${1:-}"
|
|
(( $# > 0 )) && shift || true
|
|
|
|
case "$command" in
|
|
use)
|
|
cmd_use "$@"
|
|
;;
|
|
reset)
|
|
(( $# == 0 )) || fail "reset does not take arguments"
|
|
cmd_use omarchy.bar
|
|
;;
|
|
defaults)
|
|
cmd_defaults "$@"
|
|
;;
|
|
position)
|
|
cmd_position "$@"
|
|
;;
|
|
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 "$@"
|
|
;;
|
|
move)
|
|
cmd_move "$@"
|
|
;;
|
|
set)
|
|
cmd_set "$@"
|
|
;;
|
|
get)
|
|
cmd_get "$@"
|
|
;;
|
|
-h | --help | help | "")
|
|
usage
|
|
;;
|
|
*)
|
|
fail "unknown command: $command"
|
|
;;
|
|
esac
|