Simplify bar plugin management (#6435)
This commit is contained in:
+203
-17
@@ -1,9 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Set the active bar option, position, and transparency
|
||||
# omarchy:summary=Configure the bar and its widget layout
|
||||
# omarchy:group=bar
|
||||
# omarchy:args=use <id> | reset | defaults | position <top|bottom|left|right> | transparent <true|false|toggle>
|
||||
# omarchy:examples=omarchy bar use local.neon-bar | omarchy bar reset | omarchy bar defaults | omarchy bar position top | omarchy bar transparent true
|
||||
# omarchy:args=use <id> | reset | defaults | position <top|bottom|left|right> | transparent <true|false|toggle> | move <id> [placement] | set <id> <key> <value> [--json] [placement]
|
||||
# omarchy:examples=omarchy bar use local.neon-bar | omarchy bar move omarchy.clock --section center --index 0 | omarchy bar set omarchy.clock format HH:mm
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -18,15 +18,26 @@ Usage: omarchy bar <command> [args...]
|
||||
defaults Restore the default bar and service widgets
|
||||
position <top|bottom|left|right> Bar position
|
||||
transparent <true|false|toggle> Bar transparency
|
||||
move <id> [placement] Move a widget within or between sections
|
||||
set <id> <key> <value> [--json] [placement]
|
||||
Set a per-widget option
|
||||
|
||||
Bar widgets are added, moved, removed, and configured with 'omarchy bar plugin'.
|
||||
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'.
|
||||
|
||||
Examples:
|
||||
omarchy bar use local.neon-bar
|
||||
omarchy bar reset
|
||||
omarchy bar defaults
|
||||
omarchy bar position top
|
||||
omarchy bar transparent true
|
||||
omarchy bar move omarchy.media left
|
||||
omarchy bar move omarchy.clock --section center --index 0
|
||||
omarchy bar set omarchy.clock format HH:mm
|
||||
USAGE
|
||||
}
|
||||
|
||||
@@ -38,6 +49,87 @@ bar_option_exists() {
|
||||
' >/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() {
|
||||
@@ -59,15 +151,48 @@ cmd_use() {
|
||||
|
||||
cmd_defaults() {
|
||||
(( $# == 0 )) || fail "defaults does not take arguments"
|
||||
# Restore the whole default bar, then re-add widgets for services the user
|
||||
# actually has installed (they aren't in the shipped default layout).
|
||||
commit "$NORMALIZE | .bar = \$defaults[0].bar" --slurpfile defaults "$DEFAULTS_FILE"
|
||||
local service
|
||||
|
||||
local catalog
|
||||
local optional_widgets="[]"
|
||||
local service widget
|
||||
catalog=$(omarchy-plugin-catalog)
|
||||
for service in dropbox tailscale; do
|
||||
if "omarchy-installed-service-$service"; then
|
||||
omarchy-bar-plugin add "omarchy.$service"
|
||||
widget=$(jq -c --arg id "omarchy.$service" '
|
||||
map(select(.id == $id))[0] as $plugin
|
||||
| {
|
||||
id: $id,
|
||||
section: (
|
||||
$plugin.barWidget.defaultSection // "center"
|
||||
| if IN("left", "center", "right") then . else "center" end
|
||||
)
|
||||
}
|
||||
' <<<"$catalog")
|
||||
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"
|
||||
echo "Restored the default Omarchy bar"
|
||||
}
|
||||
|
||||
@@ -94,6 +219,67 @@ cmd_transparent() {
|
||||
fi
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- dispatch
|
||||
|
||||
command="${1:-}"
|
||||
@@ -116,11 +302,11 @@ case "$command" in
|
||||
transparent)
|
||||
cmd_transparent "$@"
|
||||
;;
|
||||
plugin)
|
||||
exec omarchy-bar-plugin "$@"
|
||||
move)
|
||||
cmd_move "$@"
|
||||
;;
|
||||
add | move | remove | rm | drop | set | replace)
|
||||
fail "bar widgets are managed with: omarchy bar plugin $command ..."
|
||||
set)
|
||||
cmd_set "$@"
|
||||
;;
|
||||
-h | --help | help | "")
|
||||
usage
|
||||
|
||||
@@ -1,503 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Add, move, remove, and configure bar plugin widgets in the layout
|
||||
# omarchy:group=bar
|
||||
# omarchy:args=add <id> [placement] | move <id> [placement] | remove <id> [placement] | set <id> <key> <value> [--json] [placement] | replace <old-id> <new-id>
|
||||
# omarchy:examples=omarchy bar plugin add omarchy.tailscale | omarchy bar plugin add omarchy.clock center | omarchy bar plugin move omarchy.media left | omarchy bar plugin move omarchy.clock --section center --index 0 | omarchy bar plugin remove omarchy.tailscale | omarchy bar plugin set omarchy.clock format HH:mm
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source omarchy-shell-config
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: omarchy bar plugin <command> [args...]
|
||||
|
||||
add <id> [placement] Add a bar widget
|
||||
move <id> [placement] [--from-section S --from-index N]
|
||||
Move a widget within/between sections
|
||||
remove <id> [placement] (alias: rm, drop)
|
||||
Remove a widget
|
||||
set <id> <key> <value> [--json] [placement]
|
||||
Set a per-widget option
|
||||
replace <old-id> <new-id> Replace the first instance of a widget id
|
||||
|
||||
Placement (for add/move/remove/set):
|
||||
--section <left|center|right> Target section
|
||||
--index <n> Insert/remove at index in the target section
|
||||
--before <id> Insert before the first matching widget
|
||||
--after <id> Insert after the first matching widget
|
||||
--from-section <section> Source section (move/remove/set)
|
||||
--from-index <n> Source index (move/remove/set)
|
||||
--duplicate add: allow a second instance of the widget
|
||||
--all remove: remove every matching instance
|
||||
|
||||
Without placement flags, 'add' inserts after the section anchor (workspaces on
|
||||
left, weather on center, tray on right) and dedupes across sections; 'move'
|
||||
preserves the source section; 'remove' drops the first match (or every match
|
||||
with --all).
|
||||
|
||||
Widget ids are listed by 'omarchy plugin list'.
|
||||
|
||||
Examples:
|
||||
omarchy bar plugin add omarchy.tailscale
|
||||
omarchy bar plugin add omarchy.clock center
|
||||
omarchy bar plugin move omarchy.media left
|
||||
omarchy bar plugin move omarchy.clock --section center --index 0
|
||||
omarchy bar plugin remove omarchy.tailscale
|
||||
omarchy bar plugin remove omarchy.tailscale --all
|
||||
omarchy bar plugin set omarchy.clock format HH:mm
|
||||
omarchy bar plugin set omarchy.indicators items '["Dnd","NightLight"]' --json
|
||||
USAGE
|
||||
}
|
||||
|
||||
# jq defs used by the mutation pipelines. All `def`s come first so the pipeline
|
||||
# that follows them stays valid jq; NORMALIZE adds its own shape helpers.
|
||||
JQ_DEFS='
|
||||
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];
|
||||
def find_all($id; $only):
|
||||
[ ["left","center","right"][] as $section
|
||||
| ((.bar.layout[$section] // []) | to_entries[])
|
||||
| select((($only == "" or $section == $only)) and ((.value | entry_id) == $id))
|
||||
| {section: $section, index: .key, entry: .value} ];
|
||||
def entry_at($section; $index):
|
||||
if $index < 0 or $index >= (.bar.layout[$section] | length) then
|
||||
error("no widget at " + $section + "[" + ($index | tostring) + "]")
|
||||
else .bar.layout[$section][$index] end;
|
||||
def resolve_target($defaultSection):
|
||||
. as $root
|
||||
| (if $before != "" or $after != "" then
|
||||
($root | find_all($before + $after; $targetSection) | .[0]
|
||||
// error("could not find target widget " + ($before + $after) + ($targetSection | if . == "" then "" else " in " + . end)))
|
||||
| .section as $section | (.index + (if $after == "" then 0 else 1 end)) as $index
|
||||
| {section: $section, index: $index, anchor: false}
|
||||
elif $targetSection != "" or $targetIndex != "" then
|
||||
{ section: ($targetSection | if . == "" then $defaultSection else . end),
|
||||
index: (if $targetIndex == "" then null else ($targetIndex | tonumber) end),
|
||||
anchor: false }
|
||||
else
|
||||
{ section: $defaultSection, index: null, anchor: true }
|
||||
end)
|
||||
| .section as $section
|
||||
| .index as $rawIndex
|
||||
| (if $rawIndex == null then
|
||||
if .anchor then
|
||||
($root.bar.layout[$section] | map(entry_id) | index(anchor_for($section))) as $anchorIndex
|
||||
| if $anchorIndex == null then ($root.bar.layout[$section] | length) else ($anchorIndex + 1) end
|
||||
else ($root.bar.layout[$section] | length) end
|
||||
elif $rawIndex > ($root.bar.layout[$section] | length) then ($root.bar.layout[$section] | length)
|
||||
else $rawIndex end) as $index
|
||||
| {section: $section, index: $index};
|
||||
def resolve_source:
|
||||
if $fromIndex != "" then
|
||||
if $fromSection == "" then error("--from-index requires --from-section")
|
||||
else
|
||||
($fromIndex | tonumber) as $index
|
||||
| entry_at($fromSection; $index) as $entry
|
||||
| if $widgetId != "" and ($entry | entry_id) != $widgetId then
|
||||
error("widget at " + $fromSection + "[" + ($index | tostring) + "] is not " + $widgetId)
|
||||
else {section: $fromSection, index: $index, entry: $entry} end
|
||||
end
|
||||
else
|
||||
(find_all($widgetId; $fromSection)
|
||||
| if length == 0 then
|
||||
error("could not find widget " + $widgetId + ($fromSection | if . == "" then "" else " in " + . end))
|
||||
else .[0] end)
|
||||
end;
|
||||
'
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
widget_is_known() {
|
||||
omarchy-plugin-catalog | jq -e --arg id "$1" '
|
||||
any(.[]; (.kinds | index("bar-widget")) and .id == $id)
|
||||
' >/dev/null
|
||||
}
|
||||
|
||||
widget_default_section() {
|
||||
omarchy-plugin-catalog | jq -r --arg id "$1" '
|
||||
(map(select(.id == $id))[0].barWidget.defaultSection // "center") as $section
|
||||
| if ["left", "center", "right"] | index($section) then $section else "center" end
|
||||
'
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- placement
|
||||
|
||||
# Parse placement flags shared by add/move/remove/set. Sets the globals below.
|
||||
PLACEMENT_SECTION=""
|
||||
PLACEMENT_INDEX=""
|
||||
PLACEMENT_BEFORE=""
|
||||
PLACEMENT_AFTER=""
|
||||
PLACEMENT_FROM_SECTION=""
|
||||
PLACEMENT_FROM_INDEX=""
|
||||
PLACEMENT_DUPLICATE="false"
|
||||
PLACEMENT_ALL="false"
|
||||
|
||||
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
|
||||
;;
|
||||
--duplicate)
|
||||
PLACEMENT_DUPLICATE="true"
|
||||
shift
|
||||
;;
|
||||
--all)
|
||||
PLACEMENT_ALL="true"
|
||||
shift
|
||||
;;
|
||||
-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"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- commands
|
||||
|
||||
cmd_add() {
|
||||
local id="${1:-}"
|
||||
[[ -n $id ]] || fail "add requires a widget id"
|
||||
shift
|
||||
|
||||
local positional_section=""
|
||||
while (( $# > 0 )); do
|
||||
if [[ $1 == --* ]]; then
|
||||
break
|
||||
fi
|
||||
[[ -z $positional_section ]] || fail "unexpected argument: $1"
|
||||
positional_section="$1"
|
||||
shift
|
||||
done
|
||||
|
||||
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_DUPLICATE == false ) ]] || fail "--duplicate needs explicit placement flags"
|
||||
if [[ -n $positional_section ]]; then
|
||||
validate_section "$positional_section"
|
||||
PLACEMENT_SECTION="$positional_section"
|
||||
fi
|
||||
|
||||
widget_is_known "$id" || fail "$id is not a known bar widget; run 'omarchy plugin list' to see valid ids"
|
||||
|
||||
local default_section="${PLACEMENT_SECTION:-$(widget_default_section "$id")}"
|
||||
local explicit="false"
|
||||
if [[ -n $PLACEMENT_INDEX || -n $PLACEMENT_BEFORE || -n $PLACEMENT_AFTER || $PLACEMENT_DUPLICATE == true ]]; then
|
||||
explicit="true"
|
||||
fi
|
||||
|
||||
if [[ $explicit == "false" ]]; then
|
||||
# Anchor-based dedupe insert: remove any existing instance across sections,
|
||||
# then insert after the section anchor (workspaces/weather/tray) — or append
|
||||
# when the anchor is absent.
|
||||
commit "$JQ_DEFS $NORMALIZE
|
||||
| .bar.layout.left = (.bar.layout.left | map(select(entry_id != \$id)))
|
||||
| .bar.layout.center = (.bar.layout.center | map(select(entry_id != \$id)))
|
||||
| .bar.layout.right = (.bar.layout.right | map(select(entry_id != \$id)))
|
||||
| (.bar.layout[\$section] | map(entry_id) | index(anchor_for(\$section))) as \$anchorIndex
|
||||
| .bar.layout[\$section] = (
|
||||
if \$anchorIndex == null then .bar.layout[\$section] + [{id: \$id}]
|
||||
else .bar.layout[\$section][0:\$anchorIndex+1] + [{id: \$id}] + .bar.layout[\$section][\$anchorIndex+1:] end)
|
||||
" \
|
||||
--arg id "$id" \
|
||||
--arg section "$default_section"
|
||||
else
|
||||
local prog
|
||||
prog=$(cat <<JQ
|
||||
$JQ_DEFS $NORMALIZE
|
||||
| (.bar.layout.left + .bar.layout.center + .bar.layout.right | map(entry_id) | index(\$id)) as \$existing
|
||||
| if \$existing != null and \$allowDuplicate != "true" then
|
||||
error(\$id + " is already in the bar; use 'omarchy bar plugin move' or pass --duplicate")
|
||||
else . end
|
||||
| resolve_target(\$defaultSection) as \$target
|
||||
| .bar.layout[\$target.section] = (.bar.layout[\$target.section][0:\$target.index] + [{id: \$id}] + .bar.layout[\$target.section][\$target.index:])
|
||||
JQ
|
||||
)
|
||||
commit "$prog" \
|
||||
--arg id "$id" \
|
||||
--arg defaultSection "$default_section" \
|
||||
--arg targetSection "$PLACEMENT_SECTION" \
|
||||
--arg targetIndex "$PLACEMENT_INDEX" \
|
||||
--arg before "$PLACEMENT_BEFORE" \
|
||||
--arg after "$PLACEMENT_AFTER" \
|
||||
--arg allowDuplicate "$PLACEMENT_DUPLICATE"
|
||||
fi
|
||||
echo "Added $id to the bar"
|
||||
}
|
||||
|
||||
cmd_move() {
|
||||
local id="${1:-}"
|
||||
[[ -n $id ]] || fail "move requires a widget id"
|
||||
shift
|
||||
|
||||
local positional_section=""
|
||||
while (( $# > 0 )); do
|
||||
if [[ $1 == --* ]]; then
|
||||
break
|
||||
fi
|
||||
[[ -z $positional_section ]] || fail "unexpected argument: $1"
|
||||
positional_section="$1"
|
||||
shift
|
||||
done
|
||||
|
||||
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"
|
||||
if [[ -n $positional_section ]]; then
|
||||
validate_section "$positional_section"
|
||||
PLACEMENT_SECTION="$positional_section"
|
||||
fi
|
||||
|
||||
local default_section="${PLACEMENT_SECTION:-}"
|
||||
|
||||
# A bare section names the section, not the slot, so let it fall through to
|
||||
# the same anchor placement 'add' uses. Passing it as an explicit target
|
||||
# instead drops the widget on the far end of the row.
|
||||
local target_section="$PLACEMENT_SECTION"
|
||||
if [[ -z $PLACEMENT_INDEX && -z $PLACEMENT_BEFORE && -z $PLACEMENT_AFTER ]]; then
|
||||
target_section=""
|
||||
fi
|
||||
|
||||
local prog
|
||||
prog=$(cat <<JQ
|
||||
$JQ_DEFS $NORMALIZE
|
||||
| resolve_source as \$source
|
||||
| .bar.layout[\$source.section] = (.bar.layout[\$source.section][0:\$source.index] + .bar.layout[\$source.section][\$source.index+1:])
|
||||
| (\$defaultSection | if . == "" then \$source.section else . end) as \$fallback
|
||||
| resolve_target(\$fallback) as \$target
|
||||
| (\$source.entry | if type == "object" then .id = \$id else . end) as \$entry
|
||||
| .bar.layout[\$target.section] = (.bar.layout[\$target.section][0:\$target.index] + [\$entry] + .bar.layout[\$target.section][\$target.index:])
|
||||
JQ
|
||||
)
|
||||
commit "$prog" \
|
||||
--arg id "$id" \
|
||||
--arg defaultSection "$default_section" \
|
||||
--arg targetSection "$target_section" \
|
||||
--arg targetIndex "$PLACEMENT_INDEX" \
|
||||
--arg before "$PLACEMENT_BEFORE" \
|
||||
--arg after "$PLACEMENT_AFTER" \
|
||||
--arg fromSection "$PLACEMENT_FROM_SECTION" \
|
||||
--arg fromIndex "$PLACEMENT_FROM_INDEX" \
|
||||
--arg widgetId "$id"
|
||||
echo "Moved $id"
|
||||
}
|
||||
|
||||
cmd_remove() {
|
||||
local id=""
|
||||
if (( $# > 0 )) && [[ $1 != --* ]]; then
|
||||
id="$1"
|
||||
shift
|
||||
fi
|
||||
parse_placement "$@"
|
||||
|
||||
local prog
|
||||
prog=$(cat <<JQ
|
||||
$JQ_DEFS $NORMALIZE
|
||||
| if \$fromIndex != "" then
|
||||
resolve_source as \$source
|
||||
| .bar.layout[\$source.section] = (.bar.layout[\$source.section][0:\$source.index] + .bar.layout[\$source.section][\$source.index+1:])
|
||||
elif \$targetIndex != "" then
|
||||
if \$targetSection == "" then error("--index requires --section for remove") else . end
|
||||
| (\$targetIndex | tonumber) as \$index
|
||||
| .bar.layout[\$targetSection][\$index] as \$entry
|
||||
| if \$widgetId != "" and (\$entry | entry_id) != \$widgetId then
|
||||
error("widget at " + \$targetSection + "[" + (\$index | tostring) + "] is not " + \$widgetId)
|
||||
else . end
|
||||
| .bar.layout[\$targetSection] = (.bar.layout[\$targetSection][0:\$index] + .bar.layout[\$targetSection][\$index+1:])
|
||||
else
|
||||
find_all(\$widgetId; \$targetSection) as \$matches
|
||||
| if (\$matches | length) == 0 then
|
||||
error("could not find widget " + \$widgetId + (\$targetSection | if . == "" then "" else " in " + . end))
|
||||
elif \$removeAll != "true" then
|
||||
(\$matches | sort_by(.section) | reverse | .[0]) as \$first
|
||||
| .bar.layout[\$first.section] = (.bar.layout[\$first.section][0:\$first.index] + .bar.layout[\$first.section][\$first.index+1:])
|
||||
else
|
||||
reduce (\$matches | sort_by(.section) | reverse | .[]) as \$m (.;
|
||||
.bar.layout[\$m.section] = (.bar.layout[\$m.section][0:\$m.index] + .bar.layout[\$m.section][\$m.index+1:]))
|
||||
end
|
||||
end
|
||||
JQ
|
||||
)
|
||||
commit "$prog" \
|
||||
--arg widgetId "$id" \
|
||||
--arg targetSection "$PLACEMENT_SECTION" \
|
||||
--arg targetIndex "$PLACEMENT_INDEX" \
|
||||
--arg fromSection "$PLACEMENT_FROM_SECTION" \
|
||||
--arg fromIndex "$PLACEMENT_FROM_INDEX" \
|
||||
--arg removeAll "$PLACEMENT_ALL"
|
||||
if [[ -n $id ]]; then
|
||||
echo "Removed $id from the bar"
|
||||
else
|
||||
echo "Removed bar widget"
|
||||
fi
|
||||
}
|
||||
|
||||
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"
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--json) value_is_json="true" ;;
|
||||
-h | --help) usage; exit 0 ;;
|
||||
*) break ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
parse_placement "$@"
|
||||
|
||||
if [[ $value_is_json == "true" ]]; then
|
||||
jq -n --argjson value "$value" empty >/dev/null 2>&1 || fail "invalid JSON value: $value"
|
||||
fi
|
||||
|
||||
local value_arg
|
||||
if [[ $value_is_json == "true" ]]; then
|
||||
value_arg=(--argjson value "$value")
|
||||
else
|
||||
value_arg=(--arg value "$value")
|
||||
fi
|
||||
|
||||
local prog
|
||||
prog=$(cat <<JQ
|
||||
$JQ_DEFS $NORMALIZE
|
||||
| if \$fromIndex != "" then
|
||||
resolve_source as \$source
|
||||
| .bar.layout[\$source.section][\$source.index] = (.bar.layout[\$source.section][\$source.index] | if type == "object" then .[\$key] = \$value else error("widget entry must be an object") end)
|
||||
elif \$targetIndex != "" then
|
||||
if \$targetSection == "" then error("--index requires --section for set") else . end
|
||||
| (\$targetIndex | tonumber) as \$index
|
||||
| .bar.layout[\$targetSection][\$index] as \$entry
|
||||
| if (\$entry | entry_id) != \$widgetId then
|
||||
error("widget at " + \$targetSection + "[" + (\$index | tostring) + "] is not " + \$widgetId)
|
||||
elif \$entry | type != "object" then
|
||||
error("widget entry must be an object")
|
||||
else . end
|
||||
| .bar.layout[\$targetSection][\$index][\$key] = \$value
|
||||
else
|
||||
find_all(\$widgetId; \$targetSection) as \$matches
|
||||
| if (\$matches | length) == 0 then
|
||||
error("could not find widget " + \$widgetId + (\$targetSection | if . == "" then "" else " in " + . end))
|
||||
else
|
||||
(\$matches | sort_by(.section) | .[0]) as \$first
|
||||
| if \$first.entry | type != "object" then error("widget entry must be an object") else . end
|
||||
| .bar.layout[\$first.section][\$first.index][\$key] = \$value
|
||||
end
|
||||
end
|
||||
JQ
|
||||
)
|
||||
commit "$prog" \
|
||||
--arg widgetId "$id" \
|
||||
--arg key "$key" \
|
||||
"${value_arg[@]}" \
|
||||
--arg targetSection "$PLACEMENT_SECTION" \
|
||||
--arg targetIndex "$PLACEMENT_INDEX" \
|
||||
--arg fromSection "$PLACEMENT_FROM_SECTION" \
|
||||
--arg fromIndex "$PLACEMENT_FROM_INDEX"
|
||||
echo "Set $key on $id"
|
||||
}
|
||||
|
||||
cmd_replace() {
|
||||
local old="${1:-}"
|
||||
local new="${2:-}"
|
||||
[[ -n $old ]] || fail "replace requires the source widget id"
|
||||
[[ -n $new ]] || fail "replace requires the replacement widget id"
|
||||
(( $# == 2 )) || fail "replace takes two widget ids"
|
||||
|
||||
local prog
|
||||
prog=$(cat <<JQ
|
||||
$JQ_DEFS $NORMALIZE
|
||||
| resolve_source as \$source
|
||||
| .bar.layout[\$source.section][\$source.index] = (
|
||||
\$source.entry | if type == "object" then .id = \$new else { id: \$new } end
|
||||
)
|
||||
JQ
|
||||
)
|
||||
# resolve_source finds by widget id when no placement is given; replace
|
||||
# takes no placement flags, so pass empty placement explicitly.
|
||||
commit "$prog" \
|
||||
--arg widgetId "$old" \
|
||||
--arg new "$new" \
|
||||
--arg fromSection "" \
|
||||
--arg fromIndex ""
|
||||
echo "Replaced $old with $new"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- dispatch
|
||||
|
||||
command="${1:-}"
|
||||
(( $# > 0 )) && shift || true
|
||||
|
||||
case "$command" in
|
||||
add)
|
||||
cmd_add "$@"
|
||||
;;
|
||||
move)
|
||||
cmd_move "$@"
|
||||
;;
|
||||
remove | rm | drop)
|
||||
cmd_remove "$@"
|
||||
;;
|
||||
set)
|
||||
cmd_set "$@"
|
||||
;;
|
||||
replace)
|
||||
cmd_replace "$@"
|
||||
;;
|
||||
-h | --help | help | "")
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
fail "unknown command: $command"
|
||||
;;
|
||||
esac
|
||||
@@ -6,7 +6,7 @@ echo "Installing all dependencies..."
|
||||
omarchy-pkg-add dropbox dropbox-cli libappindicator-gtk3 python-gpgme nautilus-dropbox
|
||||
|
||||
echo "Adding Dropbox to the bar..."
|
||||
omarchy-bar-plugin add omarchy.dropbox
|
||||
omarchy-plugin-enable omarchy.dropbox
|
||||
|
||||
echo "Starting Dropbox..."
|
||||
uwsm-app -- dropbox-cli start &>/dev/null &
|
||||
|
||||
@@ -17,6 +17,6 @@ echo -e "\nReceiving Taildrop files in $HOME/Downloads..."
|
||||
systemctl --user enable --now omarchy-tailscale-receive.service
|
||||
|
||||
echo -e "\nAdding Tailscale to the bar..."
|
||||
omarchy-bar-plugin add omarchy.tailscale
|
||||
omarchy-plugin-enable omarchy.tailscale
|
||||
|
||||
omarchy-webapp-install "Tailscale" "https://login.tailscale.com/admin/machines" https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/tailscale-light.png
|
||||
|
||||
+12
-14
@@ -33,7 +33,9 @@ confirm() {
|
||||
fi
|
||||
}
|
||||
|
||||
place_bar_widget() {
|
||||
ENABLE_PLACEMENT=()
|
||||
|
||||
select_bar_widget_placement() {
|
||||
local id="$1"
|
||||
local section
|
||||
local default_section
|
||||
@@ -47,8 +49,7 @@ place_bar_widget() {
|
||||
section=$(printf '%s\n' left center right |
|
||||
gum choose --header="Place $id in which bar section?" --selected "$default_section") || return 0
|
||||
[[ -n $section ]] || return 0
|
||||
omarchy-bar-plugin move "$id" --section "$section" >/dev/null &&
|
||||
echo "Placed $id in the $section section"
|
||||
ENABLE_PLACEMENT=(--section "$section")
|
||||
}
|
||||
|
||||
plugin_id_manifest() {
|
||||
@@ -151,20 +152,17 @@ if [[ -z $enable_after ]]; then
|
||||
fi
|
||||
|
||||
if [[ $enable_after == true ]]; then
|
||||
select_bar_widget_placement "$id"
|
||||
discovered=0
|
||||
for (( attempt = 0; attempt < 40; attempt++ )); do
|
||||
result=$(omarchy-shell shell setPluginEnabled "$id" true)
|
||||
[[ $result == "ok" ]] && break
|
||||
if omarchy-plugin-list --json | jq -e --arg id "$id" 'any(.[]; .id == $id)' >/dev/null; then
|
||||
discovered=1
|
||||
break
|
||||
fi
|
||||
sleep 0.05
|
||||
done
|
||||
[[ $result == "ok" ]] || fail "plugin '$id' is not known"
|
||||
if omarchy-plugin-catalog | jq -e --arg id "$id" '
|
||||
any(.[]; .id == $id and (.kinds | index("bar")))
|
||||
' >/dev/null; then
|
||||
echo "Now using $id as the bar"
|
||||
else
|
||||
echo "Enabled $id"
|
||||
fi
|
||||
place_bar_widget "$id"
|
||||
(( discovered )) || fail "plugin '$id' is not known"
|
||||
omarchy-plugin-enable "$id" "${ENABLE_PLACEMENT[@]}"
|
||||
else
|
||||
echo "Enable it later with: omarchy plugin enable $id"
|
||||
fi
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
# Walks $OMARCHY_PATH/shell/plugins and ~/.config/omarchy/plugins, reads every
|
||||
# manifest.json / *.manifest.json, and emits one JSON object per plugin with
|
||||
# computed fields (sourceDir, barWidgetPath, barPath, firstParty). This is the
|
||||
# single source of truth that omarchy-bar (widget/option enumeration),
|
||||
# omarchy-bar-plugin (add validation), and omarchy-plugin-clone (source
|
||||
# enumeration) read, so the bar and plugin commands never re-implement
|
||||
# manifest walking.
|
||||
# single source of truth used by bar selection and plugin enable/clone, so
|
||||
# those commands never re-implement manifest walking.
|
||||
|
||||
set -o pipefail
|
||||
|
||||
|
||||
@@ -79,36 +79,6 @@ update_manifest() {
|
||||
mv "$manifest.tmp" "$manifest"
|
||||
}
|
||||
|
||||
switch_to_clone() {
|
||||
local source_id="$1"
|
||||
local new_id="$2"
|
||||
local is_bar="$3"
|
||||
local is_bar_widget="$4"
|
||||
local has_non_widget_kind="$5"
|
||||
|
||||
if [[ $is_bar == "true" ]]; then
|
||||
omarchy-bar use "$new_id"
|
||||
elif [[ $is_bar_widget == "true" ]]; then
|
||||
local shell_config
|
||||
shell_config=$(omarchy-shell shell listShellConfig)
|
||||
if jq -e --arg id "$source_id" '
|
||||
[
|
||||
.bar.layout.left[]?,
|
||||
.bar.layout.center[]?,
|
||||
.bar.layout.right[]?
|
||||
] | any(.[]; (if type == "object" then .id else . end) == $id)
|
||||
' <<<"$shell_config" >/dev/null; then
|
||||
omarchy-bar-plugin replace "$source_id" "$new_id"
|
||||
else
|
||||
omarchy-bar-plugin add "$new_id"
|
||||
fi
|
||||
[[ $has_non_widget_kind == "false" ]] || omarchy-plugin-disable "$source_id"
|
||||
else
|
||||
omarchy-plugin-disable "$source_id"
|
||||
omarchy-plugin-enable "$new_id"
|
||||
fi
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: omarchy plugin clone <source-id>
|
||||
@@ -145,14 +115,11 @@ source_info=$(omarchy-plugin-catalog | jq -r --arg id "$source_id" '
|
||||
| [
|
||||
.sourceDir,
|
||||
.manifestPath,
|
||||
(.name // .id),
|
||||
((.kinds | index("bar")) != null),
|
||||
((.kinds | index("bar-widget")) != null),
|
||||
(any(.kinds[]; . != "bar-widget"))
|
||||
(.name // .id)
|
||||
] | @tsv
|
||||
')
|
||||
[[ -n $source_info ]] || fail "unknown built-in plugin: $source_id"
|
||||
IFS=$'\t' read -r source_dir source_manifest source_name is_bar is_bar_widget has_non_widget_kind <<<"$source_info"
|
||||
IFS=$'\t' read -r source_dir source_manifest source_name <<<"$source_info"
|
||||
|
||||
new_id="local.${source_id#omarchy.}"
|
||||
display_name="My $source_name"
|
||||
@@ -182,7 +149,7 @@ for (( attempt = 0; attempt < 40; attempt++ )); do
|
||||
sleep 0.05
|
||||
done
|
||||
(( discovered )) || fail "cloned plugin '$new_id' was not discovered"
|
||||
switch_to_clone "$source_id" "$new_id" "$is_bar" "$is_bar_widget" "$has_non_widget_kind"
|
||||
omarchy-plugin-enable "$new_id" >/dev/null
|
||||
clone_complete=1
|
||||
omarchy-notification-send -g \
|
||||
"Editing Cloned Plugin" \
|
||||
|
||||
@@ -27,12 +27,69 @@ if (( $# > 0 )) && omarchy-plugin-catalog | jq -e --arg id "$id" '
|
||||
fail "'$id' is a bar; it replaces the bar in use rather than taking a place in one"
|
||||
fi
|
||||
|
||||
result=$(omarchy-shell shell setPluginEnabled "$id" true)
|
||||
[[ $result == "ok" ]] ||
|
||||
fail "plugin '$id' is not known; run: omarchy-shell shell rescanPlugins"
|
||||
section=""
|
||||
index=""
|
||||
before=""
|
||||
after=""
|
||||
if (( $# > 0 )) && [[ $1 != --* ]]; then
|
||||
section="$1"
|
||||
shift
|
||||
fi
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--section)
|
||||
[[ -z $section ]] || fail "specify a section positionally or with --section, not both"
|
||||
section="${2:-}"
|
||||
[[ -n $section ]] || fail "--section requires a section"
|
||||
shift 2
|
||||
;;
|
||||
--index)
|
||||
index="${2:-}"
|
||||
[[ -n $index ]] || fail "--index requires an index"
|
||||
shift 2
|
||||
;;
|
||||
--before)
|
||||
before="${2:-}"
|
||||
[[ -n $before ]] || fail "--before requires a widget id"
|
||||
shift 2
|
||||
;;
|
||||
--after)
|
||||
after="${2:-}"
|
||||
[[ -n $after ]] || fail "--after requires a widget id"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
fail "unknown placement option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if (( $# > 0 )); then
|
||||
omarchy-bar-plugin move "$id" "$@"
|
||||
[[ -z $section || $section =~ ^(left|center|right)$ ]] ||
|
||||
fail "section must be left, center, or right"
|
||||
[[ -z $index || $index =~ ^[0-9]+$ ]] ||
|
||||
fail "index must be a non-negative integer"
|
||||
[[ -z $before || -z $after ]] || fail "use only one of --before or --after"
|
||||
|
||||
placement=$(jq -cn \
|
||||
--arg section "$section" \
|
||||
--arg index "$index" \
|
||||
--arg before "$before" \
|
||||
--arg after "$after" '
|
||||
{}
|
||||
+ (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)
|
||||
')
|
||||
|
||||
result=$(omarchy-shell shell enablePlugin "$id" "$placement")
|
||||
if [[ $result == "unknown" ]]; then
|
||||
fail "plugin '$id' is not known; run: omarchy-shell shell rescanPlugins"
|
||||
elif [[ $result != "ok" ]]; then
|
||||
fail "$result"
|
||||
fi
|
||||
|
||||
if [[ $placement != "{}" ]]; then
|
||||
echo "Enabled and moved $id"
|
||||
elif omarchy-plugin-catalog | jq -e --arg id "$id" '
|
||||
any(.[]; .id == $id and (.kinds | index("bar")))
|
||||
|
||||
@@ -80,7 +80,6 @@ if [[ -n $shell_plugins ]]; then
|
||||
fi
|
||||
|
||||
cloned_from=""
|
||||
restored_source=0
|
||||
if [[ -f $target/manifest.json ]]; then
|
||||
cloned_from=$(jq -r '.omarchy.clonedFrom // empty' "$target/manifest.json")
|
||||
fi
|
||||
@@ -93,24 +92,7 @@ else
|
||||
confirm "Remove '$id'? The folder will be backed up." || fail "aborted"
|
||||
fi
|
||||
|
||||
if [[ -n $cloned_from ]]; then
|
||||
is_bar=$(jq -r '(.kinds | index("bar")) != null' "$target/manifest.json")
|
||||
is_bar_widget=$(jq -r '(.kinds | index("bar-widget")) != null' "$target/manifest.json")
|
||||
has_non_widget_kind=$(jq -r 'any(.kinds[]; . != "bar-widget")' "$target/manifest.json")
|
||||
|
||||
if [[ $is_bar == "true" && $was_enabled == "true" ]]; then
|
||||
omarchy-bar use "$cloned_from"
|
||||
restored_source=1
|
||||
elif [[ $is_bar_widget == "true" && $was_enabled == "true" ]]; then
|
||||
omarchy-bar-plugin replace "$id" "$cloned_from"
|
||||
[[ $has_non_widget_kind == "false" ]] || omarchy-plugin-enable "$cloned_from"
|
||||
restored_source=1
|
||||
elif [[ $has_non_widget_kind == "true" ]]; then
|
||||
[[ $was_enabled != "true" ]] || omarchy-shell shell setPluginEnabled "$id" false >/dev/null
|
||||
omarchy-plugin-enable "$cloned_from"
|
||||
restored_source=1
|
||||
fi
|
||||
elif [[ $was_enabled == "true" ]]; then
|
||||
if [[ $was_enabled == "true" ]]; then
|
||||
omarchy-shell shell setPluginEnabled "$id" false >/dev/null
|
||||
fi
|
||||
|
||||
@@ -134,7 +116,7 @@ fi
|
||||
|
||||
omarchy-shell shell rescanPlugins >/dev/null
|
||||
|
||||
if (( restored_source )); then
|
||||
if [[ -n $cloned_from && $was_enabled == "true" ]]; then
|
||||
echo "Restored $cloned_from."
|
||||
elif [[ $was_enabled == "true" ]]; then
|
||||
echo "Plugin was enabled and was unloaded from omarchy-shell."
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
dropbox-cli stop 2>/dev/null || true
|
||||
omarchy-bar-plugin remove omarchy.dropbox
|
||||
omarchy-plugin-disable omarchy.dropbox
|
||||
omarchy-pkg-drop dropbox dropbox-cli libappindicator-gtk3 python-gpgme nautilus-dropbox
|
||||
|
||||
echo ""
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
tailscale down 2>/dev/null || true
|
||||
systemctl --user disable --now omarchy-tailscale-receive.service 2>/dev/null || true
|
||||
sudo systemctl disable --now tailscaled.service 2>/dev/null || true
|
||||
omarchy-bar-plugin remove omarchy.tailscale
|
||||
omarchy-plugin-disable omarchy.tailscale
|
||||
omarchy-webapp-remove "Tailscale" 2>/dev/null || true
|
||||
omarchy-pkg-drop tailscale
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ Run `omarchy --help` for the full list. The most common groups:
|
||||
| `omarchy restart` | Restart a service/app | `omarchy restart shell` |
|
||||
| `omarchy toggle` | Toggle feature on/off | `omarchy toggle nightlight` |
|
||||
| `omarchy theme` | Theme management | `omarchy theme set <name>` |
|
||||
| `omarchy bar` | Bar layout and widgets | `omarchy bar plugin move omarchy.clock --section right` |
|
||||
| `omarchy bar` | Bar layout and widgets | `omarchy bar move omarchy.clock --section right` |
|
||||
| `omarchy plugin` | Manage/clone shell plugins | `omarchy plugin clone omarchy.clock` |
|
||||
| `omarchy hook` | Install automation hooks | `omarchy hook install theme-set <script>` |
|
||||
| `omarchy install` | Install optional software / packages | `omarchy install docker dbs` |
|
||||
|
||||
@@ -81,7 +81,7 @@ arguments — add `--yes` to skip every prompt (the path for scripts and agents)
|
||||
You can still install by hand: drop a plugin into
|
||||
`~/.config/omarchy/plugins/<id>/`, run `omarchy-shell shell rescanPlugins`, then
|
||||
`omarchy plugin enable <id>`. A bar widget starts in its declared default
|
||||
section and can be moved with `omarchy bar plugin move`; enabling a full bar
|
||||
section and can be moved with `omarchy bar move`; enabling a full bar
|
||||
replaces the one in use.
|
||||
The lower-level IPC methods remain available through `omarchy-shell shell ...`.
|
||||
|
||||
@@ -100,6 +100,9 @@ individual plugins (`bar`, `image-selector`, …).
|
||||
| `rescanPlugins` | re-walk plugin dirs and hot-reload plugin code |
|
||||
| `reloadConfig` | reload shell.json |
|
||||
| `setPluginEnabled <id> <"true"\|…>` | flip enabled bit (`ok` / `unknown`) |
|
||||
| `enablePlugin <id> <placementJson>` | enable and place in one mutation |
|
||||
| `moveBarWidget <id> <placementJson>` | move a configured widget |
|
||||
| `setBarWidget <id> <key> <valueJson> <selectorJson>` | set an inline widget option |
|
||||
| `listPlugins` | JSON of every discovered plugin |
|
||||
|
||||
`setPluginEnabled` takes a string; only literal `"true"` enables.
|
||||
@@ -143,7 +146,7 @@ Rules:
|
||||
4. Built-in bar widget ids are namespaced (`omarchy.clock`, `omarchy.audio`, …).
|
||||
The migration rewrites older ids such as `Clock` and `AudioPanel` forward.
|
||||
5. Third-party enabled ⇔ present; for full bar options that means `bar.id`.
|
||||
First-party non-bar plugins are always enabled.
|
||||
First-party non-bar plugins are enabled unless listed in `disabledPlugins[]`.
|
||||
6. `allowMultiple: true` in the manifest permits multiple instances.
|
||||
7. `idle.screensaver` and `idle.lock` are seconds since user idle began.
|
||||
8. `version: 1` is required.
|
||||
|
||||
+7
-7
@@ -133,12 +133,12 @@ You can still drop a plugin in without git:
|
||||
2. `omarchy-shell shell rescanPlugins`.
|
||||
3. `omarchy plugin enable <id>`. Bar widgets start in
|
||||
`barWidget.defaultSection`, or in the center when it is omitted, and can be
|
||||
moved with `omarchy bar plugin move`; a full bar replaces the one in use.
|
||||
moved with `omarchy bar move`; a full bar replaces the one in use.
|
||||
|
||||
The lower-level IPC equivalents remain available via `omarchy-shell shell rescanPlugins`,
|
||||
`omarchy-shell shell setPluginEnabled <id> true`, and `omarchy-shell shell listPlugins`.
|
||||
The `omarchy plugin` commands wrap those calls and can also edit the persisted
|
||||
bar layout in `shell.json`.
|
||||
`omarchy-shell shell enablePlugin <id> '{}'`, and `omarchy-shell shell listPlugins`.
|
||||
The `omarchy plugin` commands wrap those calls. `omarchy bar move` and
|
||||
`omarchy bar set` edit the persisted widget layout in `shell.json`.
|
||||
|
||||
To hack on a built-in plugin safely, clone it into user config instead of
|
||||
editing the built-in source. The complete plugin directory is copied, including
|
||||
@@ -265,9 +265,9 @@ becomes the authoritative file — we do **not** deep-merge defaults back in.
|
||||
like `Clock` and `AudioPanel` forward.
|
||||
5. **Third-party enabled ⇔ present.** A third-party plugin is enabled iff
|
||||
its id appears somewhere in shell.json. For full bar options, that means
|
||||
`bar.id`; for bar widgets, `omarchy bar plugin` adds/removes layout entries;
|
||||
other plugin kinds are enabled with the shell IPC. First-party non-bar
|
||||
plugins are always enabled.
|
||||
`bar.id`; for bar widgets, plugin enable/disable adds/removes layout entries;
|
||||
other plugin kinds are enabled the same way. First-party non-bar plugins
|
||||
are enabled unless listed in `disabledPlugins[]`.
|
||||
6. **Multiple instances** are allowed when a manifest sets
|
||||
`allowMultiple: true`. Each instance is independent — e.g. two clock
|
||||
widgets in different timezones are just two `{"id":"omarchy.clock", "timezone": ...}`
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
These plugins ship with Omarchy and are discovered by the shell at startup.
|
||||
They use the same `manifest.json` contract as third-party plugins; the
|
||||
only difference is that the shell flags them with `__isFirstParty: true`.
|
||||
First-party non-bar plugins are always enabled; `omarchy.bar` is the default
|
||||
bar option and becomes inactive only while a third-party `kind: "bar"` plugin is
|
||||
selected. Services and keep-loaded panels are mounted at startup; other panels,
|
||||
overlays, and menus are loaded on demand.
|
||||
First-party non-bar plugins are enabled unless listed in `disabledPlugins[]`;
|
||||
`omarchy.bar` is the default bar option and becomes inactive only while another
|
||||
`kind: "bar"` plugin is selected. Services and keep-loaded panels are mounted
|
||||
at startup; other panels, overlays, and menus are loaded on demand.
|
||||
|
||||
User-installed plugins live alongside these conceptually but on disk under
|
||||
`~/.config/omarchy/plugins/<plugin-id>/` rather than in this directory.
|
||||
|
||||
@@ -16,7 +16,7 @@ the shell for its whole session.
|
||||
|
||||
The bar config lives under the `bar:` key of [`~/.config/omarchy/shell.json`](../../README.md#shelljson-shape). 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` and `omarchy bar transparent` commands do the same from scripts. For scriptable widget changes, use `omarchy bar plugin add`, `omarchy bar plugin move`, `omarchy bar plugin remove`, and `omarchy bar plugin set` (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 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`).
|
||||
|
||||
Example `shell.json` (bar subtree only shown):
|
||||
|
||||
@@ -178,6 +178,6 @@ namespaced ids.
|
||||
Third-party widgets ship as separate plugins under
|
||||
`~/.config/omarchy/plugins/<plugin-id>/` with their own `manifest.json`
|
||||
declaring `kinds: ["bar-widget"]` and a `barWidget` entry point. See
|
||||
[../../README.md](../../README.md) for the manifest schema. Enable,
|
||||
rescan, and place third-party plugins with `omarchy plugin enable`,
|
||||
`omarchy-shell shell rescanPlugins`, and `omarchy bar plugin add`.
|
||||
[../../README.md](../../README.md) for the manifest schema. Rescan, enable,
|
||||
and place third-party plugins with `omarchy-shell shell rescanPlugins`,
|
||||
`omarchy plugin enable`, and `omarchy bar move`.
|
||||
|
||||
@@ -29,7 +29,7 @@ shows up at the next refresh, so nothing polls the disk waiting for it.
|
||||
That self-hiding is why the widget ships in the default bar layout: a machine
|
||||
that has never run Claude Code or Codex draws nothing, and the icon arrives on
|
||||
its own the first time a scan finds usage. Drop it with
|
||||
`omarchy bar plugin remove omarchy.model-usage`.
|
||||
`omarchy plugin disable omarchy.model-usage`.
|
||||
|
||||
## Providers
|
||||
|
||||
@@ -52,7 +52,7 @@ falls back to local stats only.
|
||||
|
||||
Settings live in the widget's entry in `~/.config/omarchy/shell.json`. The
|
||||
top-level keys can be set with
|
||||
`omarchy bar plugin set omarchy.model-usage <key> <value>`:
|
||||
`omarchy bar set omarchy.model-usage <key> <value>`:
|
||||
|
||||
| Key | Default | What it does |
|
||||
|---|---|---|
|
||||
@@ -65,8 +65,8 @@ top-level keys can be set with
|
||||
Numbers need `--json`, or they land in `shell.json` as strings:
|
||||
|
||||
```bash
|
||||
omarchy bar plugin set omarchy.model-usage refreshIntervalSec 300 --json
|
||||
omarchy bar plugin set omarchy.model-usage syncDir '~/Sync/model-usage'
|
||||
omarchy bar set omarchy.model-usage refreshIntervalSec 300 --json
|
||||
omarchy bar set omarchy.model-usage syncDir '~/Sync/model-usage'
|
||||
```
|
||||
|
||||
Per-provider settings are nested, and `set` writes its key literally rather
|
||||
@@ -74,7 +74,7 @@ than walking a dotted path — so pass the whole `providers` object as JSON (or
|
||||
edit `shell.json` directly):
|
||||
|
||||
```bash
|
||||
omarchy bar plugin set omarchy.model-usage providers '{
|
||||
omarchy bar set omarchy.model-usage providers '{
|
||||
"claude": {
|
||||
"enabled": true,
|
||||
"statsPath": "~/.claude/stats-cache.json",
|
||||
|
||||
@@ -46,4 +46,4 @@ Renders the Tailscale mark natively as a theme-colored 3×3 dot grid, matching t
|
||||
|
||||
## Add to the bar
|
||||
|
||||
This widget ships as first-party plugin `omarchy.tailscale`. Add it with `omarchy bar plugin add omarchy.tailscale`, or add an entry such as `{ "id": "omarchy.tailscale" }` to one of the `bar.layout` sections in `~/.config/omarchy/shell.json`; the shell reloads `shell.json` automatically.
|
||||
This widget ships as first-party plugin `omarchy.tailscale`. Add it with `omarchy plugin enable omarchy.tailscale`, then place it with `omarchy bar move omarchy.tailscale` if desired.
|
||||
|
||||
@@ -24,6 +24,7 @@ QtObject {
|
||||
property var installedPlugins: ({})
|
||||
property int registryRevision: 0
|
||||
property bool scanning: false
|
||||
property string lastEnableError: ""
|
||||
|
||||
signal pluginsChanged()
|
||||
signal scanFinished()
|
||||
@@ -171,6 +172,27 @@ QtObject {
|
||||
return ["left", "center", "right"].indexOf(section) !== -1 ? section : "center"
|
||||
}
|
||||
|
||||
function barEntryId(entry) {
|
||||
return Util.canonicalWidgetId(String(Util.isPlainObject(entry) ? entry.id : entry || ""))
|
||||
}
|
||||
|
||||
function findBarLocation(config, id, section) {
|
||||
if (!Util.isPlainObject(config) || !Util.isPlainObject(config.bar)
|
||||
|| !Util.isPlainObject(config.bar.layout)) return { found: false }
|
||||
var key = Util.canonicalWidgetId(String(id))
|
||||
var sections = ["left", "center", "right"]
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
if (section && sections[s] !== section) continue
|
||||
var entries = config.bar.layout[sections[s]]
|
||||
if (!Array.isArray(entries)) continue
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
if (barEntryId(entries[i]) === key)
|
||||
return { found: true, kind: "bar", section: sections[s], index: i }
|
||||
}
|
||||
}
|
||||
return { found: false }
|
||||
}
|
||||
|
||||
function findEntryLocation(config, id) {
|
||||
if (!Util.isPlainObject(config)) return { found: false }
|
||||
var key = Util.canonicalWidgetId(String(id))
|
||||
@@ -179,14 +201,8 @@ QtObject {
|
||||
if (selectedBar === key) return { found: true, kind: "bar-option" }
|
||||
}
|
||||
if (Util.isPlainObject(config.bar) && Util.isPlainObject(config.bar.layout)) {
|
||||
var sections = ["left", "center", "right"]
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
var arr = config.bar.layout[sections[s]]
|
||||
if (!Array.isArray(arr)) continue
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (arr[i] && Util.canonicalWidgetId(arr[i].id) === key) return { found: true, kind: "bar", section: sections[s], index: i }
|
||||
}
|
||||
}
|
||||
var barLocation = findBarLocation(config, key, "")
|
||||
if (barLocation.found) return barLocation
|
||||
}
|
||||
if (Array.isArray(config.plugins)) {
|
||||
for (var j = 0; j < config.plugins.length; j++) {
|
||||
@@ -196,13 +212,210 @@ QtObject {
|
||||
return { found: false }
|
||||
}
|
||||
|
||||
function barTarget(config, placement, fallbackSection) {
|
||||
var target = placement || {}
|
||||
var section = ["left", "center", "right"].indexOf(String(target.section || "")) !== -1
|
||||
? String(target.section) : fallbackSection
|
||||
var relativeId = String(target.before || target.after || "")
|
||||
if (relativeId) {
|
||||
var relative = findBarLocation(config, relativeId, section && target.section ? section : "")
|
||||
if (!relative.found) return { error: "could not find target widget " + relativeId }
|
||||
return {
|
||||
section: relative.section,
|
||||
index: relative.index + (target.after ? 1 : 0)
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(config.bar.layout[section])) config.bar.layout[section] = []
|
||||
if (target.index !== undefined && target.index !== null) {
|
||||
var requested = Math.max(0, Math.floor(Number(target.index)))
|
||||
return { section: section, index: Math.min(requested, config.bar.layout[section].length) }
|
||||
}
|
||||
|
||||
var anchors = { left: "omarchy.workspaces", center: "omarchy.weather", right: "omarchy.tray" }
|
||||
var anchor = findBarLocation(config, anchors[section], section)
|
||||
return {
|
||||
section: section,
|
||||
index: anchor.found ? anchor.index + 1 : config.bar.layout[section].length
|
||||
}
|
||||
}
|
||||
|
||||
function moveBarEntry(config, id, placement) {
|
||||
var key = Util.canonicalWidgetId(String(id))
|
||||
var source
|
||||
if (placement.fromIndex !== undefined && placement.fromIndex !== null) {
|
||||
var fromSection = String(placement.fromSection || "")
|
||||
if (!fromSection) return "from-index requires from-section"
|
||||
var entries = config.bar.layout[fromSection]
|
||||
var fromIndex = Math.floor(Number(placement.fromIndex))
|
||||
if (!Array.isArray(entries) || fromIndex < 0 || fromIndex >= entries.length)
|
||||
return "no widget at " + fromSection + "[" + fromIndex + "]"
|
||||
if (barEntryId(entries[fromIndex]) !== key)
|
||||
return "widget at " + fromSection + "[" + fromIndex + "] is not " + key
|
||||
source = { found: true, section: fromSection, index: fromIndex }
|
||||
} else {
|
||||
source = findBarLocation(config, key, String(placement.fromSection || ""))
|
||||
if (!source.found) return "could not find widget " + key
|
||||
}
|
||||
|
||||
var entry = config.bar.layout[source.section][source.index]
|
||||
config.bar.layout[source.section].splice(source.index, 1)
|
||||
var target = barTarget(config, placement, source.section)
|
||||
if (target.error) {
|
||||
config.bar.layout[source.section].splice(source.index, 0, entry)
|
||||
return target.error
|
||||
}
|
||||
config.bar.layout[target.section].splice(target.index, 0, entry)
|
||||
return ""
|
||||
}
|
||||
|
||||
function moveBarWidget(id, placement) {
|
||||
var error = ""
|
||||
shellConfigMutator(function(config) {
|
||||
ensureConfigShape(config)
|
||||
error = moveBarEntry(config, id, placement || {})
|
||||
})
|
||||
if (error) return error
|
||||
registryRevision++
|
||||
pluginsChanged()
|
||||
return ""
|
||||
}
|
||||
|
||||
function setBarWidget(id, key, value, selector) {
|
||||
var error = ""
|
||||
shellConfigMutator(function(config) {
|
||||
ensureConfigShape(config)
|
||||
var location
|
||||
var requested = selector || {}
|
||||
var section = String(requested.fromSection || requested.section || "")
|
||||
var index = requested.fromIndex !== undefined && requested.fromIndex !== null
|
||||
? requested.fromIndex : requested.index
|
||||
if (index !== undefined && index !== null) {
|
||||
if (!section) {
|
||||
error = "index requires section"
|
||||
return
|
||||
}
|
||||
var entries = config.bar.layout[section]
|
||||
var numericIndex = Math.floor(Number(index))
|
||||
if (!Array.isArray(entries) || numericIndex < 0 || numericIndex >= entries.length) {
|
||||
error = "no widget at " + section + "[" + numericIndex + "]"
|
||||
return
|
||||
}
|
||||
location = { found: true, section: section, index: numericIndex }
|
||||
} else {
|
||||
location = findBarLocation(config, id, section)
|
||||
}
|
||||
if (!location.found) {
|
||||
error = "could not find widget " + id
|
||||
return
|
||||
}
|
||||
if (barEntryId(config.bar.layout[location.section][location.index]) !== String(id)) {
|
||||
error = "widget at " + location.section + "[" + location.index + "] is not " + id
|
||||
return
|
||||
}
|
||||
var entry = config.bar.layout[location.section][location.index]
|
||||
if (!Util.isPlainObject(entry)) {
|
||||
error = "widget entry must be an object"
|
||||
return
|
||||
}
|
||||
entry[String(key)] = value
|
||||
})
|
||||
if (error) return error
|
||||
registryRevision++
|
||||
pluginsChanged()
|
||||
return ""
|
||||
}
|
||||
|
||||
function ensureConfigShape(config) {
|
||||
if (!Util.isPlainObject(config.bar)) config.bar = { layout: { left: [], center: [], right: [] } }
|
||||
if (!Util.isPlainObject(config.bar.layout)) config.bar.layout = { left: [], center: [], right: [] }
|
||||
var sections = ["left", "center", "right"]
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (!Array.isArray(config.bar.layout[sections[i]])) config.bar.layout[sections[i]] = []
|
||||
}
|
||||
if (!Array.isArray(config.plugins)) config.plugins = []
|
||||
}
|
||||
|
||||
// Bar widgets use the default section declared in their manifest, falling
|
||||
// back to center. Panels/overlays/menus/services go into the plugins[] array.
|
||||
// Built-ins are already loaded, so shell.json only ever records the
|
||||
// deviation: an added third-party plugin in plugins[], a switched-off
|
||||
// built-in in disabledPlugins[].
|
||||
function setEnabled(id, value) {
|
||||
function removeDisabled(config, id) {
|
||||
if (!Array.isArray(config.disabledPlugins)) return
|
||||
config.disabledPlugins = config.disabledPlugins.filter(function(entry) { return entry !== id })
|
||||
if (config.disabledPlugins.length === 0) delete config.disabledPlugins
|
||||
}
|
||||
|
||||
function addDisabled(config, id) {
|
||||
if (isDisabled(config, id)) return
|
||||
if (!Array.isArray(config.disabledPlugins)) config.disabledPlugins = []
|
||||
config.disabledPlugins.push(id)
|
||||
}
|
||||
|
||||
function cloneShouldRestoreSource(config, id) {
|
||||
return Array.isArray(config.cloneSourceRestores) && config.cloneSourceRestores.indexOf(id) !== -1
|
||||
}
|
||||
|
||||
function setCloneShouldRestoreSource(config, id, value) {
|
||||
var restores = Array.isArray(config.cloneSourceRestores) ? config.cloneSourceRestores : []
|
||||
restores = restores.filter(function(entry) { return entry !== id })
|
||||
if (value) restores.push(id)
|
||||
if (restores.length) config.cloneSourceRestores = restores
|
||||
else delete config.cloneSourceRestores
|
||||
}
|
||||
|
||||
function activeCloneFor(config, sourceId) {
|
||||
for (var candidate in installedPlugins) {
|
||||
var candidateManifest = installedPlugins[candidate]
|
||||
var candidateMetadata = candidateManifest && Util.isPlainObject(candidateManifest.omarchy)
|
||||
? candidateManifest.omarchy : null
|
||||
if (!candidateMetadata || String(candidateMetadata.clonedFrom || "") !== sourceId) continue
|
||||
if (Array.isArray(candidateManifest.kinds) && candidateManifest.kinds.indexOf("bar") !== -1) {
|
||||
if (Util.canonicalWidgetId(String(config.bar.id || "")) === candidate) return candidate
|
||||
} else if (findEntryLocation(config, candidate).found) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function restoreCloneSource(config, cloneId, sourceId) {
|
||||
var cloneManifest = installedPlugins[cloneId]
|
||||
var isBarOption = cloneManifest && Array.isArray(cloneManifest.kinds)
|
||||
&& cloneManifest.kinds.indexOf("bar") !== -1
|
||||
if (isBarOption) {
|
||||
if (sourceId === "omarchy.bar") delete config.bar.id
|
||||
else config.bar.id = sourceId
|
||||
} else {
|
||||
var cloneLocation = findEntryLocation(config, cloneId)
|
||||
if (cloneLocation.kind === "bar") {
|
||||
var cloneEntry = config.bar.layout[cloneLocation.section][cloneLocation.index]
|
||||
var sections = ["left", "center", "right"]
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
for (var i = config.bar.layout[sections[s]].length - 1; i >= 0; i--) {
|
||||
if (barEntryId(config.bar.layout[sections[s]][i]) === sourceId)
|
||||
config.bar.layout[sections[s]].splice(i, 1)
|
||||
}
|
||||
}
|
||||
cloneLocation = findBarLocation(config, cloneId, "")
|
||||
if (cloneLocation.found) {
|
||||
var restoredEntry = Util.isPlainObject(cloneEntry) ? Util.cloneJson(cloneEntry) : {}
|
||||
restoredEntry.id = sourceId
|
||||
config.bar.layout[cloneLocation.section][cloneLocation.index] = restoredEntry
|
||||
}
|
||||
} else if (cloneLocation.kind === "plugin") {
|
||||
config.plugins.splice(cloneLocation.index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
if (cloneShouldRestoreSource(config, cloneId)) removeDisabled(config, sourceId)
|
||||
setCloneShouldRestoreSource(config, cloneId, false)
|
||||
}
|
||||
|
||||
function setEnabled(id, value, placement) {
|
||||
var key = Util.canonicalWidgetId(String(id))
|
||||
lastEnableError = ""
|
||||
if (!shellConfigMutator) {
|
||||
console.warn("PluginRegistry.setEnabled called before shellConfigMutator wired")
|
||||
return false
|
||||
@@ -216,17 +429,33 @@ QtObject {
|
||||
var isBarWidget = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar-widget") !== -1
|
||||
var hasNonWidgetKind = manifest && Array.isArray(manifest.kinds)
|
||||
&& manifest.kinds.some(function(kind) { return kind !== "bar-widget" })
|
||||
var metadata = manifest && Util.isPlainObject(manifest.omarchy) ? manifest.omarchy : null
|
||||
var clonedFrom = metadata ? Util.canonicalWidgetId(String(metadata.clonedFrom || "")) : ""
|
||||
shellConfigMutator(function(config) {
|
||||
// Ensure shape exists.
|
||||
if (!Util.isPlainObject(config.bar)) config.bar = { layout: { left: [], center: [], right: [] } }
|
||||
if (!Util.isPlainObject(config.bar.layout)) config.bar.layout = { left: [], center: [], right: [] }
|
||||
if (!Array.isArray(config.plugins)) config.plugins = []
|
||||
ensureConfigShape(config)
|
||||
|
||||
if (value && placement && (placement.before || placement.after)) {
|
||||
var relativeId = String(placement.before || placement.after)
|
||||
if (!findBarLocation(config, relativeId, String(placement.section || "")).found) {
|
||||
lastEnableError = "could not find target widget " + relativeId
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (value && manifest && manifest.__isFirstParty) {
|
||||
var activeClone = activeCloneFor(config, key)
|
||||
if (activeClone) {
|
||||
restoreCloneSource(config, activeClone, key)
|
||||
removeDisabled(config, key)
|
||||
}
|
||||
}
|
||||
|
||||
if (isBarOption) {
|
||||
if (value) {
|
||||
config.bar.id = key
|
||||
} else if (Util.canonicalWidgetId(String(config.bar.id || "")) === key) {
|
||||
delete config.bar.id
|
||||
if (clonedFrom && clonedFrom !== "omarchy.bar") config.bar.id = clonedFrom
|
||||
else delete config.bar.id
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -235,33 +464,45 @@ QtObject {
|
||||
var location = findEntryLocation(config, key)
|
||||
|
||||
if (value) {
|
||||
// Leave shell.json without the key once nothing is switched off, so a
|
||||
// config that never disabled anything reads as it always did.
|
||||
if (Array.isArray(config.disabledPlugins)) {
|
||||
config.disabledPlugins = config.disabledPlugins.filter(function(entry) { return entry !== key })
|
||||
if (config.disabledPlugins.length === 0) delete config.disabledPlugins
|
||||
}
|
||||
if (location.found) return
|
||||
removeDisabled(config, key)
|
||||
var entry = { id: key }
|
||||
if (isBarWidget) {
|
||||
var section = defaultBarWidgetSection(manifest)
|
||||
if (!Array.isArray(config.bar.layout[section])) config.bar.layout[section] = []
|
||||
config.bar.layout[section].push(entry)
|
||||
} else if (!isFirstParty) {
|
||||
var insertedWithPlacement = false
|
||||
if (!location.found && isBarWidget) {
|
||||
var sourceLocation = clonedFrom ? findEntryLocation(config, clonedFrom) : { found: false }
|
||||
if (sourceLocation.kind === "bar") {
|
||||
var sourceEntry = config.bar.layout[sourceLocation.section][sourceLocation.index]
|
||||
var replacement = Util.isPlainObject(sourceEntry) ? Util.cloneJson(sourceEntry) : entry
|
||||
replacement.id = key
|
||||
config.bar.layout[sourceLocation.section][sourceLocation.index] = replacement
|
||||
} else {
|
||||
var section = defaultBarWidgetSection(manifest)
|
||||
var target = barTarget(config, placement || {}, section)
|
||||
config.bar.layout[target.section].splice(target.index, 0, entry)
|
||||
insertedWithPlacement = true
|
||||
}
|
||||
} else if (!location.found && !isFirstParty) {
|
||||
config.plugins.push(entry)
|
||||
}
|
||||
|
||||
if (isBarWidget && !insertedWithPlacement && placement && Object.keys(placement).length)
|
||||
moveBarEntry(config, key, placement)
|
||||
|
||||
if (clonedFrom && hasNonWidgetKind && !isDisabled(config, clonedFrom)) {
|
||||
addDisabled(config, clonedFrom)
|
||||
setCloneShouldRestoreSource(config, key, true)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (location.kind === "bar") config.bar.layout[location.section].splice(location.index, 1)
|
||||
if (clonedFrom) restoreCloneSource(config, key, clonedFrom)
|
||||
else if (location.kind === "bar") config.bar.layout[location.section].splice(location.index, 1)
|
||||
else if (location.kind === "plugin") config.plugins.splice(location.index, 1)
|
||||
|
||||
// Dropping the layout entry is the whole story for a widget. Anything
|
||||
// else built-in loads by default, so switching it off has to be stated.
|
||||
if (isFirstParty && (!isBarWidget || hasNonWidgetKind) && !isDisabled(config, key)) {
|
||||
if (!Array.isArray(config.disabledPlugins)) config.disabledPlugins = []
|
||||
config.disabledPlugins.push(key)
|
||||
}
|
||||
if (isFirstParty && !isBarWidget) addDisabled(config, key)
|
||||
})
|
||||
if (lastEnableError) return false
|
||||
registryRevision++
|
||||
pluginsChanged()
|
||||
return true
|
||||
|
||||
+31
-9
@@ -54,7 +54,6 @@ ShellRoot {
|
||||
|
||||
property var defaultsConfig: builtinShellConfig
|
||||
property var shellConfig: builtinShellConfig
|
||||
property bool suppressUserReload: false
|
||||
property bool pluginReloading: false
|
||||
property bool pluginReloadPending: false
|
||||
|
||||
@@ -107,7 +106,6 @@ ShellRoot {
|
||||
}
|
||||
|
||||
function persistShellConfig(nextConfig) {
|
||||
suppressUserReload = true
|
||||
var payload = JSON.parse(JSON.stringify(nextConfig))
|
||||
payload.version = 1
|
||||
shellConfig = payload
|
||||
@@ -135,13 +133,7 @@ ShellRoot {
|
||||
watchChanges: true
|
||||
atomicWrites: true
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
if (shell.suppressUserReload) {
|
||||
shell.suppressUserReload = false
|
||||
return
|
||||
}
|
||||
shell.applyShellConfig()
|
||||
}
|
||||
onLoaded: shell.applyShellConfig()
|
||||
onLoadFailed: function(error) { shell.applyShellConfig() }
|
||||
onFileChanged: reload()
|
||||
}
|
||||
@@ -908,6 +900,36 @@ ShellRoot {
|
||||
return shell.pluginRegistry.setEnabled(id, enabled === "true") ? "ok" : "unknown"
|
||||
}
|
||||
|
||||
function enablePlugin(id: string, placementJson: string): string {
|
||||
try {
|
||||
var placement = JSON.parse(placementJson || "{}")
|
||||
if (shell.pluginRegistry.setEnabled(id, true, placement)) return "ok"
|
||||
return shell.pluginRegistry.lastEnableError || "unknown"
|
||||
} catch (e) {
|
||||
return "invalid placement: " + e
|
||||
}
|
||||
}
|
||||
|
||||
function moveBarWidget(id: string, placementJson: string): string {
|
||||
try {
|
||||
var error = shell.pluginRegistry.moveBarWidget(id, JSON.parse(placementJson || "{}"))
|
||||
return error ? error : "ok"
|
||||
} catch (e) {
|
||||
return "invalid placement: " + e
|
||||
}
|
||||
}
|
||||
|
||||
function setBarWidget(id: string, key: string, valueJson: string, selectorJson: string): string {
|
||||
try {
|
||||
var value = JSON.parse(valueJson)
|
||||
var selector = JSON.parse(selectorJson || "{}")
|
||||
var error = shell.pluginRegistry.setBarWidget(id, key, value, selector)
|
||||
return error ? error : "ok"
|
||||
} catch (e) {
|
||||
return "invalid widget setting: " + e
|
||||
}
|
||||
}
|
||||
|
||||
function listPlugins(): string {
|
||||
var out = []
|
||||
var plugins = shell.pluginRegistry.installedPlugins
|
||||
|
||||
+62
-108
@@ -208,14 +208,27 @@ pass "Hyprland bootstrap reloads cached Omarchy config modules"
|
||||
TMPDIR=$(mktemp -d)
|
||||
mkdir -p "$TMPDIR/home/.config/omarchy"
|
||||
|
||||
ipc_mock_bin="$TMPDIR/ipc-mock"
|
||||
mkdir -p "$ipc_mock_bin"
|
||||
cat >"$ipc_mock_bin/omarchy-shell" <<'SH'
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
mkdir -p "$HOME/.local/state/omarchy"
|
||||
printf '%s\n' "$*" >>"$HOME/.local/state/omarchy/shell-ipc-calls"
|
||||
printf 'ok\n'
|
||||
SH
|
||||
chmod +x "$ipc_mock_bin/omarchy-shell"
|
||||
export PATH="$ipc_mock_bin:$PATH"
|
||||
|
||||
cat >"$TMPDIR/home/.config/omarchy/shell.json" <<'JSON'
|
||||
{
|
||||
"version": 1,
|
||||
"bar": {
|
||||
"layout": {
|
||||
"left": [{ "id": "omarchy.menu" }, { "id": "omarchy.workspaces" }],
|
||||
"center": [{ "id": "omarchy.clock" }, { "id": "omarchy.weather" }],
|
||||
"right": [{ "id": "omarchy.tray" }, { "id": "omarchy.bluetooth" }]
|
||||
"left": [{ "id": "omarchy.menu" }, { "id": "omarchy.workspaces" }, { "id": "omarchy.active-window" }],
|
||||
"center": [{ "id": "omarchy.clock" }, { "id": "omarchy.weather" }, { "id": "omarchy.system-update" }, { "id": "omarchy.tailscale" }],
|
||||
"right": [{ "id": "omarchy.tray" }, { "id": "omarchy.microphone" }, { "id": "omarchy.bluetooth" }]
|
||||
}
|
||||
},
|
||||
"plugins": []
|
||||
@@ -250,75 +263,20 @@ HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar reset
|
||||
jq -e '.bar.id == null' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config resets to built-in bar option"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin add omarchy.tailscale
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.center | ids == ["omarchy.clock", "omarchy.weather", "omarchy.tailscale"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config defaults widgets without a section to center"
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar move omarchy.active-window right
|
||||
grep -Fqx 'shell moveBarWidget omarchy.active-window {"section":"right"}' \
|
||||
"$TMPDIR/home/.local/state/omarchy/shell-ipc-calls"
|
||||
pass "bar move accepts a positional target section"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin add omarchy.active-window
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.left | ids == ["omarchy.menu", "omarchy.workspaces", "omarchy.active-window"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config uses a widget's default section"
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar move omarchy.active-window left
|
||||
grep -Fqx 'shell moveBarWidget omarchy.active-window {"section":"left"}' \
|
||||
"$TMPDIR/home/.local/state/omarchy/shell-ipc-calls"
|
||||
pass "bar move can restore a widget with positional syntax"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin add omarchy.media
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.center | ids == ["omarchy.clock", "omarchy.weather", "omarchy.media", "omarchy.tailscale"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin remove omarchy.media >/dev/null
|
||||
pass "shell config honors a center default section"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin add omarchy.dropbox
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.right | ids == ["omarchy.tray", "omarchy.dropbox", "omarchy.bluetooth"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin remove omarchy.dropbox >/dev/null
|
||||
pass "shell config honors a right default section"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin add omarchy.system-update center
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.center | ids == ["omarchy.clock", "omarchy.weather", "omarchy.system-update", "omarchy.tailscale"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config appends center widgets after weather"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin add omarchy.microphone right
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.right | ids == ["omarchy.tray", "omarchy.microphone", "omarchy.bluetooth"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config moves existing widgets without duplicates"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin move omarchy.active-window right
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
(.bar.layout.left | ids == ["omarchy.menu", "omarchy.workspaces"]) and
|
||||
(.bar.layout.right | ids == ["omarchy.tray", "omarchy.active-window", "omarchy.microphone", "omarchy.bluetooth"])
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "bar plugin move accepts a positional target section"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin move omarchy.active-window left
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
(.bar.layout.left | ids == ["omarchy.menu", "omarchy.workspaces", "omarchy.active-window"]) and
|
||||
(.bar.layout.right | ids == ["omarchy.tray", "omarchy.microphone", "omarchy.bluetooth"])
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "bar plugin move can restore a widget with positional syntax"
|
||||
|
||||
if HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin move omarchy.active-window left --section right 2>/dev/null; then
|
||||
fail "bar plugin move accepted positional and flagged target sections"
|
||||
if HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar move omarchy.active-window left --section right 2>/dev/null; then
|
||||
fail "bar move accepted positional and flagged target sections"
|
||||
fi
|
||||
pass "bar plugin move rejects conflicting target section syntax"
|
||||
|
||||
if HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin add local.nonexistent-widget 2>/dev/null; then
|
||||
fail "bar plugin add accepted an unknown widget"
|
||||
fi
|
||||
pass "bar plugin add rejects an unknown widget"
|
||||
pass "bar move rejects conflicting target section syntax"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar position bottom
|
||||
jq -e '
|
||||
@@ -339,43 +297,25 @@ HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar transparent toggle
|
||||
jq -e '.bar.transparent == false' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config toggles bar transparency"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin drop omarchy.active-window
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
(.bar.layout.left | ids == ["omarchy.menu", "omarchy.workspaces"]) and
|
||||
(.bar.layout.center | ids == ["omarchy.clock", "omarchy.weather", "omarchy.system-update", "omarchy.tailscale"]) and
|
||||
(.bar.layout.right | ids == ["omarchy.tray", "omarchy.microphone", "omarchy.bluetooth"])
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config drops widgets from any section"
|
||||
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"
|
||||
pass "bar set accepts false JSON values"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin remove omarchy.system-update
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.center | ids == ["omarchy.clock", "omarchy.weather", "omarchy.tailscale"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config removes widgets with remove alias"
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar set omarchy.bluetooth optional null --json
|
||||
grep -Fqx 'shell setBarWidget omarchy.bluetooth optional null {}' \
|
||||
"$TMPDIR/home/.local/state/omarchy/shell-ipc-calls"
|
||||
pass "bar set accepts null JSON values"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin set omarchy.bluetooth enabled false --json
|
||||
jq -e '
|
||||
any(.bar.layout.right[]; .id == "omarchy.bluetooth" and .enabled == false)
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "bar plugin set accepts false JSON values"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin set omarchy.bluetooth optional null --json
|
||||
jq -e '
|
||||
any(.bar.layout.right[]; .id == "omarchy.bluetooth" and has("optional") and .optional == null)
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "bar plugin set accepts null JSON values"
|
||||
|
||||
if HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin set omarchy.bluetooth broken '{' --json 2>/dev/null; then
|
||||
fail "bar plugin set accepted malformed JSON"
|
||||
if HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar set omarchy.bluetooth broken '{' --json 2>/dev/null; then
|
||||
fail "bar set accepted malformed JSON"
|
||||
fi
|
||||
pass "bar plugin set rejects malformed JSON"
|
||||
pass "bar set rejects malformed JSON"
|
||||
|
||||
if HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar-plugin set omarchy.bluetooth broken 'false null' --json 2>/dev/null; then
|
||||
fail "bar plugin set accepted multiple JSON values"
|
||||
if HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar set omarchy.bluetooth broken 'false null' --json 2>/dev/null; then
|
||||
fail "bar set accepted multiple JSON values"
|
||||
fi
|
||||
pass "bar plugin set rejects multiple JSON values"
|
||||
pass "bar set rejects multiple JSON values"
|
||||
|
||||
mock_bin="$TMPDIR/mock-bin"
|
||||
mkdir -p "$mock_bin"
|
||||
@@ -400,7 +340,8 @@ SH
|
||||
|
||||
cat >"$mock_bin/omarchy-shell" <<'SH'
|
||||
#!/bin/bash
|
||||
exit 0
|
||||
[[ ${OMARCHY_TEST_SHELL_DOWN:-0} == "1" ]] && exit 1
|
||||
printf 'ok\n'
|
||||
SH
|
||||
|
||||
cat >"$mock_bin/omarchy-installed-service-dropbox" <<'SH'
|
||||
@@ -430,10 +371,22 @@ pass "bar defaults restores the stock bar"
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" PATH="$mock_path" OMARCHY_TEST_DROPBOX=1 OMARCHY_TEST_TAILSCALE=1 omarchy-bar defaults
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
(.bar.layout.right | ids | index("omarchy.dropbox") != null) and
|
||||
(.bar.layout.center | ids | index("omarchy.tailscale") != null)
|
||||
(.bar.layout.center | ids) as $center |
|
||||
(.bar.layout.right | ids | index("omarchy.dropbox") == 1) and
|
||||
($center | index("omarchy.tailscale") == (($center | index("omarchy.weather")) + 1))
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "bar defaults adds widgets for running optional services"
|
||||
pass "bar defaults places plugins for running optional services"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" PATH="$mock_path" \
|
||||
OMARCHY_TEST_SHELL_DOWN=1 OMARCHY_TEST_DROPBOX=1 OMARCHY_TEST_TAILSCALE=1 \
|
||||
omarchy-bar defaults
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
(.bar.layout.center | ids) as $center |
|
||||
(.bar.layout.right | ids | index("omarchy.dropbox") == 1) and
|
||||
($center | index("omarchy.tailscale") == (($center | index("omarchy.weather")) + 1))
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "bar defaults places service widgets without a running shell"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" PATH="$mock_path" OMARCHY_TEST_DROPBOX=0 OMARCHY_TEST_TAILSCALE=0 omarchy-refresh-shell
|
||||
jq -e '
|
||||
@@ -446,11 +399,12 @@ pass "shell refresh keeps optional service widgets absent when services are unav
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" PATH="$mock_path" OMARCHY_TEST_DROPBOX=1 OMARCHY_TEST_TAILSCALE=1 omarchy-refresh-shell
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
(.bar.layout.right | ids | index("omarchy.dropbox") != null) and
|
||||
(.bar.layout.center | ids | index("omarchy.tailscale") != null)
|
||||
(.bar.layout.center | ids) as $center |
|
||||
(.bar.layout.right | ids | index("omarchy.dropbox") == 1) and
|
||||
($center | index("omarchy.tailscale") == (($center | index("omarchy.weather")) + 1))
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
[[ -f $TMPDIR/home/.local/state/omarchy/restart-shell-called ]] || fail "shell refresh restarts shell"
|
||||
pass "shell refresh adds optional service widgets when services are available"
|
||||
pass "shell refresh places optional service widgets when services are available"
|
||||
|
||||
if grep -RIl 'upgrade-to-quattro\|Omarchy 4\.0 is upgraded' "$ROOT/migrations" >/dev/null; then
|
||||
fail "4.0 upgrade is not modeled as a migration"
|
||||
|
||||
@@ -85,9 +85,20 @@ ShellRoot {
|
||||
scan += block("firstparty", "/first/hybrid", manifest("omarchy.hybrid", ["menu", "bar-widget"], { menu: "Menu.qml", barWidget: "Widget.qml" }))
|
||||
scan += block("thirdparty", "/third/panel", manifest("third.panel", ["panel"], { panel: "Panel.qml" }))
|
||||
scan += block("thirdparty", "/third/widget", manifest("third.widget", ["bar-widget"], { barWidget: "Widget.qml" }, { defaultSection: "left" }))
|
||||
scan += block("thirdparty", "/third/center-widget", manifest("third.center-widget", ["bar-widget"], { barWidget: "Widget.qml" }))
|
||||
scan += block("thirdparty", "/third/right-widget", manifest("third.right-widget", ["bar-widget"], { barWidget: "Widget.qml" }, { defaultSection: "right" }))
|
||||
var localWidget = manifest("local.first-widget", ["bar-widget"], { barWidget: "Widget.qml" })
|
||||
localWidget.omarchy = { clonedFrom: "omarchy.first-widget" }
|
||||
scan += block("thirdparty", "/third/local-widget", localWidget)
|
||||
var localHybrid = manifest("local.hybrid", ["menu", "bar-widget"], { menu: "Menu.qml", barWidget: "Widget.qml" })
|
||||
localHybrid.omarchy = { clonedFrom: "omarchy.hybrid" }
|
||||
scan += block("thirdparty", "/third/local-hybrid", localHybrid)
|
||||
var localPanel = manifest("local.grouped-panel", ["panel"], { panel: "Panel.qml" })
|
||||
localPanel.omarchy = { clonedFrom: "omarchy.grouped-panel" }
|
||||
scan += block("thirdparty", "/third/local-panel", localPanel)
|
||||
var localBar = manifest("local.bar", ["bar"], { bar: "Bar.qml" })
|
||||
localBar.omarchy = { clonedFrom: "omarchy.bar" }
|
||||
scan += block("thirdparty", "/third/local-bar", localBar)
|
||||
scan += block("thirdparty", "/third/bar", manifest("third.bar", ["bar"], { bar: "Bar.qml" }))
|
||||
scan += block("thirdparty", "/third/shadow", manifest("omarchy.first-widget", ["panel"], { panel: "Panel.qml" }))
|
||||
scan += block("thirdparty", "/third/reserved", manifest("omarchy.reserved", ["panel"], { panel: "Panel.qml" }))
|
||||
@@ -100,13 +111,18 @@ ShellRoot {
|
||||
registry.parseScanOutput(scan)
|
||||
|
||||
root.assertDeepEqual(pluginIds(), [
|
||||
"local.bar",
|
||||
"local.first-widget",
|
||||
"local.grouped-panel",
|
||||
"local.hybrid",
|
||||
"omarchy.bar",
|
||||
"omarchy.first-widget",
|
||||
"omarchy.grouped-panel",
|
||||
"omarchy.hybrid",
|
||||
"third.bar",
|
||||
"third.center-widget",
|
||||
"third.panel",
|
||||
"third.right-widget",
|
||||
"third.widget"
|
||||
], "registry merges valid first-party and third-party manifests")
|
||||
|
||||
@@ -148,9 +164,126 @@ ShellRoot {
|
||||
registry.setEnabled("third.widget", false)
|
||||
root.assertDeepEqual(root.config.bar.layout.left, [], "disabling bar widgets removes layout entry")
|
||||
|
||||
root.config = {
|
||||
version: 1,
|
||||
bar: {
|
||||
layout: {
|
||||
left: [{ id: "omarchy.workspaces" }, { id: "omarchy.menu" }],
|
||||
center: [{ id: "omarchy.weather" }, { id: "omarchy.clock" }],
|
||||
right: [{ id: "omarchy.tray" }]
|
||||
}
|
||||
},
|
||||
plugins: []
|
||||
}
|
||||
registry.setEnabled("third.widget", true)
|
||||
root.assertDeepEqual(
|
||||
root.config.bar.layout.left,
|
||||
[{ id: "omarchy.workspaces" }, { id: "third.widget" }, { id: "omarchy.menu" }],
|
||||
"enabling a widget inserts it after its section anchor"
|
||||
)
|
||||
registry.setEnabled("third.center-widget", true)
|
||||
root.assertDeepEqual(
|
||||
root.config.bar.layout.center,
|
||||
[{ id: "omarchy.weather" }, { id: "third.center-widget" }, { id: "omarchy.clock" }],
|
||||
"widgets without a default section use the center anchor"
|
||||
)
|
||||
registry.setEnabled("third.right-widget", true)
|
||||
root.assertDeepEqual(
|
||||
root.config.bar.layout.right,
|
||||
[{ id: "omarchy.tray" }, { id: "third.right-widget" }],
|
||||
"right widgets use the right anchor"
|
||||
)
|
||||
|
||||
root.config = { version: 1, bar: { layout: { left: [], center: [], right: [] } }, plugins: [] }
|
||||
registry.setEnabled("third.right-widget", true)
|
||||
root.assertDeepEqual(root.config.bar.layout.right, [{ id: "third.right-widget" }], "widgets append when their section anchor is absent")
|
||||
|
||||
root.config = {
|
||||
version: 1,
|
||||
bar: { layout: { left: [{ id: "third.widget", size: 3 }], center: [], right: [] } },
|
||||
plugins: []
|
||||
}
|
||||
root.assertEqual(registry.moveBarWidget("third.widget", { section: "right" }), "", "registry moves widgets")
|
||||
root.assertDeepEqual(root.config.bar.layout.right, [{ id: "third.widget", size: 3 }], "registry move preserves widget settings")
|
||||
root.assertEqual(registry.setBarWidget("third.widget", "size", 7, {}), "", "registry sets widget options")
|
||||
root.assertEqual(root.config.bar.layout.right[0].size, 7, "registry persists widget options")
|
||||
|
||||
root.config = { version: 1, bar: { layout: { left: [], center: [], right: [] } }, plugins: [] }
|
||||
registry.setEnabled("third.widget", true, { section: "right", index: 0 })
|
||||
root.assertDeepEqual(root.config.bar.layout.right, [{ id: "third.widget" }], "enabling with placement is one registry transition")
|
||||
|
||||
root.config = {
|
||||
version: 1,
|
||||
bar: { layout: { left: [], center: [{ id: "omarchy.first-widget", size: 4 }], right: [] } },
|
||||
plugins: []
|
||||
}
|
||||
registry.setEnabled("local.first-widget", true)
|
||||
root.assertDeepEqual(
|
||||
root.config.bar.layout.center,
|
||||
[{ id: "local.first-widget", size: 4 }],
|
||||
"enabling a widget clone replaces its source in place"
|
||||
)
|
||||
root.assertEqual(registry.resolveEnabledId("omarchy.first-widget"), "local.first-widget", "enabled clones receive calls made to their source id")
|
||||
registry.setEnabled("omarchy.first-widget", true)
|
||||
root.assertDeepEqual(
|
||||
root.config.bar.layout.center,
|
||||
[{ id: "omarchy.first-widget", size: 4 }],
|
||||
"enabling a clone source switches back without duplicates"
|
||||
)
|
||||
registry.setEnabled("local.first-widget", true)
|
||||
registry.setEnabled("local.first-widget", false)
|
||||
root.assertDeepEqual(
|
||||
root.config.bar.layout.center,
|
||||
[{ id: "omarchy.first-widget", size: 4 }],
|
||||
"disabling a widget clone restores its source in place"
|
||||
)
|
||||
|
||||
root.config = {
|
||||
version: 1,
|
||||
bar: { layout: { left: [], center: ["omarchy.first-widget"], right: [] } },
|
||||
plugins: []
|
||||
}
|
||||
registry.setEnabled("local.first-widget", true)
|
||||
root.assertDeepEqual(root.config.bar.layout.center, [{ id: "local.first-widget" }], "clone replacement normalizes string entries")
|
||||
registry.setEnabled("local.first-widget", false)
|
||||
root.assertDeepEqual(root.config.bar.layout.center, [{ id: "omarchy.first-widget" }], "clone restoration normalizes string entries")
|
||||
|
||||
root.config = {
|
||||
version: 1,
|
||||
bar: { layout: { left: [{ id: "omarchy.hybrid" }], center: [], right: [] } },
|
||||
plugins: []
|
||||
}
|
||||
registry.setEnabled("local.hybrid", true)
|
||||
root.assertDeepEqual(root.config.bar.layout.left, [{ id: "local.hybrid" }], "enabling a multi-kind clone replaces its widget")
|
||||
root.assertDeepEqual(root.config.disabledPlugins, ["omarchy.hybrid"], "enabling a multi-kind clone disables the source")
|
||||
registry.setEnabled("local.hybrid", false)
|
||||
root.assertDeepEqual(root.config.bar.layout.left, [{ id: "omarchy.hybrid" }], "disabling a multi-kind clone restores its widget")
|
||||
root.assertTrue(root.config.disabledPlugins === undefined, "disabling a multi-kind clone enables the source")
|
||||
|
||||
root.config = { version: 1, bar: { layout: { left: [], center: [], right: [] } }, plugins: [] }
|
||||
registry.setEnabled("local.grouped-panel", true)
|
||||
root.assertDeepEqual(root.config.plugins, [{ id: "local.grouped-panel" }], "enabling an ordinary clone adds it")
|
||||
root.assertDeepEqual(root.config.disabledPlugins, ["omarchy.grouped-panel"], "enabling an ordinary clone disables the source")
|
||||
registry.setEnabled("local.grouped-panel", false)
|
||||
root.assertDeepEqual(root.config.plugins, [], "disabling an ordinary clone removes it")
|
||||
root.assertTrue(root.config.disabledPlugins === undefined, "disabling an ordinary clone restores the source")
|
||||
|
||||
root.config = {
|
||||
version: 1,
|
||||
bar: { layout: { left: [], center: [], right: [] } },
|
||||
plugins: [],
|
||||
disabledPlugins: ["omarchy.grouped-panel"]
|
||||
}
|
||||
registry.setEnabled("local.grouped-panel", true)
|
||||
root.assertDeepEqual(root.config.disabledPlugins, ["omarchy.grouped-panel"], "cloning an already-disabled source keeps it disabled")
|
||||
root.assertTrue(root.config.cloneSourceRestores === undefined, "an already-disabled source is not marked for restoration")
|
||||
registry.setEnabled("local.grouped-panel", false)
|
||||
root.assertDeepEqual(root.config.disabledPlugins, ["omarchy.grouped-panel"], "disabling the clone preserves the source's prior disabled state")
|
||||
|
||||
registry.setEnabled("local.bar", true)
|
||||
root.assertEqual(root.config.bar.id, "local.bar", "enabling a cloned bar selects it")
|
||||
registry.setEnabled("local.bar", false)
|
||||
root.assertTrue(root.config.bar.id === undefined, "disabling a cloned built-in bar restores it")
|
||||
|
||||
root.config = {
|
||||
version: 1,
|
||||
@@ -201,8 +334,8 @@ ShellRoot {
|
||||
}
|
||||
registry.setEnabled("omarchy.hybrid", false)
|
||||
root.assertDeepEqual(root.config.bar.layout.left, [], "disabling a multi-kind built-in removes its widget")
|
||||
root.assertDeepEqual(root.config.disabledPlugins, ["omarchy.hybrid"], "disabling a multi-kind built-in unloads its other kinds")
|
||||
root.assertTrue(!registry.isEnabled("omarchy.hybrid"), "a disabled multi-kind built-in is not loadable")
|
||||
root.assertTrue(root.config.disabledPlugins === undefined, "disabling a multi-kind widget records nothing else")
|
||||
root.assertTrue(registry.isEnabled("omarchy.hybrid"), "a multi-kind built-in remains loadable without its widget")
|
||||
|
||||
var localBase = registry.pluginsDir + "/local.clock"
|
||||
root.assertEqual(registry.localPluginIdForPath(localBase + "/BarWidget.qml"), "local.clock", "local clone changes are watched")
|
||||
|
||||
@@ -221,7 +221,7 @@ const pluginAdd = fs.readFileSync(path.join(root, 'bin/omarchy-plugin-add'), 'ut
|
||||
const pluginEnable = fs.readFileSync(path.join(root, 'bin/omarchy-plugin-enable'), 'utf8')
|
||||
assert(
|
||||
/Now using \$id as the bar/.test(pluginEnable)
|
||||
&& /Now using \$id as the bar[\s\S]*?place_bar_widget/.test(pluginAdd),
|
||||
&& /omarchy-plugin-enable "\$id" "\$\{ENABLE_PLACEMENT\[@\]\}"/.test(pluginAdd),
|
||||
'plugin enable reports a bar as replacing the one in use, whether enabled or freshly added'
|
||||
)
|
||||
assert(
|
||||
|
||||
@@ -24,19 +24,14 @@ elif [[ $* == *"listPlugins"* ]]; then
|
||||
find "$HOME/.config/omarchy/plugins" -mindepth 2 -maxdepth 2 -name manifest.json -print0 |
|
||||
xargs -0 -r jq -s 'map({id: .id, enabled: true})'
|
||||
fi
|
||||
elif [[ $* == *"setPluginEnabled"* ]]; then
|
||||
printf 'omarchy-shell %s\n' "$*" >>"$FAKE_CALLS"
|
||||
printf 'ok\n'
|
||||
fi
|
||||
exit 0
|
||||
SH
|
||||
|
||||
for command in omarchy-bar omarchy-bar-plugin; do
|
||||
cat >"$TMPDIR/bin/$command" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s %s\n' "${0##*/}" "$*" >>"$FAKE_CALLS"
|
||||
exec "$OMARCHY_TEST_ROOT/bin/${0##*/}" "$@"
|
||||
SH
|
||||
done
|
||||
|
||||
for command in omarchy-plugin-enable omarchy-plugin-disable omarchy-notification-send; do
|
||||
for command in omarchy-plugin-enable omarchy-notification-send; do
|
||||
cat >"$TMPDIR/bin/$command" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s %s\n' "${0##*/}" "$*" >>"$FAKE_CALLS"
|
||||
@@ -87,36 +82,16 @@ jq -e '
|
||||
' "$clock/manifest.json" >/dev/null || fail "clock clone manifest is incorrect"
|
||||
pass "clone updates identity without replacing the manifest"
|
||||
|
||||
grep -qx 'omarchy-bar-plugin replace omarchy.clock local.clock' "$CALLS" ||
|
||||
fail "clone does not replace an active bar widget"
|
||||
grep -qx 'omarchy-plugin-enable local.clock' "$CALLS" ||
|
||||
fail "clone does not enable the editable copy"
|
||||
grep -qx 'omarchy-notification-send -g Editing Cloned Plugin Original plugin has been replace by clone.' "$CALLS" ||
|
||||
fail "clone does not notify that the editable clone is active"
|
||||
jq -e '
|
||||
any(.bar.layout.center[];
|
||||
.id == "local.clock" and
|
||||
.format == "dddd HH:mm" and
|
||||
.formatAlt == "d MMMM \u0027W\u0027ww yyyy")
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null ||
|
||||
fail "clone does not preserve the active widget's placement and settings"
|
||||
pass "clone switches bar widgets in place and confirms the editable clone"
|
||||
pass "clone enables bar widgets and confirms the editable clone"
|
||||
|
||||
jq '.bar.layout.center += ["omarchy.keyboard-layout"]' \
|
||||
"$TMPDIR/home/.config/omarchy/shell.json" >"$TMPDIR/shell.json"
|
||||
mv "$TMPDIR/shell.json" "$TMPDIR/home/.config/omarchy/shell.json"
|
||||
FAKE_CLONE_CONFIG='{
|
||||
"bar": {
|
||||
"layout": {
|
||||
"left": [],
|
||||
"center": ["omarchy.keyboard-layout"],
|
||||
"right": []
|
||||
}
|
||||
}
|
||||
}' clone_plugin omarchy.keyboard-layout >/dev/null
|
||||
jq -e '
|
||||
any(.bar.layout.center[]; .id == "local.keyboard-layout")
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null ||
|
||||
fail "clone does not replace a string-form bar entry"
|
||||
pass "clone replaces legacy string-form bar entries"
|
||||
clone_plugin omarchy.keyboard-layout >/dev/null
|
||||
grep -qx 'omarchy-plugin-enable local.keyboard-layout' "$CALLS" ||
|
||||
fail "clone does not enable a clone of a legacy string-form bar entry"
|
||||
pass "clone enables clones of legacy string-form bar entries"
|
||||
|
||||
clone_plugin omarchy.menu >/dev/null
|
||||
menu="$TMPDIR/home/.config/omarchy/plugins/local.menu"
|
||||
@@ -130,29 +105,26 @@ jq -e '
|
||||
.entryPoints.menu == "Menu.qml" and
|
||||
.entryPoints.barWidget == "BarWidget.qml"
|
||||
' "$menu/manifest.json" >/dev/null || fail "menu clone loses plugin kinds"
|
||||
grep -qx 'omarchy-plugin-disable omarchy.menu' "$CALLS" ||
|
||||
fail "clone leaves the built-in half of a multi-kind plugin enabled"
|
||||
pass "clone preserves multi-kind plugins"
|
||||
grep -qx 'omarchy-plugin-enable local.menu' "$CALLS" ||
|
||||
fail "clone does not enable a multi-kind plugin"
|
||||
pass "clone preserves and enables multi-kind plugins"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" PATH="$TMPDIR/bin:$ROOT/bin:$PATH" \
|
||||
remove_output=$(HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" PATH="$TMPDIR/bin:$ROOT/bin:$PATH" \
|
||||
FAKE_CALLS="$CALLS" OMARCHY_TEST_ROOT="$ROOT" \
|
||||
omarchy-plugin-remove local.menu --yes >/dev/null
|
||||
grep -qx 'omarchy-bar-plugin replace local.menu omarchy.menu' "$CALLS" &&
|
||||
grep -qx 'omarchy-plugin-enable omarchy.menu' "$CALLS" ||
|
||||
fail "removing a clone does not restore its built-in source"
|
||||
pass "removing a clone restores its built-in source"
|
||||
omarchy-plugin-remove local.menu --yes)
|
||||
grep -qx 'omarchy-shell shell setPluginEnabled local.menu false' "$CALLS" ||
|
||||
fail "removing an enabled clone does not disable it first"
|
||||
grep -q 'Restored omarchy.menu.' <<<"$remove_output" ||
|
||||
fail "removing a clone does not report its restored source"
|
||||
pass "removing an enabled clone goes through plugin disable and reports its source"
|
||||
|
||||
clone_plugin omarchy.active-window >/dev/null
|
||||
[[ -f $TMPDIR/home/.config/omarchy/plugins/local.active-window/ActiveWindow.qml ]] ||
|
||||
fail "flat bar plugin clone is incomplete"
|
||||
pass "flat bar plugins clone from adjacent manifests"
|
||||
|
||||
grep -qx 'omarchy-bar-plugin add local.active-window' "$CALLS" ||
|
||||
grep -qx 'omarchy-plugin-enable local.active-window' "$CALLS" ||
|
||||
fail "clone does not activate a bar widget whose source is absent"
|
||||
jq -e '
|
||||
any(.bar.layout.left[]; .id == "local.active-window")
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null ||
|
||||
fail "clone does not add the absent widget using its default placement"
|
||||
pass "clone activates an absent bar widget"
|
||||
|
||||
clone_plugin omarchy.indicators >/dev/null
|
||||
@@ -170,17 +142,13 @@ clone_plugin omarchy.tray >/dev/null
|
||||
pass "flat bar plugins keep local script dependencies"
|
||||
|
||||
clone_plugin omarchy.bar >/dev/null
|
||||
grep -qx 'omarchy-bar use local.bar' "$CALLS" ||
|
||||
grep -qx 'omarchy-plugin-enable local.bar' "$CALLS" ||
|
||||
fail "clone does not select a cloned bar"
|
||||
jq -e '.bar.id == "local.bar"' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null ||
|
||||
fail "clone does not persist the cloned bar as active"
|
||||
pass "clone switches full bars"
|
||||
|
||||
clone_plugin omarchy.background >/dev/null
|
||||
grep -qx 'omarchy-plugin-enable local.background' "$CALLS" ||
|
||||
fail "clone does not enable an ordinary cloned plugin"
|
||||
grep -qx 'omarchy-plugin-disable omarchy.background' "$CALLS" ||
|
||||
fail "clone does not disable the ordinary source plugin"
|
||||
pass "clone switches ordinary plugins"
|
||||
|
||||
mkdir -p "$TMPDIR/home/.config/omarchy/plugins/acme.example"
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
mkdir -p "$TMPDIR/home" "$TMPDIR/bin"
|
||||
calls="$TMPDIR/calls"
|
||||
|
||||
cat >"$TMPDIR/bin/omarchy-shell" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$*" >>"$OMARCHY_TEST_CALLS"
|
||||
printf 'ok\n'
|
||||
SH
|
||||
chmod +x "$TMPDIR/bin/omarchy-shell"
|
||||
|
||||
run_enable() {
|
||||
HOME="$TMPDIR/home" \
|
||||
OMARCHY_PATH="$ROOT" \
|
||||
OMARCHY_TEST_CALLS="$calls" \
|
||||
PATH="$TMPDIR/bin:$ROOT/bin:$PATH" \
|
||||
omarchy-plugin-enable "$@"
|
||||
}
|
||||
|
||||
run_enable omarchy.active-window --section right >/dev/null
|
||||
grep -Fqx 'shell enablePlugin omarchy.active-window {"section":"right"}' "$calls" ||
|
||||
fail "plugin enable did not combine activation and placement"
|
||||
pass "plugin enable combines activation and placement in one shell mutation"
|
||||
|
||||
run_enable omarchy.clock --before omarchy.weather >/dev/null
|
||||
grep -Fqx 'shell enablePlugin omarchy.clock {"before":"omarchy.weather"}' "$calls" ||
|
||||
fail "plugin enable did not preserve relative placement"
|
||||
pass "plugin enable forwards relative placement"
|
||||
|
||||
run_enable omarchy.dropbox >/dev/null
|
||||
grep -Fqx 'shell enablePlugin omarchy.dropbox {}' "$calls" ||
|
||||
fail "plugin enable did not use manifest-default placement"
|
||||
pass "plugin enable leaves default placement to the registry"
|
||||
|
||||
if run_enable omarchy.bar --section right >/dev/null 2>&1; then
|
||||
fail "plugin enable accepted placement for a full bar"
|
||||
fi
|
||||
pass "plugin enable rejects placement for full bars"
|
||||
@@ -296,7 +296,7 @@ if (( worst > screens - 1 )); then
|
||||
fi
|
||||
pass "each widget registers its IPC handler once per screen"
|
||||
|
||||
HOME="$test_home" OMARCHY_PATH="$test_root" PATH="$ROOT/bin:$PATH" "$ROOT/bin/omarchy-bar-plugin" remove omarchy.audio
|
||||
HOME="$test_home" OMARCHY_PATH="$test_root" PATH="$ROOT/bin:$PATH" "$ROOT/bin/omarchy-plugin-disable" omarchy.audio
|
||||
|
||||
for _ in {1..80}; do
|
||||
shell_config=$(shell_ipc shell listShellConfig 2>/dev/null || true)
|
||||
@@ -313,7 +313,7 @@ done
|
||||
|
||||
jq -e 'all(.bar.layout.right[]; (.id // .) != "omarchy.audio")' <<<"$shell_config" >/dev/null || {
|
||||
printf 'Shell config after reload:\n%s\n' "$shell_config" | jq . >&2
|
||||
fail_with_log "bar remove reloads shell config"
|
||||
fail_with_log "plugin disable reloads shell config"
|
||||
}
|
||||
|
||||
jq -e 'all(.[]; .id != "omarchy.audio")' <<<"$geometry" >/dev/null || {
|
||||
|
||||
Reference in New Issue
Block a user