diff --git a/bin/omarchy-font-set b/bin/omarchy-font-set index 56f21051..7345fbe6 100755 --- a/bin/omarchy-font-set +++ b/bin/omarchy-font-set @@ -29,40 +29,28 @@ if [[ -n $font_name ]]; then sed -i "s/font_family = .*/font_family = $font_name/g" ~/.config/hypr/hyprlock.conf sed -i "s/font-family: .*/font-family: '$font_name';/g" ~/.config/waybar/style.css mkdir -p "$HOME/.config/omarchy" - python3 - "$HOME/.config/omarchy/shell.json" "$font_name" <<'PY' -import json -import sys + shell_config="$HOME/.config/omarchy/shell.json" + tmp=$(mktemp) -path, font_name = sys.argv[1], sys.argv[2] -try: - with open(path) as file: - data = json.load(file) - if not isinstance(data, dict): - data = {} -except (FileNotFoundError, json.JSONDecodeError): - data = {} + if [[ -s $shell_config ]] && jq -e 'type == "object"' "$shell_config" >/dev/null 2>&1; then + jq --arg font_name "$font_name" ' + .version = 1 | + .bar = ((.bar // {}) | if type == "object" then . else {} end) | + .bar.fontFamily = $font_name + ' "$shell_config" >"$tmp" + else + jq -n --arg font_name "$font_name" '{ version: 1, bar: { fontFamily: $font_name } }' >"$tmp" + fi -data.setdefault("version", 1) -bar = data.get("bar") -if not isinstance(bar, dict): - bar = {} - data["bar"] = bar -bar["fontFamily"] = font_name - -with open(path, "w") as file: - json.dump(data, file, indent=2) - file.write("\n") -PY + mv "$tmp" "$shell_config" sed -i "s/font-family: .*/font-family: '$font_name';/g" ~/.config/swayosd/style.css xmlstarlet ed -L \ -u '//match[@target="pattern"][test/string="monospace"]/edit[@name="family"]/string' \ -v "$font_name" \ ~/.config/fontconfig/fonts.conf - omarchy-restart-waybar - OMARCHY_PATH=${OMARCHY_PATH:-$HOME/.local/share/omarchy} - if quickshell list -p "$OMARCHY_PATH/default/quickshell/omarchy-shell" 2>/dev/null | grep -q '^Instance '; then - omarchy-restart-bar + if pgrep -x waybar >/dev/null; then + omarchy-restart-waybar fi omarchy-restart-swayosd diff --git a/bin/omarchy-menu b/bin/omarchy-menu index 25f56a3b..e338fad9 100755 --- a/bin/omarchy-menu +++ b/bin/omarchy-menu @@ -1,58 +1,31 @@ #!/bin/bash -# omarchy:summary=Launch the Omarchy Menu or takes a parameter to jump straight to a submenu. +# omarchy:summary=Launch the Quickshell Omarchy menu or jump straight to a submenu/action +# omarchy:args=[menu-or-action|--json|--provider [menu-id]] +# omarchy:examples=omarchy menu | omarchy menu setup | omarchy menu style.theme | omarchy menu --json | omarchy menu --provider fonts style.font -# Set to true when going directly to a submenu, so we can exit directly -BACK_TO_EXIT=false +set -o pipefail -back_to() { - local parent_menu="$1" +OMARCHY_PATH=${OMARCHY_PATH:-$HOME/.local/share/omarchy} +DEFAULT_MENU="$OMARCHY_PATH/default/omarchy/omarchy-menu.jsonc" +USER_MENU="$HOME/.config/omarchy/extensions/omarchy-menu.jsonc" - if [[ $BACK_TO_EXIT == "true" ]]; then - exit 0 - elif [[ -n $parent_menu ]]; then - "$parent_menu" - else - show_main_menu - fi -} +# Menu hooks users may edit +# Menu rows live in default/omarchy/omarchy-menu.jsonc. +# Keep Bash here only for multi-step actions or truly runtime-generated lists. -toggle_existing_menu() { - if pgrep -f "walker.*--dmenu" >/dev/null; then - walker --close >/dev/null 2>&1 - exit 0 - fi -} - -menu() { - local prompt="$1" - local options="$2" - local extra="$3" - local preselect="$4" - - read -r -a args <<<"$extra" - - if [[ -n $preselect ]]; then - local index - index=$(echo -e "$options" | grep -nxF "$preselect" | cut -d: -f1) - if [[ -n $index ]]; then - args+=("-c" "$index") - fi - fi - - echo -e "$options" | omarchy-launch-walker --dmenu --width 295 --minheight 1 --maxheight 630 -p "$prompt…" "${args[@]}" 2>/dev/null -} +# Action helpers available to omarchy-menu.jsonc terminal() { xdg-terminal-exec --app-id=org.omarchy.terminal "$@" } present_terminal() { - omarchy-launch-floating-terminal-with-presentation $1 + omarchy-launch-floating-terminal-with-presentation "$*" } open_in_editor() { - notify-send -u low "Editing config file" "$1" + omarchy-notification-send -u low "Editing config file" "$1" omarchy-launch-editor "$1" } @@ -77,59 +50,15 @@ install_terminal() { present_terminal "omarchy-install-terminal $1" } -aur_install() { - present_terminal "echo 'Installing $1 from AUR...'; omarchy-pkg-aur-add $2" -} - -aur_install_and_launch() { - present_terminal "echo 'Installing $1 from AUR...'; omarchy-pkg-aur-add $2 && setsid gtk-launch $3" -} - -show_learn_menu() { - case $(menu "Learn" " Keybindings\n Tmux keybindings\n Omarchy\n Hyprland\n󰣇 Arch\n Neovim\n󱆃 Bash") in - *Tmux*) omarchy-menu-tmux-keybindings ;; - *Keybindings*) omarchy-menu-keybindings ;; - *Omarchy*) omarchy-launch-webapp "https://learn.omacom.io/2/the-omarchy-manual" ;; - *Hyprland*) omarchy-launch-webapp "https://wiki.hypr.land/" ;; - *Arch*) omarchy-launch-webapp "https://wiki.archlinux.org/title/Main_page" ;; - *Bash*) omarchy-launch-webapp "https://devhints.io/bash" ;; - *Neovim*) omarchy-launch-webapp "https://www.lazyvim.org/keymaps" ;; - *) show_main_menu ;; - esac -} - -show_trigger_menu() { - case $(menu "Trigger" "󰔛 Reminder\n Capture\n󰧸 Transcode\n Share\n󰔎 Toggle\n Hardware") in - *Reminder*) show_reminder_menu ;; - *Capture*) show_capture_menu ;; - *Transcode*) omarchy-transcode || back_to show_trigger_menu ;; - *Share*) show_share_menu ;; - *Toggle*) show_toggle_menu ;; - *Hardware*) show_hardware_menu ;; - *) show_main_menu ;; - esac -} - -show_reminder_menu() { - case $(menu "Reminder" "󰔛 Set one\n󰔛 Show all\n󰔛 Clear all") in - *Set*) show_custom_reminder_input ;; - *"Show all"*) omarchy-reminder show ;; - *"Clear all"*) omarchy-reminder clear ;; - *) back_to show_trigger_menu ;; - esac -} - show_custom_reminder_input() { local minutes minutes=$(omarchy-menu-input "Remind in minutes") - if [[ $minutes =~ ^[0-9]+$ ]] && ((minutes > 0)); then + if [[ $minutes =~ ^[0-9]+$ ]] && (( minutes > 0 )); then show_reminder_message_input "$minutes" elif [[ -n $minutes ]]; then omarchy-notification-send -g 󰔛 "Invalid reminder" "Enter the number of minutes" -u critical show_custom_reminder_input - else - back_to show_reminder_menu fi } @@ -145,778 +74,748 @@ show_reminder_message_input() { fi } -show_capture_menu() { - case $(menu "Capture" " Screenshot\n Screenrecord\n󰴑 Text Extraction\n󰃉 Color") in - *Screenshot*) omarchy-capture-screenshot ;; - *Screenrecord*) show_screenrecord_menu ;; - *Text*) omarchy-capture-text-extraction ;; - *Color*) pkill hyprpicker || hyprpicker -a ;; - *) back_to show_trigger_menu ;; - esac -} - get_webcam_list() { v4l2-ctl --list-devices 2>/dev/null | while IFS= read -r line; do if [[ $line != $'\t'* && -n $line ]]; then local name="$line" + local device IFS= read -r device || break - device=$(echo "$device" | tr -d '\t' | head -1) + device=$(tr -d '\t' <<<"$device" | head -1) [[ -n $device ]] && echo "$device $name" fi done } show_webcam_select_menu() { - local devices=$(get_webcam_list) - local count=$(echo "$devices" | grep -c . 2>/dev/null || echo 0) + local devices + local count + devices=$(get_webcam_list) + if [[ -z $devices ]]; then + count=0 + else + count=$(grep -c . <<<"$devices") + fi - if [[ -z $devices ]] || ((count == 0)); then - notify-send "No webcam devices found" -u critical -t 3000 + if [[ -z $devices ]] || (( count == 0 )); then + omarchy-notification-send "No webcam devices found" -u critical -t 3000 return 1 fi - if ((count == 1)); then - echo "$devices" | awk '{print $1}' + if (( count == 1 )); then + printf '%s\n' "${devices%%[[:space:]]*}" else - menu "Select Webcam" "$devices" | awk '{print $1}' + local selection + selection=$(omarchy-menu-select "Select Webcam" "$devices") || return 1 + printf '%s\n' "${selection%%[[:space:]]*}" fi } -show_screenrecord_menu() { - omarchy-capture-screenrecording --stop-recording && exit 0 +stop_active_screenrecording() { + omarchy-capture-screenrecording --stop-recording +} - case $(menu "Screenrecord" " With no audio\n With desktop audio\n With desktop + microphone audio\n With desktop + microphone audio + webcam") in - *"With no audio") omarchy-capture-screenrecording ;; - *"With desktop audio") omarchy-capture-screenrecording --with-desktop-audio ;; - *"With desktop + microphone audio") omarchy-capture-screenrecording --with-desktop-audio --with-microphone-audio ;; - *"With desktop + microphone audio + webcam") - local device=$(show_webcam_select_menu) || { - back_to show_capture_menu +screenrecord_with_webcam() { + local device + device=$(show_webcam_select_menu) || return 1 + omarchy-capture-screenrecording --with-desktop-audio --with-microphone-audio --with-webcam --webcam-device="$device" +} + + +# Runtime-generated providers users may edit +# Use provider:"fonts" only when a submenu calls provider_fonts (or a command) +# to return JSON. Normal submenus are inferred from dotted JSONC ids. + +provider_fonts() { + local menu_id="$1" + local current_font + local index=0 + local font + local label + current_font=$(omarchy-font-current 2>/dev/null) + + while IFS= read -r font; do + [[ -n $font ]] || continue + (( index++ )) + label="$font$(current_marker "$font" "$current_font")" + json_item "$menu_id.$index-$(slugify "$font")" "" "$label" "omarchy-font-set $(shell_quote "$font")" "$font typeface" + done < <(omarchy-font-list 2>/dev/null) +} + +provider_power_profiles() { + local menu_id="$1" + local current_profile + local profile + current_profile=$(powerprofilesctl get 2>/dev/null) + + while IFS= read -r profile; do + [[ -n $profile ]] || continue + json_item "$menu_id.$(slugify "$profile")" "󱐋" "$profile$(current_marker "$profile" "$current_profile")" "powerprofilesctl set $(shell_quote "$profile")" "$profile power profile" + done < <(omarchy-powerprofiles-list 2>/dev/null) +} + + +# Framework helpers below this line +# Most menu changes should not require editing below here. + +# Generic helpers + +shell_quote() { + printf "'%s'" "${1//\'/\'\\\'\'}" +} + +slugify() { + local value="${1,,}" + value="${value//[^a-z0-9]/-}" + + while [[ $value == *--* ]]; do + value="${value//--/-}" + done + + while [[ $value == -* ]]; do + value="${value#-}" + done + + while [[ $value == *- ]]; do + value="${value%-}" + done + + printf '%s' "${value:-item}" +} + +current_marker() { + if [[ $1 == $2 ]]; then + printf ' ✓' + fi +} + +declare -A CMD_PRESENT_CACHE=() +declare -A STATUS_CACHE=() + +cached_status() { + local key="$1" + shift + + if [[ -z ${STATUS_CACHE[$key]+x} ]]; then + "$@" >/dev/null 2>&1 + STATUS_CACHE[$key]=$? + fi + + return "${STATUS_CACHE[$key]}" +} + +omarchy-cmd-present() { + local cmd + + for cmd in "$@"; do + if [[ -z ${CMD_PRESENT_CACHE[$cmd]+x} ]]; then + if command -v "$cmd" >/dev/null 2>&1; then + CMD_PRESENT_CACHE[$cmd]=0 + else + CMD_PRESENT_CACHE[$cmd]=1 + fi + fi + + (( CMD_PRESENT_CACHE[$cmd] == 0 )) || return 1 + done + + return 0 +} + +omarchy-toggle-enabled() { + cached_status "toggle:$*" command omarchy-toggle-enabled "$@" +} + +omarchy-hibernation-available() { + cached_status hibernation command omarchy-hibernation-available +} + +omarchy-hw-hybrid-gpu() { + cached_status hw-hybrid-gpu command omarchy-hw-hybrid-gpu +} + +omarchy-hw-touchpad() { + cached_status hw-touchpad command omarchy-hw-touchpad +} + +omarchy-hw-touchscreen() { + cached_status hw-touchscreen command omarchy-hw-touchscreen +} + +omarchy-hw-dell-xps-haptic-touchpad() { + cached_status hw-dell-xps-haptic-touchpad command omarchy-hw-dell-xps-haptic-touchpad +} + +default_browser_is() { + if [[ -z ${DEFAULT_BROWSER_CACHE+x} ]]; then + DEFAULT_BROWSER_CACHE=$(command omarchy-default-browser 2>/dev/null || true) + fi + + [[ $DEFAULT_BROWSER_CACHE == "$1" ]] +} + +default_terminal_is() { + if [[ -z ${DEFAULT_TERMINAL_CACHE+x} ]]; then + DEFAULT_TERMINAL_CACHE=$(command omarchy-default-terminal 2>/dev/null || true) + fi + + [[ $DEFAULT_TERMINAL_CACHE == "$1" ]] +} + +default_editor_is() { + if [[ -z ${DEFAULT_EDITOR_CACHE+x} ]]; then + DEFAULT_EDITOR_CACHE=$(command omarchy-default-editor 2>/dev/null || true) + fi + + [[ $DEFAULT_EDITOR_CACHE == "$1" ]] +} + +haptic_touchpad_is() { + if [[ -z ${HAPTIC_TOUCHPAD_CACHE+x} ]]; then + HAPTIC_TOUCHPAD_CACHE=$(command dell-xps-touchpad-haptics get 2>/dev/null || true) + fi + + [[ $HAPTIC_TOUCHPAD_CACHE == "$1" ]] +} + +json_string() { + local value="${1-}" + value="${value//\\/\\\\}" + value="${value//\"/\\\"}" + value="${value//$'\t'/\\t}" + value="${value//$'\r'/\\r}" + value="${value//$'\n'/\\n}" + printf '"%s"' "$value" +} + +json_item() { + local id="$1" + local icon="$2" + local label="$3" + local action="${4:-}" + local keywords="${5:-}" + local description="${6:-}" + local target="${7:-}" + local provider="${8:-}" + + printf '{"id": ' + json_string "$id" + printf ', "icon": ' + json_string "$icon" + printf ', "label": ' + json_string "$label" + + if [[ -n $action ]]; then + printf ', "action": ' + json_string "$action" + fi + + if [[ -n $keywords ]]; then + printf ', "keywords": ' + json_string "$keywords" + fi + + if [[ -n $description ]]; then + printf ', "description": ' + json_string "$description" + fi + + if [[ -n $target ]]; then + printf ', "target": ' + json_string "$target" + fi + + if [[ -n $provider ]]; then + printf ', "provider": ' + json_string "$provider" + fi + + printf '}\n' +} + + +# Menu definition loading +menu_sources() { + local path + + for path in "$DEFAULT_MENU" "$USER_MENU"; do + [[ -f $path ]] || continue + jq -Rrs ' + # Our menu files are JSONC: full-line // comments plus trailing commas. + # Keep parsing in jq so the menu build path stays fast and dependency-free. + gsub("(?m)^\\s*//[^\\n]*(\\n|$)"; "") | + gsub(",(?\\s*[}\\]])"; "\(.tail)") | + fromjson | + if type == "object" then + (if has("items") and (.items | type) == "object" then .items else . end) + else + {} + end | + to_entries[] | + select(.value | type == "object") | + .value + { id: .key } + ' "$path" + done +} + +normalize_menu_json() { + jq -sc ' + def aliases_array: + if ((.aliases // []) | type) == "array" then (.aliases // []) + elif ((.aliases // "") != "") then [(.aliases // "")] + else [] end; + + def normalize_item: + def inferred_parent: + if has("parent") then .parent + elif (.id | contains(".")) then (.id | split(".")[:-1] | join(".")) + else "root" end; + + def inferred_kind: + if ((.action // "") != "") then "action" + elif ((.target // "") != "") then "link" + else "menu" end; + + def id_keywords: + .id | gsub("[._-]+"; " "); + + def alias_keywords: + aliases_array | map(gsub("[._-]+"; " ")) | join(" "); + + def keyword_string: + ([id_keywords, alias_keywords, (.keywords // "")] | join(" ") | split(" ") | map(select(. != ""))) as $terms | + reduce $terms[] as $term ([]; if index($term) then . else . + [$term] end) | + join(" "); + + { + id, + parent: (if .id == "root" then "" else inferred_parent end), + kind: inferred_kind, + icon: (.icon // ""), + label: (.label // .id), + target: (.target // ""), + keywords: keyword_string, + description: (.description // ""), + action: (.action // ""), + provider: (.provider // ""), + aliases: aliases_array, + when: (.when // ""), + checked: (.checked // "") + }; + + reduce (.[] | select(.id)) as $item ({ order: [], by_id: {} }; + if .by_id[$item.id] == null then .order += [$item.id] else . end | + .by_id[$item.id] = ((.by_id[$item.id] // {}) + $item) + ) | + { items: [ .order[] as $id | (.by_id[$id] + { id: $id }) | normalize_item ] } + ' +} + +menu_all_json() { + if [[ -z ${MENU_ALL_JSON_CACHE+x} ]]; then + MENU_ALL_JSON_CACHE=$(menu_sources | normalize_menu_json) || return + fi + + printf '%s\n' "$MENU_ALL_JSON_CACHE" +} + +menu_static_json() { + if [[ -z ${MENU_STATIC_JSON_CACHE+x} ]]; then + MENU_STATIC_JSON_CACHE=$(menu_all_json | jq ' + (.items | map({ key: .id, value: . }) | from_entries) as $by_id | + def ancestor_ids($id): + ($id | split(".")) as $parts | + [range(1; $parts | length) | $parts[0:.] | join(".")]; + def has_provider_ancestor($id): + any(ancestor_ids($id)[]; (($by_id[.] // {}).provider // "") != ""); + + { items: [.items[] | select(has_provider_ancestor(.id) | not)] } + ') || return + fi + + printf '%s\n' "$MENU_STATIC_JSON_CACHE" +} + +emit_config_items() { + local fields + local id + local icon + local label + local action + local keywords + local description + local target + local provider + local when + local checked + + while IFS= read -r fields; do + eval "set -- $fields" + id="$1" + icon="$2" + label="$3" + action="$4" + keywords="$5" + description="$6" + target="$7" + provider="$8" + when="$9" + checked="${10}" + + [[ -n $id ]] || continue + if [[ -n $when ]] && ! eval "$when" >/dev/null 2>&1; then + continue + fi + + if [[ -n $checked ]] && eval "$checked" >/dev/null 2>&1; then + label="$label ✓" + fi + + json_item "$id" "$icon" "$label" "$action" "$keywords" "$description" "$target" "$provider" + done < <(jq -r '.items[] | [ + (.id // ""), + (.icon // ""), + (.label // .id // ""), + (.action // ""), + (.keywords // ""), + (.description // ""), + (.target // ""), + (.provider // ""), + (.when // ""), + (.checked // "") + ] | @sh') +} + +menu_evaluated_json() { + if [[ -z ${MENU_EVALUATED_JSON_CACHE+x} ]]; then + MENU_EVALUATED_JSON_CACHE=$(menu_all_json | emit_config_items | normalize_menu_json) || return + fi + + printf '%s\n' "$MENU_EVALUATED_JSON_CACHE" +} + +menu_json() { + if [[ -z ${MENU_JSON_CACHE+x} ]]; then + MENU_JSON_CACHE=$(menu_static_json | emit_config_items | normalize_menu_json) || return + fi + + printf '%s\n' "$MENU_JSON_CACHE" +} + +# Provider dispatch +provider_mount_id() { + local provider="$1" + + menu_all_json | jq -r --arg provider "$provider" '.items[] | select(.provider == $provider) | .id' | head -n 1 +} + +provider_json() { + local provider="$1" + local menu_id="${2:-}" + local provider_function + + [[ -n $provider ]] || return 1 + menu_id=${menu_id:-$(provider_mount_id "$provider")} + [[ -n $menu_id ]] || return 1 + + if [[ $provider =~ ^[a-z0-9-]+$ ]]; then + provider_function="provider_${provider//-/_}" + case "$provider_function" in + provider_mount_id | provider_json) ;; + *) + if declare -F "$provider_function" >/dev/null; then + "$provider_function" "$menu_id" | normalize_menu_json + return + fi + ;; + esac + + if omarchy-cmd-present "$provider"; then + "$provider" "$menu_id" | normalize_menu_json return - } - omarchy-capture-screenrecording --with-desktop-audio --with-microphone-audio --with-webcam --webcam-device="$device" - ;; - *) back_to show_capture_menu ;; - esac -} - -show_share_menu() { - case $(menu "Share" " Clipboard\n File \n Folder\n󰥦 Receive") in - *Receive*) uwsm-app -- localsend ;; - *Clipboard*) omarchy-menu-share clipboard ;; - *File*) terminal bash -c "omarchy-menu-share file" ;; - *Folder*) terminal bash -c "omarchy-menu-share folder" ;; - *) back_to show_trigger_menu ;; - esac -} - -show_toggle_menu() { - local options="󱄄 Screensaver\n󰔎 Nightlight\n󱫖 Idle Lock\n󰂛 Notifications\n󰍜 Top Bar\n󱂬 Workspace Layout\n Window Gaps\n 1-Window Ratio\n󰍹 Monitor Scaling\n Direct Boot\n󰟵 Passwordless Sudo" - - case $(menu "Toggle" "$options") in - *Screensaver*) omarchy-toggle-screensaver ;; - *Nightlight*) omarchy-toggle-nightlight ;; - *Idle*) omarchy-toggle-idle ;; - *Notifications*) omarchy-toggle-notification-silencing ;; - *Bar*) omarchy-toggle-bar ;; - *Layout*) omarchy-hyprland-workspace-layout-toggle ;; - *Ratio*) omarchy-hyprland-window-single-square-aspect-toggle ;; - *Gaps*) omarchy-hyprland-window-gaps-toggle ;; - *Scaling*) omarchy-hyprland-monitor-scaling-cycle ;; - *"Direct Boot"*) present_terminal omarchy-config-direct-boot ;; - *"Passwordless Sudo"*) present_terminal omarchy-sudo-passwordless ;; - *) back_to show_trigger_menu ;; - esac -} - -show_hardware_menu() { - local options="󰛧 Laptop Display\n 󰍹 Mirror Display" - - if omarchy-hw-hybrid-gpu; then - options="$options\n Hybrid GPU" + fi fi - if omarchy-hw-touchpad; then - options="$options\n󰟸 Touchpad" - fi - - if omarchy-hw-dell-xps-haptic-touchpad && omarchy-cmd-present dell-xps-touchpad-haptics; then - options="$options\n󰌌 Touchpad Haptics" - fi - - if omarchy-hw-touchscreen; then - options="$options\n󰆽 Touchscreen" - fi - - case $(menu "Toggle" "$options") in - *Laptop*) omarchy-hyprland-monitor-internal toggle ;; - *Mirror*) omarchy-hyprland-monitor-internal-mirror toggle ;; - *Haptics*) show_hardware_touchpad_haptics_menu ;; - *Touchpad*) omarchy-toggle-touchpad ;; - *Touchscreen*) omarchy-toggle-touchscreen ;; - *"Hybrid GPU"*) present_terminal omarchy-toggle-hybrid-gpu ;; - *) back_to show_trigger_menu ;; - esac + echo "omarchy-menu: unknown provider '$provider'" >&2 + return 1 } -show_hardware_touchpad_haptics_menu() { - local current=$(dell-xps-touchpad-haptics get) - local selected=$(menu "Touchpad Haptics" "low\nmid\nhigh" "" "$current") - if [[ -n $selected ]]; then - dell-xps-touchpad-haptics set "$selected" - else - back_to show_hardware_menu +# Routes and actions +route_target() { + local route="${1,,}" + local target + local all_json + route="${route//_/-}" + + if [[ -z $route || $route == "go" || $route == "menu" ]]; then + printf 'root' + return + fi + + all_json=$(menu_all_json) || return + target=$(jq -r --arg route "$route" ' + def aliases: + if (.aliases | type) == "array" then .aliases + elif (.aliases | type) == "string" then [.aliases] + else [] end; + + first(.items[] | select(any(aliases[]?; (ascii_downcase | gsub("_"; "-")) == $route)) | .id) // empty + ' <<<"$all_json") || return + + printf '%s' "${target:-$route}" +} + +route_field() { + local target="$1" + local field="$2" + local all_json + local fields + local when + local value + + all_json=$(menu_all_json) || return + fields=$(jq -r --arg id "$target" --arg field "$field" ' + first(.items[] | select(.id == $id) | [(.when // ""), (.[$field] // "")] | @sh) // empty + ' <<<"$all_json") || return + [[ -n $fields ]] || return + + eval "set -- $fields" + when="$1" + value="$2" + + if [[ -n $when ]] && ! eval "$when" >/dev/null 2>&1; then + return + fi + + printf '%s' "$value" +} + +route_kind() { + local target="$1" + + if [[ $target == "root" ]]; then + printf 'menu' + return + fi + + route_field "$target" kind +} + +route_link_target() { + route_field "$1" target +} + +route_action() { + route_field "$1" action +} + +run_action() { + local id="$1" + local action="${2:-}" + + if [[ -z $action ]]; then + action=$(route_action "$id") || return 0 + fi + + [[ -n $action ]] || return 0 + eval "$action" +} + + +# Quickshell launcher +ensure_menu_style_file() { + local toggles_dir="$HOME/.local/state/omarchy/toggles" + local style_file="$toggles_dir/quickshell-menu.json" + local radius=0 + + [[ -f $style_file ]] && return + + mkdir -p "$toggles_dir" + if [[ -f $toggles_dir/walker.css ]] && grep -q 'border-radius: 6px;' "$toggles_dir/walker.css"; then + radius=6 + fi + + printf '{ "radius": %s }\n' "$radius" >"$style_file" +} + +usage() { + cat < [menu-id] + +Launch the Quickshell Omarchy menu. Pass a menu name (install, style, system, +capture, toggle, etc.) to open there, or an action id to run it directly. + +Options: + --json Print the static menu JSON used by Quickshell + --provider [id] Print JSON rows from a runtime provider + -h, --help Show this help +USAGE +} + +menu_layer_visible() { + hyprctl layers -j 2>/dev/null | jq -e '.. | objects | select(.namespace? == "omarchy-menu")' >/dev/null +} + +menu_lock_path() { + printf '%s/omarchy-menu.lock' "${XDG_RUNTIME_DIR:-/run/user/$UID}" +} + +menu_payload_json() { + local menu_file="$1" + local initial_menu="$2" + local selection_file="$3" + local done_file="$4" + local colors_payload_base64="$5" + local font_family="$6" + + jq -nc \ + --arg menu_file "$menu_file" \ + --arg initial_menu "$initial_menu" \ + --arg selection_file "$selection_file" \ + --arg done_file "$done_file" \ + --arg colors_payload_base64 "$colors_payload_base64" \ + --arg font_family "$font_family" \ + '{ + menuJsonFile: $menu_file, + initialMenu: $initial_menu, + selectionFile: $selection_file, + doneFile: $done_file, + colorsRawBase64: $colors_payload_base64, + fontFamily: $font_family + }' +} + +close_visible_quickshell_menu() { + menu_layer_visible || return 1 + omarchy-shell-ipc shell hide omarchy.menu >/dev/null 2>&1 || return 1 +} + +open_quickshell_menu() { + local initial_menu="$1" + local menu_payload + local colors_file="$HOME/.config/omarchy/current/theme/colors.toml" + local colors_payload_base64="" + local font_family + local menu_file="" + local selection_file + local done_file + local selected_id + local selected_action + local menu_started=false + local lock_path + local lock_fd + local summon_result + local payload + + lock_path=$(menu_lock_path) + exec {lock_fd}>"$lock_path" || return 1 + if ! flock -n "$lock_fd"; then + close_visible_quickshell_menu && return 0 + flock "$lock_fd" || return 1 + fi + + if close_visible_quickshell_menu; then + return 0 + fi + + ensure_menu_style_file + + selection_file=$(mktemp) + done_file=$(mktemp) + rm -f "$done_file" + trap "rm -f $(shell_quote "$selection_file") $(shell_quote "$done_file")" EXIT + + menu_payload=$(menu_json) || return + if [[ $initial_menu != "root" ]] && ! jq -e --arg id "$initial_menu" '.items[] | select(.id == $id)' <<<"$menu_payload" >/dev/null; then + menu_payload=$(menu_evaluated_json) || return + fi + + if [[ -f $colors_file ]]; then + colors_payload_base64=$(base64 -w0 "$colors_file") + fi + + font_family=$(omarchy-font-current 2>/dev/null || true) + font_family=${font_family:-monospace} + + menu_file=$(mktemp) + printf '%s' "$menu_payload" >"$menu_file" + trap "rm -f $(shell_quote "$menu_file") $(shell_quote "$selection_file") $(shell_quote "$done_file")" EXIT + payload=$(menu_payload_json "$menu_file" "$initial_menu" "$selection_file" "$done_file" "$colors_payload_base64" "$font_family") || return + + summon_result=$(omarchy-shell-ipc shell summon omarchy.menu "$payload" 2>/dev/null || true) + if [[ $summon_result != "ok" ]]; then + echo "omarchy-menu: Omarchy shell did not accept the menu request" >&2 + return 1 + fi + + for (( i = 0; i < 500; i++ )); do + if [[ -e $done_file ]] || menu_layer_visible; then + menu_started=true + break + fi + sleep 0.01 + done + + if [[ $menu_started != true ]]; then + omarchy-shell-ipc shell hide omarchy.menu >/dev/null 2>&1 || true + echo "omarchy-menu: Quickshell menu did not open" >&2 + return 1 + fi + + while [[ ! -e $done_file ]]; do + menu_layer_visible || break + sleep 0.01 + done + + if [[ -s $selection_file ]]; then + IFS=$'\t' read -r selected_id selected_action <"$selection_file" + exec {lock_fd}>&- + run_action "$selected_id" "$selected_action" fi } -show_style_menu() { - case $(menu "Style" "󰸌 Theme\n󰟵 Unlock\n Font\n Background\n󰍜 Bar\n󰘇 Corners\n Hyprland\n󱄄 Screensaver\n About") in +# CLI dispatch +case "${1:-}" in +--help | -h) + usage + exit 0 + ;; +--json) + menu_json || exit $? + exit 0 + ;; +--provider) + provider_json "${2:-}" "${3:-}" + exit $? + ;; +screenrecord | screenrecording | screen-record) + close_visible_quickshell_menu || true + stop_active_screenrecording && exit 0 + ;; +esac - *Theme*) show_theme_menu ;; - *Unlock*) omarchy-launch-walker -m menus:omarchyunlocks --width 800 --minheight 400 ;; - *Font*) show_font_menu ;; - *Background*) show_background_menu ;; - *Corners*) show_style_corners_menu ;; - *Hyprland*) open_in_editor "$(hypr_config_file looknfeel)" ;; - *Bar*) show_bar_position_menu ;; - *Screensaver*) show_screensaver_menu ;; - *About*) show_about_menu ;; - *) show_main_menu ;; - esac -} - -show_style_corners_menu() { - case $(omarchy-menu-select "Corners" "󰝣 Sharp" "󰘇 Round") in - *Sharp*) omarchy-style-corners sharp ;; - *Round*) omarchy-style-corners round ;; - *) show_style_menu ;; - esac -} - -show_bar_position_menu() { - case $(menu "Bar Position" "󰁝 Top\n󰁅 Bottom\n󰁍 Left\n󰁔 Right") in - *Top*) omarchy-style-bar-position top ;; - *Bottom*) omarchy-style-bar-position bottom ;; - *Left*) omarchy-style-bar-position left ;; - *Right*) omarchy-style-bar-position right ;; - *) show_style_menu ;; - esac -} - -show_about_menu() { - case $(menu "About" " Edit Text\n Set From Image\n Restore Default") in - *Text*) omarchy-branding-about text ;; - *Image*) omarchy-branding-about image ;; - *Default*) omarchy-branding-about reset ;; - *) show_style_menu ;; - esac -} - -show_screensaver_menu() { - case $(menu "Screensaver" " Edit Text\n Set From Image\n Restore Default") in - *Text*) omarchy-branding-screensaver text ;; - *Image*) omarchy-branding-screensaver image ;; - *Default*) omarchy-branding-screensaver reset ;; - *) show_style_menu ;; - esac -} - -show_theme_menu() { - theme=$(omarchy-theme-switcher) - [[ -n $theme ]] && omarchy-theme-set "$theme" -} - -show_background_menu() { - background=$(omarchy-theme-bg-switcher) - [[ -n $background ]] && omarchy-theme-bg-set "$background" -} - -show_font_menu() { - theme=$(menu "Font" "$(omarchy-font-list)" "--width 350" "$(omarchy-font-current)") - if [[ $theme == "CNCLD" || -z $theme ]]; then - back_to show_style_menu - else - omarchy-font-set "$theme" - fi -} - -show_setup_menu() { - local options=" Audio\n Wifi\n󰂯 Bluetooth\n󱐋 Power Profile\n System Sleep\n󰍹 Monitors" - [[ -f ~/.config/hypr/bindings.lua ]] && options="$options\n Keybindings" - [[ -f ~/.config/hypr/input.lua ]] && options="$options\n Input" - options="$options\n Defaults\n󰱔 DNS\n Security\n Config" - - case $(menu "Setup" "$options") in - *Audio*) omarchy-launch-audio ;; - *Wifi*) omarchy-launch-wifi ;; - *Bluetooth*) omarchy-launch-bluetooth ;; - *Power*) show_setup_power_menu ;; - *System*) show_setup_system_menu ;; - *Monitors*) open_in_editor "$(hypr_config_file monitors)" ;; - *Keybindings*) open_in_editor "$(hypr_config_file bindings)" ;; - *Input*) open_in_editor "$(hypr_config_file input)" ;; - *Defaults*) show_setup_default_menu ;; - *DNS*) present_terminal omarchy-setup-dns ;; - *Security*) show_setup_security_menu ;; - *Config*) show_setup_config_menu ;; - *) show_main_menu ;; - esac -} - -show_setup_power_menu() { - profile=$(menu "Power Profile" "$(omarchy-powerprofiles-list)" "" "$(powerprofilesctl get)") - - if [[ $profile == "CNCLD" || -z $profile ]]; then - back_to show_setup_menu - else - powerprofilesctl set "$profile" - fi -} - -show_setup_security_menu() { - case $(menu "Setup" "󰈷 Fingerprint\n Fido2") in - *Fingerprint*) present_terminal omarchy-setup-security-fingerprint ;; - *Fido2*) present_terminal omarchy-setup-security-fido2 ;; - *) show_setup_menu ;; - esac -} - -show_setup_default_menu() { - case $(menu "Default" " Browser\n Terminal\n Editor") in - *Browser*) show_setup_default_browser_menu ;; - *Terminal*) show_setup_default_terminal_menu ;; - *Editor*) show_setup_default_editor_menu ;; - *) show_setup_menu ;; - esac -} - -browser_desktop_exists() { - [[ -f ~/.local/share/applications/$1 || -f ~/.nix-profile/share/applications/$1 || -f /usr/share/applications/$1 ]] -} - -show_setup_default_browser_menu() { - local options="" - browser_desktop_exists chromium.desktop && options="$options Chromium" - browser_desktop_exists google-chrome.desktop && options="${options:+$options\n}󰊯 Chrome" - browser_desktop_exists brave-browser.desktop && options="${options:+$options\n}󰖟 Brave" - browser_desktop_exists brave-origin-beta.desktop && options="${options:+$options\n}󰖟 Brave Origin" - browser_desktop_exists microsoft-edge.desktop && options="${options:+$options\n}󰇩 Edge" - browser_desktop_exists firefox.desktop && options="${options:+$options\n}󰈹 Firefox" - browser_desktop_exists zen.desktop && options="${options:+$options\n}󰖟 Zen" - - local current="" - case "$(omarchy-default-browser)" in - chromium) current=" Chromium" ;; - chrome) current="󰊯 Chrome" ;; - brave) current="󰖟 Brave" ;; - brave-origin) current="󰖟 Brave Origin" ;; - edge) current="󰇩 Edge" ;; - firefox) current="󰈹 Firefox" ;; - zen) current="󰖟 Zen" ;; - esac - - case $(menu "Default Browser" "$options" "" "$current") in - *Chromium*) omarchy-default-browser chromium ;; - *Chrome*) omarchy-default-browser chrome ;; - *"Brave Origin"*) omarchy-default-browser brave-origin ;; - *Brave*) omarchy-default-browser brave ;; - *Edge*) omarchy-default-browser edge ;; - *Firefox*) omarchy-default-browser firefox ;; - *Zen*) omarchy-default-browser zen ;; - *) show_setup_default_menu ;; - esac -} - -show_setup_default_terminal_menu() { - local options="" - omarchy-cmd-present alacritty && options="$options Alacritty" - omarchy-cmd-present foot && options="${options:+$options\n} Foot" - omarchy-cmd-present ghostty && options="${options:+$options\n} Ghostty" - omarchy-cmd-present kitty && options="${options:+$options\n} Kitty" - - local current="" - case "$(omarchy-default-terminal)" in - alacritty) current=" Alacritty" ;; - foot) current=" Foot" ;; - ghostty) current=" Ghostty" ;; - kitty) current=" Kitty" ;; - esac - - case $(menu "Default Terminal" "$options" "" "$current") in - *Alacritty*) omarchy-default-terminal alacritty ;; - *Foot*) omarchy-default-terminal foot ;; - *Ghostty*) omarchy-default-terminal ghostty ;; - *Kitty*) omarchy-default-terminal kitty ;; - *) show_setup_default_menu ;; - esac -} - -show_setup_default_editor_menu() { - local options="" - omarchy-cmd-present nvim && options="$options Neovim" - omarchy-cmd-present code && options="${options:+$options\n} VSCode" - omarchy-cmd-present cursor && options="${options:+$options\n} Cursor" - omarchy-cmd-present zeditor && options="${options:+$options\n} Zed" - omarchy-cmd-present sublime_text && options="${options:+$options\n} Sublime Text" - omarchy-cmd-present helix && options="${options:+$options\n} Helix" - omarchy-cmd-present vim && options="${options:+$options\n} Vim" - omarchy-cmd-present emacs && options="${options:+$options\n} Emacs" - - local current="" - case "$(omarchy-default-editor)" in - nvim) current=" Neovim" ;; - code) current=" VSCode" ;; - cursor) current=" Cursor" ;; - zed | zeditor) current=" Zed" ;; - sublime_text) current=" Sublime Text" ;; - helix) current=" Helix" ;; - vim) current=" Vim" ;; - emacs) current=" Emacs" ;; - esac - - case $(menu "Default Editor" "$options" "" "$current") in - *Neovim*) omarchy-default-editor nvim ;; - *VSCode*) omarchy-default-editor code ;; - *Cursor*) omarchy-default-editor cursor ;; - *Zed*) omarchy-default-editor zed ;; - *Sublime*) omarchy-default-editor sublime_text ;; - *Helix*) omarchy-default-editor helix ;; - *Vim*) omarchy-default-editor vim ;; - *Emacs*) omarchy-default-editor emacs ;; - *) show_setup_default_menu ;; - esac -} - -show_setup_config_menu() { - case $(menu "Setup" " Hyprland\n Hypridle\n Hyprlock\n Hyprsunset\n Swayosd\n󰌧 Walker\n󰍜 Bar\n󰞅 XCompose") in - *Hyprland*) open_in_editor "$(hypr_config_file hyprland)" ;; - *Hypridle*) open_in_editor ~/.config/hypr/hypridle.conf && omarchy-restart-hypridle ;; - *Hyprlock*) open_in_editor ~/.config/hypr/hyprlock.conf ;; - *Hyprsunset*) open_in_editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset ;; - *Swayosd*) open_in_editor ~/.config/swayosd/config.toml && omarchy-restart-swayosd ;; - *Walker*) open_in_editor ~/.config/walker/config.toml && omarchy-restart-walker ;; - *Bar*) open_in_editor ~/.config/omarchy/bar.json && omarchy-restart-bar ;; - *XCompose*) open_in_editor ~/.XCompose && omarchy-restart-xcompose ;; - *) show_setup_menu ;; - esac -} - -show_setup_system_menu() { - local options="" - - if omarchy-toggle-enabled suspend-off; then - options="$options󰒲 Enable Suspend" - else - options="$options󰒲 Disable Suspend" - fi - - if omarchy-hibernation-available; then - options="$options\n󰤁 Disable Hibernate" - else - options="$options\n󰤁 Enable Hibernate" - fi - - case $(menu "System" "$options") in - *Suspend*) omarchy-toggle-suspend ;; - *"Enable Hibernate"*) present_terminal omarchy-hibernation-setup ;; - *"Disable Hibernate"*) present_terminal omarchy-hibernation-remove ;; - *) show_setup_menu ;; - esac -} - -show_install_menu() { - case $(menu "Install" "󰣇 Package\n󰣇 AUR\n Web App\n TUI\n Service\n Style\n󰵮 Development\n Editor\n Terminal\n Browser\n󱚤 AI\n Gaming\n󰍲 Windows") in - *Package*) terminal omarchy-pkg-install ;; - *AUR*) terminal omarchy-pkg-aur-install ;; - *Web*) present_terminal omarchy-webapp-install ;; - *TUI*) present_terminal omarchy-tui-install ;; - *Service*) show_install_service_menu ;; - *Style*) show_install_style_menu ;; - *Development*) show_install_development_menu ;; - *Editor*) show_install_editor_menu ;; - *Terminal*) show_install_terminal_menu ;; - *Browser*) show_install_browser_menu ;; - *Gaming*) show_install_gaming_menu ;; - *AI*) show_install_ai_menu ;; - *Windows*) present_terminal "omarchy-windows-vm install" ;; - *) show_main_menu ;; - esac -} - -show_install_browser_menu() { - case $(menu "Install" " Chrome\n Edge\n Brave\n Brave Origin\n Firefox\n󰖟 Zen") in - *Chrome*) present_terminal "omarchy-install-browser chrome" ;; - *Edge*) present_terminal "omarchy-install-browser edge" ;; - *"Brave Origin"*) present_terminal "omarchy-install-browser brave-origin" ;; - *Brave*) present_terminal "omarchy-install-browser brave" ;; - *Firefox*) present_terminal "omarchy-install-browser firefox" ;; - *Zen*) present_terminal "omarchy-install-browser zen" ;; - *) show_install_menu ;; - esac -} - -show_install_service_menu() { - case $(menu "Install" " Dropbox\n Tailscale\n󱇱 NordVPN [AUR]\n󰏖 ONCE\n󰟵 Bitwarden\n Chromium Account") in - *Dropbox*) present_terminal omarchy-install-dropbox ;; - *Tailscale*) present_terminal omarchy-install-tailscale ;; - *NordVPN*) present_terminal omarchy-install-nordvpn ;; - *ONCE*) present_terminal omarchy-install-once ;; - *Bitwarden*) install_and_launch "Bitwarden" "bitwarden bitwarden-cli" "bitwarden" ;; - *Chromium*) present_terminal omarchy-install-chromium-google-account ;; - *) show_install_menu ;; - esac -} - -show_install_editor_menu() { - case $(menu "Install" " VSCode\n Cursor\n Zed\n Sublime Text\n Helix\n Vim\n Emacs") in - *VSCode*) present_terminal omarchy-install-vscode ;; - *Cursor*) install_and_launch "Cursor" "cursor-bin" "cursor" ;; - *Zed*) present_terminal omarchy-install-zed ;; - *Sublime*) install_and_launch "Sublime Text" "sublime-text-4" "sublime_text" ;; - *Helix*) present_terminal omarchy-install-helix ;; - *Vim*) install "Vim" "vim" ;; - *Emacs*) install "Emacs" "emacs-wayland" && systemctl --user enable --now emacs.service ;; - *) show_install_menu ;; - esac -} - -show_install_terminal_menu() { - case $(menu "Install" " Alacritty\n Foot\n Ghostty\n Kitty") in - *Alacritty*) install_terminal "alacritty" ;; - *Foot*) install_terminal "foot" ;; - *Ghostty*) install_terminal "ghostty" ;; - *Kitty*) install_terminal "kitty" ;; - *) show_install_menu ;; - esac -} - -show_install_ai_menu() { - ollama_pkg=$( - (omarchy-cmd-present nvidia-smi && echo ollama-cuda) || - (omarchy-cmd-present rocminfo && echo ollama-rocm) || - echo ollama - ) - - case $(menu "Install" " Dictation\n󱚤 LM Studio\n󱚤 Ollama\n󱚤 Crush") in - *Dictation*) present_terminal omarchy-voxtype-install ;; - *Studio*) install "LM Studio" "lmstudio-bin" ;; - *Ollama*) install "Ollama" $ollama_pkg ;; - *Crush*) install "Crush" "crush-bin" ;; - *) show_install_menu ;; - esac -} - -show_install_gaming_menu() { - case $(menu "Install" " Steam\n󰯉 RetroArch\n󰍳 Minecraft\n󰢹 NVIDIA GeForce NOW\n Xbox Cloud Gaming\n󰂯 Xbox Controller\n󰍹 Moonlight (GameStream)\n Lutris (Battle.net)\n󱓟 Heroic (Epic Games)\n󰯉 RetroArch Game Launcher") in - *Steam*) present_terminal omarchy-install-gaming-steam ;; - *"RetroArch Game Launcher"*) omarchy-games-retro-install ;; - *RetroArch*) present_terminal omarchy-install-gaming-retroarch ;; - *Minecraft*) install_and_launch "Minecraft" "minecraft-launcher" "minecraft-launcher" ;; - *GeForce*) present_terminal omarchy-install-gaming-geforce-now ;; - *"Xbox Cloud"*) present_terminal omarchy-install-gaming-xbox-cloud ;; - *Xbox*) present_terminal omarchy-install-gaming-xbox-controllers ;; - *Lutris*) present_terminal omarchy-install-gaming-lutris ;; - *Heroic*) present_terminal omarchy-install-gaming-heroic ;; - *Moonlight*) present_terminal omarchy-install-gaming-moonlight ;; - *) show_install_menu ;; - esac -} - -show_install_style_menu() { - case $(menu "Install" "󰸌 Theme\n Background\n Font") in - *Theme*) present_terminal omarchy-theme-install ;; - *Background*) omarchy-theme-bg-install ;; - *Font*) show_install_font_menu ;; - *) show_install_menu ;; - esac -} - -show_install_font_menu() { - case $(menu "Install" " Cascadia Mono\n Meslo LG Mono\n Fira Code\n Victor Code\n Bitstream Vera Mono\n Iosevka" "--width 350") in - *Cascadia*) install_font "Cascadia Mono" "ttf-cascadia-mono-nerd" "CaskaydiaMono Nerd Font" ;; - *Meslo*) install_font "Meslo LG Mono" "ttf-meslo-nerd" "MesloLGL Nerd Font" ;; - *Fira*) install_font "Fira Code" "ttf-firacode-nerd" "FiraCode Nerd Font" ;; - *Victor*) install_font "Victor Code" "ttf-victor-mono-nerd" "VictorMono Nerd Font" ;; - *Bitstream*) install_font "Bitstream Vera Code" "ttf-bitstream-vera-mono-nerd" "BitstromWera Nerd Font" ;; - *Iosevka*) install_font "Iosevka" "ttf-iosevka-nerd" "Iosevka Nerd Font Mono" ;; - *) show_install_menu ;; - esac -} - -show_install_development_menu() { - case $(menu "Install" "󰫏 Ruby on Rails\n Docker DB\n JavaScript\n Go\n PHP\n Python\n Elixir\n Zig\n Rust\n Java\n .NET\n OCaml\n Clojure\n Scala") in - *Rails*) present_terminal "omarchy-install-dev-env ruby" ;; - *Docker*) present_terminal omarchy-install-docker-dbs ;; - *JavaScript*) show_install_javascript_menu ;; - *Go*) present_terminal "omarchy-install-dev-env go" ;; - *PHP*) show_install_php_menu ;; - *Python*) present_terminal "omarchy-install-dev-env python" ;; - *Elixir*) show_install_elixir_menu ;; - *Zig*) present_terminal "omarchy-install-dev-env zig" ;; - *Rust*) present_terminal "omarchy-install-dev-env rust" ;; - *Java*) present_terminal "omarchy-install-dev-env java" ;; - *NET*) present_terminal "omarchy-install-dev-env dotnet" ;; - *OCaml*) present_terminal "omarchy-install-dev-env ocaml" ;; - *Clojure*) present_terminal "omarchy-install-dev-env clojure" ;; - *Scala*) present_terminal "omarchy-install-dev-env scala" ;; - *) show_install_menu ;; - esac -} - -show_install_javascript_menu() { - case $(menu "Install" " Node.js\n Bun\n Deno") in - *Node*) present_terminal "omarchy-install-dev-env node" ;; - *Bun*) present_terminal "omarchy-install-dev-env bun" ;; - *Deno*) present_terminal "omarchy-install-dev-env deno" ;; - *) show_install_development_menu ;; - esac -} - -show_install_php_menu() { - case $(menu "Install" " PHP\n Laravel\n Symfony") in - *PHP*) present_terminal "omarchy-install-dev-env php" ;; - *Laravel*) present_terminal "omarchy-install-dev-env laravel" ;; - *Symfony*) present_terminal "omarchy-install-dev-env symfony" ;; - *) show_install_development_menu ;; - esac -} - -show_install_elixir_menu() { - case $(menu "Install" " Elixir\n Phoenix") in - *Elixir*) present_terminal "omarchy-install-dev-env elixir" ;; - *Phoenix*) present_terminal "omarchy-install-dev-env phoenix" ;; - *) show_install_development_menu ;; - esac -} - -show_remove_menu() { - case $(menu "Remove" "󰣇 Package\n Web App\n TUI\n󰵮 Development\n󰸌 Theme\n Browser\n Dictation\n Gaming\n󰍲 Windows\n󰏓 Preinstalls\n Security") in - *Package*) terminal omarchy-pkg-remove ;; - *Web*) present_terminal omarchy-webapp-remove ;; - *TUI*) present_terminal omarchy-tui-remove ;; - *Development*) show_remove_development_menu ;; - *Theme*) present_terminal omarchy-theme-remove ;; - *Browser*) show_remove_browser_menu ;; - *Dictation*) present_terminal omarchy-voxtype-remove ;; - *Gaming*) show_remove_gaming_menu ;; - *Windows*) present_terminal "omarchy-windows-vm remove" ;; - *Preinstalls*) present_terminal omarchy-remove-preinstalls ;; - *Security*) show_remove_security_menu ;; - *) show_main_menu ;; - esac -} - -show_remove_security_menu() { - case $(menu "Remove" "󰈷 Fingerprint\n Fido2") in - *Fingerprint*) present_terminal omarchy-remove-security-fingerprint ;; - *Fido2*) present_terminal omarchy-remove-security-fido2 ;; - *) show_remove_menu ;; - esac -} - -show_remove_browser_menu() { - case $(menu "Remove" " Chrome\n Edge\n Brave\n Brave Origin\n Firefox\n Zen") in - *Chrome*) present_terminal "omarchy-remove-browser chrome" ;; - *Edge*) present_terminal "omarchy-remove-browser edge" ;; - *"Brave Origin"*) present_terminal "omarchy-remove-browser brave-origin" ;; - *Brave*) present_terminal "omarchy-remove-browser brave" ;; - *Firefox*) present_terminal "omarchy-remove-browser firefox" ;; - *Zen*) present_terminal "omarchy-remove-browser zen" ;; - *) show_remove_menu ;; - esac -} - -show_remove_gaming_menu() { - case $(menu "Remove" " Steam\n RetroArch\n󰍳 Minecraft\n󰢹 NVIDIA GeForce NOW\n Xbox Cloud Gaming\n󰖺 Xbox Controller (󰂯)\n󰍹 Moonlight (GameStream)\n Lutris (Battle.net)\n󱓟 Heroic (Epic Games)") in - *Steam*) present_terminal omarchy-remove-gaming-steam ;; - *RetroArch*) present_terminal omarchy-remove-gaming-retroarch ;; - *Minecraft*) present_terminal omarchy-remove-gaming-minecraft ;; - *GeForce*) present_terminal omarchy-remove-gaming-geforce-now ;; - *"Xbox Cloud"*) present_terminal omarchy-remove-gaming-xbox-cloud ;; - *Xbox*) present_terminal omarchy-remove-gaming-xbox-controllers ;; - *Moonlight*) present_terminal omarchy-remove-gaming-moonlight ;; - *Lutris*) present_terminal omarchy-remove-gaming-lutris ;; - *Heroic*) present_terminal omarchy-remove-gaming-heroic ;; - *) show_remove_menu ;; - esac -} - -show_remove_development_menu() { - case $(menu "Remove" "󰫏 Ruby on Rails\n JavaScript\n Go\n PHP\n Python\n Elixir\n Zig\n Rust\n Java\n .NET\n OCaml\n Clojure\n Scala") in - *Rails*) present_terminal "omarchy-remove-dev-env ruby" ;; - *JavaScript*) show_remove_javascript_menu ;; - *Go*) present_terminal "omarchy-remove-dev-env go" ;; - *PHP*) show_remove_php_menu ;; - *Python*) present_terminal "omarchy-remove-dev-env python" ;; - *Elixir*) show_remove_elixir_menu ;; - *Zig*) present_terminal "omarchy-remove-dev-env zig" ;; - *Rust*) present_terminal "omarchy-remove-dev-env rust" ;; - *Java*) present_terminal "omarchy-remove-dev-env java" ;; - *NET*) present_terminal "omarchy-remove-dev-env dotnet" ;; - *OCaml*) present_terminal "omarchy-remove-dev-env ocaml" ;; - *Clojure*) present_terminal "omarchy-remove-dev-env clojure" ;; - *Scala*) present_terminal "omarchy-remove-dev-env scala" ;; - *) show_remove_menu ;; - esac -} - -show_remove_javascript_menu() { - case $(menu "Remove" " Node.js\n Bun\n Deno") in - *Node*) present_terminal "omarchy-remove-dev-env node" ;; - *Bun*) present_terminal "omarchy-remove-dev-env bun" ;; - *Deno*) present_terminal "omarchy-remove-dev-env deno" ;; - *) show_remove_development_menu ;; - esac -} - -show_remove_php_menu() { - case $(menu "Remove" " PHP\n Laravel\n Symfony") in - *PHP*) present_terminal "omarchy-remove-dev-env php" ;; - *Laravel*) present_terminal "omarchy-remove-dev-env laravel" ;; - *Symfony*) present_terminal "omarchy-remove-dev-env symfony" ;; - *) show_remove_development_menu ;; - esac -} - -show_remove_elixir_menu() { - case $(menu "Remove" " Elixir\n Phoenix") in - *Elixir*) present_terminal "omarchy-remove-dev-env elixir" ;; - *Phoenix*) present_terminal "omarchy-remove-dev-env phoenix" ;; - *) show_remove_development_menu ;; - esac -} - -show_update_menu() { - case $(menu "Update" "  Omarchy\n󰔫 Channel\n Config\n󰸌 Extra Themes\n Process\n󰇅 Hardware\n Firmware\n Password\n Timezone\n Time") in - *Omarchy*) present_terminal omarchy-update ;; - *Channel*) show_update_channel_menu ;; - *Config*) show_update_config_menu ;; - *Themes*) present_terminal omarchy-theme-update ;; - *Process*) show_update_process_menu ;; - *Hardware*) show_update_hardware_menu ;; - *Firmware*) present_terminal omarchy-update-firmware ;; - *Timezone*) present_terminal omarchy-tz-select ;; - *Time*) present_terminal omarchy-update-time ;; - *Password*) show_update_password_menu ;; - *) show_main_menu ;; - esac -} - -show_update_channel_menu() { - case $(menu "Update channel" "🟢 Stable\n🟡 RC\n🟠 Edge\n🔴 Dev") in - *Stable*) present_terminal "omarchy-channel-set stable" ;; - *RC*) present_terminal "omarchy-channel-set rc" ;; - *Edge*) present_terminal "omarchy-channel-set edge" ;; - *Dev*) present_terminal "omarchy-channel-set dev" ;; - *) show_update_menu ;; - esac -} -show_update_process_menu() { - case $(menu "Restart" " Hypridle\n Hyprsunset\n󰎟 Mako\n Swayosd\n󰌧 Walker\n󰍜 Bar") in - *Hypridle*) omarchy-restart-hypridle ;; - *Hyprsunset*) omarchy-restart-hyprsunset ;; - *Mako*) omarchy-restart-mako ;; - *Swayosd*) omarchy-restart-swayosd ;; - *Walker*) omarchy-restart-walker ;; - *Bar*) omarchy-restart-bar ;; - *) show_update_menu ;; - esac -} - -show_update_config_menu() { - case $(menu "Use default config" " Hyprland\n Hypridle\n Hyprlock\n Hyprsunset\n󱣴 Plymouth\n Swayosd\n Tmux\n󰌧 Walker\n󰍜 Bar") in - *Hyprland*) present_terminal omarchy-refresh-hyprland ;; - *Hypridle*) present_terminal omarchy-refresh-hypridle ;; - *Hyprlock*) present_terminal omarchy-refresh-hyprlock ;; - *Hyprsunset*) present_terminal omarchy-refresh-hyprsunset ;; - *Plymouth*) present_terminal omarchy-refresh-plymouth ;; - *Swayosd*) present_terminal omarchy-refresh-swayosd ;; - *Tmux*) present_terminal omarchy-refresh-tmux ;; - *Walker*) present_terminal omarchy-refresh-walker ;; - *Bar*) present_terminal omarchy-refresh-bar ;; - *) show_update_menu ;; - esac -} - -show_update_hardware_menu() { - case $(menu "Restart" " Audio\n󱚾 Wi-Fi\n󰂯 Bluetooth\n󰟸 Trackpad") in - *Audio*) present_terminal omarchy-restart-pipewire ;; - *Wi-Fi*) present_terminal omarchy-restart-wifi ;; - *Bluetooth*) present_terminal omarchy-restart-bluetooth ;; - *Trackpad*) present_terminal omarchy-restart-trackpad ;; - *) show_update_menu ;; - esac -} - -show_update_password_menu() { - case $(menu "Update Password" " Drive Encryption\n User") in - *Drive*) present_terminal omarchy-drive-password ;; - *User*) present_terminal passwd ;; - *) show_update_menu ;; - esac -} - -show_about() { - omarchy-launch-about -} - -show_system_menu() { - local options="󱄄 Screensaver\n Lock" - ! omarchy-toggle-enabled suspend-off && options="$options\n󰒲 Suspend" - omarchy-hibernation-available && options="$options\n󰤁 Hibernate" - options="$options\n󰍃 Logout\n󰜉 Restart\n󰐥 Shutdown" - - case $(menu "System" "$options") in - *Screensaver*) omarchy-launch-screensaver force ;; - *Lock*) omarchy-system-lock ;; - *Suspend*) systemctl suspend ;; - *Hibernate*) systemctl hibernate ;; - *Logout*) omarchy-system-logout ;; - *Restart*) omarchy-system-reboot ;; - *Shutdown*) omarchy-system-shutdown ;; - *) back_to show_main_menu ;; - esac -} - -show_main_menu() { - go_to_menu "$(menu "Go" "󰀻 Apps\n󰧑 Learn\n󱓞 Trigger\n Style\n Setup\n󰉉 Install\n󰭌 Remove\n Update\n About\n System")" -} - -go_to_menu() { - case "${1,,}" in - *apps*) walker -p "Launch…" ;; - *learn*) show_learn_menu ;; - *trigger*) show_trigger_menu ;; - *toggle*) show_toggle_menu ;; - *hardware*) show_hardware_menu ;; - *share*) show_share_menu ;; - *reminder-set*) show_custom_reminder_input ;; - *reminder*) show_reminder_menu ;; - *background*) show_background_menu ;; - *capture*) show_capture_menu ;; - *style*) show_style_menu ;; - *theme*) show_theme_menu ;; - *screenrecord*) show_screenrecord_menu ;; - *setup*) show_setup_menu ;; - *power*) show_setup_power_menu ;; - *install*) show_install_menu ;; - *remove*) show_remove_menu ;; - *update*) show_update_menu ;; - *about*) show_about ;; - *system*) show_system_menu ;; - esac -} - -# Allow user extensions and overrides -USER_EXTENSIONS="$HOME/.config/omarchy/extensions/menu.sh" -[[ -f $USER_EXTENSIONS ]] && source "$USER_EXTENSIONS" - -toggle_existing_menu - -if [[ -n $1 ]]; then - BACK_TO_EXIT=true - go_to_menu "$1" -else - show_main_menu +if [[ -z ${1:-} ]]; then + open_quickshell_menu root + exit $? fi + +target=$(route_target "${1:-}") || exit $? +kind=$(route_kind "$target") || exit $? + +case "$kind" in +action) + close_visible_quickshell_menu || true + run_action "$target" + ;; +menu) + open_quickshell_menu "$target" + ;; +link) + open_quickshell_menu "$(route_link_target "$target")" + ;; +*) + open_quickshell_menu root + ;; +esac diff --git a/bin/omarchy-refresh-bar b/bin/omarchy-refresh-bar index b2e2f03a..7a20739f 100755 --- a/bin/omarchy-refresh-bar +++ b/bin/omarchy-refresh-bar @@ -7,14 +7,11 @@ set -e USER_CONFIG="$HOME/.config/omarchy/shell.json" -# Removing the user file lets the shell fall back to shell-defaults.json on -# its next reload. omarchy-restart-bar (which is now an alias for restarting -# omarchy-shell) reloads the shell so the change takes effect immediately. +# Removing the user file lets the running shell fall back to shell-defaults.json +# via its FileView watcher. The next shell start will also use the defaults. if [[ -f $USER_CONFIG ]]; then backup="$USER_CONFIG.bak.$(date +%s)" cp "$USER_CONFIG" "$backup" echo "Backed up user shell config to $backup" rm -f "$USER_CONFIG" fi - -omarchy-restart-bar diff --git a/bin/omarchy-restart-bar b/bin/omarchy-restart-quickshell similarity index 70% rename from bin/omarchy-restart-bar rename to bin/omarchy-restart-quickshell index b892b5be..dde50bb9 100755 --- a/bin/omarchy-restart-bar +++ b/bin/omarchy-restart-quickshell @@ -1,7 +1,7 @@ #!/bin/bash -# omarchy:summary=Restart the bar -# omarchy:examples=omarchy restart bar +# omarchy:summary=Restart Omarchy Quickshell +# omarchy:examples=omarchy restart quickshell | omarchy-restart-quickshell OMARCHY_PATH=${OMARCHY_PATH:-$HOME/.local/share/omarchy} CONFIG_DIR="$OMARCHY_PATH/default/quickshell/omarchy-shell" diff --git a/bin/omarchy-style-bar-position b/bin/omarchy-style-bar-position index 4cea7539..af3a83e2 100755 --- a/bin/omarchy-style-bar-position +++ b/bin/omarchy-style-bar-position @@ -15,29 +15,16 @@ if [[ ! $position =~ ^(top|bottom|left|right)$ ]]; then fi mkdir -p "$(dirname "$CONFIG_FILE")" -python3 - "$CONFIG_FILE" "$position" <<'PY' -import json -import sys +tmp=$(mktemp) -path, position = sys.argv[1], sys.argv[2] -try: - with open(path) as file: - data = json.load(file) - if not isinstance(data, dict): - data = {} -except (FileNotFoundError, json.JSONDecodeError): - data = {} +if [[ -s $CONFIG_FILE ]] && jq -e 'type == "object"' "$CONFIG_FILE" >/dev/null 2>&1; then + jq --arg position "$position" ' + .version = 1 | + .bar = ((.bar // {}) | if type == "object" then . else {} end) | + .bar.position = $position + ' "$CONFIG_FILE" >"$tmp" +else + jq -n --arg position "$position" '{ version: 1, bar: { position: $position } }' >"$tmp" +fi -data.setdefault("version", 1) -bar = data.get("bar") -if not isinstance(bar, dict): - bar = {} - data["bar"] = bar -bar["position"] = position - -with open(path, "w") as file: - json.dump(data, file, indent=2) - file.write("\n") -PY - -omarchy-restart-bar +mv "$tmp" "$CONFIG_FILE" diff --git a/bin/omarchy-theme-set b/bin/omarchy-theme-set index 5e3a670d..8dafed8b 100755 --- a/bin/omarchy-theme-set +++ b/bin/omarchy-theme-set @@ -53,10 +53,6 @@ fi if pgrep -x waybar >/dev/null; then omarchy-restart-waybar fi -OMARCHY_PATH=${OMARCHY_PATH:-$HOME/.local/share/omarchy} -if quickshell list -p "$OMARCHY_PATH/default/quickshell/omarchy-shell" 2>/dev/null | grep -q '^Instance '; then - omarchy-restart-bar -fi omarchy-restart-swayosd omarchy-restart-terminal omarchy-restart-hyprctl diff --git a/bin/omarchy-voxtype-install b/bin/omarchy-voxtype-install index f94c6a23..c337c756 100755 --- a/bin/omarchy-voxtype-install +++ b/bin/omarchy-voxtype-install @@ -20,10 +20,9 @@ if gum confirm "Install Voxtype + AI model (~150MB) to enable dictation?"; then fi voxtype setup systemd - omarchy-restart-waybar - OMARCHY_PATH=${OMARCHY_PATH:-$HOME/.local/share/omarchy} - if quickshell list -p "$OMARCHY_PATH/default/quickshell/omarchy-shell" 2>/dev/null | grep -q '^Instance '; then - omarchy-restart-bar + if pgrep -x waybar >/dev/null; then + omarchy-restart-waybar fi + omarchy-restart-quickshell notify-send " Voxtype Dictation Ready" "Hold F9 to dictate (or toggle with Super + Ctrl + X)." -t 10000 fi diff --git a/bin/omarchy-voxtype-model b/bin/omarchy-voxtype-model index 1bc7e9e5..b811a5b0 100755 --- a/bin/omarchy-voxtype-model +++ b/bin/omarchy-voxtype-model @@ -5,8 +5,7 @@ set -e omarchy-launch-floating-terminal-with-presentation "voxtype setup model" -omarchy-restart-waybar -OMARCHY_PATH=${OMARCHY_PATH:-$HOME/.local/share/omarchy} -if quickshell list -p "$OMARCHY_PATH/default/quickshell/omarchy-shell" 2>/dev/null | grep -q '^Instance '; then - omarchy-restart-bar +if pgrep -x waybar >/dev/null; then + omarchy-restart-waybar fi +omarchy-restart-quickshell diff --git a/config/omarchy/extensions/menu.sh b/config/omarchy/extensions/menu.sh deleted file mode 100644 index f7d0b277..00000000 --- a/config/omarchy/extensions/menu.sh +++ /dev/null @@ -1,20 +0,0 @@ -# Overwrite parts of the omarchy-menu with user-specific submenus. -# See $OMARCHY_PATH/bin/omarchy-menu for functions that can be overwritten. -# -# WARNING: Overwritten functions will obviously not be updated when Omarchy changes. -# -# Example of minimal system menu: -# -# show_system_menu() { -# case $(menu "System" " Lock\n󰐥 Shutdown") in -# *Lock*) omarchy-system-lock ;; -# *Shutdown*) omarchy-system-shutdown ;; -# *) back_to show_main_menu ;; -# esac -# } -# -# Example of overriding just the about menu action: (Using zsh instead of bash (default)) -# -# show_about() { -# exec omarchy-launch-or-focus-tui "zsh -c 'fastfetch; read -k 1'" -# } diff --git a/config/omarchy/extensions/omarchy-menu.jsonc b/config/omarchy/extensions/omarchy-menu.jsonc new file mode 100644 index 00000000..e17fccc9 --- /dev/null +++ b/config/omarchy/extensions/omarchy-menu.jsonc @@ -0,0 +1,31 @@ +{ + // Extend the Quickshell Omarchy menu with JSONC. + // + // IDs are object keys. The parent is inferred from the dotted id, so + // "personal.notes" appears under "personal", and "personal" appears on the + // root menu. Reuse an existing id to override/extend it. + // + // Fields: + // icon Nerd Font glyph shown in the icon column. + // label Visible row title. + // action Shell command to run. If omitted, the row is a submenu. + // target Existing submenu id to open. Use for links/aliases. + // provider Runtime provider function/command returning JSON rows. + // aliases Alternate `omarchy-menu ` routes; also searchable. + // keywords Extra search terms beyond id/label/aliases. + // description Optional subtitle and extra search text. + // when Shell condition; hide row when it fails. + // checked Shell condition; append ✓ when it succeeds. + // + // Examples: + // "personal": {"icon":"","label":"Personal","keywords":"notes projects"}, + // "personal.notes": {"icon":"󰎞","label":"Notes","action":"omarchy-launch-editor ~/notes","keywords":"notes"}, + // "personal.files": {"icon":"","label":"Files","action":"uwsm-app -- nautilus ~/Documents","keywords":"files documents"}, + // + // Only use provider when a provider_name function or command named "name" + // returns JSON rows. Static submenus only need dotted ids. + // + // Example: replace the default About action by reusing the same id. Existing + // fields are kept unless overridden. + // "about": {"icon":"","label":"About","action":"omarchy-launch-or-focus-tui \"zsh -c 'fastfetch; read -k 1'\""}, +} diff --git a/default/hypr/apps/hyprshot.lua b/default/hypr/apps/hyprshot.lua index 409f2605..dde19803 100644 --- a/default/hypr/apps/hyprshot.lua +++ b/default/hypr/apps/hyprshot.lua @@ -1,2 +1,2 @@ -- Remove 1px border around hyprshot screenshots. -hl.layer_rule({ match = { namespace = "selection" }, no_anim = true }) +hl.layer_rule({ match = { namespace = "selection" }, no_anim = true, animation = "none" }) diff --git a/default/hypr/apps/quickshell.lua b/default/hypr/apps/quickshell.lua new file mode 100644 index 00000000..5d61dca7 --- /dev/null +++ b/default/hypr/apps/quickshell.lua @@ -0,0 +1,2 @@ +-- Keep the menu instant: no layer-shell fade/slide animation. +hl.layer_rule({ match = { namespace = "omarchy-menu" }, no_anim = true, animation = "none" }) diff --git a/default/hypr/apps/system.lua b/default/hypr/apps/system.lua index 9ce1fcd1..0429a880 100644 --- a/default/hypr/apps/system.lua +++ b/default/hypr/apps/system.lua @@ -23,4 +23,4 @@ hl.window_rule({ match = { tag = "pop" }, rounding = 8 }) hl.window_rule({ match = { tag = "noidle" }, idle_inhibit = "always" }) -- Image selector. -hl.layer_rule({ match = { namespace = "omarchy-image-selector" }, no_anim = true }) +hl.layer_rule({ match = { namespace = "omarchy-image-selector" }, no_anim = true, animation = "none" }) diff --git a/default/hypr/apps/walker.lua b/default/hypr/apps/walker.lua index 1d6f167b..c104b543 100644 --- a/default/hypr/apps/walker.lua +++ b/default/hypr/apps/walker.lua @@ -1,2 +1,2 @@ -- Application-specific animation. -hl.layer_rule({ match = { namespace = "walker" }, no_anim = true }) +hl.layer_rule({ match = { namespace = "walker" }, no_anim = true, animation = "none" }) diff --git a/default/hypr/autostart.lua b/default/hypr/autostart.lua index 61ae1ced..34c80e4c 100644 --- a/default/hypr/autostart.lua +++ b/default/hypr/autostart.lua @@ -1,7 +1,7 @@ hl.on("hyprland.start", function() hl.exec_cmd("uwsm-app -- hypridle") hl.exec_cmd("uwsm-app -- mako") - hl.exec_cmd("! omarchy-toggle-enabled bar-off && uwsm-app -- env OMARCHY_PATH=$HOME/.local/share/omarchy quickshell -p $HOME/.local/share/omarchy/default/quickshell/omarchy-shell") + hl.exec_cmd("! omarchy-toggle-enabled bar-off && omarchy-restart-quickshell") hl.exec_cmd("uwsm-app -- fcitx5 --disable notificationitem") hl.exec_cmd("uwsm-app -- swaybg -i ~/.config/omarchy/current/background -m fill") hl.exec_cmd("/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1") diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc new file mode 100644 index 00000000..3070341e --- /dev/null +++ b/default/omarchy/omarchy-menu.jsonc @@ -0,0 +1,333 @@ +{ + // Omarchy menu definition. + // JSONC is used for Neovim-friendly highlighting, comments, and trailing commas. + // IDs are object keys. Dotted IDs imply hierarchy: trigger.share.file belongs + // under trigger.share. + // Kind is inferred: action -> action, target -> link, otherwise submenu. + // Dotted IDs define the tree. Use provider:"name" only when the submenu + // calls provider_name() or a command named "name" to return JSON rows. + // Optional fields: + // aliases alternate `omarchy-menu ` routes; also searchable + // keywords extra search terms beyond id/label/aliases + // when shell condition; hide row when it fails + // checked shell condition; append ✓ when it succeeds + + // Root Menu + "apps": {"icon":"󰀻","label":"Apps","aliases":["app","applications"],"keywords":"launch launcher","action":"omarchy-launch-walker -p 'Launch…'"}, + "learn": {"icon":"󰧑","label":"Learn","keywords":"manual docs help reference"}, + "trigger": {"icon":"󱓞","label":"Trigger","keywords":"actions capture share toggle hardware reminder"}, + "style": {"icon":"","label":"Style","keywords":"theme background font bar quickshell hyprland appearance"}, + "setup": {"icon":"","label":"Setup","aliases":["settings"],"keywords":"configure defaults wifi bluetooth audio"}, + "install": {"icon":"󰉉","label":"Install","keywords":"package app service browser editor terminal development gaming"}, + "remove": {"icon":"󰭌","label":"Remove","aliases":["uninstall"],"keywords":"package app browser development gaming"}, + "update": {"icon":"","label":"Update","aliases":["restart","refresh"],"keywords":"upgrade channel firmware timezone"}, + "about": {"icon":"","label":"About","keywords":"omarchy system information","action":"omarchy-launch-about"}, + "system": {"icon":"","label":"System","aliases":["power-menu"],"keywords":"lock logout restart shutdown suspend hibernate"}, + + // System + "system.screensaver": {"icon":"󱄄","label":"Screensaver","action":"omarchy-launch-screensaver force"}, + "system.lock": {"icon":"","label":"Lock","keywords":"screen","action":"omarchy-system-lock"}, + "system.suspend": {"icon":"󰒲","label":"Suspend","keywords":"sleep","when":"! omarchy-toggle-enabled suspend-off","action":"systemctl suspend"}, + "system.hibernate": {"icon":"󰤁","label":"Hibernate","keywords":"sleep","when":"omarchy-hibernation-available","action":"systemctl hibernate"}, + "system.logout": {"icon":"󰍃","label":"Logout","keywords":"sign out","action":"omarchy-system-logout"}, + "system.restart": {"icon":"󰜉","label":"Restart","keywords":"reboot","action":"omarchy-system-reboot"}, + "system.shutdown": {"icon":"󰐥","label":"Shutdown","keywords":"power off","action":"omarchy-system-shutdown"}, + + // Learn + "learn.keybindings": {"icon":"","label":"Keybindings","keywords":"keyboard shortcuts bindings","action":"omarchy-menu-keybindings"}, + "learn.tmux-keybindings": {"icon":"","label":"Tmux keybindings","keywords":"keyboard shortcuts bindings terminal","action":"omarchy-menu-tmux-keybindings"}, + "learn.omarchy": {"icon":"","label":"Omarchy","keywords":"manual documentation","action":"omarchy-launch-webapp 'https://learn.omacom.io/2/the-omarchy-manual'"}, + "learn.hyprland": {"icon":"","label":"Hyprland","keywords":"wiki docs","action":"omarchy-launch-webapp 'https://wiki.hypr.land/'"}, + "learn.arch": {"icon":"󰣇","label":"Arch","keywords":"linux wiki docs","action":"omarchy-launch-webapp 'https://wiki.archlinux.org/title/Main_page'"}, + "learn.neovim": {"icon":"","label":"Neovim","keywords":"lazyvim keymaps docs","action":"omarchy-launch-webapp 'https://www.lazyvim.org/keymaps'"}, + "learn.bash": {"icon":"󱆃","label":"Bash","keywords":"shell cheatsheet","action":"omarchy-launch-webapp 'https://devhints.io/bash'"}, + + // Trigger + "trigger.reminder": {"icon":"󰔛","label":"Reminder","aliases":["reminder"],"keywords":"timer notification remind"}, + "trigger.capture": {"icon":"","label":"Capture","aliases":["capture","screenshot","screenrecord","screen-record","screenrecording"],"keywords":"color text extraction"}, + "trigger.capture.screenshot": {"icon":"","label":"Screenshot","keywords":"picture","action":"omarchy-capture-screenshot"}, + "trigger.capture.screenrecord.stop": {"icon":"","label":"Stop Screenrecording","keywords":"recording video","when":"pgrep -f '^gpu-screen-recorder' >/dev/null","action":"stop_active_screenrecording"}, + "trigger.capture.screenrecord": {"icon":"","label":"Screenrecord","keywords":"record screen video"}, + "trigger.capture.text": {"icon":"󰴑","label":"Text Extraction","keywords":"ocr","action":"omarchy-capture-text-extraction"}, + "trigger.capture.color": {"icon":"󰃉","label":"Color","keywords":"picker hyprpicker","action":"pkill hyprpicker || hyprpicker -a"}, + "trigger.capture.screenrecord.no-audio": {"icon":"","label":"With no audio","action":"omarchy-capture-screenrecording"}, + "trigger.capture.screenrecord.desktop-audio": {"icon":"","label":"With desktop audio","action":"omarchy-capture-screenrecording --with-desktop-audio"}, + "trigger.capture.screenrecord.microphone": {"icon":"","label":"With desktop + microphone audio","keywords":"mic","action":"omarchy-capture-screenrecording --with-desktop-audio --with-microphone-audio"}, + "trigger.capture.screenrecord.webcam": {"icon":"","label":"With desktop + microphone audio + webcam","keywords":"camera","action":"screenrecord_with_webcam"}, + "trigger.transcode": {"icon":"󰧸","label":"Transcode","keywords":"convert image video","action":"omarchy-transcode"}, + "trigger.share": {"icon":"","label":"Share","aliases":["share"],"keywords":"clipboard file folder localsend"}, + "trigger.toggle": {"icon":"󰔎","label":"Toggle","aliases":["toggle","toggles"],"keywords":"screensaver nightlight idle notifications bar quickshell"}, + "trigger.hardware": {"icon":"","label":"Hardware","aliases":["hardware","hw"],"keywords":"monitor touchpad touchscreen gpu"}, + "trigger.hardware.laptop-display": {"icon":"󰛧","label":"Laptop Display","keywords":"monitor","action":"omarchy-hyprland-monitor-internal toggle"}, + "trigger.hardware.mirror-display": {"icon":"󰍹","label":"Mirror Display","keywords":"monitor","action":"omarchy-hyprland-monitor-internal-mirror toggle"}, + "trigger.hardware.hybrid-gpu": {"icon":"","label":"Hybrid GPU","keywords":"graphics","when":"omarchy-hw-hybrid-gpu >/dev/null","action":"present_terminal omarchy-toggle-hybrid-gpu"}, + "trigger.hardware.touchpad": {"icon":"󰟸","label":"Touchpad","keywords":"trackpad","when":"omarchy-hw-touchpad >/dev/null","action":"omarchy-toggle-touchpad"}, + "trigger.hardware.touchpad-haptics": {"icon":"󰌌","label":"Touchpad Haptics","when":"omarchy-hw-dell-xps-haptic-touchpad >/dev/null && omarchy-cmd-present dell-xps-touchpad-haptics"}, + "trigger.hardware.touchpad-haptics.low": {"icon":"󰌌","label":"low","when":"omarchy-hw-dell-xps-haptic-touchpad >/dev/null && omarchy-cmd-present dell-xps-touchpad-haptics","checked":"haptic_touchpad_is low","action":"dell-xps-touchpad-haptics set low"}, + "trigger.hardware.touchpad-haptics.mid": {"icon":"󰌌","label":"mid","keywords":"medium","when":"omarchy-hw-dell-xps-haptic-touchpad >/dev/null && omarchy-cmd-present dell-xps-touchpad-haptics","checked":"haptic_touchpad_is mid","action":"dell-xps-touchpad-haptics set mid"}, + "trigger.hardware.touchpad-haptics.high": {"icon":"󰌌","label":"high","when":"omarchy-hw-dell-xps-haptic-touchpad >/dev/null && omarchy-cmd-present dell-xps-touchpad-haptics","checked":"haptic_touchpad_is high","action":"dell-xps-touchpad-haptics set high"}, + "trigger.hardware.touchscreen": {"icon":"󰆽","label":"Touchscreen","keywords":"tablet","when":"omarchy-hw-touchscreen >/dev/null","action":"omarchy-toggle-touchscreen"}, + "trigger.reminder.set": {"icon":"󰔛","label":"Set one","aliases":["reminder-set","remind"],"keywords":"timer","action":"show_custom_reminder_input"}, + "trigger.reminder.show": {"icon":"󰔛","label":"Show all","keywords":"list","action":"omarchy-reminder show"}, + "trigger.reminder.clear": {"icon":"󰔛","label":"Clear all","keywords":"delete","action":"omarchy-reminder clear"}, + "trigger.share.clipboard": {"icon":"","label":"Clipboard","action":"omarchy-menu-share clipboard"}, + "trigger.share.file": {"icon":"","label":"File","action":"terminal bash -c 'omarchy-menu-share file'"}, + "trigger.share.folder": {"icon":"","label":"Folder","keywords":"directory","action":"terminal bash -c 'omarchy-menu-share folder'"}, + "trigger.share.receive": {"icon":"󰥦","label":"Receive","keywords":"localsend","action":"uwsm-app -- localsend"}, + "trigger.toggle.screensaver": {"icon":"󱄄","label":"Screensaver","action":"omarchy-toggle-screensaver"}, + "trigger.toggle.nightlight": {"icon":"󰔎","label":"Nightlight","keywords":"night light hyprsunset","action":"omarchy-toggle-nightlight"}, + "trigger.toggle.idle-lock": {"icon":"󱫖","label":"Idle Lock","keywords":"hypridle","action":"omarchy-toggle-idle"}, + "trigger.toggle.notifications": {"icon":"󰂛","label":"Notifications","keywords":"mako silence","action":"omarchy-toggle-notification-silencing"}, + "trigger.toggle.top-bar": {"icon":"󰍜","label":"Bar","keywords":"top bar quickshell","action":"omarchy-toggle-bar"}, + "trigger.toggle.workspace-layout": {"icon":"󱂬","label":"Workspace Layout","action":"omarchy-hyprland-workspace-layout-toggle"}, + "trigger.toggle.window-gaps": {"icon":"","label":"Window Gaps","action":"omarchy-hyprland-window-gaps-toggle"}, + "trigger.toggle.one-window-ratio": {"icon":"","label":"1-Window Ratio","keywords":"square","action":"omarchy-hyprland-window-single-square-aspect-toggle"}, + "trigger.toggle.monitor-scaling": {"icon":"󰍹","label":"Monitor Scaling","keywords":"display","action":"omarchy-hyprland-monitor-scaling-cycle"}, + "trigger.toggle.direct-boot": {"icon":"","label":"Direct Boot","action":"present_terminal omarchy-config-direct-boot"}, + "trigger.toggle.passwordless-sudo": {"icon":"󰟵","label":"Passwordless Sudo","action":"present_terminal omarchy-sudo-passwordless"}, + + // Style + "style.theme": {"icon":"󰸌","label":"Theme","aliases":["theme","themes"],"keywords":"colors","action":"theme=$(omarchy-theme-switcher); [[ -n $theme ]] && omarchy-theme-set \"$theme\""}, + "style.unlock": {"icon":"󰟵","label":"Unlock","keywords":"themes","action":"omarchy-launch-walker -m menus:omarchyunlocks --width 800 --minheight 400"}, + "style.font": {"icon":"","label":"Font","keywords":"typeface","provider":"fonts"}, + "style.background": {"icon":"","label":"Background","aliases":["background","wallpaper"],"action":"background=$(omarchy-theme-bg-switcher); [[ -n $background ]] && omarchy-theme-bg-set \"$background\""}, + "style.bar": {"icon":"󰍜","label":"Bar","keywords":"quickshell top bottom left right settings"}, + "style.corners": {"icon":"󰘇","label":"Corners","keywords":"sharp round rounded"}, + "style.hyprland": {"icon":"","label":"Hyprland","keywords":"look feel","action":"open_in_editor \"$(hypr_config_file looknfeel)\""}, + "style.screensaver": {"icon":"󱄄","label":"Screensaver","keywords":"branding"}, + "style.about": {"icon":"","label":"About","keywords":"branding"}, + "style.install": {"icon":"󰉉","label":"Install","keywords":"theme background font"}, + "style.bar.top": {"icon":"󰁝","label":"Top","keywords":"position","action":"omarchy-style-bar-position top"}, + "style.bar.bottom": {"icon":"󰁅","label":"Bottom","keywords":"position","action":"omarchy-style-bar-position bottom"}, + "style.bar.left": {"icon":"󰁍","label":"Left","keywords":"position","action":"omarchy-style-bar-position left"}, + "style.bar.right": {"icon":"󰁔","label":"Right","keywords":"position","action":"omarchy-style-bar-position right"}, + "style.corners.sharp": {"icon":"󰝣","label":"Sharp","keywords":"square","action":"omarchy-style-corners sharp"}, + "style.corners.round": {"icon":"󰘇","label":"Round","keywords":"rounded","action":"omarchy-style-corners round"}, + "style.about.text": {"icon":"","label":"Edit Text","keywords":"branding","action":"omarchy-branding-about text"}, + "style.about.image": {"icon":"","label":"Set From Image","keywords":"branding","action":"omarchy-branding-about image"}, + "style.about.default": {"icon":"","label":"Restore Default","keywords":"branding","action":"omarchy-branding-about reset"}, + "style.screensaver.text": {"icon":"","label":"Edit Text","keywords":"branding","action":"omarchy-branding-screensaver text"}, + "style.screensaver.image": {"icon":"","label":"Set From Image","keywords":"branding","action":"omarchy-branding-screensaver image"}, + "style.screensaver.default": {"icon":"","label":"Restore Default","keywords":"branding","action":"omarchy-branding-screensaver reset"}, + "style.install.theme": {"icon":"󰸌","label":"Theme","action":"present_terminal omarchy-theme-install"}, + "style.install.background": {"icon":"","label":"Background","keywords":"wallpaper","action":"omarchy-theme-bg-install"}, + "style.install.font": {"icon":"","label":"Font"}, + "style.install.font.cascadia": {"icon":"","label":"Cascadia Mono","action":"install_font 'Cascadia Mono' ttf-cascadia-mono-nerd 'CaskaydiaMono Nerd Font'"}, + "style.install.font.meslo": {"icon":"","label":"Meslo LG Mono","action":"install_font 'Meslo LG Mono' ttf-meslo-nerd 'MesloLGL Nerd Font'"}, + "style.install.font.fira": {"icon":"","label":"Fira Code","action":"install_font 'Fira Code' ttf-firacode-nerd 'FiraCode Nerd Font'"}, + "style.install.font.victor": {"icon":"","label":"Victor Code","action":"install_font 'Victor Code' ttf-victor-mono-nerd 'VictorMono Nerd Font'"}, + "style.install.font.bitstream": {"icon":"","label":"Bitstream Vera Mono","action":"install_font 'Bitstream Vera Code' ttf-bitstream-vera-mono-nerd 'BitstromWera Nerd Font'"}, + "style.install.font.iosevka": {"icon":"","label":"Iosevka","action":"install_font Iosevka ttf-iosevka-nerd 'Iosevka Nerd Font Mono'"}, + + // Setup + "setup.audio": {"icon":"","label":"Audio","keywords":"sound","action":"omarchy-launch-audio"}, + "setup.wifi": {"icon":"","label":"Wifi","keywords":"wireless network","action":"omarchy-launch-wifi"}, + "setup.bluetooth": {"icon":"󰂯","label":"Bluetooth","action":"omarchy-launch-bluetooth"}, + "setup.power": {"icon":"󱐋","label":"Power Profile","aliases":["power","power-profile","power-profiles"],"keywords":"performance battery","provider":"power-profiles"}, + "setup.sleep": {"icon":"","label":"System Sleep","keywords":"suspend hibernate"}, + "setup.sleep.suspend-enable": {"icon":"󰒲","label":"Enable Suspend","when":"omarchy-toggle-enabled suspend-off","action":"omarchy-toggle-suspend"}, + "setup.sleep.suspend-disable": {"icon":"󰒲","label":"Disable Suspend","when":"! omarchy-toggle-enabled suspend-off","action":"omarchy-toggle-suspend"}, + "setup.sleep.hibernate-enable": {"icon":"󰤁","label":"Enable Hibernate","when":"! omarchy-hibernation-available","action":"present_terminal omarchy-hibernation-setup"}, + "setup.sleep.hibernate-disable": {"icon":"󰤁","label":"Disable Hibernate","when":"omarchy-hibernation-available","action":"present_terminal omarchy-hibernation-remove"}, + "setup.monitors": {"icon":"󰍹","label":"Monitors","keywords":"monitor display hyprland","action":"open_in_editor \"$(hypr_config_file monitors)\""}, + "setup.keybindings": {"icon":"","label":"Keybindings","keywords":"keyboard shortcuts","when":"[[ -f $(hypr_config_file bindings) ]]","action":"open_in_editor \"$(hypr_config_file bindings)\""}, + "setup.input": {"icon":"","label":"Input","keywords":"keyboard touchpad mouse","when":"[[ -f $(hypr_config_file input) ]]","action":"open_in_editor \"$(hypr_config_file input)\""}, + "setup.default": {"icon":"","label":"Defaults","aliases":["default","defaults"],"keywords":"browser terminal editor"}, + "setup.default.browser": {"icon":"","label":"Browser"}, + "setup.default.browser.chromium": {"icon":"","label":"Chromium","when":"omarchy-cmd-present chromium","checked":"default_browser_is chromium","action":"omarchy-default-browser chromium"}, + "setup.default.browser.chrome": {"icon":"󰊯","label":"Chrome","keywords":"google","when":"omarchy-cmd-present google-chrome-stable","checked":"default_browser_is chrome","action":"omarchy-default-browser chrome"}, + "setup.default.browser.brave": {"icon":"󰖟","label":"Brave","when":"omarchy-cmd-present brave","checked":"default_browser_is brave","action":"omarchy-default-browser brave"}, + "setup.default.browser.brave-origin": {"icon":"󰖟","label":"Brave Origin","when":"omarchy-cmd-present brave-origin-beta","checked":"default_browser_is brave-origin","action":"omarchy-default-browser brave-origin"}, + "setup.default.browser.edge": {"icon":"󰇩","label":"Edge","when":"omarchy-cmd-present microsoft-edge-stable","checked":"default_browser_is edge","action":"omarchy-default-browser edge"}, + "setup.default.browser.firefox": {"icon":"󰈹","label":"Firefox","when":"omarchy-cmd-present firefox","checked":"default_browser_is firefox","action":"omarchy-default-browser firefox"}, + "setup.default.browser.zen": {"icon":"󰖟","label":"Zen","when":"omarchy-cmd-present zen-browser","checked":"default_browser_is zen","action":"omarchy-default-browser zen"}, + "setup.default.terminal": {"icon":"","label":"Terminal"}, + "setup.default.terminal.alacritty": {"icon":"","label":"Alacritty","when":"omarchy-cmd-present alacritty","checked":"default_terminal_is alacritty","action":"omarchy-default-terminal alacritty"}, + "setup.default.terminal.foot": {"icon":"","label":"Foot","when":"omarchy-cmd-present foot","checked":"default_terminal_is foot","action":"omarchy-default-terminal foot"}, + "setup.default.terminal.ghostty": {"icon":"","label":"Ghostty","when":"omarchy-cmd-present ghostty","checked":"default_terminal_is ghostty","action":"omarchy-default-terminal ghostty"}, + "setup.default.terminal.kitty": {"icon":"","label":"Kitty","when":"omarchy-cmd-present kitty","checked":"default_terminal_is kitty","action":"omarchy-default-terminal kitty"}, + "setup.default.editor": {"icon":"","label":"Editor"}, + "setup.default.editor.neovim": {"icon":"","label":"Neovim","keywords":"nvim","when":"omarchy-cmd-present nvim","checked":"default_editor_is nvim","action":"omarchy-default-editor nvim"}, + "setup.default.editor.vscode": {"icon":"","label":"VSCode","keywords":"code","when":"omarchy-cmd-present code","checked":"default_editor_is code","action":"omarchy-default-editor code"}, + "setup.default.editor.cursor": {"icon":"","label":"Cursor","when":"omarchy-cmd-present cursor","checked":"default_editor_is cursor","action":"omarchy-default-editor cursor"}, + "setup.default.editor.zed": {"icon":"","label":"Zed","when":"omarchy-cmd-present zeditor","checked":"default_editor_is zeditor","action":"omarchy-default-editor zed"}, + "setup.default.editor.sublime": {"icon":"","label":"Sublime Text","when":"omarchy-cmd-present sublime_text","checked":"default_editor_is sublime_text","action":"omarchy-default-editor sublime_text"}, + "setup.default.editor.helix": {"icon":"","label":"Helix","when":"omarchy-cmd-present helix","checked":"default_editor_is helix","action":"omarchy-default-editor helix"}, + "setup.default.editor.vim": {"icon":"","label":"Vim","when":"omarchy-cmd-present vim","checked":"default_editor_is vim","action":"omarchy-default-editor vim"}, + "setup.default.editor.emacs": {"icon":"","label":"Emacs","when":"omarchy-cmd-present emacs","checked":"default_editor_is emacs","action":"omarchy-default-editor emacs"}, + "setup.dns": {"icon":"󰱔","label":"DNS","keywords":"network","action":"present_terminal omarchy-setup-dns"}, + "setup.security": {"icon":"","label":"Security","keywords":"fingerprint fido2"}, + "setup.config": {"icon":"","label":"Config","keywords":"files hyprland bar quickshell walker waybar"}, + "setup.security.fingerprint": {"icon":"󰈷","label":"Fingerprint","action":"present_terminal omarchy-setup-security-fingerprint"}, + "setup.security.fido2": {"icon":"","label":"Fido2","keywords":"key","action":"present_terminal omarchy-setup-security-fido2"}, + "setup.config.hyprland": {"icon":"","label":"Hyprland","action":"open_in_editor \"$(hypr_config_file hyprland)\""}, + "setup.config.hypridle": {"icon":"","label":"Hypridle","keywords":"idle","action":"open_in_editor ~/.config/hypr/hypridle.conf && omarchy-restart-hypridle"}, + "setup.config.hyprlock": {"icon":"","label":"Hyprlock","keywords":"lock","action":"open_in_editor ~/.config/hypr/hyprlock.conf"}, + "setup.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"night light","action":"open_in_editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset"}, + "setup.config.swayosd": {"icon":"","label":"Swayosd","action":"open_in_editor ~/.config/swayosd/config.toml && omarchy-restart-swayosd"}, + "setup.config.walker": {"icon":"󰌧","label":"Walker","keywords":"launcher","action":"open_in_editor ~/.config/walker/config.toml && omarchy-restart-walker"}, + "setup.config.bar": {"icon":"󰍜","label":"Bar","keywords":"quickshell shell config","action":"omarchy-launch-bar-settings"}, + "setup.config.xcompose": {"icon":"󰞅","label":"XCompose","keywords":"compose key","action":"open_in_editor ~/.XCompose && omarchy-restart-xcompose"}, + + // Install + "install.package": {"icon":"󰣇","label":"Package","keywords":"pacman aur","action":"terminal omarchy-pkg-install"}, + "install.aur": {"icon":"󰣇","label":"AUR","keywords":"package","action":"terminal omarchy-pkg-aur-install"}, + "install.webapp": {"icon":"","label":"Web App","keywords":"pwa","action":"present_terminal omarchy-webapp-install"}, + "install.tui": {"icon":"","label":"TUI","keywords":"terminal app","action":"present_terminal omarchy-tui-install"}, + "install.service": {"icon":"","label":"Service","keywords":"dropbox tailscale vpn"}, + "install.style": {"icon":"","label":"Style","keywords":"theme background font","target":"style.install"}, + "install.development": {"icon":"󰵮","label":"Development","keywords":"languages programming"}, + "install.editor": {"icon":"","label":"Editor","keywords":"ide"}, + "install.terminal": {"icon":"","label":"Terminal","keywords":"emulator"}, + "install.browser": {"icon":"","label":"Browser","keywords":"web"}, + "install.ai": {"icon":"󱚤","label":"AI","keywords":"dictation ollama lm studio crush"}, + "install.gaming": {"icon":"","label":"Gaming","keywords":"steam retroarch minecraft lutris heroic"}, + "install.windows": {"icon":"󰍲","label":"Windows","keywords":"vm","action":"present_terminal 'omarchy-windows-vm install'"}, + "install.browser.chrome": {"icon":"","label":"Chrome","action":"present_terminal 'omarchy-install-browser chrome'"}, + "install.browser.edge": {"icon":"","label":"Edge","action":"present_terminal 'omarchy-install-browser edge'"}, + "install.browser.brave": {"icon":"","label":"Brave","action":"present_terminal 'omarchy-install-browser brave'"}, + "install.browser.brave-origin": {"icon":"","label":"Brave Origin","action":"present_terminal 'omarchy-install-browser brave-origin'"}, + "install.browser.firefox": {"icon":"","label":"Firefox","action":"present_terminal 'omarchy-install-browser firefox'"}, + "install.browser.zen": {"icon":"󰖟","label":"Zen","action":"present_terminal 'omarchy-install-browser zen'"}, + "install.service.dropbox": {"icon":"","label":"Dropbox","action":"present_terminal omarchy-install-dropbox"}, + "install.service.tailscale": {"icon":"","label":"Tailscale","keywords":"vpn","action":"present_terminal omarchy-install-tailscale"}, + "install.service.nordvpn": {"icon":"󱇱","label":"NordVPN [AUR]","keywords":"vpn","action":"present_terminal omarchy-install-nordvpn"}, + "install.service.once": {"icon":"󰏖","label":"ONCE","action":"present_terminal omarchy-install-once"}, + "install.service.bitwarden": {"icon":"󰟵","label":"Bitwarden","keywords":"password","action":"install_and_launch Bitwarden 'bitwarden bitwarden-cli' bitwarden"}, + "install.service.chromium-account": {"icon":"","label":"Chromium Account","keywords":"google","action":"present_terminal omarchy-install-chromium-google-account"}, + "install.editor.vscode": {"icon":"","label":"VSCode","keywords":"code","action":"present_terminal omarchy-install-vscode"}, + "install.editor.cursor": {"icon":"","label":"Cursor","action":"install_and_launch Cursor cursor-bin cursor"}, + "install.editor.zed": {"icon":"","label":"Zed","action":"present_terminal omarchy-install-zed"}, + "install.editor.sublime": {"icon":"","label":"Sublime Text","action":"install_and_launch 'Sublime Text' sublime-text-4 sublime_text"}, + "install.editor.helix": {"icon":"","label":"Helix","action":"present_terminal omarchy-install-helix"}, + "install.editor.vim": {"icon":"","label":"Vim","action":"install Vim vim"}, + "install.editor.emacs": {"icon":"","label":"Emacs","action":"install Emacs emacs-wayland && systemctl --user enable --now emacs.service"}, + "install.terminal.alacritty": {"icon":"","label":"Alacritty","action":"install_terminal alacritty"}, + "install.terminal.foot": {"icon":"","label":"Foot","action":"install_terminal foot"}, + "install.terminal.ghostty": {"icon":"","label":"Ghostty","action":"install_terminal ghostty"}, + "install.terminal.kitty": {"icon":"","label":"Kitty","action":"install_terminal kitty"}, + "install.ai.dictation": {"icon":"","label":"Dictation","keywords":"voice","action":"present_terminal omarchy-voxtype-install"}, + "install.ai.lm-studio": {"icon":"󱚤","label":"LM Studio","action":"install 'LM Studio' lmstudio-bin"}, + "install.ai.ollama": {"icon":"󱚤","label":"Ollama","action":"if omarchy-cmd-present nvidia-smi; then ollama_pkg=ollama-cuda; elif omarchy-cmd-present rocminfo; then ollama_pkg=ollama-rocm; else ollama_pkg=ollama; fi; install Ollama \"$ollama_pkg\""}, + "install.ai.crush": {"icon":"󱚤","label":"Crush","action":"install Crush crush-bin"}, + "install.gaming.steam": {"icon":"","label":"Steam","action":"present_terminal omarchy-install-gaming-steam"}, + "install.gaming.retroarch": {"icon":"󰯉","label":"RetroArch","action":"present_terminal omarchy-install-gaming-retroarch"}, + "install.gaming.minecraft": {"icon":"󰍳","label":"Minecraft","action":"install_and_launch Minecraft minecraft-launcher minecraft-launcher"}, + "install.gaming.geforce-now": {"icon":"󰢹","label":"NVIDIA GeForce NOW","action":"present_terminal omarchy-install-gaming-geforce-now"}, + "install.gaming.xbox-cloud": {"icon":"","label":"Xbox Cloud Gaming","action":"present_terminal omarchy-install-gaming-xbox-cloud"}, + "install.gaming.xbox-controller": {"icon":"󰂯","label":"Xbox Controller","action":"present_terminal omarchy-install-gaming-xbox-controllers"}, + "install.gaming.moonlight": {"icon":"󰍹","label":"Moonlight (GameStream)","action":"present_terminal omarchy-install-gaming-moonlight"}, + "install.gaming.lutris": {"icon":"","label":"Lutris (Battle.net)","action":"present_terminal omarchy-install-gaming-lutris"}, + "install.gaming.heroic": {"icon":"󱓟","label":"Heroic (Epic Games)","action":"present_terminal omarchy-install-gaming-heroic"}, + "install.gaming.retro-launcher": {"icon":"󰯉","label":"RetroArch Game Launcher","action":"omarchy-games-retro-install"}, + "install.development.rails": {"icon":"󰫏","label":"Ruby on Rails","action":"present_terminal 'omarchy-install-dev-env ruby'"}, + "install.development.docker-dbs": {"icon":"","label":"Docker DB","keywords":"databases","action":"present_terminal omarchy-install-docker-dbs"}, + "install.development.javascript": {"icon":"","label":"JavaScript","keywords":"node bun deno"}, + "install.development.go": {"icon":"","label":"Go","keywords":"golang","action":"present_terminal 'omarchy-install-dev-env go'"}, + "install.development.php": {"icon":"","label":"PHP","keywords":"laravel symfony"}, + "install.development.python": {"icon":"","label":"Python","action":"present_terminal 'omarchy-install-dev-env python'"}, + "install.development.elixir": {"icon":"","label":"Elixir","keywords":"phoenix"}, + "install.development.zig": {"icon":"","label":"Zig","action":"present_terminal 'omarchy-install-dev-env zig'"}, + "install.development.rust": {"icon":"","label":"Rust","action":"present_terminal 'omarchy-install-dev-env rust'"}, + "install.development.java": {"icon":"","label":"Java","action":"present_terminal 'omarchy-install-dev-env java'"}, + "install.development.dotnet": {"icon":"","label":".NET","action":"present_terminal 'omarchy-install-dev-env dotnet'"}, + "install.development.ocaml": {"icon":"","label":"OCaml","action":"present_terminal 'omarchy-install-dev-env ocaml'"}, + "install.development.clojure": {"icon":"","label":"Clojure","action":"present_terminal 'omarchy-install-dev-env clojure'"}, + "install.development.scala": {"icon":"","label":"Scala","action":"present_terminal 'omarchy-install-dev-env scala'"}, + "install.development.javascript.node": {"icon":"","label":"Node.js","action":"present_terminal 'omarchy-install-dev-env node'"}, + "install.development.javascript.bun": {"icon":"","label":"Bun","action":"present_terminal 'omarchy-install-dev-env bun'"}, + "install.development.javascript.deno": {"icon":"","label":"Deno","action":"present_terminal 'omarchy-install-dev-env deno'"}, + "install.development.php.php": {"icon":"","label":"PHP","action":"present_terminal 'omarchy-install-dev-env php'"}, + "install.development.php.laravel": {"icon":"","label":"Laravel","action":"present_terminal 'omarchy-install-dev-env laravel'"}, + "install.development.php.symfony": {"icon":"","label":"Symfony","action":"present_terminal 'omarchy-install-dev-env symfony'"}, + "install.development.elixir.elixir": {"icon":"","label":"Elixir","action":"present_terminal 'omarchy-install-dev-env elixir'"}, + "install.development.elixir.phoenix": {"icon":"","label":"Phoenix","action":"present_terminal 'omarchy-install-dev-env phoenix'"}, + + // Remove + "remove.package": {"icon":"󰣇","label":"Package","keywords":"uninstall","action":"terminal omarchy-pkg-remove"}, + "remove.webapp": {"icon":"","label":"Web App","keywords":"uninstall","action":"present_terminal omarchy-webapp-remove"}, + "remove.tui": {"icon":"","label":"TUI","keywords":"uninstall terminal app","action":"present_terminal omarchy-tui-remove"}, + "remove.development": {"icon":"󰵮","label":"Development","keywords":"uninstall languages"}, + "remove.theme": {"icon":"󰸌","label":"Theme","keywords":"uninstall","action":"present_terminal omarchy-theme-remove"}, + "remove.browser": {"icon":"","label":"Browser","keywords":"uninstall"}, + "remove.dictation": {"icon":"","label":"Dictation","keywords":"uninstall voice","action":"present_terminal omarchy-voxtype-remove"}, + "remove.gaming": {"icon":"","label":"Gaming","keywords":"uninstall steam retroarch minecraft"}, + "remove.windows": {"icon":"󰍲","label":"Windows","keywords":"uninstall vm","action":"present_terminal 'omarchy-windows-vm remove'"}, + "remove.preinstalls": {"icon":"󰏓","label":"Preinstalls","action":"present_terminal omarchy-remove-preinstalls"}, + "remove.security": {"icon":"","label":"Security","keywords":"fingerprint fido2"}, + "remove.security.fingerprint": {"icon":"󰈷","label":"Fingerprint","action":"present_terminal omarchy-remove-security-fingerprint"}, + "remove.security.fido2": {"icon":"","label":"Fido2","action":"present_terminal omarchy-remove-security-fido2"}, + "remove.browser.chrome": {"icon":"","label":"Chrome","action":"present_terminal 'omarchy-remove-browser chrome'"}, + "remove.browser.edge": {"icon":"","label":"Edge","action":"present_terminal 'omarchy-remove-browser edge'"}, + "remove.browser.brave": {"icon":"","label":"Brave","action":"present_terminal 'omarchy-remove-browser brave'"}, + "remove.browser.brave-origin": {"icon":"","label":"Brave Origin","action":"present_terminal 'omarchy-remove-browser brave-origin'"}, + "remove.browser.firefox": {"icon":"","label":"Firefox","action":"present_terminal 'omarchy-remove-browser firefox'"}, + "remove.browser.zen": {"icon":"","label":"Zen","action":"present_terminal 'omarchy-remove-browser zen'"}, + "remove.gaming.steam": {"icon":"","label":"Steam","action":"present_terminal omarchy-remove-gaming-steam"}, + "remove.gaming.retroarch": {"icon":"","label":"RetroArch","action":"present_terminal omarchy-remove-gaming-retroarch"}, + "remove.gaming.minecraft": {"icon":"󰍳","label":"Minecraft","action":"present_terminal omarchy-remove-gaming-minecraft"}, + "remove.gaming.geforce-now": {"icon":"󰢹","label":"NVIDIA GeForce NOW","action":"present_terminal omarchy-remove-gaming-geforce-now"}, + "remove.gaming.xbox-cloud": {"icon":"","label":"Xbox Cloud Gaming","action":"present_terminal omarchy-remove-gaming-xbox-cloud"}, + "remove.gaming.xbox-controller": {"icon":"󰖺","label":"Xbox Controller (󰂯)","action":"present_terminal omarchy-remove-gaming-xbox-controllers"}, + "remove.gaming.moonlight": {"icon":"󰍹","label":"Moonlight (GameStream)","action":"present_terminal omarchy-remove-gaming-moonlight"}, + "remove.gaming.lutris": {"icon":"","label":"Lutris (Battle.net)","action":"present_terminal omarchy-remove-gaming-lutris"}, + "remove.gaming.heroic": {"icon":"󱓟","label":"Heroic (Epic Games)","action":"present_terminal omarchy-remove-gaming-heroic"}, + "remove.development.rails": {"icon":"󰫏","label":"Ruby on Rails","action":"present_terminal 'omarchy-remove-dev-env ruby'"}, + "remove.development.javascript": {"icon":"","label":"JavaScript","keywords":"node bun deno"}, + "remove.development.go": {"icon":"","label":"Go","keywords":"golang","action":"present_terminal 'omarchy-remove-dev-env go'"}, + "remove.development.php": {"icon":"","label":"PHP","keywords":"laravel symfony"}, + "remove.development.python": {"icon":"","label":"Python","action":"present_terminal 'omarchy-remove-dev-env python'"}, + "remove.development.elixir": {"icon":"","label":"Elixir","keywords":"phoenix"}, + "remove.development.zig": {"icon":"","label":"Zig","action":"present_terminal 'omarchy-remove-dev-env zig'"}, + "remove.development.rust": {"icon":"","label":"Rust","action":"present_terminal 'omarchy-remove-dev-env rust'"}, + "remove.development.java": {"icon":"","label":"Java","action":"present_terminal 'omarchy-remove-dev-env java'"}, + "remove.development.dotnet": {"icon":"","label":".NET","action":"present_terminal 'omarchy-remove-dev-env dotnet'"}, + "remove.development.ocaml": {"icon":"","label":"OCaml","action":"present_terminal 'omarchy-remove-dev-env ocaml'"}, + "remove.development.clojure": {"icon":"","label":"Clojure","action":"present_terminal 'omarchy-remove-dev-env clojure'"}, + "remove.development.scala": {"icon":"","label":"Scala","action":"present_terminal 'omarchy-remove-dev-env scala'"}, + "remove.development.javascript.node": {"icon":"","label":"Node.js","action":"present_terminal 'omarchy-remove-dev-env node'"}, + "remove.development.javascript.bun": {"icon":"","label":"Bun","action":"present_terminal 'omarchy-remove-dev-env bun'"}, + "remove.development.javascript.deno": {"icon":"","label":"Deno","action":"present_terminal 'omarchy-remove-dev-env deno'"}, + "remove.development.php.php": {"icon":"","label":"PHP","action":"present_terminal 'omarchy-remove-dev-env php'"}, + "remove.development.php.laravel": {"icon":"","label":"Laravel","action":"present_terminal 'omarchy-remove-dev-env laravel'"}, + "remove.development.php.symfony": {"icon":"","label":"Symfony","action":"present_terminal 'omarchy-remove-dev-env symfony'"}, + "remove.development.elixir.elixir": {"icon":"","label":"Elixir","action":"present_terminal 'omarchy-remove-dev-env elixir'"}, + "remove.development.elixir.phoenix": {"icon":"","label":"Phoenix","action":"present_terminal 'omarchy-remove-dev-env phoenix'"}, + + // Update + "update.omarchy": {"icon":"","label":"Omarchy","keywords":"system","action":"present_terminal omarchy-update"}, + "update.channel": {"icon":"󰔫","label":"Channel","keywords":"stable rc edge dev"}, + "update.config": {"icon":"","label":"Config","keywords":"refresh default"}, + "update.themes": {"icon":"󰸌","label":"Extra Themes","action":"present_terminal omarchy-theme-update"}, + "update.process": {"icon":"","label":"Process","keywords":"restart services"}, + "update.hardware": {"icon":"󰇅","label":"Hardware","keywords":"restart audio wifi bluetooth trackpad"}, + "update.firmware": {"icon":"","label":"Firmware","keywords":"fwupd","action":"present_terminal omarchy-update-firmware"}, + "update.password": {"icon":"","label":"Password","keywords":"drive user"}, + "update.timezone": {"icon":"","label":"Timezone","keywords":"time zone","action":"present_terminal omarchy-tz-select"}, + "update.time": {"icon":"","label":"Time","keywords":"clock ntp","action":"present_terminal omarchy-update-time"}, + "update.channel.stable": {"icon":"🟢","label":"Stable","action":"present_terminal 'omarchy-channel-set stable'"}, + "update.channel.rc": {"icon":"🟡","label":"RC","keywords":"release candidate","action":"present_terminal 'omarchy-channel-set rc'"}, + "update.channel.edge": {"icon":"🟠","label":"Edge","action":"present_terminal 'omarchy-channel-set edge'"}, + "update.channel.dev": {"icon":"🔴","label":"Dev","keywords":"development","action":"present_terminal 'omarchy-channel-set dev'"}, + "update.process.hypridle": {"icon":"","label":"Hypridle","keywords":"restart","action":"omarchy-restart-hypridle"}, + "update.process.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"restart","action":"omarchy-restart-hyprsunset"}, + "update.process.mako": {"icon":"󰎟","label":"Mako","keywords":"restart notifications","action":"omarchy-restart-mako"}, + "update.process.swayosd": {"icon":"","label":"Swayosd","keywords":"restart","action":"omarchy-restart-swayosd"}, + "update.process.walker": {"icon":"󰌧","label":"Walker","keywords":"restart launcher","action":"omarchy-restart-walker"}, + "update.process.quickshell": {"icon":"󰍜","label":"Quickshell","keywords":"restart bar shell","action":"omarchy-restart-quickshell"}, + "update.config.hyprland": {"icon":"","label":"Hyprland","keywords":"refresh","action":"present_terminal omarchy-refresh-hyprland"}, + "update.config.hypridle": {"icon":"","label":"Hypridle","keywords":"refresh","action":"present_terminal omarchy-refresh-hypridle"}, + "update.config.hyprlock": {"icon":"","label":"Hyprlock","keywords":"refresh","action":"present_terminal omarchy-refresh-hyprlock"}, + "update.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"refresh","action":"present_terminal omarchy-refresh-hyprsunset"}, + "update.config.plymouth": {"icon":"󱣴","label":"Plymouth","keywords":"refresh","action":"present_terminal omarchy-refresh-plymouth"}, + "update.config.swayosd": {"icon":"","label":"Swayosd","keywords":"refresh","action":"present_terminal omarchy-refresh-swayosd"}, + "update.config.tmux": {"icon":"","label":"Tmux","keywords":"refresh","action":"present_terminal omarchy-refresh-tmux"}, + "update.config.walker": {"icon":"󰌧","label":"Walker","keywords":"refresh","action":"present_terminal omarchy-refresh-walker"}, + "update.config.bar": {"icon":"󰍜","label":"Bar","keywords":"refresh quickshell","action":"present_terminal omarchy-refresh-bar"}, + "update.hardware.audio": {"icon":"","label":"Audio","keywords":"restart pipewire","action":"present_terminal omarchy-restart-pipewire"}, + "update.hardware.wifi": {"icon":"󱚾","label":"Wi-Fi","keywords":"restart wireless","action":"present_terminal omarchy-restart-wifi"}, + "update.hardware.bluetooth": {"icon":"󰂯","label":"Bluetooth","keywords":"restart","action":"present_terminal omarchy-restart-bluetooth"}, + "update.hardware.trackpad": {"icon":"󰟸","label":"Trackpad","keywords":"restart touchpad","action":"present_terminal omarchy-restart-trackpad"}, + "update.password.drive": {"icon":"","label":"Drive Encryption","action":"present_terminal omarchy-drive-password"}, + "update.password.user": {"icon":"","label":"User","keywords":"passwd","action":"present_terminal passwd"}, +} diff --git a/default/quickshell/omarchy-shell/README.md b/default/quickshell/omarchy-shell/README.md index f6b4e5cb..3751f4dd 100644 --- a/default/quickshell/omarchy-shell/README.md +++ b/default/quickshell/omarchy-shell/README.md @@ -30,6 +30,7 @@ default/quickshell/omarchy-shell/ bar/ first-party plugins (see plugins/README.md) bar-settings/ image-picker/ + menu/ ``` The plugin discovery path is documented in [plugins/README.md](plugins/README.md). @@ -98,7 +99,9 @@ are discovered the same way and cannot be disabled. The shell exposes a single `shell` IPC target plus whatever extra targets individual plugins register (e.g. the bar's `bar` target for refresh -hooks, the background switcher's `image-selector` target). +hooks, the image picker's `image-selector` target). `omarchy-menu` uses the +shell target to summon the first-party `omarchy.menu` plugin instead of +running a separate Quickshell instance. | Method | Returns | Effect | |------------------------------------------|---------|-------------------------------------------------------| @@ -116,6 +119,9 @@ Direct invocation: quickshell ipc -p $OMARCHY_PATH/default/quickshell/omarchy-shell call shell ping ``` +Hyprland starts the shell through `omarchy-restart-quickshell` on boot. +Use `omarchy restart quickshell` to reload the long-running shell process. + A convenience wrapper, [`omarchy-shell-ipc`](../../../bin/omarchy-shell-ipc), starts the shell if it is not already running, then forwards a `call`. It is the canonical way for other Omarchy CLIs to talk to the shell. diff --git a/default/quickshell/omarchy-shell/plugins/README.md b/default/quickshell/omarchy-shell/plugins/README.md index 5c764833..2479d218 100644 --- a/default/quickshell/omarchy-shell/plugins/README.md +++ b/default/quickshell/omarchy-shell/plugins/README.md @@ -13,6 +13,7 @@ User-installed plugins live alongside these conceptually but on disk under | Bar | `omarchy.bar` | `bar` | persistent | `bar/Bar.qml` | | Bar settings | `omarchy.bar-settings` | `panel` | on-demand | `bar-settings/BarSettingsPanel.qml` | | Image picker | `omarchy.image-picker` | `overlay` | on-demand | `image-picker/ImagePicker.qml` | +| Omarchy menu | `omarchy.menu` | `menu` | on-demand | `menu/Menu.qml` | ## Bar @@ -62,8 +63,23 @@ clears it without writing a selection. The plugin has `keepLoaded: true` so the layer-shell window survives between summons within a single shell session. +## Omarchy menu + +Quickshell-powered replacement for the legacy Walker-driven `omarchy-menu`. +The menu UI lives in `menu/Menu.qml` as a first-party `menu` plugin and is +summoned through the shell (`omarchy-shell-ipc shell summon omarchy.menu ...`), +so it shares the long-running `omarchy-shell` process instead of starting a +second Quickshell instance. + +The menu definition lives outside the shell host code: + +- defaults: `default/omarchy/omarchy-menu.jsonc` +- user extensions: `~/.config/omarchy/extensions/omarchy-menu.jsonc` + +`bin/omarchy-menu` still owns the Bash action helpers and dynamic providers; +it builds the menu JSON, summons `omarchy.menu`, waits for the selection, and +runs the selected action. + ## Coming soon -- `omarchy.menu` — folds the existing `omarchy-menu` (currently on another - branch) into the shell as a `menu` plugin. Not present on this branch. - `omarchy.theme-switcher` — folds theme switching into the shell. diff --git a/default/quickshell/omarchy-shell/plugins/menu/Menu.qml b/default/quickshell/omarchy-shell/plugins/menu/Menu.qml new file mode 100644 index 00000000..9e4cf701 --- /dev/null +++ b/default/quickshell/omarchy-shell/plugins/menu/Menu.qml @@ -0,0 +1,914 @@ +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import QtQuick + +Item { + id: root + + // Injected by omarchy-shell when this plugin is summoned. + property string omarchyPath: Quickshell.env("OMARCHY_PATH") || (Quickshell.env("HOME") + "/.local/share/omarchy") + property var shell: null + property var manifest: null + property var pluginRegistry: null + + // Plugin lifecycle hooks. The host calls open(payloadJson) after + // `omarchy-shell-ipc shell summon omarchy.menu ...` and close() when hidden. + property string menuJsonFile: "" + property string pendingInitialMenu: "root" + property string pendingSelectionFile: "" + property string pendingDoneFile: "" + property string pendingColorsRaw: "" + + function decodeBase64(value) { + var s = String(value || "") + if (!s) return "" + try { return Qt.atob(s) } catch (e) { return s } + } + + function open(payloadJson) { + var payload = ({}) + try { payload = JSON.parse(payloadJson || "{}") } catch (e) { payload = ({}) } + + root.pendingInitialMenu = payload.initialMenu || "root" + root.pendingSelectionFile = payload.selectionFile || "" + root.pendingDoneFile = payload.doneFile || "" + root.pendingColorsRaw = payload.colorsRawBase64 ? root.decodeBase64(payload.colorsRawBase64) : (payload.colorsRaw || "") + if (payload.fontFamily) root.fontFamily = payload.fontFamily + + var menuJson = payload.menuJsonBase64 ? root.decodeBase64(payload.menuJsonBase64) : (payload.menuJson || "") + if (menuJson) { + root.openMenu(menuJson, root.pendingInitialMenu, root.pendingSelectionFile, root.pendingDoneFile, root.pendingColorsRaw) + return + } + + root.menuJsonFile = payload.menuJsonFile || "" + if (root.menuJsonFile) menuJsonFileView.reload() + } + + function close() { + root.closeMenu("") + } + + property string menuBin: omarchyPath + "/bin/omarchy-menu" + property string fontFamily: Quickshell.env("OMARCHY_MENU_FONT") || "monospace" + property string colorsFile: Quickshell.env("OMARCHY_MENU_COLORS_FILE") || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/colors.toml") + property string styleFile: Quickshell.env("OMARCHY_MENU_STYLE_FILE") || (Quickshell.env("HOME") + "/.local/state/omarchy/toggles/quickshell-menu.json") + property string selectionFile: "" + property string doneFile: "" + property bool requestActive: false + property bool opened: false + property bool rowsLoaded: false + property string activeMenu: "root" + property string filterText: "" + property int selectedIndex: 0 + property int requestSerial: 0 + property int applySerial: 0 + property var items: ({}) + property var itemOrder: [] + property var navStack: [] + property var providersLoaded: ({}) + property var providerQueue: [] + property var doneFilesToRelease: [] + property color accent: "#89b4fa" + property color background: "#101315" + property color foreground: "#cacccc" + property color border: foreground + property int cornerRadius: 0 + property int contentMargin: 18 + property int headerHeight: 34 + property int contentSpacing: 6 + property int baseRowHeight: 50 + property int detailRowHeight: 58 + property int rowSpacing: 3 + property int dividerHeight: 17 + property bool searchDivider: false + property int layoutSerial: 0 + property int cardWidth: Math.min(300, panel.width - 48) + property int visibleRowsHeight: rowListHeight(layoutSerial, displayModel.count, filterText, searchDivider) + property int cardHeight: Math.min(Math.max(220, contentMargin * 2 + headerHeight + contentSpacing + visibleRowsHeight), panel.height - 48) + + function shellQuote(value) { + return "'" + String(value).replace(/'/g, "'\\''") + "'" + } + + function decodeField(value) { + return String(value || "").replace(/\v/g, "\n").replace(/\f/g, "\t") + } + + function withAlpha(color, alpha) { + return Qt.rgba(color.r, color.g, color.b, alpha) + } + + function rowHeightForDetail(detail) { + return root.filterText && detail ? root.detailRowHeight : root.baseRowHeight + } + + function rowListHeight(_serial, _count, _filter, _divider) { + if (displayModel.count === 0) return root.baseRowHeight + + var count = Math.min(displayModel.count, 10) + var total = 0 + var previousSection = "" + + for (var i = 0; i < count; i++) { + var row = displayModel.get(i) + if (i > 0) total += root.rowSpacing + if (row.section === "drilldown" && previousSection !== "drilldown") total += root.dividerHeight + total += root.rowHeightForDetail(row.detail) + previousSection = row.section + } + + return total + } + + function item(id) { + return root.items[id] || null + } + + function loadMenuJson(raw) { + var nextItems = ({}) + var nextOrder = [] + var payload = JSON.parse(raw || "{}") + var rows = payload.items || [] + + for (var i = 0; i < rows.length; i++) { + var entry = rows[i] + var id = entry.id || "" + if (!id) continue + + var order = nextItems[id] ? nextItems[id].order : nextOrder.length + if (!nextItems[id]) nextOrder.push(id) + + nextItems[id] = { + id: id, + parent: entry.parent || "", + kind: entry.kind || "action", + icon: entry.icon || "", + label: entry.label || id, + target: entry.target || "", + keywords: entry.keywords || "", + description: entry.description || "", + action: entry.action || "", + provider: entry.provider || "", + order: order + } + } + + if (!nextItems.root) { + nextItems.root = { id: "root", parent: "", kind: "menu", icon: "", label: "Go", target: "", keywords: "", description: "", order: -1 } + } + + root.items = nextItems + root.itemOrder = nextOrder + root.rowsLoaded = true + } + + function mergeProviderJson(raw, menuId) { + var payload = ({}) + try { + payload = JSON.parse(raw || "{}") + } catch (e) { + return + } + + var rows = payload.items || [] + var changed = false + + for (var i = 0; i < rows.length; i++) { + var entry = rows[i] + var id = entry.id || "" + if (!id) continue + + if (!root.items[id]) root.itemOrder.push(id) + root.items[id] = { + id: id, + parent: entry.parent || menuId, + kind: entry.kind || "action", + icon: entry.icon || "", + label: entry.label || id, + target: entry.target || "", + keywords: entry.keywords || "", + description: entry.description || "", + action: entry.action || "", + provider: entry.provider || "", + order: root.itemOrder.indexOf(id) + } + changed = true + } + + if (changed) root.rebuildDisplay() + } + + function startProviderForMenu(id) { + var entry = root.item(id) + if (!entry || !entry.provider || root.providersLoaded[id]) return + + root.providersLoaded[id] = true + providerProc.menuId = id + providerProc.output = "" + providerProc.command = [root.menuBin, "--provider", entry.provider, id] + providerProc.running = true + } + + function startNextProvider() { + if (providerProc.running) return + + while (root.providerQueue.length > 0) { + var id = root.providerQueue.shift() + var entry = root.item(id) + if (!entry || !entry.provider || root.providersLoaded[id]) continue + + root.startProviderForMenu(id) + return + } + } + + function loadProviderForMenu(id) { + var entry = root.item(id) + if (!entry || !entry.provider || root.providersLoaded[id]) return + + if (providerProc.running) { + if (root.providerQueue.indexOf(id) < 0) root.providerQueue = root.providerQueue.concat([id]) + return + } + + root.startProviderForMenu(id) + } + + function loadProvidersForSearch() { + var active = root.item(root.activeMenu) ? root.activeMenu : "root" + + for (var i = 0; i < root.itemOrder.length; i++) { + var entry = root.item(root.itemOrder[i]) + if (!entry || !entry.provider || root.providersLoaded[entry.id]) continue + if (active !== "root" && entry.id !== active && !root.isDescendantOf(entry.id, active)) continue + + root.loadProviderForMenu(entry.id) + } + } + + function depthFor(id) { + var depth = 0 + var current = root.item(id) + var guard = 0 + + while (current && current.parent && current.parent !== "root" && guard < 32) { + depth += 1 + current = root.item(current.parent) + guard += 1 + } + + return depth + } + + function pathFor(id) { + var labels = [] + var current = root.item(id) + var guard = 0 + + while (current && current.id !== "root" && guard < 32) { + labels.unshift(current.label) + current = root.item(current.parent) + guard += 1 + } + + return labels.join(" › ") + } + + function parentPathFor(id) { + var entry = root.item(id) + if (!entry || !entry.parent || entry.parent === "root") return "" + + return root.pathFor(entry.parent) + } + + function breadcrumbFor(id) { + var path = root.pathFor(id) + return path ? ("Go › " + path) : "Go" + } + + function isDescendantOf(id, ancestorId) { + if (ancestorId === "root") return id !== "root" + + var current = root.item(id) + var guard = 0 + while (current && current.parent && guard < 32) { + if (current.parent === ancestorId) return true + current = root.item(current.parent) + guard += 1 + } + + return false + } + + function childCount(id) { + var count = 0 + for (var i = 0; i < root.itemOrder.length; i++) { + var entry = root.item(root.itemOrder[i]) + if (entry && entry.parent === id) count += 1 + } + + return count + } + + function matchesQuery(entry, query) { + if (!entry || entry.id === "root") return false + + var haystack = (entry.label + " " + root.pathFor(entry.id) + " " + entry.keywords + " " + entry.description).toLowerCase() + var terms = query.toLowerCase().trim().split(/\s+/) + + for (var i = 0; i < terms.length; i++) { + if (terms[i] && haystack.indexOf(terms[i]) === -1) return false + } + + return true + } + + function searchScore(entry, query) { + var needle = query.toLowerCase().trim() + var label = entry.label.toLowerCase() + var path = root.pathFor(entry.id).toLowerCase() + var keywords = entry.keywords.toLowerCase() + var parent = root.item(entry.parent) + var parentLabel = parent ? parent.label.toLowerCase() : "" + var score = 80 + + if (label === needle) score = entry.parent === "root" ? 2 : 0 + else if (label.indexOf(needle) === 0) score = 10 + else if (path.indexOf(needle) === 0) score = entry.kind === "action" ? 12 : 14 + else if (parentLabel === needle) score = entry.kind === "action" ? 18 : 20 + else if (label.indexOf(needle) >= 0) score = 30 + else if (path.indexOf(needle) >= 0) score = 40 + else if (keywords.indexOf(needle) >= 0) score = 50 + + if (entry.kind === "menu" || entry.kind === "link") score -= 2 + + return score * 1000 + root.depthFor(entry.id) * 25 + entry.order + } + + function displayRow(entry, detail, score, section) { + var target = entry.kind === "link" ? entry.target : entry.id + return { + itemId: entry.id, + kind: entry.kind, + icon: entry.icon, + label: entry.label, + target: target, + detail: detail || "", + path: root.pathFor(entry.id), + childCount: (entry.kind === "menu" || entry.kind === "link") ? root.childCount(target) : 0, + action: entry.action || "", + provider: entry.provider || "", + score: score || 0, + section: section || "" + } + } + + function rebuildDisplay() { + displayModel.clear() + + if (!root.rowsLoaded) return + + var active = root.item(root.activeMenu) ? root.activeMenu : "root" + root.activeMenu = active + var rows = [] + var query = root.filterText.trim() + root.searchDivider = false + + if (query) { + var currentRows = [] + var drilldownRows = [] + + for (var i = 0; i < root.itemOrder.length; i++) { + var entry = root.item(root.itemOrder[i]) + if (!entry || entry.id === "root") continue + if (!root.isDescendantOf(entry.id, active)) continue + if (!root.matchesQuery(entry, query)) continue + + var detail = root.parentPathFor(entry.id) + var row = root.displayRow(entry, detail, root.searchScore(entry, query)) + if (entry.parent === active) currentRows.push(row) + else drilldownRows.push(row) + } + + var searchSort = function(a, b) { + if (a.score !== b.score) return a.score - b.score + return a.path.localeCompare(b.path) + } + + currentRows.sort(searchSort) + drilldownRows.sort(searchSort) + root.searchDivider = currentRows.length > 0 && drilldownRows.length > 0 + if (root.searchDivider) { + for (var d = 0; d < drilldownRows.length; d++) drilldownRows[d].section = "drilldown" + } + rows = currentRows.concat(drilldownRows) + } else { + if (active !== "root") { + var parentTarget = root.item(active).parent || "root" + var backTarget = root.navStack.length > 0 ? root.navStack[root.navStack.length - 1] : parentTarget + rows.push({ itemId: "__back", kind: "back", icon: "", label: "Back", target: backTarget, detail: root.breadcrumbFor(backTarget), path: "", childCount: 0, action: "", score: -1, section: "" }) + } + + for (var j = 0; j < root.itemOrder.length; j++) { + var child = root.item(root.itemOrder[j]) + if (!child || child.parent !== active) continue + rows.push(root.displayRow(child, child.description, child.order)) + } + } + + for (var k = 0; k < rows.length; k++) displayModel.append(rows[k]) + layoutSerial += 1 + + if (displayModel.count === 0) selectedIndex = 0 + else if (selectedIndex >= displayModel.count) selectedIndex = displayModel.count - 1 + else if (selectedIndex < 0) selectedIndex = 0 + + Qt.callLater(function() { + if (displayModel.count > 0) resultList.positionViewAtIndex(root.selectedIndex, ListView.Contain) + }) + } + + function select(delta) { + if (displayModel.count === 0) return + + selectedIndex += delta + if (selectedIndex < 0) selectedIndex = 0 + if (selectedIndex >= displayModel.count) selectedIndex = displayModel.count - 1 + resultList.positionViewAtIndex(selectedIndex, ListView.Contain) + } + + function setFilter(nextFilter) { + root.filterText = nextFilter + root.selectedIndex = 0 + if (root.filterText.trim()) root.loadProvidersForSearch() + root.rebuildDisplay() + } + + function setActiveMenu(id, pushHistory) { + if (!root.item(id)) id = "root" + if (pushHistory && id !== root.activeMenu) root.navStack = root.navStack.concat([root.activeMenu]) + root.activeMenu = id + root.filterText = "" + root.selectedIndex = 0 + root.rebuildDisplay() + root.loadProviderForMenu(id) + } + + function goBack() { + if (root.activeMenu === "root") return false + + if (root.navStack.length > 0) { + var previous = root.navStack[root.navStack.length - 1] + root.navStack = root.navStack.slice(0, root.navStack.length - 1) + root.setActiveMenu(previous, false) + return true + } + + var active = root.item(root.activeMenu) + root.setActiveMenu((active && active.parent) ? active.parent : "root", false) + return true + } + + function activateIndex(index) { + if (index < 0 || index >= displayModel.count) return + + var row = displayModel.get(index) + if (row.kind === "back") { + root.goBack() + } else if (row.kind === "menu" || row.kind === "link") { + root.setActiveMenu(row.target || row.itemId, true) + } else { + root.applySelected(row.itemId, row.action) + } + } + + function releaseNextDoneFile() { + if (releaseProc.running || doneFilesToRelease.length === 0) return + + var path = doneFilesToRelease.shift() + releaseProc.command = ["bash", "-lc", ": > " + shellQuote(path)] + releaseProc.running = true + } + + function finishDoneFile(path) { + if (!path) return + doneFilesToRelease.push(path) + releaseNextDoneFile() + } + + function resetRequest() { + requestActive = false + selectionFile = "" + doneFile = "" + } + + function applySelected(id, action) { + if (!id || !selectionFile || !doneFile) { + cancel() + return + } + + var activeSelectionFile = selectionFile + var activeDoneFile = doneFile + applySerial = requestSerial + resetRequest() + opened = false + + applyProc.command = ["bash", "-lc", "printf '%s\\t%s\\n' " + shellQuote(id) + " " + shellQuote(action || "") + " > " + shellQuote(activeSelectionFile) + "; : > " + shellQuote(activeDoneFile)] + applyProc.running = true + } + + function cancel() { + if (requestActive) finishDoneFile(doneFile) + resetRequest() + opened = false + filterText = "" + } + + function closeMenu(nextDoneFile) { + requestSerial += 1 + + if (requestActive) finishDoneFile(doneFile) + if (nextDoneFile && nextDoneFile !== doneFile) finishDoneFile(nextDoneFile) + + resetRequest() + opened = false + filterText = "" + } + + function openExistingMenu(initialMenu, nextSelectionFile, nextDoneFile) { + requestSerial += 1 + + if (requestActive && doneFile && doneFile !== nextDoneFile) finishDoneFile(doneFile) + + selectionFile = nextSelectionFile + doneFile = nextDoneFile + requestActive = !!doneFile + activeMenu = root.item(initialMenu) ? initialMenu : "root" + navStack = [] + filterText = "" + selectedIndex = 0 + opened = true + rebuildDisplay() + loadProviderForMenu(activeMenu) + + Qt.callLater(function() { keyCatcher.forceActiveFocus() }) + } + + function openMenu(menuJson, initialMenu, nextSelectionFile, nextDoneFile, colorsRaw) { + loadMenuJson(menuJson) + if (colorsRaw) loadColors(colorsRaw) + providersLoaded = ({}) + providerQueue = [] + openExistingMenu(initialMenu, nextSelectionFile, nextDoneFile) + } + + function loadColors(raw) { + var lines = String(raw || "").split("\n") + for (var i = 0; i < lines.length; i++) { + var match = lines[i].match(/^\s*([A-Za-z0-9_-]+)\s*=\s*["']?(#[0-9A-Fa-f]{6})/) + if (!match) continue + if (match[1] === "foreground") foreground = match[2] + else if (match[1] === "background") background = match[2] + else if (match[1] === "color4" || match[1] === "accent") accent = match[2] + } + border = foreground + } + + function loadStyle(raw) { + try { + var style = JSON.parse(raw || "{}") + root.cornerRadius = Number(style.radius || 0) + } catch (e) {} + } + + ListModel { id: displayModel } + + Process { + id: providerProc + property string menuId: "" + property string output: "" + stdout: SplitParser { + onRead: function(data) { + providerProc.output += data + "\n" + } + } + onExited: { + root.mergeProviderJson(output, menuId) + if (root.filterText.trim()) root.loadProvidersForSearch() + root.startNextProvider() + } + } + + FileView { + id: menuJsonFileView + path: root.menuJsonFile + printErrors: false + onLoaded: root.openMenu(text(), root.pendingInitialMenu, root.pendingSelectionFile, root.pendingDoneFile, root.pendingColorsRaw) + } + + FileView { + path: root.colorsFile + watchChanges: true + onLoaded: root.loadColors(text()) + onFileChanged: { reload(); root.loadColors(text()) } + } + + FileView { + path: root.styleFile + watchChanges: true + onLoaded: root.loadStyle(text()) + onFileChanged: { reload(); root.loadStyle(text()) } + } + + Process { + id: applyProc + onExited: { + if (root.applySerial === root.requestSerial) root.opened = false + } + } + + Process { + id: releaseProc + onExited: root.releaseNextDoneFile() + } + + PanelWindow { + id: panel + visible: root.opened && root.rowsLoaded + anchors { top: true; bottom: true; left: true; right: true } + color: "transparent" + WlrLayershell.namespace: "omarchy-menu" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + exclusionMode: ExclusionMode.Ignore + + MouseArea { + anchors.fill: parent + onClicked: root.cancel() + } + + Rectangle { + id: card + width: root.cardWidth + height: root.cardHeight + radius: root.cornerRadius + anchors.centerIn: parent + color: root.background + border.color: root.withAlpha(root.border, 0.36) + border.width: 1 + + MouseArea { anchors.fill: parent; onClicked: {} } + + Item { + id: keyCatcher + anchors.fill: parent + focus: true + + Keys.priority: Keys.BeforeItem + Keys.onPressed: function(event) { + if (event.key === Qt.Key_Escape) { + if (root.filterText) root.setFilter("") + else root.cancel() + event.accepted = true + } else if (event.key === Qt.Key_Backspace) { + if (root.filterText.length > 0) root.setFilter(root.filterText.slice(0, -1)) + else root.goBack() + event.accepted = true + } else if (event.key === Qt.Key_Up) { + root.select(-1) + event.accepted = true + } else if (event.key === Qt.Key_Down) { + root.select(1) + event.accepted = true + } else if (event.key === Qt.Key_PageUp) { + root.select(-6) + event.accepted = true + } else if (event.key === Qt.Key_PageDown) { + root.select(6) + event.accepted = true + } else if (event.key === Qt.Key_Left) { + if (!root.filterText) root.goBack() + event.accepted = true + } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Right) { + root.activateIndex(root.selectedIndex) + event.accepted = true + } else if (event.text && event.text.length === 1 && event.text.charCodeAt(0) >= 32 && event.text.charCodeAt(0) !== 127 && (event.modifiers === Qt.NoModifier || event.modifiers === Qt.ShiftModifier)) { + root.setFilter(root.filterText + event.text) + event.accepted = true + } + } + } + + Column { + anchors.fill: parent + anchors.margins: root.contentMargin + spacing: root.contentSpacing + + Rectangle { + width: parent.width + height: root.headerHeight + radius: root.cornerRadius + color: "transparent" + border.width: 0 + + Text { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + text: root.filterText || ((root.item(root.activeMenu) ? root.item(root.activeMenu).label : "Go") + "…") + color: root.foreground + opacity: root.filterText ? 1 : 0.58 + font.family: root.fontFamily + font.pixelSize: 16 + elide: Text.ElideRight + } + + } + + Item { + width: parent.width + height: root.visibleRowsHeight + + ListView { + id: resultList + anchors.fill: parent + model: displayModel + clip: true + spacing: root.rowSpacing + boundsBehavior: Flickable.StopAtBounds + + section.property: "section" + section.criteria: ViewSection.FullString + section.delegate: Item { + required property string section + + width: ListView.view.width + height: section === "drilldown" ? root.dividerHeight : 0 + visible: section === "drilldown" + + Rectangle { + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.right: parent.right + anchors.rightMargin: 4 + anchors.verticalCenter: parent.verticalCenter + height: 1 + color: root.withAlpha(root.foreground, 0.2) + } + } + + delegate: Rectangle { + id: row + required property int index + required property string itemId + required property string kind + required property string icon + required property string label + required property string target + required property string detail + required property string path + required property string action + required property int childCount + + width: ListView.view.width + height: root.rowHeightForDetail(row.detail) + radius: root.cornerRadius + color: index === root.selectedIndex ? root.withAlpha(root.foreground, 0.08) : root.withAlpha(root.foreground, mouseArea.containsMouse ? 0.045 : 0) + border.color: "transparent" + border.width: 0 + + Rectangle { + visible: false + width: 4 + height: parent.height - 18 + radius: Math.min(root.cornerRadius, 4) + color: root.accent + anchors.left: parent.left + anchors.leftMargin: 8 + anchors.verticalCenter: parent.verticalCenter + } + + Text { + id: iconText + text: row.icon + color: index === root.selectedIndex ? root.accent : root.foreground + opacity: row.kind === "back" ? 0.7 : 1 + font.family: root.fontFamily + font.pixelSize: 18 + width: 36 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.left: parent.left + anchors.leftMargin: 8 + y: contentColumn.y + labelText.y + (labelText.height - height) / 2 + } + + Column { + id: contentColumn + anchors.left: iconText.right + anchors.leftMargin: 6 + anchors.right: trail.left + anchors.rightMargin: 6 + anchors.verticalCenter: parent.verticalCenter + spacing: 3 + + Text { + id: labelText + width: parent.width + text: row.label + color: index === root.selectedIndex ? root.accent : root.foreground + font.family: root.fontFamily + font.pixelSize: 16 + font.weight: Font.Medium + elide: Text.ElideRight + } + + Text { + width: parent.width + text: row.detail + visible: root.filterText && row.detail.length > 0 + color: root.foreground + opacity: 0.52 + font.family: root.fontFamily + font.pixelSize: 11 + elide: Text.ElideRight + } + } + + Row { + id: trail + width: 14 + anchors.right: parent.right + anchors.rightMargin: 8 + y: contentColumn.y + labelText.y + (labelText.height - height) / 2 + spacing: 0 + + Text { + visible: false + text: row.childCount + color: root.foreground + opacity: 0.45 + font.family: root.fontFamily + font.pixelSize: 12 + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: row.kind === "menu" || row.kind === "link" ? "›" : (row.kind === "back" ? "" : "↵") + color: index === root.selectedIndex ? root.accent : root.foreground + opacity: row.kind === "back" ? 0 : 0.36 + font.family: root.fontFamily + font.pixelSize: 16 + font.weight: Font.Normal + anchors.verticalCenter: parent.verticalCenter + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: root.activateIndex(row.index) + } + } + } + + Column { + anchors.centerIn: parent + spacing: 8 + visible: displayModel.count === 0 + + Text { + text: "󰈉" + color: root.accent + opacity: 0.8 + font.family: root.fontFamily + font.pixelSize: 28 + horizontalAlignment: Text.AlignHCenter + width: 320 + } + + Text { + text: root.filterText ? "No matches for “" + root.filterText + "”" : "Nothing here yet" + color: root.foreground + opacity: 0.7 + font.family: root.fontFamily + font.pixelSize: 14 + horizontalAlignment: Text.AlignHCenter + width: 320 + } + } + } + + Item { + width: parent.width + height: 0 + } + } + } + } +} diff --git a/default/quickshell/omarchy-shell/plugins/menu/manifest.json b/default/quickshell/omarchy-shell/plugins/menu/manifest.json new file mode 100644 index 00000000..7a0ea37d --- /dev/null +++ b/default/quickshell/omarchy-shell/plugins/menu/manifest.json @@ -0,0 +1,15 @@ +{ + "schemaVersion": 1, + "id": "omarchy.menu", + "name": "Omarchy menu", + "version": "1.0.0", + "author": "Omarchy", + "description": "Quickshell-powered Omarchy command menu", + "kinds": ["menu"], + "activation": "on-demand", + "keepLoaded": true, + "entryPoints": { "menu": "Menu.qml" }, + "ipc": { + "summon": "menu" + } +}