Split omarchy-plugin into siblings and consolidate bar config into omarchy-bar
omarchy-plugin mixed plugin lifecycle, clone/edit, and bar-layout mutation.
The bar layout was mutated by two separate engines (an embedded Python
mutate_bar here and jq in omarchy-config-shell-bar), each with its own
manifest walker.
- omarchy-plugin is now a ~220-line router; clone and edit move to
omarchy-plugin-clone and omarchy-plugin-edit, matching the existing
plugin-{source,add,update,remove,available,validate,scan} sibling pattern
- omarchy-config-shell-bar is replaced by omarchy-bar, the single bar engine
(show/layout/list/options/use/reset/add/move/remove/set/replace/position/
transparent/settings) with one jq pipeline; the Python mutate_bar and its
python3 dependency are gone
- omarchy-plugin-catalog is the shared manifest scanner used by omarchy-bar
(widget/option enumeration, add validation) and omarchy-plugin-clone
(source enumeration), replacing three find|jq re-implementations
- service install/remove and refresh-shell call omarchy-bar; docs and tests
updated to the new command surface
This commit is contained in:
@@ -27,6 +27,7 @@ declare -A BINARY_TO_KEY
|
||||
declare -A GROUP_DESCRIPTIONS
|
||||
|
||||
GROUP_DESCRIPTIONS[audio]="Audio input and output controls"
|
||||
GROUP_DESCRIPTIONS[bar]="Omarchy shell bar layout and settings"
|
||||
GROUP_DESCRIPTIONS[battery]="Battery status helpers"
|
||||
GROUP_DESCRIPTIONS[bluetooth]="Bluetooth device controls"
|
||||
GROUP_DESCRIPTIONS[branch]="Omarchy git branch management"
|
||||
|
||||
Executable
+844
@@ -0,0 +1,844 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Mutate the user shell.json bar layout, active bar option, and bar settings
|
||||
# omarchy:group=bar
|
||||
# omarchy:args=show | layout [--json] | list [--json] [--all] | options [--json] | use <plugin> | reset | add [plugin] [placement] | move <plugin> [placement] | remove [plugin] [placement] | set <plugin> <key> <value> [--json] [placement] | replace <old> <new> | position <top|bottom|left|right> | transparent <true|false> | settings
|
||||
# omarchy:examples=omarchy bar show | omarchy bar list | omarchy bar add omarchy.tailscale | omarchy bar add omarchy.clock --section center --before omarchy.weather | omarchy bar move omarchy.clock --section center --index 0 | omarchy bar remove omarchy.tailscale | omarchy bar set omarchy.clock format HH:mm | omarchy bar use local.neon-bar | omarchy bar position top | omarchy bar transparent true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CONFIG_FILE="$HOME/.config/omarchy/shell.json"
|
||||
OMARCHY_ROOT="${OMARCHY_PATH:-}"
|
||||
DEFAULTS_FILE="$OMARCHY_ROOT/config/omarchy/shell.json"
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: omarchy bar <command> [args...]
|
||||
|
||||
Inspect:
|
||||
show Current bar JSON
|
||||
layout [--json] Current bar layout (section/index/widget)
|
||||
list [--json] [--all] Addable bar widgets (alias: available, widgets)
|
||||
options [--json] Bar replacement options
|
||||
|
||||
Select:
|
||||
use <id> Use a bar option as the active bar
|
||||
reset Return to the built-in Omarchy bar
|
||||
|
||||
Edit layout:
|
||||
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
|
||||
|
||||
Other:
|
||||
replace <old-id> <new-id> Replace the first instance of a widget id
|
||||
position <top|bottom|left|right> Bar position
|
||||
transparent <true|false> Bar transparency
|
||||
settings Open the inline bar config panel
|
||||
|
||||
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).
|
||||
|
||||
Examples:
|
||||
omarchy bar show
|
||||
omarchy bar layout
|
||||
omarchy bar list
|
||||
omarchy bar add omarchy.tailscale
|
||||
omarchy bar add omarchy.clock --section center --before omarchy.weather
|
||||
omarchy bar move omarchy.clock --section center --index 0
|
||||
omarchy bar remove omarchy.tailscale
|
||||
omarchy bar remove omarchy.tailscale --all
|
||||
omarchy bar set omarchy.clock format HH:mm
|
||||
omarchy bar set omarchy.indicators items '["Dnd","NightLight"]' --json
|
||||
omarchy bar use local.neon-bar
|
||||
omarchy bar reset
|
||||
omarchy bar position top
|
||||
omarchy bar transparent true
|
||||
USAGE
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "omarchy-bar: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_command() {
|
||||
command -v "$1" >/dev/null 2>&1 || fail "$1 is required"
|
||||
}
|
||||
|
||||
require_omarchy_path() {
|
||||
[[ -n $OMARCHY_ROOT ]] || fail "OMARCHY_PATH is not set"
|
||||
}
|
||||
|
||||
refresh_shell_config() {
|
||||
if ! omarchy-shell shell reloadConfig >/dev/null 2>&1; then
|
||||
omarchy-shell -q shell rescanPlugins >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
source_file() {
|
||||
local source="$CONFIG_FILE"
|
||||
|
||||
if [[ ! -s $source ]]; then
|
||||
require_omarchy_path
|
||||
source="$DEFAULTS_FILE"
|
||||
fi
|
||||
|
||||
[[ -s $source ]] || fail "could not find shell config or defaults"
|
||||
printf '%s\n' "$source"
|
||||
}
|
||||
|
||||
# jq defs shared across mutations. All `def`s come first so the pipeline that
|
||||
# follows them stays valid jq. `entry_id` and the shape helpers are used by both
|
||||
# the normalize pipeline and the resolve_target/resolve_source helpers.
|
||||
JQ_DEFS='
|
||||
def object_or_empty: if type == "object" then . else {} end;
|
||||
def array_or_empty: if type == "array" then . else [] end;
|
||||
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;
|
||||
'
|
||||
|
||||
# jq pipeline that normalizes shell.json into a well-shaped object with
|
||||
# version=1, bar.layout.{left,center,right} arrays, and plugins array. Every
|
||||
# mutation pipes through this so downstream jq can assume structure.
|
||||
NORMALIZE='
|
||||
object_or_empty
|
||||
| .version = 1
|
||||
| .bar = (.bar | object_or_empty)
|
||||
| .bar.layout = (.bar.layout | object_or_empty)
|
||||
| .bar.layout.left = (.bar.layout.left | array_or_empty)
|
||||
| .bar.layout.center = (.bar.layout.center | array_or_empty)
|
||||
| .bar.layout.right = (.bar.layout.right | array_or_empty)
|
||||
| .plugins = (.plugins | array_or_empty)
|
||||
'
|
||||
|
||||
# Apply a jq program to the source file and atomically write the result to the
|
||||
# user config, then refresh the running shell. Extra args after the program are
|
||||
# forwarded to jq (e.g. --arg/--argjson).
|
||||
_BAR_TMP=""
|
||||
cleanup_bar_tmp() {
|
||||
if [[ -n ${_BAR_TMP:-} ]]; then rm -f "$_BAR_TMP"; fi
|
||||
}
|
||||
trap cleanup_bar_tmp EXIT
|
||||
|
||||
commit() {
|
||||
local program="$1"
|
||||
shift
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
_BAR_TMP=$(mktemp)
|
||||
if ! jq -S -e "$@" "$program" "$(source_file)" >"$_BAR_TMP"; then
|
||||
rm -f "$_BAR_TMP"
|
||||
_BAR_TMP=""
|
||||
fail "could not update shell config"
|
||||
fi
|
||||
mv "$_BAR_TMP" "$CONFIG_FILE"
|
||||
_BAR_TMP=""
|
||||
refresh_shell_config
|
||||
}
|
||||
|
||||
section_valid() {
|
||||
[[ $1 =~ ^(left|center|right)$ ]]
|
||||
}
|
||||
|
||||
validate_section() {
|
||||
section_valid "$1" || fail "section must be left, center, or right"
|
||||
}
|
||||
|
||||
validate_index() {
|
||||
[[ $1 =~ ^[0-9]+$ ]] || fail "index must be a non-negative integer"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- catalog
|
||||
|
||||
catalog_json() {
|
||||
require_omarchy_path
|
||||
require_command jq
|
||||
omarchy-plugin-catalog
|
||||
}
|
||||
|
||||
widget_is_known() {
|
||||
local id="$1"
|
||||
catalog_json | jq -e --arg id "$id" '
|
||||
any(.[]; (.kinds | index("bar-widget")) and .id == $id)
|
||||
' >/dev/null
|
||||
}
|
||||
|
||||
current_bar_ids_json() {
|
||||
jq -c '
|
||||
def array_or_empty: if type == "array" then . else [] end;
|
||||
def entry_id: if type == "object" then (.id // "" | tostring) else tostring end;
|
||||
|
||||
["left", "center", "right"] as $sections |
|
||||
[ $sections[] as $section |
|
||||
((.bar.layout[$section] // []) | array_or_empty)[] |
|
||||
entry_id |
|
||||
select(length > 0)
|
||||
]
|
||||
' "$(source_file)"
|
||||
}
|
||||
|
||||
current_bar_entries_json() {
|
||||
jq -c '
|
||||
def array_or_empty: if type == "array" then . else [] end;
|
||||
def entry_id($entry): if ($entry | type) == "object" then ($entry.id // "" | tostring) else ($entry | tostring) end;
|
||||
|
||||
["left", "center", "right"] as $sections |
|
||||
[ $sections[] as $section |
|
||||
((.bar.layout[$section] // []) | array_or_empty | to_entries[]) |
|
||||
{ section: $section, index: .key, id: entry_id(.value) } |
|
||||
select(.id | length > 0)
|
||||
]
|
||||
' "$(source_file)"
|
||||
}
|
||||
|
||||
available_widgets_json() {
|
||||
local include_all="$1"
|
||||
local current_ids
|
||||
current_ids=$(current_bar_ids_json)
|
||||
|
||||
catalog_json | jq --argjson currentIds "$current_ids" --argjson includeAll "$include_all" '
|
||||
map(select((.kinds | index("bar-widget")) and .barWidgetPath != null))
|
||||
| map({
|
||||
id: .id,
|
||||
name: (.barWidget.displayName // .name // .id),
|
||||
description: (.barWidget.description // .description // ""),
|
||||
category: (.barWidget.category // "Plugin"),
|
||||
allowMultiple: ((.barWidget.allowMultiple // false) == true)
|
||||
})
|
||||
| unique_by(.id)
|
||||
| map(. as $widget |
|
||||
$widget + {
|
||||
inBar: (($currentIds | index($widget.id)) != null),
|
||||
addable: ($widget.allowMultiple or (($currentIds | index($widget.id)) == null))
|
||||
})
|
||||
| map(select($includeAll or .addable))
|
||||
| sort_by(.category, .name, .id)
|
||||
'
|
||||
}
|
||||
|
||||
print_available_widgets() {
|
||||
local include_all="false"
|
||||
local json="false"
|
||||
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--all) include_all="true" ;;
|
||||
--json) json="true" ;;
|
||||
-h | --help) usage; exit 0 ;;
|
||||
*) fail "unknown list option: $1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ $json == "true" ]]; then
|
||||
available_widgets_json "$include_all"
|
||||
return
|
||||
fi
|
||||
|
||||
available_widgets_json "$include_all" | jq -r '
|
||||
.[] |
|
||||
[
|
||||
.id,
|
||||
(if .inBar and .allowMultiple then "addable*" elif .inBar then "in-bar" else "addable" end),
|
||||
.category,
|
||||
.name,
|
||||
.description
|
||||
] | @tsv
|
||||
' | awk -F '\t' '
|
||||
BEGIN { printf "%-28s %-10s %-14s %-22s %s\n", "ID", "STATUS", "CATEGORY", "NAME", "DESCRIPTION" }
|
||||
{ printf "%-28s %-10s %-14s %-22s %s\n", $1, $2, $3, $4, $5 }
|
||||
'
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------- bar options
|
||||
|
||||
current_bar_option_id() {
|
||||
jq -r '(.bar.id // "omarchy.bar") | tostring' "$(source_file)"
|
||||
}
|
||||
|
||||
available_bar_options_json() {
|
||||
local current_id
|
||||
current_id=$(current_bar_option_id)
|
||||
|
||||
catalog_json | jq --arg currentId "$current_id" '
|
||||
map(select((.kinds | index("bar")) and .barPath != null))
|
||||
| map({
|
||||
id: .id,
|
||||
name: (.name // .id),
|
||||
description: (.description // ""),
|
||||
firstParty: .firstParty,
|
||||
active: (.id == $currentId)
|
||||
})
|
||||
| unique_by(.id)
|
||||
| sort_by((if .id == "omarchy.bar" then 0 else 1 end), .name, .id)
|
||||
'
|
||||
}
|
||||
|
||||
print_bar_options() {
|
||||
local json="false"
|
||||
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--json) json="true" ;;
|
||||
-h | --help) usage; exit 0 ;;
|
||||
*) fail "unknown options option: $1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ $json == "true" ]]; then
|
||||
available_bar_options_json
|
||||
return
|
||||
fi
|
||||
|
||||
available_bar_options_json | jq -r '
|
||||
.[] |
|
||||
[
|
||||
.id,
|
||||
(if .active then "active" else "available" end),
|
||||
(if .firstParty then "built-in" else "plugin" end),
|
||||
.name,
|
||||
.description
|
||||
] | @tsv
|
||||
' | awk -F '\t' '
|
||||
BEGIN { printf "%-32s %-10s %-8s %-22s %s\n", "ID", "STATUS", "SOURCE", "NAME", "DESCRIPTION" }
|
||||
{ printf "%-32s %-10s %-8s %-22s %s\n", $1, $2, $3, $4, $5 }
|
||||
'
|
||||
}
|
||||
|
||||
bar_option_exists() {
|
||||
available_bar_options_json | jq -e --arg id "$1" 'any(.[]; .id == $id)' >/dev/null
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- 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: inspect
|
||||
|
||||
cmd_show() {
|
||||
(( $# == 0 )) || fail "show does not take arguments"
|
||||
jq '.bar // {}' "$(source_file)"
|
||||
}
|
||||
|
||||
cmd_layout() {
|
||||
local json="false"
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--json) json="true" ;;
|
||||
-h | --help) usage; exit 0 ;;
|
||||
*) fail "unknown layout option: $1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ $json == "true" ]]; then
|
||||
jq '.bar.layout // { left: [], center: [], right: [] }' "$(source_file)"
|
||||
return
|
||||
fi
|
||||
|
||||
jq -r '
|
||||
(.bar.layout // { left: [], center: [], right: [] }) as $layout |
|
||||
["left", "center", "right"][] as $section |
|
||||
($layout[$section] // []) as $entries |
|
||||
if ($entries | length) == 0 then
|
||||
"\($section)\t-\t-"
|
||||
else
|
||||
$entries | to_entries[] | "\($section)\t\(.key)\t\(.value.id // "<missing>")"
|
||||
end
|
||||
' "$(source_file)" | awk '
|
||||
BEGIN { printf "%-8s %-5s %s\n", "SECTION", "INDEX", "WIDGET" }
|
||||
{ printf "%-8s %-5s %s\n", $1, $2, $3 }
|
||||
'
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------- commands: select
|
||||
|
||||
cmd_use() {
|
||||
local plugin="${1:-}"
|
||||
[[ -n $plugin ]] || fail "bar option id is required"
|
||||
[[ $# -eq 1 ]] || fail "use takes a single bar option id"
|
||||
if [[ $plugin == "default" || $plugin == "built-in" ]]; then
|
||||
plugin="omarchy.bar"
|
||||
fi
|
||||
bar_option_exists "$plugin" || fail "$plugin is not a known bar option; run 'omarchy bar options'"
|
||||
|
||||
if [[ $plugin == "omarchy.bar" ]]; then
|
||||
commit "$JQ_DEFS $NORMALIZE | del(.bar.id)"
|
||||
else
|
||||
commit "$JQ_DEFS $NORMALIZE | .bar.id = \$plugin" --arg plugin "$plugin"
|
||||
fi
|
||||
echo "Using $plugin as the active bar"
|
||||
}
|
||||
|
||||
cmd_reset() {
|
||||
(( $# == 0 )) || fail "reset does not take arguments"
|
||||
commit "$JQ_DEFS $NORMALIZE | del(.bar.id)"
|
||||
echo "Reset to the built-in Omarchy bar"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------- commands: edit
|
||||
|
||||
cmd_add() {
|
||||
local id="${1:-}"
|
||||
[[ -n $id ]] || fail "bar 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
|
||||
|
||||
require_omarchy_path
|
||||
widget_is_known "$id" || fail "$id is not a known bar widget; run 'omarchy bar list' to see valid ids"
|
||||
|
||||
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.
|
||||
local default_section="${PLACEMENT_SECTION:-right}"
|
||||
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)) 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" \
|
||||
--arg anchor "$(anchor_for_section "$default_section")"
|
||||
else
|
||||
local default_section="${PLACEMENT_SECTION:-right}"
|
||||
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 'bar 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"
|
||||
}
|
||||
|
||||
anchor_for_section() {
|
||||
case "$1" in
|
||||
left) printf 'omarchy.workspaces' ;;
|
||||
center) printf 'omarchy.weather' ;;
|
||||
right) printf 'omarchy.tray' ;;
|
||||
*) fail "section must be left, center, or right" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cmd_move() {
|
||||
local id="${1:-}"
|
||||
[[ -n $id ]] || fail "bar move requires a widget id"
|
||||
shift
|
||||
parse_placement "$@"
|
||||
|
||||
local default_section="${PLACEMENT_SECTION:-}"
|
||||
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 "$PLACEMENT_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 bar 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 "bar set requires a widget id"
|
||||
[[ -n $key ]] || fail "bar set requires a setting key"
|
||||
(( $# >= 3 )) || fail "bar 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 -e . <<<"$value" >/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 bar 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 "bar replace requires the source widget id"
|
||||
[[ -n $new ]] || fail "bar replace requires the replacement widget id"
|
||||
[[ $# -eq 2 ]] || fail "bar replace takes two widget ids"
|
||||
|
||||
local prog
|
||||
prog=$(cat <<JQ
|
||||
$JQ_DEFS $NORMALIZE
|
||||
| resolve_source as \$source
|
||||
| if \$source.entry | type != "object" then error("widget entry must be an object") else . end
|
||||
| .bar.layout[\$source.section][\$source.index].id = \$new
|
||||
JQ
|
||||
)
|
||||
commit "$prog" \
|
||||
--arg widgetId "$old" \
|
||||
--arg new "$new" \
|
||||
--arg fromSection "$PLACEMENT_FROM_SECTION" \
|
||||
--arg fromIndex "$PLACEMENT_FROM_INDEX"
|
||||
echo "Replaced $old with $new"
|
||||
}
|
||||
|
||||
cmd_position() {
|
||||
local position="${1:-}"
|
||||
[[ -n $position ]] || fail "position is required"
|
||||
[[ $# -eq 1 ]] || fail "position takes a single value"
|
||||
[[ $position =~ ^(top|bottom|left|right)$ ]] || fail "position must be top, bottom, left, or right"
|
||||
commit "$JQ_DEFS $NORMALIZE | .bar.position = \$position" --arg position "$position"
|
||||
echo "Bar position set to $position"
|
||||
}
|
||||
|
||||
cmd_transparent() {
|
||||
local transparent="${1:-}"
|
||||
[[ -n $transparent ]] || fail "transparent is required"
|
||||
[[ $# -eq 1 ]] || fail "transparent takes a single value"
|
||||
[[ $transparent =~ ^(true|false)$ ]] || fail "transparent must be true or false"
|
||||
commit "$JQ_DEFS $NORMALIZE | .bar.transparent = \$transparent" --argjson transparent "$transparent"
|
||||
echo "Bar transparency set to $transparent"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------- dispatch
|
||||
|
||||
command="${1:-}"
|
||||
[[ $# -gt 0 ]] && shift || true
|
||||
|
||||
case "$command" in
|
||||
show | current)
|
||||
cmd_show "$@"
|
||||
;;
|
||||
layout)
|
||||
cmd_layout "$@"
|
||||
;;
|
||||
list | available | widgets)
|
||||
print_available_widgets "$@"
|
||||
;;
|
||||
options | option)
|
||||
print_bar_options "$@"
|
||||
;;
|
||||
selected | active)
|
||||
(( $# == 0 )) || fail "$command does not take arguments"
|
||||
current_bar_option_id
|
||||
;;
|
||||
use)
|
||||
cmd_use "$@"
|
||||
;;
|
||||
reset)
|
||||
cmd_reset "$@"
|
||||
;;
|
||||
add)
|
||||
cmd_add "$@"
|
||||
;;
|
||||
move)
|
||||
cmd_move "$@"
|
||||
;;
|
||||
remove | rm | drop)
|
||||
cmd_remove "$@"
|
||||
;;
|
||||
set)
|
||||
cmd_set "$@"
|
||||
;;
|
||||
replace)
|
||||
cmd_replace "$@"
|
||||
;;
|
||||
position)
|
||||
cmd_position "$@"
|
||||
;;
|
||||
transparent)
|
||||
cmd_transparent "$@"
|
||||
;;
|
||||
settings | edit)
|
||||
(( $# == 0 )) || fail "settings does not take arguments"
|
||||
exec omarchy-launch-bar-settings
|
||||
;;
|
||||
-h | --help | help | "")
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
fail "unknown command: $command"
|
||||
;;
|
||||
esac
|
||||
@@ -1,601 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Mutate the user shell.json bar layout and active bar option
|
||||
# omarchy:args=show | options [--json] | use <plugin> | reset | list [--json] [--all] | add [plugin] [left|center|right] | remove [plugin] | drop [plugin] | position <top|bottom|left|right> | transparent <true|false>
|
||||
# omarchy:examples=omarchy config shell bar show | omarchy config shell bar options | omarchy config shell bar use local.neon-bar | omarchy config shell bar reset | omarchy config shell bar list | omarchy config shell bar add omarchy.tailscale | omarchy config shell bar position top | omarchy config shell bar transparent true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CONFIG_FILE="$HOME/.config/omarchy/shell.json"
|
||||
OMARCHY_ROOT="${OMARCHY_PATH:-}"
|
||||
DEFAULTS_FILE="$OMARCHY_ROOT/config/omarchy/shell.json"
|
||||
|
||||
usage() {
|
||||
echo "Usage: omarchy-config-shell-bar show | options [--json] | use <plugin> | reset | list [--json] [--all] | add [plugin] [left|center|right] | remove [plugin] | drop [plugin] | position <top|bottom|left|right> | transparent <true|false>" >&2
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "omarchy-config-shell-bar: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
refresh_shell_config() {
|
||||
if ! omarchy-shell shell reloadConfig >/dev/null 2>&1; then
|
||||
omarchy-shell -q shell rescanPlugins >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
source_file() {
|
||||
local source="$CONFIG_FILE"
|
||||
|
||||
if [[ ! -s $source ]]; then
|
||||
[[ -n $OMARCHY_ROOT ]] || fail "OMARCHY_PATH is not set"
|
||||
source="$DEFAULTS_FILE"
|
||||
fi
|
||||
|
||||
[[ -s $source ]] || fail "could not find shell config or defaults"
|
||||
printf '%s\n' "$source"
|
||||
}
|
||||
|
||||
bar_widget_manifest_paths() {
|
||||
{
|
||||
if [[ -n $OMARCHY_ROOT && -d $OMARCHY_ROOT/shell/plugins ]]; then
|
||||
find "$OMARCHY_ROOT/shell/plugins" -mindepth 2 -maxdepth 4 -type f \( -name manifest.json -o -name '*.manifest.json' \) 2>/dev/null
|
||||
fi
|
||||
|
||||
if [[ -d $HOME/.config/omarchy/plugins ]]; then
|
||||
find -L "$HOME/.config/omarchy/plugins" -mindepth 2 -maxdepth 2 -type f -name manifest.json 2>/dev/null
|
||||
fi
|
||||
} | sort -u
|
||||
}
|
||||
|
||||
bar_option_manifest_paths() {
|
||||
{
|
||||
if [[ -n $OMARCHY_ROOT && -d $OMARCHY_ROOT/shell/plugins ]]; then
|
||||
find "$OMARCHY_ROOT/shell/plugins" -mindepth 2 -maxdepth 4 -type f \( -name manifest.json -o -name '*.manifest.json' \) 2>/dev/null
|
||||
fi
|
||||
|
||||
if [[ -d $HOME/.config/omarchy/plugins ]]; then
|
||||
find -L "$HOME/.config/omarchy/plugins" -mindepth 2 -maxdepth 2 -type f -name manifest.json 2>/dev/null
|
||||
fi
|
||||
} | sort -u
|
||||
}
|
||||
|
||||
current_bar_option_id() {
|
||||
jq -r '(.bar.id // "omarchy.bar") | tostring' "$(source_file)"
|
||||
}
|
||||
|
||||
available_bar_options_json() {
|
||||
local current_id
|
||||
local manifest_paths=()
|
||||
|
||||
current_id=$(current_bar_option_id)
|
||||
mapfile -t manifest_paths < <(bar_option_manifest_paths)
|
||||
|
||||
if (( ${#manifest_paths[@]} == 0 )); then
|
||||
printf '[]\n'
|
||||
return
|
||||
fi
|
||||
|
||||
jq -s --arg currentId "$current_id" '
|
||||
map(
|
||||
select(((.kinds // []) | index("bar")) and ((.entryPoints.bar // "") != "")) |
|
||||
{
|
||||
id: (.id // ""),
|
||||
name: (.name // .id // ""),
|
||||
description: (.description // ""),
|
||||
firstParty: ((.id // "") | startswith("omarchy.")),
|
||||
active: ((.id // "") == $currentId)
|
||||
} |
|
||||
select(.id != "")
|
||||
) |
|
||||
unique_by(.id) |
|
||||
sort_by((if .id == "omarchy.bar" then 0 else 1 end), .name, .id)
|
||||
' "${manifest_paths[@]}"
|
||||
}
|
||||
|
||||
print_bar_options() {
|
||||
local json="false"
|
||||
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--json)
|
||||
json="true"
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
fail "unknown options option: $1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ $json == "true" ]]; then
|
||||
available_bar_options_json
|
||||
return
|
||||
fi
|
||||
|
||||
available_bar_options_json | jq -r '
|
||||
.[] |
|
||||
[
|
||||
.id,
|
||||
(if .active then "active" else "available" end),
|
||||
(if .firstParty then "built-in" else "plugin" end),
|
||||
.name,
|
||||
.description
|
||||
] | @tsv
|
||||
' | awk -F '\t' '
|
||||
BEGIN { printf "%-32s %-10s %-8s %-22s %s\n", "ID", "STATUS", "SOURCE", "NAME", "DESCRIPTION" }
|
||||
{ printf "%-32s %-10s %-8s %-22s %s\n", $1, $2, $3, $4, $5 }
|
||||
'
|
||||
}
|
||||
|
||||
bar_option_exists() {
|
||||
local id="$1"
|
||||
available_bar_options_json | jq -e --arg id "$id" 'any(.[]; .id == $id)' >/dev/null
|
||||
}
|
||||
|
||||
current_bar_ids_json() {
|
||||
jq -c '
|
||||
def array_or_empty: if type == "array" then . else [] end;
|
||||
def entry_id: if type == "object" then (.id // "" | tostring) else tostring end;
|
||||
|
||||
["left", "center", "right"] as $sections |
|
||||
[ $sections[] as $section |
|
||||
((.bar.layout[$section] // []) | array_or_empty)[] |
|
||||
entry_id |
|
||||
select(length > 0)
|
||||
]
|
||||
' "$(source_file)"
|
||||
}
|
||||
|
||||
current_bar_entries_json() {
|
||||
jq -c '
|
||||
def array_or_empty: if type == "array" then . else [] end;
|
||||
def entry_id($entry): if ($entry | type) == "object" then ($entry.id // "" | tostring) else ($entry | tostring) end;
|
||||
|
||||
["left", "center", "right"] as $sections |
|
||||
[ $sections[] as $section |
|
||||
((.bar.layout[$section] // []) | array_or_empty | to_entries[]) |
|
||||
{ section: $section, index: .key, id: entry_id(.value) } |
|
||||
select(.id | length > 0)
|
||||
]
|
||||
' "$(source_file)"
|
||||
}
|
||||
|
||||
available_widgets_json() {
|
||||
local include_all="$1"
|
||||
local current_ids
|
||||
local manifest_paths=()
|
||||
|
||||
current_ids=$(current_bar_ids_json)
|
||||
mapfile -t manifest_paths < <(bar_widget_manifest_paths)
|
||||
|
||||
if (( ${#manifest_paths[@]} == 0 )); then
|
||||
printf '[]\n'
|
||||
return
|
||||
fi
|
||||
|
||||
jq -s --argjson currentIds "$current_ids" --argjson includeAll "$include_all" '
|
||||
map(
|
||||
select(((.kinds // []) | index("bar-widget")) and ((.entryPoints.barWidget // "") != "")) |
|
||||
{
|
||||
id: (.id // ""),
|
||||
name: (.barWidget.displayName // .name // .id // ""),
|
||||
description: (.barWidget.description // .description // ""),
|
||||
category: (.barWidget.category // "Plugin"),
|
||||
allowMultiple: (.barWidget.allowMultiple == true)
|
||||
} |
|
||||
select(.id != "")
|
||||
) |
|
||||
unique_by(.id) |
|
||||
map(. as $widget |
|
||||
$widget + {
|
||||
inBar: (($currentIds | index($widget.id)) != null),
|
||||
addable: ($widget.allowMultiple or (($currentIds | index($widget.id)) == null))
|
||||
}
|
||||
) |
|
||||
map(select($includeAll or .addable)) |
|
||||
sort_by(.category, .name, .id)
|
||||
' "${manifest_paths[@]}"
|
||||
}
|
||||
|
||||
print_available_widgets() {
|
||||
local include_all="false"
|
||||
local json="false"
|
||||
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--all)
|
||||
include_all="true"
|
||||
;;
|
||||
--json)
|
||||
json="true"
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
fail "unknown list option: $1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ $json == "true" ]]; then
|
||||
available_widgets_json "$include_all"
|
||||
return
|
||||
fi
|
||||
|
||||
available_widgets_json "$include_all" | jq -r '
|
||||
.[] |
|
||||
[
|
||||
.id,
|
||||
(if .inBar and .allowMultiple then "addable*" elif .inBar then "in-bar" else "addable" end),
|
||||
.category,
|
||||
.name,
|
||||
.description
|
||||
] | @tsv
|
||||
' | awk -F '\t' '
|
||||
BEGIN { printf "%-28s %-10s %-14s %-22s %s\n", "ID", "STATUS", "CATEGORY", "NAME", "DESCRIPTION" }
|
||||
{ printf "%-28s %-10s %-14s %-22s %s\n", $1, $2, $3, $4, $5 }
|
||||
'
|
||||
}
|
||||
|
||||
choose_widget() {
|
||||
local widgets
|
||||
local options=()
|
||||
local selected=""
|
||||
|
||||
widgets=$(available_widgets_json false)
|
||||
|
||||
if ! jq -e 'length > 0' <<<"$widgets" >/dev/null; then
|
||||
fail "no addable bar widgets found"
|
||||
fi
|
||||
|
||||
mapfile -t options < <(jq -r '
|
||||
.[] |
|
||||
[.id, .name, .category, .description] | @tsv
|
||||
' <<<"$widgets" | awk -F '\t' '{ printf "%-28s %-22s [%s] %s\n", $1, $2, $3, $4 }')
|
||||
|
||||
if [[ -t 0 || -t 2 ]]; then
|
||||
selected=$(printf '%s\n' "${options[@]}" | gum filter --header "Add bar widget" --placeholder "Search addable widgets..." --limit 1 --height 14) || return 1
|
||||
else
|
||||
print_available_widgets >&2
|
||||
fail "widget id is required; use list to see addable widgets"
|
||||
fi
|
||||
|
||||
selected="${selected%%[[:space:]]*}"
|
||||
[[ -n $selected ]] || return 1
|
||||
printf '%s\n' "$selected"
|
||||
}
|
||||
|
||||
choose_removal() {
|
||||
local entries
|
||||
local widgets
|
||||
local options=()
|
||||
local selected=""
|
||||
local location=""
|
||||
local section=""
|
||||
local index=""
|
||||
|
||||
entries=$(current_bar_entries_json)
|
||||
widgets=$(available_widgets_json true)
|
||||
|
||||
if ! jq -e 'length > 0' <<<"$entries" >/dev/null; then
|
||||
fail "no bar widgets are currently configured"
|
||||
fi
|
||||
|
||||
mapfile -t options < <(jq -r --argjson widgets "$widgets" '
|
||||
def widget_meta($id): first($widgets[]? | select(.id == $id)) // {};
|
||||
|
||||
.[] as $entry |
|
||||
(widget_meta($entry.id)) as $meta |
|
||||
[
|
||||
"\($entry.section)[\($entry.index)]",
|
||||
$entry.id,
|
||||
($meta.name // $entry.id),
|
||||
($meta.category // "")
|
||||
] | @tsv
|
||||
' <<<"$entries" | awk -F '\t' '{ printf "%-10s %-28s %-22s %s\n", $1, $2, $3, $4 }')
|
||||
|
||||
if [[ -t 0 || -t 2 ]]; then
|
||||
selected=$(printf '%s\n' "${options[@]}" | gum filter --header "Remove bar widget" --placeholder "Search current widgets..." --limit 1 --height 14) || return 1
|
||||
else
|
||||
jq -r '.[] | "\(.section)[\(.index)]\t\(.id)"' <<<"$entries" >&2
|
||||
fail "widget id is required; use show to see current widgets"
|
||||
fi
|
||||
|
||||
location="${selected%%[[:space:]]*}"
|
||||
section="${location%%[*}"
|
||||
index="${location#*[}"
|
||||
index="${index%]}"
|
||||
|
||||
[[ $section =~ ^(left|center|right)$ ]] || return 1
|
||||
[[ $index =~ ^[0-9]+$ ]] || return 1
|
||||
printf '%s\t%s\n' "$section" "$index"
|
||||
}
|
||||
|
||||
command="${1:-}"
|
||||
|
||||
case "$command" in
|
||||
show|current)
|
||||
if (( $# != 1 )); then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq '.bar // {}' "$(source_file)"
|
||||
;;
|
||||
selected|active)
|
||||
if (( $# != 1 )); then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
current_bar_option_id
|
||||
;;
|
||||
options|option)
|
||||
shift
|
||||
print_bar_options "$@"
|
||||
;;
|
||||
use)
|
||||
if (( $# != 2 )); then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
plugin="${2:-}"
|
||||
[[ -n $plugin ]] || fail "bar option id is required"
|
||||
if [[ $plugin == "default" || $plugin == "built-in" ]]; then
|
||||
plugin="omarchy.bar"
|
||||
fi
|
||||
bar_option_exists "$plugin" || fail "$plugin is not a known bar option; run 'omarchy config shell bar options'"
|
||||
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
|
||||
source="$(source_file)"
|
||||
tmp=$(mktemp)
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
|
||||
if [[ $plugin == "omarchy.bar" ]]; then
|
||||
jq '
|
||||
def object_or_empty: if type == "object" then . else {} end;
|
||||
|
||||
object_or_empty
|
||||
| .version = 1
|
||||
| .bar = (.bar | object_or_empty)
|
||||
| del(.bar.id)
|
||||
' "$source" >"$tmp"
|
||||
else
|
||||
jq --arg plugin "$plugin" '
|
||||
def object_or_empty: if type == "object" then . else {} end;
|
||||
|
||||
object_or_empty
|
||||
| .version = 1
|
||||
| .bar = (.bar | object_or_empty)
|
||||
| .bar.id = $plugin
|
||||
' "$source" >"$tmp"
|
||||
fi
|
||||
|
||||
mv "$tmp" "$CONFIG_FILE"
|
||||
trap - EXIT
|
||||
omarchy-shell -q shell rescanPlugins >/dev/null 2>&1 || true
|
||||
refresh_shell_config
|
||||
;;
|
||||
reset)
|
||||
if (( $# != 1 )); then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
|
||||
source="$(source_file)"
|
||||
tmp=$(mktemp)
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
|
||||
jq '
|
||||
def object_or_empty: if type == "object" then . else {} end;
|
||||
|
||||
object_or_empty
|
||||
| .version = 1
|
||||
| .bar = (.bar | object_or_empty)
|
||||
| del(.bar.id)
|
||||
' "$source" >"$tmp"
|
||||
|
||||
mv "$tmp" "$CONFIG_FILE"
|
||||
trap - EXIT
|
||||
refresh_shell_config
|
||||
;;
|
||||
list|available|widgets)
|
||||
shift
|
||||
print_available_widgets "$@"
|
||||
;;
|
||||
add)
|
||||
if (( $# > 3 )); then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (( $# == 1 )); then
|
||||
plugin=$(choose_widget) || exit 1
|
||||
else
|
||||
plugin="${2:-}"
|
||||
fi
|
||||
section="${3:-right}"
|
||||
|
||||
[[ -n $plugin ]] || fail "plugin is required"
|
||||
[[ $section =~ ^(left|center|right)$ ]] || fail "section must be left, center, or right"
|
||||
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
|
||||
source="$(source_file)"
|
||||
tmp=$(mktemp)
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
|
||||
jq --arg section "$section" --arg plugin "$plugin" '
|
||||
def object_or_empty: if type == "object" then . else {} end;
|
||||
def array_or_empty: if type == "array" then . else [] end;
|
||||
def entry_id: if type == "object" then (.id // "" | tostring) else tostring end;
|
||||
def append_anchor($section):
|
||||
{
|
||||
left: "omarchy.workspaces",
|
||||
center: "omarchy.weather",
|
||||
right: "omarchy.tray"
|
||||
}[$section];
|
||||
def insert_after_anchor($entries; $entry; $anchor):
|
||||
($entries | map(entry_id) | index($anchor)) as $index
|
||||
| if $index == null then
|
||||
$entries + [$entry]
|
||||
else
|
||||
$entries[0:$index + 1] + [$entry] + $entries[$index + 1:]
|
||||
end;
|
||||
|
||||
object_or_empty
|
||||
| .version = 1
|
||||
| .bar = (.bar | object_or_empty)
|
||||
| .bar.layout = (.bar.layout | object_or_empty)
|
||||
| .bar.layout.left = (.bar.layout.left | array_or_empty | map(select(entry_id != $plugin)))
|
||||
| .bar.layout.center = (.bar.layout.center | array_or_empty | map(select(entry_id != $plugin)))
|
||||
| .bar.layout.right = (.bar.layout.right | array_or_empty | map(select(entry_id != $plugin)))
|
||||
| .plugins = (.plugins | array_or_empty)
|
||||
| .bar.layout[$section] = insert_after_anchor(.bar.layout[$section]; { id: $plugin }; append_anchor($section))
|
||||
' "$source" >"$tmp"
|
||||
|
||||
mv "$tmp" "$CONFIG_FILE"
|
||||
trap - EXIT
|
||||
refresh_shell_config
|
||||
;;
|
||||
position)
|
||||
if (( $# != 2 )); then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
position="${2:-}"
|
||||
|
||||
[[ $position =~ ^(top|bottom|left|right)$ ]] || fail "position must be top, bottom, left, or right"
|
||||
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
|
||||
source="$(source_file)"
|
||||
tmp=$(mktemp)
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
|
||||
jq --arg position "$position" '
|
||||
def object_or_empty: if type == "object" then . else {} end;
|
||||
|
||||
object_or_empty
|
||||
| .version = 1
|
||||
| .bar = (.bar | object_or_empty)
|
||||
| .bar.position = $position
|
||||
' "$source" >"$tmp"
|
||||
|
||||
mv "$tmp" "$CONFIG_FILE"
|
||||
trap - EXIT
|
||||
refresh_shell_config
|
||||
;;
|
||||
transparent)
|
||||
if (( $# != 2 )); then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
transparent="${2:-}"
|
||||
|
||||
[[ $transparent =~ ^(true|false)$ ]] || fail "transparent must be true or false"
|
||||
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
|
||||
source="$(source_file)"
|
||||
tmp=$(mktemp)
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
|
||||
jq --argjson transparent "$transparent" '
|
||||
def object_or_empty: if type == "object" then . else {} end;
|
||||
|
||||
object_or_empty
|
||||
| .version = 1
|
||||
| .bar = (.bar | object_or_empty)
|
||||
| .bar.transparent = $transparent
|
||||
' "$source" >"$tmp"
|
||||
|
||||
mv "$tmp" "$CONFIG_FILE"
|
||||
trap - EXIT
|
||||
refresh_shell_config
|
||||
;;
|
||||
drop|remove|rm)
|
||||
if (( $# > 2 )); then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
plugin=""
|
||||
target_section=""
|
||||
target_index=""
|
||||
|
||||
if (( $# == 1 )); then
|
||||
removal=$(choose_removal) || exit 1
|
||||
target_section="${removal%%$'\t'*}"
|
||||
target_index="${removal#*$'\t'}"
|
||||
else
|
||||
plugin="${2:-}"
|
||||
[[ -n $plugin ]] || fail "plugin is required"
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
|
||||
source="$(source_file)"
|
||||
tmp=$(mktemp)
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
|
||||
if [[ -n $target_section ]]; then
|
||||
jq --arg section "$target_section" --argjson index "$target_index" '
|
||||
def object_or_empty: if type == "object" then . else {} end;
|
||||
def array_or_empty: if type == "array" then . else [] end;
|
||||
|
||||
object_or_empty
|
||||
| .version = 1
|
||||
| .bar = (.bar | object_or_empty)
|
||||
| .bar.layout = (.bar.layout | object_or_empty)
|
||||
| .bar.layout.left = (.bar.layout.left | array_or_empty)
|
||||
| .bar.layout.center = (.bar.layout.center | array_or_empty)
|
||||
| .bar.layout.right = (.bar.layout.right | array_or_empty)
|
||||
| if (.bar.layout[$section] | length) <= $index then
|
||||
error("no widget at " + $section + "[" + ($index | tostring) + "]")
|
||||
else
|
||||
.bar.layout[$section] = (.bar.layout[$section][0:$index] + .bar.layout[$section][($index + 1):])
|
||||
end
|
||||
| .plugins = (.plugins | array_or_empty)
|
||||
' "$source" >"$tmp"
|
||||
else
|
||||
jq --arg plugin "$plugin" '
|
||||
def object_or_empty: if type == "object" then . else {} end;
|
||||
def array_or_empty: if type == "array" then . else [] end;
|
||||
def entry_id: if type == "object" then (.id // "" | tostring) else tostring end;
|
||||
|
||||
object_or_empty
|
||||
| .version = 1
|
||||
| .bar = (.bar | object_or_empty)
|
||||
| .bar.layout = (.bar.layout | object_or_empty)
|
||||
| .bar.layout.left = (.bar.layout.left | array_or_empty | map(select(entry_id != $plugin)))
|
||||
| .bar.layout.center = (.bar.layout.center | array_or_empty | map(select(entry_id != $plugin)))
|
||||
| .bar.layout.right = (.bar.layout.right | array_or_empty | map(select(entry_id != $plugin)))
|
||||
| .plugins = (.plugins | array_or_empty)
|
||||
' "$source" >"$tmp"
|
||||
fi
|
||||
|
||||
mv "$tmp" "$CONFIG_FILE"
|
||||
trap - EXIT
|
||||
refresh_shell_config
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
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-config-shell-bar add omarchy.dropbox
|
||||
omarchy-bar add omarchy.dropbox
|
||||
|
||||
echo "Starting Dropbox..."
|
||||
uwsm-app -- dropbox-cli start &>/dev/null &
|
||||
|
||||
@@ -14,6 +14,6 @@ echo -e "\nAllowing $USER to manage Tailscale..."
|
||||
sudo tailscale set --operator="$USER"
|
||||
|
||||
echo -e "\nAdding Tailscale to the bar..."
|
||||
omarchy-config-shell-bar add omarchy.tailscale
|
||||
omarchy-bar add 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
-999
File diff suppressed because it is too large
Load Diff
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Emit every first-party and user plugin manifest as JSON
|
||||
# omarchy:group=plugin
|
||||
# omarchy:hidden=true
|
||||
|
||||
# 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 and add
|
||||
# validation) and omarchy-plugin-clone (source enumeration) read, so the bar
|
||||
# and plugin commands never re-implement manifest walking.
|
||||
|
||||
set -o pipefail
|
||||
|
||||
OMARCHY_PATH="${OMARCHY_PATH:-}"
|
||||
command -v jq >/dev/null 2>&1 || { echo "omarchy-plugin-catalog: jq is required" >&2; exit 1; }
|
||||
|
||||
paths=()
|
||||
|
||||
if [[ -n $OMARCHY_PATH && -d $OMARCHY_PATH/shell/plugins ]]; then
|
||||
while IFS= read -r manifest; do
|
||||
paths+=("$manifest")
|
||||
done < <(find "$OMARCHY_PATH/shell/plugins" -mindepth 2 -maxdepth 4 -type f \( -name manifest.json -o -name '*.manifest.json' \) 2>/dev/null | sort)
|
||||
fi
|
||||
|
||||
user_dir="$HOME/.config/omarchy/plugins"
|
||||
if [[ -d $user_dir ]]; then
|
||||
while IFS= read -r manifest; do
|
||||
paths+=("$manifest")
|
||||
done < <(find -L "$user_dir" -mindepth 2 -maxdepth 2 -type f -name manifest.json 2>/dev/null | sort)
|
||||
fi
|
||||
|
||||
if (( ${#paths[@]} == 0 )); then
|
||||
echo "[]"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
jq -n '
|
||||
[ inputs
|
||||
| . as $manifest
|
||||
| input_filename as $manifestPath
|
||||
| ($manifestPath | sub("/manifest.json$"; "") | sub("/[^/]+\\.manifest\\.json$"; "")) as $sourceDir
|
||||
| {
|
||||
id: ($manifest.id // ""),
|
||||
name: ($manifest.name // $manifest.id // ""),
|
||||
description: ($manifest.description // ""),
|
||||
kinds: ($manifest.kinds // []),
|
||||
firstParty: (($manifest.id // "") | startswith("omarchy.")),
|
||||
manifestPath: $manifestPath,
|
||||
sourceDir: $sourceDir,
|
||||
entryPoints: ($manifest.entryPoints // {}),
|
||||
barWidget: ($manifest.barWidget // null),
|
||||
bar: ($manifest.bar // null)
|
||||
}
|
||||
| .barWidgetPath = (
|
||||
if (.entryPoints.barWidget // null) != null
|
||||
then (.sourceDir + "/" + .entryPoints.barWidget)
|
||||
else null end)
|
||||
| .barPath = (
|
||||
if (.entryPoints.bar // null) != null
|
||||
then (.sourceDir + "/" + .entryPoints.bar)
|
||||
else null end)
|
||||
| select(.id != "")
|
||||
]
|
||||
| unique_by(.id)
|
||||
' "${paths[@]}"
|
||||
Executable
+377
@@ -0,0 +1,377 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Clone a built-in or user Omarchy shell plugin into your own config
|
||||
# omarchy:group=plugin
|
||||
# omarchy:args=[source-id] [new-id] [--name <name>] [--replace] [--add [placement]] [--use]
|
||||
# omarchy:examples=omarchy plugin clone | omarchy plugin clone omarchy.clock local.clock | omarchy plugin clone omarchy.clock local.clock --name "My Clock" --replace
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PLUGINS_DIR="$HOME/.config/omarchy/plugins"
|
||||
|
||||
fail() {
|
||||
echo "omarchy-plugin-clone: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_command() {
|
||||
command -v "$1" >/dev/null 2>&1 || fail "$1 is required"
|
||||
}
|
||||
|
||||
require_omarchy_path() {
|
||||
[[ -n ${OMARCHY_PATH:-} ]] || fail "OMARCHY_PATH is not set"
|
||||
}
|
||||
|
||||
interactive() {
|
||||
[[ -t 0 && -t 1 ]]
|
||||
}
|
||||
|
||||
canonical_widget_id() {
|
||||
printf '%s' "$1"
|
||||
}
|
||||
|
||||
slug_id() {
|
||||
tr '[:upper:]' '[:lower:]' <<<"$1" | sed -E 's/^omarchy\.//; s/[^a-z0-9]+/-/g; s/^-+//; s/-+$//'
|
||||
}
|
||||
|
||||
validate_plugin_id() {
|
||||
local id="$1"
|
||||
[[ $id =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || fail "plugin id must contain only letters, numbers, dots, underscores, and dashes"
|
||||
[[ $id == *.* ]] || fail "plugin id must be namespaced, e.g. local.clock"
|
||||
[[ $id != omarchy.* ]] || fail "plugin ids beginning with omarchy. are reserved for built-ins"
|
||||
[[ $(canonical_widget_id "$id") == "$id" ]] || fail "$id is a reserved built-in widget id"
|
||||
}
|
||||
|
||||
catalog_json() {
|
||||
require_omarchy_path
|
||||
require_command jq
|
||||
omarchy-plugin-catalog
|
||||
}
|
||||
|
||||
# Emit: id<TAB>barWidgetPath<TAB>name<TAB>category<TAB>allowMultiple for a
|
||||
# built-in bar widget matching $1 (by id or alias). Used to find the source QML
|
||||
# file and metadata to copy when cloning a widget.
|
||||
builtin_widget_info() {
|
||||
local wanted
|
||||
wanted=$(canonical_widget_id "$1")
|
||||
|
||||
catalog_json | jq -r --arg id "$wanted" '
|
||||
.[] | select((.kinds | index("bar-widget")) and .barWidgetPath != null)
|
||||
| select(.id == $id or (((.barWidget.aliases // [])) | index($id)))
|
||||
| [.id, .barWidgetPath, (.barWidget.displayName // .name // .id), (.barWidget.category // "Plugin"), (if .barWidget.allowMultiple == true then "true" else "false" end)]
|
||||
| @tsv
|
||||
' | head -1
|
||||
}
|
||||
|
||||
clone_source_options() {
|
||||
catalog_json | jq -r '
|
||||
.[] | "\(.id)\t" +
|
||||
(if .firstParty then (if (.kinds | index("bar-widget")) then "Built-in widget" else "Built-in plugin" end) else "User plugin" end) +
|
||||
"\t" + ((.barWidget.displayName // .name // .id) // .id)
|
||||
'
|
||||
}
|
||||
|
||||
choose_clone_source() {
|
||||
local selected
|
||||
selected=$(clone_source_options | awk -F '\t' '{ printf "%-32s %-15s %s\n", $3, $2, $1 }' \
|
||||
| gum filter --header "Clone plugin" --placeholder "Search built-in and user plugins..." --limit 1) || return 1
|
||||
awk '{ print $NF }' <<<"$selected"
|
||||
}
|
||||
|
||||
choose_value() {
|
||||
local prompt="$1"
|
||||
shift
|
||||
gum choose --header "$prompt" "$@"
|
||||
}
|
||||
|
||||
input_value() {
|
||||
local prompt="$1"
|
||||
local value="${2:-}"
|
||||
gum input --prompt "$prompt " --value "$value"
|
||||
}
|
||||
|
||||
rewrite_cloned_qml() {
|
||||
local file="$1"
|
||||
local id="$2"
|
||||
local source_path="$3"
|
||||
python3 - "$file" "$id" "$source_path" <<'PY'
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
path, plugin_id, source_path = sys.argv[1], sys.argv[2], pathlib.Path(sys.argv[3])
|
||||
text = open(path, encoding="utf-8").read()
|
||||
text = re.sub(r'moduleName:\s*"[^"]+"', f'moduleName: "{plugin_id}"', text, count=1)
|
||||
|
||||
|
||||
def resolve_relative(match):
|
||||
rel = match.group(1)
|
||||
if rel.startswith(("../", "./")) or (not rel.startswith("/") and "://" not in rel):
|
||||
resolved = (source_path.parent / rel).resolve()
|
||||
return '"' + resolved.as_uri() + '"'
|
||||
return match.group(0)
|
||||
|
||||
text = re.sub(r'Qt\.resolvedUrl\("([^"]+)"\)', resolve_relative, text)
|
||||
|
||||
# Indicators dynamically loads sibling indicator components via string
|
||||
# concatenation, so the simple Qt.resolvedUrl("literal") rewrite above cannot
|
||||
# see it. Point the clone back at Omarchy's bundled indicator directory.
|
||||
if source_path.name == "Indicators.qml":
|
||||
indicators_url = (source_path.parent.parent / "indicators").resolve().as_uri() + "/"
|
||||
text = text.replace(
|
||||
'Qt.resolvedUrl("../indicators/" + indicatorSlot.indicatorId + ".qml")',
|
||||
f'"{indicators_url}" + indicatorSlot.indicatorId + ".qml"',
|
||||
)
|
||||
|
||||
# Panel-backed clones should not register the same global IPC target as the
|
||||
# built-in widget. Direct panel clones can disable Panel.manageIpc themselves;
|
||||
# wrapper widgets such as Weather disable it on the loaded panel instance.
|
||||
if "ipcTarget:" in text and "manageIpc:" not in text:
|
||||
text = text.replace(" id: root\n", " id: root\n manageIpc: false\n", 1)
|
||||
if "function injectPanel()" in text and '"manageIpc" in target' not in text:
|
||||
text = text.replace(
|
||||
" if (!target) return\n",
|
||||
" if (!target) return\n if (\"manageIpc\" in target) target.manageIpc = false\n",
|
||||
1,
|
||||
)
|
||||
|
||||
open(path, "w", encoding="utf-8").write(text)
|
||||
PY
|
||||
}
|
||||
|
||||
write_clone_manifest() {
|
||||
local file="$1"
|
||||
local id="$2"
|
||||
local name="$3"
|
||||
local description="$4"
|
||||
local category="$5"
|
||||
local multiple="$6"
|
||||
local source_id="$7"
|
||||
local source_path="$8"
|
||||
python3 - "$file" "$id" "$name" "$description" "$category" "$multiple" "$source_id" "$source_path" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
file, plugin_id, name, description, category, multiple, source_id, source_path = sys.argv[1:]
|
||||
data = {
|
||||
"schemaVersion": 1,
|
||||
"id": plugin_id,
|
||||
"name": name,
|
||||
"version": "1.0.0",
|
||||
"author": "Local",
|
||||
"description": description,
|
||||
"kinds": ["bar-widget"],
|
||||
"entryPoints": {"barWidget": "Widget.qml"},
|
||||
"barWidget": {
|
||||
"displayName": name,
|
||||
"description": description,
|
||||
"category": category,
|
||||
"allowMultiple": multiple == "true"
|
||||
},
|
||||
"omarchy": {
|
||||
"clonedFrom": source_id,
|
||||
"clonedFromPath": source_path
|
||||
}
|
||||
}
|
||||
open(file, "w", encoding="utf-8").write(json.dumps(data, indent=2) + "\n")
|
||||
PY
|
||||
}
|
||||
|
||||
update_cloned_manifest() {
|
||||
local file="$1"
|
||||
local id="$2"
|
||||
local name="$3"
|
||||
local source_id="$4"
|
||||
local source_path="$5"
|
||||
python3 - "$file" "$id" "$name" "$source_id" "$source_path" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
file, plugin_id, name, source_id, source_path = sys.argv[1:]
|
||||
with open(file, encoding="utf-8") as handle:
|
||||
data = json.load(handle)
|
||||
data["id"] = plugin_id
|
||||
data["name"] = name
|
||||
if "barWidget" in data and isinstance(data["barWidget"], dict):
|
||||
data["barWidget"]["displayName"] = name
|
||||
meta = data.get("omarchy") if isinstance(data.get("omarchy"), dict) else {}
|
||||
meta.update({"clonedFrom": source_id, "clonedFromPath": source_path})
|
||||
data["omarchy"] = meta
|
||||
open(file, "w", encoding="utf-8").write(json.dumps(data, indent=2) + "\n")
|
||||
PY
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: omarchy plugin clone [source-id] [new-id] [options]
|
||||
|
||||
Clones a built-in or user plugin into ~/.config/omarchy/plugins/<new-id>/ so you
|
||||
can edit it freely. The original stays built into Omarchy.
|
||||
|
||||
Options:
|
||||
--name <name> Display name for the clone
|
||||
--replace Replace the first bar instance of the source with the clone
|
||||
--add [placement] Add the cloned bar widget to the bar
|
||||
--use Use the clone as the active bar option (bar plugins)
|
||||
|
||||
Placement (with --add):
|
||||
--section <left|center|right>
|
||||
--index <n>
|
||||
--before <id>
|
||||
--after <id>
|
||||
|
||||
Examples:
|
||||
omarchy plugin clone
|
||||
omarchy plugin clone omarchy.clock local.clock
|
||||
omarchy plugin clone omarchy.clock local.clock --name "My Clock" --replace
|
||||
USAGE
|
||||
}
|
||||
|
||||
clone_command() {
|
||||
require_command jq
|
||||
require_command python3
|
||||
require_omarchy_path
|
||||
|
||||
local source_id=""
|
||||
local new_id=""
|
||||
if (( $# > 0 )) && [[ $1 != --* ]]; then
|
||||
source_id="$1"
|
||||
shift
|
||||
fi
|
||||
if (( $# > 0 )) && [[ $1 != --* ]]; then
|
||||
new_id="$1"
|
||||
shift
|
||||
fi
|
||||
|
||||
local display_name=""
|
||||
local replace="false"
|
||||
local add="false"
|
||||
local use_bar="false"
|
||||
local placement=()
|
||||
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--name)
|
||||
display_name="${2:-}"
|
||||
[[ -n $display_name ]] || fail "--name requires a value"
|
||||
shift 2
|
||||
;;
|
||||
--replace)
|
||||
replace="true"
|
||||
shift
|
||||
;;
|
||||
--add)
|
||||
add="true"
|
||||
shift
|
||||
;;
|
||||
--use)
|
||||
use_bar="true"
|
||||
shift
|
||||
;;
|
||||
--section | --index | --before | --after)
|
||||
placement+=("$1" "${2:-}")
|
||||
shift 2
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
return
|
||||
;;
|
||||
*)
|
||||
fail "unknown clone option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z $source_id ]]; then
|
||||
if interactive; then
|
||||
source_id=$(choose_clone_source) || fail "clone cancelled"
|
||||
else
|
||||
fail "clone source is required"
|
||||
fi
|
||||
fi
|
||||
source_id=$(canonical_widget_id "$source_id")
|
||||
|
||||
local default_slug
|
||||
default_slug=$(slug_id "$source_id")
|
||||
if [[ -z $new_id ]]; then
|
||||
if interactive; then
|
||||
new_id=$(input_value "New plugin id:" "local.$default_slug")
|
||||
else
|
||||
fail "new plugin id is required"
|
||||
fi
|
||||
fi
|
||||
validate_plugin_id "$new_id"
|
||||
|
||||
local target_dir="$PLUGINS_DIR/$new_id"
|
||||
[[ ! -e $target_dir && ! -L $target_dir ]] || fail "$target_dir already exists"
|
||||
|
||||
local source_path=""
|
||||
local source_name=""
|
||||
local source_category="Plugin"
|
||||
local source_multiple="false"
|
||||
local source_type=""
|
||||
|
||||
if IFS=$'\t' read -r _ source_path source_name source_category source_multiple < <(builtin_widget_info "$source_id"); then
|
||||
source_type="builtin-widget"
|
||||
elif [[ -f $PLUGINS_DIR/$source_id/manifest.json ]]; then
|
||||
source_type="user-plugin"
|
||||
source_path="$PLUGINS_DIR/$source_id"
|
||||
source_name=$(jq -r '.name // .id' "$source_path/manifest.json")
|
||||
else
|
||||
# Non-widget built-in plugin (e.g. a panel or bar): find it in the catalog.
|
||||
local hit
|
||||
hit=$(catalog_json | jq -r --arg id "$source_id" '.[] | select(.id == $id) | .sourceDir' | head -1)
|
||||
if [[ -n $hit ]]; then
|
||||
source_type="builtin-plugin"
|
||||
source_path="$hit"
|
||||
source_name=$(jq -r '.name // .id' "$source_path/manifest.json")
|
||||
fi
|
||||
fi
|
||||
|
||||
[[ -n $source_type ]] || fail "unknown clone source: $source_id"
|
||||
if [[ -z $display_name ]]; then
|
||||
if interactive; then
|
||||
display_name=$(input_value "Display name:" "${source_name:-$new_id}")
|
||||
else
|
||||
display_name="${source_name:-$new_id}"
|
||||
fi
|
||||
fi
|
||||
[[ -n $display_name ]] || fail "display name is required"
|
||||
|
||||
mkdir -p "$target_dir"
|
||||
if [[ $source_type == "builtin-widget" ]]; then
|
||||
cp "$source_path" "$target_dir/Widget.qml"
|
||||
rewrite_cloned_qml "$target_dir/Widget.qml" "$new_id" "$source_path"
|
||||
write_clone_manifest "$target_dir/manifest.json" "$new_id" "$display_name" "Cloned from $source_id" "$source_category" "$source_multiple" "$source_id" "$source_path"
|
||||
else
|
||||
cp -aL "$source_path/." "$target_dir/"
|
||||
update_cloned_manifest "$target_dir/manifest.json" "$new_id" "$display_name" "$source_id" "$source_path"
|
||||
fi
|
||||
|
||||
{
|
||||
printf '# %s\n\n' "$display_name"
|
||||
printf 'Cloned from `%s`.\n\n' "$source_id"
|
||||
printf 'Source: `%s`\n\n' "$source_path"
|
||||
printf 'The original remains built into Omarchy. Remove this plugin directory or swap\n'
|
||||
printf 'your bar entry back to `%s` whenever you want to return to the built-in.\n' "$source_id"
|
||||
} >"$target_dir/UPSTREAM.md"
|
||||
|
||||
omarchy-shell -q shell rescanPlugins >/dev/null 2>&1 || true
|
||||
|
||||
if [[ $replace == "true" ]]; then
|
||||
omarchy-bar replace "$source_id" "$new_id"
|
||||
echo "Cloned $source_id to $new_id and replaced the first bar instance"
|
||||
elif [[ $add == "true" ]]; then
|
||||
omarchy-bar add "$new_id" "${placement[@]}"
|
||||
echo "Cloned $source_id to $new_id"
|
||||
elif [[ $use_bar == "true" ]]; then
|
||||
omarchy-bar use "$new_id"
|
||||
echo "Cloned $source_id to $new_id and selected it as the active bar option"
|
||||
else
|
||||
echo "Cloned $source_id to $new_id"
|
||||
fi
|
||||
|
||||
if interactive; then
|
||||
cd "$target_dir"
|
||||
exec "${SHELL:-bash}"
|
||||
fi
|
||||
}
|
||||
|
||||
clone_command "$@"
|
||||
Executable
+157
@@ -0,0 +1,157 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Open a user Omarchy shell plugin directory in a shell
|
||||
# omarchy:group=plugin
|
||||
# omarchy:args=[plugin-id]
|
||||
# omarchy:examples=omarchy plugin edit | omarchy plugin edit local.clock
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PLUGINS_DIR="$HOME/.config/omarchy/plugins"
|
||||
|
||||
fail() {
|
||||
echo "omarchy-plugin-edit: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_command() {
|
||||
command -v "$1" >/dev/null 2>&1 || fail "$1 is required"
|
||||
}
|
||||
|
||||
interactive() {
|
||||
[[ -t 0 && -t 1 ]]
|
||||
}
|
||||
|
||||
canonical_widget_id() {
|
||||
printf '%s' "$1"
|
||||
}
|
||||
|
||||
slug_id() {
|
||||
tr '[:upper:]' '[:lower:]' <<<"$1" | sed -E 's/^omarchy\.//; s/[^a-z0-9]+/-/g; s/^-+//; s/-+$//'
|
||||
}
|
||||
|
||||
require_omarchy_path() {
|
||||
[[ -n ${OMARCHY_PATH:-} ]] || fail "OMARCHY_PATH is not set"
|
||||
}
|
||||
|
||||
# A built-in id is one the catalog knows as first-party (or an omarchy.* id).
|
||||
builtin_id_known() {
|
||||
local id="$1"
|
||||
require_command jq
|
||||
require_omarchy_path
|
||||
omarchy-plugin-catalog | jq -e --arg id "$id" \
|
||||
'any(.[]; .id == $id and .firstParty)' >/dev/null 2>&1
|
||||
}
|
||||
|
||||
validate_user_plugin_dir() {
|
||||
local dir="$1"
|
||||
local requested_id="${2:-}"
|
||||
local root="$PLUGINS_DIR"
|
||||
local dir_abs root_abs manifest_id
|
||||
|
||||
require_command jq
|
||||
require_command python3
|
||||
[[ -d $dir ]] || fail "plugin directory not found: $dir"
|
||||
|
||||
dir_abs=$(python3 -c 'from pathlib import Path; import sys; print(Path(sys.argv[1]).expanduser().resolve(strict=False))' "$dir")
|
||||
root_abs=$(python3 -c 'from pathlib import Path; import sys; print(Path(sys.argv[1]).expanduser().resolve(strict=False))' "$root")
|
||||
[[ $dir_abs == $root_abs/* ]] || fail "plugin edit only opens user plugins under $root"
|
||||
[[ -f $dir_abs/manifest.json ]] || fail "plugin directory missing manifest.json: $dir_abs"
|
||||
|
||||
manifest_id=$(jq -r '.id // empty' "$dir_abs/manifest.json" 2>/dev/null || true)
|
||||
[[ -n $manifest_id ]] || fail "plugin manifest missing id: $dir_abs/manifest.json"
|
||||
[[ $manifest_id != omarchy.* && $(canonical_widget_id "$manifest_id") == $manifest_id ]] \
|
||||
|| fail "$manifest_id is built in; clone it first with: omarchy plugin clone $manifest_id local.$(slug_id "$manifest_id")"
|
||||
if [[ -n $requested_id && $manifest_id != $requested_id ]]; then
|
||||
fail "plugin id mismatch: requested $requested_id but manifest declares $manifest_id"
|
||||
fi
|
||||
|
||||
printf '%s' "$dir_abs"
|
||||
}
|
||||
|
||||
plugin_dir_for_id() {
|
||||
local id="$1"
|
||||
|
||||
if [[ $id == */* ]]; then
|
||||
validate_user_plugin_dir "$id"
|
||||
return
|
||||
fi
|
||||
|
||||
id=$(canonical_widget_id "$id")
|
||||
if builtin_id_known "$id" || [[ $id == omarchy.* ]]; then
|
||||
fail "$id is built in; clone it first with: omarchy plugin clone $id local.$(slug_id "$id")"
|
||||
fi
|
||||
|
||||
validate_user_plugin_dir "$PLUGINS_DIR/$id" "$id"
|
||||
}
|
||||
|
||||
edit_source_options() {
|
||||
[[ -d $PLUGINS_DIR ]] || return 0
|
||||
find "$PLUGINS_DIR" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) 2>/dev/null | sort | while IFS= read -r dir; do
|
||||
[[ -f $dir/manifest.json ]] || continue
|
||||
id=$(jq -r '.id // empty' "$dir/manifest.json" 2>/dev/null || true)
|
||||
name=$(jq -r '.name // .id // empty' "$dir/manifest.json" 2>/dev/null || true)
|
||||
[[ -n $id ]] && printf '%s\t%s\t%s\n' "$id" "${name:-$id}" "$dir"
|
||||
done
|
||||
}
|
||||
|
||||
choose_edit_source() {
|
||||
local selected
|
||||
selected=$(edit_source_options | awk -F '\t' '{ printf "%-32s %s\n", $2, $1 }' \
|
||||
| gum filter --header "Edit plugin" --placeholder "Search user plugins..." --limit 1) || return 1
|
||||
awk '{ print $NF }' <<<"$selected"
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: omarchy plugin edit [plugin-id]
|
||||
|
||||
Opens a user plugin directory (~/.config/omarchy/plugins/<id>) in a shell so you
|
||||
can edit it. Built-in plugins must be cloned first with 'omarchy plugin clone'.
|
||||
|
||||
Without an id in a terminal, an interactive picker lists your user plugins.
|
||||
|
||||
Examples:
|
||||
omarchy plugin edit
|
||||
omarchy plugin edit local.clock
|
||||
USAGE
|
||||
}
|
||||
|
||||
edit_command() {
|
||||
local id=""
|
||||
if (( $# > 0 )) && [[ $1 != --* ]]; then
|
||||
id="$1"
|
||||
shift
|
||||
fi
|
||||
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
-h | --help)
|
||||
usage
|
||||
return
|
||||
;;
|
||||
*)
|
||||
fail "unknown edit option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z $id ]]; then
|
||||
if interactive; then
|
||||
id=$(choose_edit_source) || fail "edit cancelled"
|
||||
else
|
||||
fail "plugin id is required"
|
||||
fi
|
||||
fi
|
||||
|
||||
local dir
|
||||
dir=$(plugin_dir_for_id "$id")
|
||||
|
||||
if interactive; then
|
||||
cd "$dir"
|
||||
exec "${SHELL:-bash}"
|
||||
fi
|
||||
printf '%s\n' "$dir"
|
||||
}
|
||||
|
||||
edit_command "$@"
|
||||
@@ -8,11 +8,11 @@ set -euo pipefail
|
||||
omarchy-refresh-config omarchy/shell.json
|
||||
|
||||
if omarchy-installed-service-dropbox; then
|
||||
omarchy-config-shell-bar add omarchy.dropbox
|
||||
omarchy-bar add omarchy.dropbox
|
||||
fi
|
||||
|
||||
if omarchy-installed-service-tailscale; then
|
||||
omarchy-config-shell-bar add omarchy.tailscale
|
||||
omarchy-bar add omarchy.tailscale
|
||||
fi
|
||||
|
||||
omarchy-restart-shell
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
dropbox-cli stop 2>/dev/null || true
|
||||
omarchy-config-shell-bar drop omarchy.dropbox
|
||||
omarchy-bar drop omarchy.dropbox
|
||||
omarchy-pkg-drop dropbox dropbox-cli libappindicator-gtk3 python-gpgme nautilus-dropbox
|
||||
|
||||
echo ""
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
tailscale down 2>/dev/null || true
|
||||
sudo systemctl disable --now tailscaled.service 2>/dev/null || true
|
||||
omarchy-config-shell-bar drop omarchy.tailscale
|
||||
omarchy-bar drop omarchy.tailscale
|
||||
omarchy-webapp-remove "Tailscale" 2>/dev/null || true
|
||||
omarchy-pkg-drop tailscale
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ every prompt (the path for scripts and agents).
|
||||
Sources are recorded in `~/.config/omarchy/plugins/sources.json` and cloned into
|
||||
`~/.cache/omarchy/plugin-sources/`. You can still install by hand: drop a plugin
|
||||
into `~/.config/omarchy/plugins/<id>/`, run `omarchy plugin rescan`, then
|
||||
`omarchy plugin enable <id>` (bar widgets also need `omarchy plugin bar add <id>`;
|
||||
full bar replacements are selected with `omarchy plugin bar use <id>`).
|
||||
`omarchy plugin enable <id>` (bar widgets also need `omarchy bar add <id>`;
|
||||
full bar replacements are selected with `omarchy bar use <id>`).
|
||||
The lower-level IPC methods remain available through `omarchy-shell shell ...`.
|
||||
|
||||
## IPC
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ You can still drop a plugin in without a source:
|
||||
1. Put it in `~/.config/omarchy/plugins/<plugin-id>/` with a `manifest.json`
|
||||
plus the QML referenced from its `entryPoints`.
|
||||
2. `omarchy plugin rescan`.
|
||||
3. `omarchy plugin enable <id>` (bar widgets also need `omarchy plugin bar add <id>`; full bar replacements are selected with `omarchy plugin bar use <id>`).
|
||||
3. `omarchy plugin enable <id>` (bar widgets also need `omarchy bar add <id>`; full bar replacements are selected with `omarchy bar use <id>`).
|
||||
|
||||
The lower-level IPC equivalents remain available via `omarchy-shell shell rescanPlugins`,
|
||||
`omarchy-shell shell setPluginEnabled <id> true`, and `omarchy-shell shell listPlugins`.
|
||||
|
||||
@@ -14,9 +14,9 @@ the shell for its whole session.
|
||||
|
||||
## Customizing
|
||||
|
||||
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 inline bar config panel, `omarchy plugin bar ...`, or by editing shell.json directly, your file is canonical — there is no deep-merge.
|
||||
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 inline bar config panel, `omarchy bar ...`, or by editing shell.json directly, your file is canonical — there is no deep-merge.
|
||||
|
||||
Open quick position and transparency controls with `omarchy launch bar settings` / `omarchy plugin bar settings` (or run `omarchy-launch-bar-settings`). You can also hover the centered clock module to reveal the inline bar config button. For scriptable widget changes, use `omarchy plugin bar list`, `omarchy plugin bar add`, `omarchy plugin bar move`, `omarchy plugin bar remove`, and `omarchy plugin bar set`. Double-left-click empty center-bar space to toggle bar transparency.
|
||||
Open quick position and transparency controls with `omarchy bar settings` (or run `omarchy-launch-bar-settings`). You can also hover the centered clock module to reveal the inline bar config button. For scriptable widget changes, use `omarchy bar list`, `omarchy bar add`, `omarchy bar move`, `omarchy bar remove`, and `omarchy bar set`. Double-left-click empty center-bar space to toggle bar transparency.
|
||||
|
||||
Example `shell.json` (bar subtree only shown):
|
||||
|
||||
@@ -181,4 +181,4 @@ Third-party widgets ship as separate plugins under
|
||||
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 plugin rescan`, and `omarchy plugin bar add`.
|
||||
`omarchy plugin rescan`, and `omarchy bar add`.
|
||||
|
||||
@@ -35,4 +35,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 plugin bar 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 bar 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.
|
||||
|
||||
+20
-20
@@ -208,75 +208,75 @@ cat >"$TMPDIR/home/.config/omarchy/plugins/local.demo-bar/manifest.json" <<'JSON
|
||||
JSON
|
||||
touch "$TMPDIR/home/.config/omarchy/plugins/local.demo-bar/Bar.qml"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell-bar options --json | jq -e '
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar options --json | jq -e '
|
||||
any(.[]; .id == "omarchy.bar" and .active == true) and
|
||||
any(.[]; .id == "local.demo-bar" and .active == false)
|
||||
' >/dev/null
|
||||
pass "shell config lists bar options"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell-bar use local.demo-bar
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar use local.demo-bar
|
||||
jq -e '.bar.id == "local.demo-bar"' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config selects a bar option"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell-bar reset
|
||||
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-config-shell-bar add omarchy.tailscale
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar add omarchy.tailscale
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.right | ids == ["omarchy.tray", "omarchy.tailscale", "omarchy.bluetooth"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config appends widgets to right by default"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell-bar add local.left left
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar add omarchy.active-window left
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.left | ids == ["omarchy.menu", "omarchy.workspaces", "local.left"]
|
||||
.bar.layout.left | ids == ["omarchy.menu", "omarchy.workspaces", "omarchy.active-window"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config appends left widgets after workspaces"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell-bar add local.center center
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar add omarchy.system-update center
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.center | ids == ["omarchy.clock", "omarchy.weather", "local.center"]
|
||||
.bar.layout.center | ids == ["omarchy.clock", "omarchy.weather", "omarchy.system-update"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config appends center widgets after weather"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell-bar add local.first right
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar add omarchy.microphone right
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.right | ids == ["omarchy.tray", "local.first", "omarchy.tailscale", "omarchy.bluetooth"]
|
||||
.bar.layout.right | ids == ["omarchy.tray", "omarchy.microphone", "omarchy.tailscale", "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-config-shell-bar show | jq -e '
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar show | jq -e '
|
||||
def ids: map(.id // .);
|
||||
(.layout.right | ids == ["omarchy.tray", "local.first", "omarchy.tailscale", "omarchy.bluetooth"]) and
|
||||
(.layout.right | ids == ["omarchy.tray", "omarchy.microphone", "omarchy.tailscale", "omarchy.bluetooth"]) and
|
||||
has("version") | not
|
||||
' >/dev/null
|
||||
pass "shell config shows only bar json"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell-bar list --json | jq -e '
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar list --json | jq -e '
|
||||
any(.[]; .id == "omarchy.keyboard-layout" and .addable == true and .inBar == false) and
|
||||
all(.[]; .id != "omarchy.tailscale")
|
||||
' >/dev/null
|
||||
pass "shell config lists addable bar widgets"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell-bar list --json --all | jq -e '
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar list --json --all | jq -e '
|
||||
any(.[]; .id == "omarchy.tailscale" and .inBar == true and .addable == false) and
|
||||
any(.[]; .id == "omarchy.indicators" and .addable == true)
|
||||
' >/dev/null
|
||||
pass "shell config list --all includes current widget status"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell-bar position bottom
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar position bottom
|
||||
jq -e '
|
||||
.bar.position == "bottom" and
|
||||
.plugins == []
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config sets bar position"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell-bar transparent true
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar transparent true
|
||||
jq -e '
|
||||
.bar.transparent == true and
|
||||
.bar.position == "bottom" and
|
||||
@@ -284,16 +284,16 @@ jq -e '
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config sets bar transparency"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell-bar drop local.left
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar 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", "local.center"]) and
|
||||
(.bar.layout.right | ids == ["omarchy.tray", "local.first", "omarchy.tailscale", "omarchy.bluetooth"])
|
||||
(.bar.layout.center | ids == ["omarchy.clock", "omarchy.weather", "omarchy.system-update"]) and
|
||||
(.bar.layout.right | ids == ["omarchy.tray", "omarchy.microphone", "omarchy.tailscale", "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-config-shell-bar remove local.center
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-bar remove omarchy.system-update
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.center | ids == ["omarchy.clock", "omarchy.weather"]
|
||||
|
||||
@@ -220,7 +220,7 @@ for panel_id in omarchy.audio omarchy.bluetooth omarchy.monitor omarchy.network
|
||||
done
|
||||
pass "direct panel IPC opens and closes default panels"
|
||||
|
||||
HOME="$test_home" OMARCHY_PATH="$test_root" PATH="$ROOT/bin:$PATH" "$ROOT/bin/omarchy-config-shell-bar" remove omarchy.audio
|
||||
HOME="$test_home" OMARCHY_PATH="$test_root" PATH="$ROOT/bin:$PATH" "$ROOT/bin/omarchy-bar" remove omarchy.audio
|
||||
|
||||
for _ in {1..80}; do
|
||||
shell_config=$(shell_ipc shell listShellConfig 2>/dev/null || true)
|
||||
@@ -237,7 +237,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 "config shell bar remove reloads shell config"
|
||||
fail_with_log "bar remove reloads shell config"
|
||||
}
|
||||
|
||||
jq -e 'all(.[]; .id != "omarchy.audio")' <<<"$geometry" >/dev/null || {
|
||||
@@ -246,4 +246,4 @@ jq -e 'all(.[]; .id != "omarchy.audio")' <<<"$geometry" >/dev/null || {
|
||||
fail_with_log "runtime bar layout updates after shell config reload"
|
||||
}
|
||||
|
||||
pass "config shell bar remove reloads shell config and updates bar layout"
|
||||
pass "bar remove reloads shell config and updates bar layout"
|
||||
|
||||
Reference in New Issue
Block a user