Drop walker + elephant

This commit is contained in:
David Heinemeier Hansson
2026-05-19 20:54:20 +02:00
parent 7fe472bf8a
commit abf0a68b54
64 changed files with 1024 additions and 1033 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ Do not define:
false flags or empty args false flags or empty args
Examples: Examples:
# omarchy:summary=Restart Walker and related user services # omarchy:summary=Restart the shell
# omarchy:summary=Take a screenshot # omarchy:summary=Take a screenshot
# omarchy:group=capture # omarchy:group=capture
-1
View File
@@ -18,7 +18,6 @@ if [[ -f $FIRST_RUN_MODE ]]; then
bash "$OMARCHY_PATH/install/first-run/gnome-theme.sh" bash "$OMARCHY_PATH/install/first-run/gnome-theme.sh"
bash "$OMARCHY_PATH/install/first-run/gdk-scale.sh" bash "$OMARCHY_PATH/install/first-run/gdk-scale.sh"
bash "$OMARCHY_PATH/install/first-run/gtk-primary-paste.sh" bash "$OMARCHY_PATH/install/first-run/gtk-primary-paste.sh"
bash "$OMARCHY_PATH/install/first-run/elephant.sh"
omarchy-hook-install post-update "$OMARCHY_PATH/install/first-run/install-voxtype.hook" omarchy-hook-install post-update "$OMARCHY_PATH/install/first-run/install-voxtype.hook"
sudo rm -f /etc/sudoers.d/first-run sudo rm -f /etc/sudoers.d/first-run
-1
View File
@@ -62,7 +62,6 @@ install_admin_webapp() {
mkdir -p "$WEBAPP_ICON_DIR" mkdir -p "$WEBAPP_ICON_DIR"
cp "$SUNSHINE_ICON_SOURCE" "$WEBAPP_ICON_DIR/$SUNSHINE_ICON_NAME" cp "$SUNSHINE_ICON_SOURCE" "$WEBAPP_ICON_DIR/$SUNSHINE_ICON_NAME"
omarchy-webapp-install "$SUNSHINE_ADMIN_APP" "$SUNSHINE_ADMIN_URL" "$SUNSHINE_ICON_NAME" "$SUNSHINE_ADMIN_EXEC" omarchy-webapp-install "$SUNSHINE_ADMIN_APP" "$SUNSHINE_ADMIN_URL" "$SUNSHINE_ICON_NAME" "$SUNSHINE_ADMIN_EXEC"
omarchy-restart-walker
} }
enable_hyprland_autostart() { enable_hyprland_autostart() {
-3
View File
@@ -14,9 +14,6 @@ if omarchy-toggle-enabled screensaver-off && [[ $1 != "force" ]]; then
exit 1 exit 1
fi fi
# Silently quit Walker on overlay
walker -q
focused=$(omarchy-hyprland-monitor-focused) focused=$(omarchy-hyprland-monitor-focused)
terminal=$(xdg-terminal-exec --print-id) terminal=$(xdg-terminal-exec --print-id)
-14
View File
@@ -1,14 +0,0 @@
#!/bin/bash
# omarchy:summary=Launch Walker and ensure its Elephant data provider is running
if ! pgrep -x elephant > /dev/null; then
setsid uwsm-app -- elephant &
fi
# Ensure walker service is running
if ! pgrep -f "walker --gapplication-service" > /dev/null; then
setsid uwsm-app -- env GSK_RENDERER=cairo walker --gapplication-service &
fi
exec walker --width 644 --maxheight 300 --minheight 300 "$@"
+5 -5
View File
@@ -1,15 +1,15 @@
#!/bin/bash #!/bin/bash
# omarchy:summary=Pick a file with Walker # omarchy:summary=Pick a file from a menu
# omarchy:group=menu # omarchy:group=menu
# omarchy:name=file # omarchy:name=file
# omarchy:args=label paths formats [walker args...] # omarchy:args=label paths formats [menu args...]
# omarchy:examples=omarchy menu file "Select image" "$HOME/Pictures" "jpg png webp"|omarchy-menu-file "Select media" "$HOME/Pictures:$HOME/Videos" "jpg png mp4 mov" --width 800 # omarchy:examples=omarchy menu file "Select image" "$HOME/Pictures" "jpg png webp"|omarchy-menu-file "Select media" "$HOME/Pictures:$HOME/Videos" "jpg png mp4 mov" --width 800
set -euo pipefail set -euo pipefail
if (( $# < 3 )); then if (( $# < 3 )); then
echo "Usage: omarchy-menu-file <label> <paths> <formats> [walker args...]" >&2 echo "Usage: omarchy-menu-file <label> <paths> <formats> [menu args...]" >&2
exit 1 exit 1
fi fi
@@ -22,7 +22,7 @@ IFS=: read -r -a paths <<<"$paths_arg"
read -r -a formats <<<"$formats_arg" read -r -a formats <<<"$formats_arg"
if (( ${#paths[@]} == 0 || ${#formats[@]} == 0 )); then if (( ${#paths[@]} == 0 || ${#formats[@]} == 0 )); then
echo "Usage: omarchy-menu-file <label> <paths> <formats> [walker args...]" >&2 echo "Usage: omarchy-menu-file <label> <paths> <formats> [menu args...]" >&2
exit 1 exit 1
fi fi
@@ -45,4 +45,4 @@ done
find_args+=(")" -printf '%T@\t%p\n') find_args+=(")" -printf '%T@\t%p\n')
find "${find_args[@]}" 2>/dev/null | sort -rn | cut -f2- | find "${find_args[@]}" 2>/dev/null | sort -rn | cut -f2- |
omarchy-launch-walker --dmenu --width 800 --minheight 1 --maxheight 500 -p "$label…" "$@" 2>/dev/null omarchy-menu-select "$label" -- --width 800 --maxheight 500 "$@"
+45 -3
View File
@@ -1,9 +1,9 @@
#!/bin/bash #!/bin/bash
# omarchy:summary=Prompt for text input with Walker # omarchy:summary=Prompt for text input from a menu
# omarchy:group=menu # omarchy:group=menu
# omarchy:name=input # omarchy:name=input
# omarchy:args=prompt [walker args...] # omarchy:args=prompt [menu args...]
# omarchy:examples=omarchy menu input Reminder|omarchy-menu-input "Reminder in minutes" --width 400 # omarchy:examples=omarchy menu input Reminder|omarchy-menu-input "Reminder in minutes" --width 400
set -euo pipefail set -euo pipefail
@@ -13,4 +13,46 @@ if (( $# > 0 )); then
shift shift
fi fi
omarchy-launch-walker --dmenu --inputonly --width 295 --minheight 1 --maxheight 1 -p "$prompt…" "$@" 2>/dev/null menu_width=""
while (( $# > 0 )); do
case "$1" in
--width)
shift
if (( $# == 0 )); then
echo "omarchy-menu-input: --width requires a value" >&2
exit 1
fi
menu_width="$1"
;;
esac
shift
done
selection_file=$(mktemp)
done_file=$(mktemp)
rm -f "$done_file"
trap 'rm -f "$selection_file" "$done_file"' EXIT
payload=$(perl -MEncode=decode -MJSON::PP=encode_json -e '
my $payload = {
mode => "input",
prompt => decode("UTF-8", $ARGV[0]),
selectionFile => decode("UTF-8", $ARGV[1]),
doneFile => decode("UTF-8", $ARGV[2])
};
$payload->{width} = int($ARGV[3]) if length($ARGV[3] // "");
print encode_json($payload)
' "$prompt" "$selection_file" "$done_file" "$menu_width")
omarchy-shell shell summon omarchy.menu "$payload" >/dev/null
while [[ ! -e $done_file ]]; do
sleep 0.05
done
if [[ -s $selection_file ]]; then
cat "$selection_file"
else
exit 1
fi
+2 -2
View File
@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# omarchy:summary=Display Hyprland keybindings defined in your configuration using walker for an interactive search menu. # omarchy:summary=Display Hyprland keybindings defined in your configuration using an interactive search menu.
declare -A KEYCODE_SYM_MAP declare -A KEYCODE_SYM_MAP
# Hyprland reports XKB keycodes for code: bindings. Keep a small fallback for # Hyprland reports XKB keycodes for code: bindings. Keep a small fallback for
@@ -600,7 +600,7 @@ else
monitor_height=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .height') monitor_height=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .height')
menu_height=$((monitor_height * 40 / 100)) menu_height=$((monitor_height * 40 / 100))
selection=$(cut -f1 <<<"$records" | selection=$(cut -f1 <<<"$records" |
walker --dmenu -p 'Keybindings' --width 800 --height "$menu_height") omarchy-menu-select 'Keybindings' -- --width 800 --height "$menu_height")
if [[ -n $selection ]]; then if [[ -n $selection ]]; then
record=$(awk -F '\t' -v selection="$selection" '$1 == selection { print; exit }' <<<"$records") record=$(awk -F '\t' -v selection="$selection" '$1 == selection { print; exit }' <<<"$records")
+63 -8
View File
@@ -1,15 +1,15 @@
#!/bin/bash #!/bin/bash
# omarchy:summary=Pick one option with Walker # omarchy:summary=Pick one option from a menu
# omarchy:group=menu # omarchy:group=menu
# omarchy:name=select # omarchy:name=select
# omarchy:args=prompt option... [-- walker args...] # omarchy:args=prompt [option...] [-- menu args...]
# omarchy:examples=omarchy menu select Format jpg png|omarchy-menu-select Resolution 4k 1080p 720p -- --width 400 # omarchy:examples=omarchy menu select Format jpg png|omarchy-menu-select Resolution 4k 1080p 720p -- --width 400
set -euo pipefail set -euo pipefail
if (( $# < 2 )); then if (( $# < 1 )); then
echo "Usage: omarchy-menu-select <prompt> <option>... [-- walker args...]" >&2 echo "Usage: omarchy-menu-select <prompt> [option...] [-- menu args...]" >&2
exit 1 exit 1
fi fi
@@ -17,12 +17,34 @@ prompt="$1"
shift shift
options=() options=()
walker_args=() menu_width=""
menu_maxheight=""
while (( $# > 0 )); do while (( $# > 0 )); do
if [[ $1 == "--" ]]; then if [[ $1 == "--" ]]; then
shift shift
walker_args=("$@") while (( $# > 0 )); do
case "$1" in
--width)
shift
if (( $# == 0 )); then
echo "omarchy-menu-select: --width requires a value" >&2
exit 1
fi
menu_width="$1"
;;
--height|--maxheight)
arg="$1"
shift
if (( $# == 0 )); then
echo "omarchy-menu-select: $arg requires a value" >&2
exit 1
fi
menu_maxheight="$1"
;;
esac
shift
done
break break
fi fi
@@ -30,9 +52,42 @@ while (( $# > 0 )); do
shift shift
done done
if (( ${#options[@]} == 0 )) && [[ ! -t 0 ]]; then
mapfile -t options
fi
if (( ${#options[@]} == 0 )); then if (( ${#options[@]} == 0 )); then
echo "Usage: omarchy-menu-select <prompt> <option>... [-- walker args...]" >&2 echo "Usage: omarchy-menu-select <prompt> [option...] [-- menu args...]" >&2
exit 1 exit 1
fi fi
printf '%s\n' "${options[@]}" | omarchy-launch-walker --dmenu --width 295 --minheight 1 --maxheight 300 -p "$prompt…" "${walker_args[@]}" 2>/dev/null selection_file=$(mktemp)
done_file=$(mktemp)
rm -f "$done_file"
trap 'rm -f "$selection_file" "$done_file"' EXIT
options_json=$(perl -MEncode=decode -MJSON::PP=encode_json -e 'print encode_json([map { decode("UTF-8", $_) } @ARGV])' "${options[@]}")
payload=$(perl -MEncode=decode -MJSON::PP=encode_json,decode_json -e '
my $payload = {
mode => "select",
prompt => decode("UTF-8", $ARGV[0]),
options => decode_json($ARGV[1]),
selectionFile => decode("UTF-8", $ARGV[2]),
doneFile => decode("UTF-8", $ARGV[3])
};
$payload->{width} = int($ARGV[4]) if length($ARGV[4] // "");
$payload->{maxHeight} = int($ARGV[5]) if length($ARGV[5] // "");
print encode_json($payload)
' "$prompt" "$options_json" "$selection_file" "$done_file" "$menu_width" "$menu_maxheight")
omarchy-shell shell summon omarchy.menu "$payload" >/dev/null
while [[ ! -e $done_file ]]; do
sleep 0.05
done
if [[ -s $selection_file ]]; then
cat "$selection_file"
else
exit 1
fi
+2 -7
View File
@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# omarchy:summary=Display Tmux keybindings defined in your configuration using walker for an interactive search menu. # omarchy:summary=Display Tmux keybindings defined in your configuration using an interactive search menu.
# omarchy:args=[--print|-p] [--config <path>] # omarchy:args=[--print|-p] [--config <path>]
print_only=false print_only=false
@@ -233,11 +233,6 @@ fi
records=$(output_keybindings) records=$(output_keybindings)
if [[ -z $WAYLAND_DISPLAY ]] || omarchy-cmd-missing walker; then
printf '%s\n' "$records"
exit 0
fi
monitor_height=$(hyprctl monitors -j 2>/dev/null | jq -r '.[] | select(.focused == true) | .height' 2>/dev/null) monitor_height=$(hyprctl monitors -j 2>/dev/null | jq -r '.[] | select(.focused == true) | .height' 2>/dev/null)
if [[ ! $monitor_height =~ ^[0-9]+$ ]] || ((monitor_height <= 0)); then if [[ ! $monitor_height =~ ^[0-9]+$ ]] || ((monitor_height <= 0)); then
@@ -245,4 +240,4 @@ if [[ ! $monitor_height =~ ^[0-9]+$ ]] || ((monitor_height <= 0)); then
fi fi
menu_height=$((monitor_height * 40 / 100)) menu_height=$((monitor_height * 40 / 100))
printf '%s\n' "$records" | walker --dmenu -p 'Tmux keybindings' --width 800 --height "$menu_height" >/dev/null printf '%s\n' "$records" | omarchy-menu-select 'Tmux keybindings' -- --width 800 --height "$menu_height" >/dev/null || true
-26
View File
@@ -1,26 +0,0 @@
#!/bin/bash
# omarchy:summary=Overwrite the user configs for the Walker application launcher (which also powers the Omarchy Menu) and restart the services.
mkdir -p ~/.config/autostart/
cp $OMARCHY_PATH/default/walker/walker.desktop ~/.config/autostart/
# And restarts if it crashes or is killed
mkdir -p ~/.config/systemd/user/app-walker@autostart.service.d/
cp $OMARCHY_PATH/default/walker/restart.conf ~/.config/systemd/user/app-walker@autostart.service.d/restart.conf
systemctl --user daemon-reload
# Refresh configs
omarchy-refresh-config walker/config.toml
omarchy-refresh-config elephant/calc.toml
omarchy-refresh-config elephant/desktopapplications.toml
# Link all elephant menus
mkdir -p ~/.config/elephant/menus
for menu in $OMARCHY_PATH/default/elephant/*.lua; do
ln -snf "$menu" ~/.config/elephant/menus/"$(basename "$menu")"
done
# Restart service
omarchy-restart-walker
-24
View File
@@ -1,24 +0,0 @@
#!/bin/bash
# omarchy:summary=Restart Walker and related user services
restart_services() {
if systemctl --user is-enabled elephant.service &>/dev/null; then
systemctl --user restart elephant.service
fi
if systemctl --user is-enabled app-walker@autostart.service &>/dev/null; then
systemctl --user restart app-walker@autostart.service
else
echo -e "\e[31mUnable to restart Walker -- RESTART MANUALLY\e[0m"
fi
}
if (( EUID == 0 )); then
SCRIPT_OWNER=$(stat -c '%U' "$0")
USER_UID=$(id -u "$SCRIPT_OWNER")
systemd-run --uid="$SCRIPT_OWNER" --setenv=XDG_RUNTIME_DIR="/run/user/$USER_UID" \
bash -c "$(declare -f restart_services); restart_services"
else
restart_services
fi
+3 -3
View File
@@ -74,9 +74,9 @@ perl -MCwd=abs_path -MEncode=encode,decode -MSocket -MIO::Handle \
my $shell_qml_arg = shift @ARGV; my $shell_qml_arg = shift @ARGV;
my $shell_qml = abs_path($shell_qml_arg); my $shell_qml = abs_path($shell_qml_arg);
my $target = shift @ARGV; my $target = decode("UTF-8", shift @ARGV);
my $function = shift @ARGV; my $function = decode("UTF-8", shift @ARGV);
my @args = @ARGV; my @args = map { decode("UTF-8", $_) } @ARGV;
my $runtime_dir = $ENV{"XDG_RUNTIME_DIR"} || "/run/user/$<"; my $runtime_dir = $ENV{"XDG_RUNTIME_DIR"} || "/run/user/$<";
my $payload = chr(3) . qstring($target) . qstring($function) . pack("N", scalar @args) . join("", map { qstring($_) } @args); my $payload = chr(3) . qstring($target) . qstring($function) . pack("N", scalar @args) . join("", map { qstring($_) } @args);
my @candidates; my @candidates;
+3 -5
View File
@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# omarchy:summary=Set Hyprland and Walker corners to sharp or round (the shell mirrors Hyprland) # omarchy:summary=Set Hyprland corners to sharp or round (the shell mirrors Hyprland)
# omarchy:args=<sharp|round> # omarchy:args=<sharp|round>
# omarchy:examples=omarchy style corners round | omarchy style corners sharp # omarchy:examples=omarchy style corners round | omarchy style corners sharp
@@ -10,11 +10,9 @@ if [[ $1 != "sharp" && $1 != "round" ]]; then
fi fi
# The shell tracks Hyprland's `decoration:rounding` directly via # The shell tracks Hyprland's `decoration:rounding` directly via
# Quickshell's qs.Commons.Style singleton; no separate quickshell-corners # Quickshell's qs.Commons.Style singleton; no separate shell state file is
# state file is written. The old omarchy-style-corners-quickshell shim is # written.
# kept as a no-op so external callers don't error.
omarchy-style-corners-hyprland "$1" omarchy-style-corners-hyprland "$1"
omarchy-style-corners-walker "$1"
case $1 in case $1 in
sharp) omarchy-notification-send "Sharp corners enabled" -g 󰝣 ;; sharp) omarchy-notification-send "Sharp corners enabled" -g 󰝣 ;;
+5 -5
View File
@@ -4,11 +4,11 @@
# omarchy:args=[toggle|sharp|round] # omarchy:args=[toggle|sharp|round]
# omarchy:examples=omarchy style corners quickshell toggle | omarchy style corners quickshell round | omarchy style corners quickshell sharp # omarchy:examples=omarchy style corners quickshell toggle | omarchy style corners quickshell round | omarchy style corners quickshell sharp
# qs.Commons.Style reads decoration:rounding from hyprctl and watches the # qs.Commons.Style reads decoration:rounding from hyprctl. Theme changes
# theme name + rounded-corners toggle files for changes. There is no # refresh it through shell IPC, while user corner toggles still trigger their
# separate quickshell-menu.json to write anymore. This shim stays around # own refresh. There is no separate quickshell-menu.json to write anymore.
# so anyone still calling the old subcommand (or the dispatcher in # This shim stays around so anyone still calling the old subcommand (or the
# omarchy-style-corners) doesn't error out. # dispatcher in omarchy-style-corners) doesn't error out.
case "${1:-toggle}" in case "${1:-toggle}" in
round|sharp|toggle) exit 0 ;; round|sharp|toggle) exit 0 ;;
-32
View File
@@ -1,32 +0,0 @@
#!/bin/bash
# omarchy:summary=Set or toggle rounded Walker corners
# omarchy:args=[toggle|sharp|round]
# omarchy:examples=omarchy style corners walker toggle | omarchy style corners walker round | omarchy style corners walker sharp
TOGGLES_CSS="$HOME/.local/state/omarchy/toggles/walker.css"
set_radius() {
local radius="$1"
mkdir -p "$(dirname "$TOGGLES_CSS")"
printf '.box-wrapper { border-radius: %spx; }\n' "$radius" >"$TOGGLES_CSS"
}
case "${1:-toggle}" in
round) set_radius 6 ;;
sharp) set_radius 0 ;;
toggle)
if [[ -f $TOGGLES_CSS ]] && grep -q 'border-radius: 6px;' "$TOGGLES_CSS"; then
set_radius 0
else
set_radius 6
fi
;;
*)
echo "Usage: omarchy-style-corners-walker [toggle|sharp|round]"
exit 1
;;
esac
omarchy-restart-walker
+2 -6
View File
@@ -122,10 +122,6 @@ fi
exec 9>"$THEME_SET_LOCK" exec 9>"$THEME_SET_LOCK"
flock 9 flock 9
# Prevent file-watch reloads from changing shell colors before the background
# reveal is ready. The theme IPC below resumes reloads as it applies colors.
shell_ipc shell suspendThemeReloads || true
# Setup clean next theme directory (for atomic theme config swapping) # Setup clean next theme directory (for atomic theme config swapping)
rm -rf "$NEXT_THEME_PATH" rm -rf "$NEXT_THEME_PATH"
mkdir -p "$NEXT_THEME_PATH" mkdir -p "$NEXT_THEME_PATH"
@@ -154,8 +150,8 @@ mv "$NEXT_THEME_PATH" "$CURRENT_THEME_PATH"
# Store theme name for reference # Store theme name for reference
echo "$THEME_NAME" >"$HOME/.config/omarchy/current/theme.name" echo "$THEME_NAME" >"$HOME/.config/omarchy/current/theme.name"
# Make the running shell pick up the new palette immediately instead of waiting # Make the running shell pick up the new palette immediately while the rest of
# for file-watch debounce while the rest of the theme hooks run. # the theme hooks run.
colors_payload=$([[ -f $CURRENT_THEME_PATH/colors.toml ]] && base64 -w 0 "$CURRENT_THEME_PATH/colors.toml") colors_payload=$([[ -f $CURRENT_THEME_PATH/colors.toml ]] && base64 -w 0 "$CURRENT_THEME_PATH/colors.toml")
shell_payload=$([[ -f $CURRENT_THEME_PATH/shell.toml ]] && base64 -w 0 "$CURRENT_THEME_PATH/shell.toml") shell_payload=$([[ -f $CURRENT_THEME_PATH/shell.toml ]] && base64 -w 0 "$CURRENT_THEME_PATH/shell.toml")
if [[ $OMARCHY_THEME_SKIP_BACKGROUND == "1" ]]; then if [[ $OMARCHY_THEME_SKIP_BACKGROUND == "1" ]]; then
+1 -1
View File
@@ -13,7 +13,7 @@ usage() {
Usage: Usage:
omarchy transcode [--path path] [input] [format] [resolution] omarchy transcode [--path path] [input] [format] [resolution]
With no input, pick a picture or video with Walker from ~/Pictures and ~/Videos, With no input, pick a picture or video from ~/Pictures and ~/Videos,
or only from --path when provided. Then pick the output format and resolution. or only from --path when provided. Then pick the output format and resolution.
Options: Options:
+3 -1
View File
@@ -45,4 +45,6 @@ for APP_NAME in "${APP_NAMES[@]}"; do
echo "Removed $APP_NAME" echo "Removed $APP_NAME"
done done
omarchy-restart-walker if command -v update-desktop-database &>/dev/null; then
update-desktop-database "$DESKTOP_DIR" &>/dev/null || true
fi
-2
View File
@@ -32,6 +32,4 @@ if command -v update-desktop-database &>/dev/null; then
update-desktop-database "$APP_DIR" &>/dev/null || true update-desktop-database "$APP_DIR" &>/dev/null || true
fi fi
omarchy-restart-walker
echo "Web apps removed successfully." echo "Web apps removed successfully."
-1
View File
@@ -1 +0,0 @@
async = false
-3
View File
@@ -1,3 +0,0 @@
show_actions = false
only_search_title = true
history = false
-1
View File
@@ -1 +0,0 @@
command = 'wl-copy && ((hyprctl dispatch "hl.dsp.send_key_state({ mods = \"SHIFT\", key = \"Insert\", state = \"down\", window = \"activewindow\" })" && sleep 0.05 && hyprctl dispatch "hl.dsp.send_key_state({ mods = \"SHIFT\", key = \"Insert\", state = \"up\", window = \"activewindow\" })") || hyprctl dispatch sendshortcut "SHIFT, Insert, activewindow")'
-49
View File
@@ -1,49 +0,0 @@
force_keyboard_focus = true # forces keyboard forcus to stay in Walker
selection_wrap = true # wrap list if at bottom or top
theme = "omarchy-default" # theme to use
additional_theme_location = "~/.local/share/omarchy/default/walker/themes/"
hide_action_hints = true # globally hide the action hints
[placeholders]
"default" = { input = " Search...", list = "No Results" } # placeholders for input and empty list, key is the providers name, so f.e. "desktopapplications" or "menus:other"
[keybinds]
quick_activate = []
[columns]
symbols = 1 # providers to be queried by default
[providers]
max_results = 256 # 256 should be enough for everyone
default = [
"desktopapplications",
"websearch",
]
[[providers.prefixes]]
prefix = "/"
provider = "providerlist"
[[providers.prefixes]]
prefix = "."
provider = "files"
[[providers.prefixes]]
prefix = ":"
provider = "symbols"
[[providers.prefixes]]
prefix = "="
provider = "calc"
[[providers.prefixes]]
prefix = "@"
provider = "websearch"
[[providers.prefixes]]
prefix = "$"
provider = "clipboard"
[[emergencies]]
text = "Restart Walker"
command = "omarchy-restart-walker"
@@ -1,73 +0,0 @@
Name = "omarchyBackgroundSelector"
NamePretty = "Omarchy Background Selector"
Cache = false
HideFromProviderlist = true
SearchName = true
local function ShellEscape(s)
return "'" .. s:gsub("'", "'\\''") .. "'"
end
function FormatName(filename)
-- Remove leading number and dash
local name = filename:gsub("^%d+", ""):gsub("^%-", "")
-- Remove extension
name = name:gsub("%.[^%.]+$", "")
-- Replace dashes with spaces
name = name:gsub("-", " ")
-- Capitalize each word
name = name:gsub("%S+", function(word)
return word:sub(1, 1):upper() .. word:sub(2):lower()
end)
return name
end
function GetEntries()
local entries = {}
local home = os.getenv("HOME")
-- Read current theme name
local theme_name_file = io.open(home .. "/.config/omarchy/current/theme.name", "r")
local theme_name = theme_name_file and theme_name_file:read("*l") or nil
if theme_name_file then
theme_name_file:close()
end
-- Directories to search
local dirs = {
home .. "/.config/omarchy/current/theme/backgrounds",
}
if theme_name then
table.insert(dirs, home .. "/.config/omarchy/backgrounds/" .. theme_name)
end
-- Track added files to avoid duplicates
local seen = {}
for _, background_dir in ipairs(dirs) do
local handle = io.popen(
"find -L " .. ShellEscape(background_dir)
.. " -maxdepth 1 -type f \\( -name '*.jpg' -o -name '*.jpeg' -o -name '*.png' -o -name '*.gif' -o -name '*.bmp' -o -name '*.webp' \\) 2>/dev/null | sort"
)
if handle then
for background in handle:lines() do
local filename = background:match("([^/]+)$")
if filename and not seen[filename] then
seen[filename] = true
table.insert(entries, {
Text = FormatName(filename),
Value = background,
Actions = {
activate = "omarchy-theme-bg-set " .. ShellEscape(background),
},
Preview = background,
PreviewType = "file",
})
end
end
handle:close()
end
end
return entries
end
-96
View File
@@ -1,96 +0,0 @@
--
-- Dynamic Omarchy Theme Menu for Elephant/Walker
--
Name = "omarchythemes"
NamePretty = "Omarchy Themes"
HideFromProviderlist = true
-- Check if file exists using Lua (no subprocess)
local function file_exists(path)
local f = io.open(path, "r")
if f then
f:close()
return true
end
return false
end
-- Get first matching file from directory using ls (single call for fallback)
local function first_image_in_dir(dir)
local handle = io.popen("ls -1 '" .. dir .. "' 2>/dev/null | head -n 1")
if handle then
local file = handle:read("*l")
handle:close()
if file and file ~= "" then
return dir .. "/" .. file
end
end
return nil
end
-- Find preview.png, preview.jpg, or first backgrounds/ image in a theme dir
local function find_preview_path(dir)
local png = dir .. "/preview.png"
local jpg = dir .. "/preview.jpg"
if file_exists(png) then return png end
if file_exists(jpg) then return jpg end
return first_image_in_dir(dir .. "/backgrounds")
end
-- The main function elephant will call
function GetEntries()
local entries = {}
local user_theme_dir = os.getenv("HOME") .. "/.config/omarchy/themes"
local omarchy_path = os.getenv("OMARCHY_PATH") or ""
local default_theme_dir = omarchy_path .. "/themes"
local seen_themes = {}
-- Helper function to process themes from a directory
local function process_themes_from_dir(theme_dir)
-- Single find call to get all theme directories
local handle = io.popen("find -L '" .. theme_dir .. "' -mindepth 1 -maxdepth 1 -type d 2>/dev/null")
if not handle then
return
end
for theme_path in handle:lines() do
local theme_name = theme_path:match(".*/(.+)$")
if theme_name and not seen_themes[theme_name] then
seen_themes[theme_name] = true
-- Check the theme dir, then fall back to the default theme dir
-- (for partial user customizations that don't ship a preview)
local preview_path = find_preview_path(theme_path)
or find_preview_path(default_theme_dir .. "/" .. theme_name)
if preview_path and preview_path ~= "" then
local display_name = theme_name:gsub("_", " "):gsub("%-", " ")
display_name = display_name:gsub("(%a)([%w_']*)", function(first, rest)
return first:upper() .. rest:lower()
end)
display_name = display_name .. " "
table.insert(entries, {
Text = display_name,
Preview = preview_path,
PreviewType = "file",
Actions = {
activate = "omarchy-theme-set " .. theme_name,
},
})
end
end
end
handle:close()
end
-- Process user themes first (they take precedence)
process_themes_from_dir(user_theme_dir)
-- Then process default themes (only if not already seen)
process_themes_from_dir(default_theme_dir)
return entries
end
-92
View File
@@ -1,92 +0,0 @@
--
-- Dynamic Omarchy Unlocks Menu for Elephant/Walker
--
-- A "Default" entry restores the omarchy-shipped Plymouth via
-- omarchy-plymouth-reset. After that, every theme that has a preview-unlock.png
-- appears as a customised unlock; picking one runs omarchy-plymouth-set-by-theme
-- <theme>. Both run in a floating terminal so sudo can prompt.
--
Name = "omarchyunlocks"
NamePretty = "Omarchy Unlocks"
HideFromProviderlist = true
FixedOrder = true
local function file_exists(path)
local f = io.open(path, "r")
if f then
f:close()
return true
end
return false
end
local function shell_escape(s)
return "'" .. s:gsub("'", "'\\''") .. "'"
end
function GetEntries()
local entries = {}
local home = os.getenv("HOME")
local user_themes_dir = home .. "/.config/omarchy/themes"
local omarchy_path = os.getenv("OMARCHY_PATH") or ""
local default_themes_dir = omarchy_path .. "/themes"
local default_preview = omarchy_path .. "/default/plymouth/preview-unlock.png"
local seen_themes = {}
local function process_themes_from_dir(themes_dir)
local handle = io.popen("find -L '" .. themes_dir .. "' -mindepth 1 -maxdepth 1 -type d 2>/dev/null")
if not handle then
return
end
for theme_path in handle:lines() do
local theme_name = theme_path:match(".*/(.+)$")
if theme_name and not seen_themes[theme_name] then
seen_themes[theme_name] = true
local preview_path = theme_path .. "/preview-unlock.png"
if file_exists(preview_path) then
local display_name = theme_name:gsub("_", " "):gsub("%-", " ")
display_name = display_name:gsub("(%a)([%w_']*)", function(first, rest)
return first:upper() .. rest:lower()
end)
display_name = display_name .. " "
table.insert(entries, {
Text = display_name,
Preview = preview_path,
PreviewType = "file",
Actions = {
activate = "omarchy-launch-floating-terminal-with-presentation "
.. shell_escape("omarchy-plymouth-set-by-theme " .. shell_escape(theme_name)),
},
})
end
end
end
handle:close()
end
process_themes_from_dir(user_themes_dir)
process_themes_from_dir(default_themes_dir)
-- Default entry last — restores the shipped Plymouth.
local default_entry = {
Text = "Default ",
Actions = {
activate = "omarchy-launch-floating-terminal-with-presentation "
.. shell_escape("omarchy-plymouth-reset"),
},
}
if file_exists(default_preview) then
default_entry.Preview = default_preview
default_entry.PreviewType = "file"
end
table.insert(entries, default_entry)
return entries
end
+2 -1
View File
@@ -6,7 +6,8 @@
hl.layer_rule({ match = { namespace = "omarchy-bar" }, no_anim = true, animation = "none" }) hl.layer_rule({ match = { namespace = "omarchy-bar" }, no_anim = true, animation = "none" })
hl.layer_rule({ match = { namespace = "omarchy-menu" }, no_anim = true, animation = "none" }) hl.layer_rule({ match = { namespace = "omarchy-menu" }, no_anim = true, animation = "none" })
-- Image selector, emoji picker, and clipboard overlays should also pop without animation. -- App launcher, image selector, emoji picker, and clipboard overlays should also pop without animation.
hl.layer_rule({ match = { namespace = "omarchy-app-launcher" }, no_anim = true, animation = "none" })
hl.layer_rule({ match = { namespace = "omarchy-image-selector" }, no_anim = true, animation = "none" }) hl.layer_rule({ match = { namespace = "omarchy-image-selector" }, no_anim = true, animation = "none" })
hl.layer_rule({ match = { namespace = "omarchy-emoji-picker" }, no_anim = true, animation = "none" }) hl.layer_rule({ match = { namespace = "omarchy-emoji-picker" }, no_anim = true, animation = "none" })
hl.layer_rule({ match = { namespace = "omarchy-clipboard-picker" }, no_anim = true, animation = "none" }) hl.layer_rule({ match = { namespace = "omarchy-clipboard-picker" }, no_anim = true, animation = "none" })
-2
View File
@@ -1,2 +0,0 @@
-- Application-specific animation.
hl.layer_rule({ match = { namespace = "walker" }, no_anim = true, animation = "none" })
+1 -1
View File
@@ -1,4 +1,4 @@
hl.bind("SUPER + SPACE", hl.dsp.exec_cmd("omarchy-launch-walker"), { description = "Launch apps" }) hl.bind("SUPER + SPACE", hl.dsp.exec_cmd("omarchy-shell shell toggle omarchy.app-launcher \"{}\""), { description = "Launch apps" })
hl.bind("SUPER + CTRL + E", hl.dsp.exec_cmd("omarchy-shell shell toggle omarchy.emoji-picker \"{}\""), { description = "Emoji picker" }) hl.bind("SUPER + CTRL + E", hl.dsp.exec_cmd("omarchy-shell shell toggle omarchy.emoji-picker \"{}\""), { description = "Emoji picker" })
hl.bind("SUPER + CTRL + C", hl.dsp.exec_cmd("omarchy-shell menu toggle capture"), { description = "Capture menu" }) hl.bind("SUPER + CTRL + C", hl.dsp.exec_cmd("omarchy-shell menu toggle capture"), { description = "Capture menu" })
hl.bind("SUPER + CTRL + O", hl.dsp.exec_cmd("omarchy-shell menu toggle toggle"), { description = "Toggle menu" }) hl.bind("SUPER + CTRL + O", hl.dsp.exec_cmd("omarchy-shell menu toggle toggle"), { description = "Toggle menu" })
+5 -6
View File
@@ -2,10 +2,10 @@
name: omarchy name: omarchy
description: > description: >
REQUIRED for end-user customization of Linux desktop, window manager, or system config. REQUIRED for end-user customization of Linux desktop, window manager, or system config.
Use when editing ~/.config/hypr/, ~/.config/omarchy/, ~/.config/walker/, Use when editing ~/.config/hypr/, ~/.config/omarchy/,
~/.config/alacritty/, ~/.config/foot/, ~/.config/kitty/, or ~/.config/ghostty/. ~/.config/alacritty/, ~/.config/foot/, ~/.config/kitty/, or ~/.config/ghostty/.
Triggers: Hyprland, window rules, animations, keybindings, monitors, gaps, borders, Triggers: Hyprland, window rules, animations, keybindings, monitors, gaps, borders,
blur, opacity, omarchy-shell, bar, walker, terminal config, themes, background, blur, opacity, omarchy-shell, bar, terminal config, themes, background,
night light, idle, lock screen, screenshots, reminders, layer rules, workspace night light, idle, lock screen, screenshots, reminders, layer rules, workspace
settings, display config, and user-facing omarchy commands. Excludes Omarchy settings, display config, and user-facing omarchy commands. Excludes Omarchy
source development in ~/.local/share/omarchy/ and `omarchy dev` workflows. source development in ~/.local/share/omarchy/ and `omarchy dev` workflows.
@@ -23,7 +23,7 @@ It is not for contributing to Omarchy source code.
**ALWAYS invoke this skill for end-user requests involving ANY of these:** **ALWAYS invoke this skill for end-user requests involving ANY of these:**
- Editing ANY file in `~/.config/hypr/` (window rules, animations, keybindings, monitors, etc.) - Editing ANY file in `~/.config/hypr/` (window rules, animations, keybindings, monitors, etc.)
- Editing `~/.config/omarchy/shell.json` (status bar layout, widgets) or `~/.config/walker/` - Editing `~/.config/omarchy/shell.json` (status bar layout, widgets)
- Editing terminal configs (alacritty, foot, kitty, ghostty) - Editing terminal configs (alacritty, foot, kitty, ghostty)
- Editing ANY file in `~/.config/omarchy/` - Editing ANY file in `~/.config/omarchy/`
- Window behavior, animations, opacity, blur, gaps, borders - Window behavior, animations, opacity, blur, gaps, borders
@@ -78,7 +78,7 @@ Omarchy is built on:
| **Arch Linux** | Base OS | `/etc/`, `~/.config/` | | **Arch Linux** | Base OS | `/etc/`, `~/.config/` |
| **Hyprland** | Wayland compositor/WM | `~/.config/hypr/` | | **Hyprland** | Wayland compositor/WM | `~/.config/hypr/` |
| **Omarchy shell** | Status bar + notifications (Quickshell) | `~/.config/omarchy/shell.json` | | **Omarchy shell** | Status bar + notifications (Quickshell) | `~/.config/omarchy/shell.json` |
| **Walker** | App launcher | `~/.config/walker/` | | **App launcher** | Quickshell app launcher | `~/.config/omarchy/shell.json` |
| **Alacritty/Foot/Kitty/Ghostty** | Terminals | `~/.config/<terminal>/` | | **Alacritty/Foot/Kitty/Ghostty** | Terminals | `~/.config/<terminal>/` |
| **Omarchy OSD** | On-screen display | Quickshell plugin | | **Omarchy OSD** | On-screen display | Quickshell plugin |
@@ -182,7 +182,6 @@ changes. For more invasive changes (new plugin, packaged update):
| lazygit | `~/.config/lazygit/config.yml` | | lazygit | `~/.config/lazygit/config.yml` |
| starship | `~/.config/starship.toml` | | starship | `~/.config/starship.toml` |
| git | `~/.config/git/config` | | git | `~/.config/git/config` |
| walker | `~/.config/walker/config.toml` |
## Safe Customization Patterns ## Safe Customization Patterns
@@ -202,7 +201,7 @@ cp ~/.config/hypr/bindings.conf ~/.config/hypr/bindings.conf.bak.$(date +%s)
# 4. Apply changes # 4. Apply changes
# - Hyprland: auto-reloads on save, but MUST validate with `hyprctl reload` and `hyprctl configerrors` # - Hyprland: auto-reloads on save, but MUST validate with `hyprctl reload` and `hyprctl configerrors`
# - Omarchy shell: shell.json hot-reloads; use `omarchy-restart-shell` for plugin/widget code changes # - Omarchy shell: shell.json hot-reloads; use `omarchy-restart-shell` for plugin/widget code changes
# - Walker: MUST restart with `omarchy restart walker` # - App launcher: restart with `omarchy restart shell`
# - Terminals: MUST restart with `omarchy restart terminal` # - Terminals: MUST restart with `omarchy restart terminal`
``` ```
+2 -6
View File
@@ -13,7 +13,7 @@
// checked shell condition; append ✓ when it succeeds // checked shell condition; append ✓ when it succeeds
// Root Menu // Root Menu
"apps": {"icon":"󰀻","label":"Apps","aliases":["app","applications"],"keywords":"launch launcher","action":"omarchy-launch-walker -p 'Launch…'"}, "apps": {"icon":"󰀻","label":"Apps","aliases":["app","applications"],"keywords":"launch launcher quickshell","action":"omarchy-shell shell summon omarchy.app-launcher '{}'"},
"learn": {"icon":"󰧑","label":"Learn","keywords":"manual docs help reference"}, "learn": {"icon":"󰧑","label":"Learn","keywords":"manual docs help reference"},
"trigger": {"icon":"󱓞","label":"Trigger","keywords":"actions capture share toggle hardware reminder"}, "trigger": {"icon":"󱓞","label":"Trigger","keywords":"actions capture share toggle hardware reminder"},
"style": {"icon":"","label":"Style","keywords":"theme background font bar quickshell hyprland appearance"}, "style": {"icon":"","label":"Style","keywords":"theme background font bar quickshell hyprland appearance"},
@@ -87,7 +87,6 @@
// Style // Style
"style.theme": {"icon":"󰸌","label":"Theme","aliases":["theme","themes"],"keywords":"colors","action":"theme=$(omarchy-theme-switcher); [[ -n $theme ]] && omarchy-theme-set \"$theme\""}, "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.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.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.bar": {"icon":"󰍜","label":"Bar","keywords":"quickshell top bottom left right settings"},
@@ -158,12 +157,11 @@
"setup.dns.google": {"icon":"󰖩","label":"Google","checked":"[[ $(omarchy-dns) == \"Google\" ]]","action":"omarchy-dns Google"}, "setup.dns.google": {"icon":"󰖩","label":"Google","checked":"[[ $(omarchy-dns) == \"Google\" ]]","action":"omarchy-dns Google"},
"setup.dns.custom": {"icon":"󰖩","label":"Custom","checked":"[[ $(omarchy-dns) == \"Custom\" ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-dns Custom"}, "setup.dns.custom": {"icon":"󰖩","label":"Custom","checked":"[[ $(omarchy-dns) == \"Custom\" ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-dns Custom"},
"setup.security": {"icon":"","label":"Security","keywords":"fingerprint fido2"}, "setup.security": {"icon":"","label":"Security","keywords":"fingerprint fido2"},
"setup.config": {"icon":"","label":"Config","keywords":"files hyprland bar quickshell walker"}, "setup.config": {"icon":"","label":"Config","keywords":"files hyprland bar quickshell"},
"setup.security.fingerprint": {"icon":"󰈷","label":"Fingerprint","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fingerprint"}, "setup.security.fingerprint": {"icon":"󰈷","label":"Fingerprint","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fingerprint"},
"setup.security.fido2": {"icon":"","label":"Fido2","keywords":"key","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"}, "setup.security.fido2": {"icon":"","label":"Fido2","keywords":"key","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"},
"setup.config.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/hyprland.lua\""}, "setup.config.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/hyprland.lua\""},
"setup.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"night light","action":"omarchy-launch-config-editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset"}, "setup.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"night light","action":"omarchy-launch-config-editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset"},
"setup.config.walker": {"icon":"󰌧","label":"Walker","keywords":"launcher","action":"omarchy-launch-config-editor ~/.config/walker/config.toml && omarchy-restart-walker"},
"setup.config.bar": {"icon":"󰍜","label":"Bar","keywords":"quickshell shell config settings widgets position","action":"omarchy-launch-bar-settings"}, "setup.config.bar": {"icon":"󰍜","label":"Bar","keywords":"quickshell shell config settings widgets position","action":"omarchy-launch-bar-settings"},
"setup.config.xcompose": {"icon":"󰞅","label":"XCompose","keywords":"compose key","action":"omarchy-launch-config-editor ~/.XCompose && omarchy-restart-xcompose"}, "setup.config.xcompose": {"icon":"󰞅","label":"XCompose","keywords":"compose key","action":"omarchy-launch-config-editor ~/.XCompose && omarchy-restart-xcompose"},
@@ -311,14 +309,12 @@
"update.channel.dev": {"icon":"🔴","label":"Dev","keywords":"development","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-channel-set dev'"}, "update.channel.dev": {"icon":"🔴","label":"Dev","keywords":"development","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-channel-set dev'"},
"update.process.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"restart","action":"omarchy-restart-hyprsunset"}, "update.process.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"restart","action":"omarchy-restart-hyprsunset"},
"update.process.osd": {"icon":"","label":"OSD","keywords":"restart","action":"omarchy-restart-shell"}, "update.process.osd": {"icon":"","label":"OSD","keywords":"restart","action":"omarchy-restart-shell"},
"update.process.walker": {"icon":"󰌧","label":"Walker","keywords":"restart launcher","action":"omarchy-restart-walker"},
"update.process.shell": {"icon":"󰍜","label":"Shell","keywords":"restart bar quickshell omarchy-shell","action":"omarchy-restart-shell"}, "update.process.shell": {"icon":"󰍜","label":"Shell","keywords":"restart bar quickshell omarchy-shell","action":"omarchy-restart-shell"},
"update.config.hyprland": {"icon":"","label":"Hyprland","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-hyprland"}, "update.config.hyprland": {"icon":"","label":"Hyprland","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-hyprland"},
"update.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-hyprsunset"}, "update.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-hyprsunset"},
"update.config.plymouth": {"icon":"󱣴","label":"Plymouth","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-plymouth"}, "update.config.plymouth": {"icon":"󱣴","label":"Plymouth","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-plymouth"},
"update.config.osd": {"icon":"","label":"OSD","keywords":"refresh","action":"omarchy-restart-shell"}, "update.config.osd": {"icon":"","label":"OSD","keywords":"refresh","action":"omarchy-restart-shell"},
"update.config.tmux": {"icon":"","label":"Tmux","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-tmux"}, "update.config.tmux": {"icon":"","label":"Tmux","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-tmux"},
"update.config.walker": {"icon":"󰌧","label":"Walker","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-walker"},
"update.config.shell": {"icon":"󰍜","label":"Shell","keywords":"refresh bar quickshell","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-shell"}, "update.config.shell": {"icon":"󰍜","label":"Shell","keywords":"refresh bar quickshell","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-shell"},
"update.hardware.audio": {"icon":"","label":"Audio","keywords":"restart pipewire","action":"omarchy-launch-floating-terminal-with-presentation omarchy-restart-pipewire"}, "update.hardware.audio": {"icon":"","label":"Audio","keywords":"restart pipewire","action":"omarchy-launch-floating-terminal-with-presentation omarchy-restart-pipewire"},
"update.hardware.wifi": {"icon":"󱚾","label":"Wi-Fi","keywords":"restart wireless","action":"omarchy-launch-floating-terminal-with-presentation omarchy-restart-wifi"}, "update.hardware.wifi": {"icon":"󱚾","label":"Wi-Fi","keywords":"restart wireless","action":"omarchy-launch-floating-terminal-with-presentation omarchy-restart-wifi"},
-6
View File
@@ -1,6 +0,0 @@
@define-color selected-text {{ accent }};
@define-color text {{ foreground }};
@define-color base {{ background }};
@define-color border {{ foreground }};
@define-color foreground {{ foreground }};
@define-color background {{ background }};
-3
View File
@@ -1,3 +0,0 @@
[Service]
Restart=always
RestartSec=2
@@ -1,156 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"></requires>
<object class="GtkWindow" id="Window">
<style>
<class name="window"></class>
</style>
<property name="resizable">true</property>
<property name="title">Walker</property>
<child>
<object class="GtkBox" id="BoxWrapper">
<style>
<class name="box-wrapper"></class>
</style>
<property name="width-request">644</property>
<property name="overflow">hidden</property>
<property name="orientation">horizontal</property>
<property name="valign">center</property>
<property name="halign">center</property>
<child>
<object class="GtkBox" id="Box">
<style>
<class name="box"></class>
</style>
<property name="orientation">vertical</property>
<property name="hexpand-set">true</property>
<property name="hexpand">true</property>
<property name="spacing">10</property>
<child>
<object class="GtkBox" id="SearchContainer">
<style>
<class name="search-container"></class>
</style>
<property name="overflow">hidden</property>
<property name="orientation">horizontal</property>
<property name="halign">fill</property>
<property name="hexpand-set">true</property>
<property name="hexpand">true</property>
<child>
<object class="GtkEntry" id="Input">
<style>
<class name="input"></class>
</style>
<property name="halign">fill</property>
<property name="hexpand-set">true</property>
<property name="hexpand">true</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="ContentContainer">
<style>
<class name="content-container"></class>
</style>
<property name="orientation">horizontal</property>
<property name="spacing">10</property>
<property name="vexpand">true</property>
<property name="vexpand-set">true</property>
<child>
<object class="GtkLabel" id="ElephantHint">
<style>
<class name="elephant-hint"></class>
</style>
<property name="hexpand">true</property>
<property name="height-request">100</property>
<property name="label">Waiting for elephant...</property>
</object>
</child>
<child>
<object class="GtkLabel" id="Placeholder">
<style>
<class name="placeholder"></class>
</style>
<property name="label">No Results</property>
<property name="yalign">0.0</property>
<property name="hexpand">true</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="Scroll">
<style>
<class name="scroll"></class>
</style>
<property name="hexpand">true</property>
<property name="can_focus">false</property>
<property name="overlay-scrolling">true</property>
<property name="max-content-width">600</property>
<property name="max-content-height">300</property>
<property name="min-content-height">0</property>
<property name="propagate-natural-height">true</property>
<property name="propagate-natural-width">true</property>
<property name="hscrollbar-policy">automatic</property>
<property name="vscrollbar-policy">automatic</property>
<child>
<object class="GtkGridView" id="List">
<style>
<class name="list"></class>
</style>
<property name="max_columns">1</property>
<property name="can_focus">false</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="Preview">
<style>
<class name="preview"></class>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="Keybinds">
<property name="hexpand">true</property>
<property name="margin-top">10</property>
<style>
<class name="keybinds"></class>
</style>
<child>
<object class="GtkBox" id="GlobalKeybinds">
<property name="spacing">10</property>
<style>
<class name="global-keybinds"></class>
</style>
</object>
</child>
<child>
<object class="GtkBox" id="ItemKeybinds">
<property name="hexpand">true</property>
<property name="halign">end</property>
<property name="spacing">10</property>
<style>
<class name="item-keybinds"></class>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="Error">
<style>
<class name="error"></class>
</style>
<property name="xalign">0</property>
<property name="visible">false</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
@@ -1,122 +0,0 @@
@import "../../../../../../../.config/omarchy/current/theme/walker.css";
@import "../../../../../../../.local/state/omarchy/toggles/walker.css";
* {
all: unset;
}
* {
font-family: monospace;
font-size: 18px;
color: @text;
}
scrollbar {
opacity: 0;
}
.normal-icons {
-gtk-icon-size: 16px;
}
.large-icons {
-gtk-icon-size: 32px;
}
.box-wrapper {
background: alpha(@base, 0.95);
padding: 20px;
border: 2px solid @border;
}
.preview-box {
}
.box {
}
.search-container {
background: @base;
padding: 10px;
}
.input placeholder {
opacity: 0.5;
}
.input {
}
.input:focus,
.input:active {
box-shadow: none;
outline: none;
}
.content-container {
}
.placeholder {
}
.scroll {
}
.list {
}
child,
child > * {
}
child:hover .item-box {
}
child:selected .item-box {
}
child:selected .item-box * {
color: @selected-text;
}
child:selected {
background: alpha(@text, 0.07);
}
.item-box {
padding-left: 14px;
}
.item-text-box {
all: unset;
padding: 14px 0;
}
.item-text {
}
.item-subtext {
font-size: 0px;
min-height: 0px;
margin: 0px;
padding: 0px;
}
.item-image {
margin-right: 14px;
-gtk-icon-transform: scale(0.9);
}
.current {
font-style: italic;
}
.keybind-hints {
background: @background;
padding: 10px;
margin-top: 10px;
}
.preview {
}
-7
View File
@@ -1,7 +0,0 @@
[Desktop Entry]
Name=Walker
Comment=Walker Service
Exec=walker --gapplication-service
StartupNotify=false
Terminal=false
Type=Application
-1
View File
@@ -20,7 +20,6 @@ run_logged $OMARCHY_INSTALL/config/user-dirs.sh
run_logged $OMARCHY_INSTALL/config/toggles.sh run_logged $OMARCHY_INSTALL/config/toggles.sh
run_logged $OMARCHY_INSTALL/config/nautilus-python.sh run_logged $OMARCHY_INSTALL/config/nautilus-python.sh
run_logged $OMARCHY_INSTALL/config/localdb.sh run_logged $OMARCHY_INSTALL/config/localdb.sh
run_logged $OMARCHY_INSTALL/config/walker-elephant.sh
run_logged $OMARCHY_INSTALL/config/fast-shutdown.sh run_logged $OMARCHY_INSTALL/config/fast-shutdown.sh
run_logged $OMARCHY_INSTALL/config/unmount-fuse.sh run_logged $OMARCHY_INSTALL/config/unmount-fuse.sh
run_logged $OMARCHY_INSTALL/config/sudoless-asdcontrol.sh run_logged $OMARCHY_INSTALL/config/sudoless-asdcontrol.sh
-1
View File
@@ -1,3 +1,2 @@
# Make sure toggles are available # Make sure toggles are available
mkdir -p ~/.local/state/omarchy/toggles mkdir -p ~/.local/state/omarchy/toggles
touch ~/.local/state/omarchy/toggles/walker.css
-31
View File
@@ -1,31 +0,0 @@
#!/bin/bash
# Ensure Walker service is started automatically on boot
mkdir -p ~/.config/autostart/
cp $OMARCHY_PATH/default/walker/walker.desktop ~/.config/autostart/
# And is restarted if it crashes or is killed
mkdir -p ~/.config/systemd/user/app-walker@autostart.service.d/
cp $OMARCHY_PATH/default/walker/restart.conf ~/.config/systemd/user/app-walker@autostart.service.d/restart.conf
# Create pacman hook to restart walker after updates
sudo mkdir -p /etc/pacman.d/hooks
sudo tee /etc/pacman.d/hooks/walker-restart.hook > /dev/null << EOF
[Trigger]
Type = Package
Operation = Upgrade
Target = walker
Target = walker-debug
Target = elephant*
[Action]
Description = Restarting Walker services after system update
When = PostTransaction
Exec = $OMARCHY_PATH/bin/omarchy-restart-walker
EOF
# Link the visual theme menu config
mkdir -p ~/.config/elephant/menus
ln -snf $OMARCHY_PATH/default/elephant/omarchy_themes.lua ~/.config/elephant/menus/omarchy_themes.lua
ln -snf $OMARCHY_PATH/default/elephant/omarchy_background_selector.lua ~/.config/elephant/menus/omarchy_background_selector.lua
ln -snf $OMARCHY_PATH/default/elephant/omarchy_unlocks.lua ~/.config/elephant/menus/omarchy_unlocks.lua
-2
View File
@@ -1,2 +0,0 @@
elephant service enable
systemctl --user start elephant.service
+1 -1
View File
@@ -39,6 +39,7 @@ ffmpegthumbnailer
fontconfig fontconfig
foot foot
fzf fzf
git
github-cli github-cli
gnome-calculator gnome-calculator
gnome-keyring gnome-keyring
@@ -88,7 +89,6 @@ nvim
obs-studio obs-studio
obsidian obsidian
omarchy-nvim omarchy-nvim
omarchy-walker
pamixer pamixer
pinta pinta
playerctl playerctl
-1
View File
@@ -9,7 +9,6 @@ broadcom-wl
btrfs-progs btrfs-progs
dkms dkms
egl-wayland egl-wayland
git
gst-plugin-pipewire gst-plugin-pipewire
gtk4-layer-shell gtk4-layer-shell
libpulse libpulse
-11
View File
@@ -1,11 +0,0 @@
echo "Replace wofi with walker as the default launcher"
if omarchy-cmd-missing walker; then
omarchy-pkg-add walker-bin libqalculate
omarchy-pkg-drop wofi
rm -rf ~/.config/wofi
mkdir -p ~/.config/walker
cp -r ~/.local/share/omarchy/config/walker/* ~/.config/walker/
fi
-55
View File
@@ -1,55 +0,0 @@
echo "Migrate to Walker 2.0.0"
NEEDS_MIGRATION=false
PACKAGES=(
"elephant"
"elephant-calc"
"elephant-clipboard"
"elephant-bluetooth"
"elephant-desktopapplications"
"elephant-files"
"elephant-menus"
"elephant-providerlist"
"elephant-runner"
"elephant-symbols"
"elephant-unicode"
"elephant-websearch"
"elephant-todo"
"walker"
"libqalculate"
)
for pkg in "${PACKAGES[@]}"; do
if ! omarchy-pkg-present "$pkg"; then
NEEDS_MIGRATION=true
break
fi
done
WALKER_MAJOR=$(walker -v 2>&1 | grep -oP '^\d+' || echo "0")
if (( WALKER_MAJOR < 2 )); then
NEEDS_MIGRATION=true
fi
# Ensure basic config is present
mkdir -p ~/.config/walker
cp -r ~/.local/share/omarchy/config/walker/* ~/.config/walker/
if $NEEDS_MIGRATION; then
kill -9 $(pgrep -x walker) 2>/dev/null || true
omarchy-pkg-drop walker-bin walker-bin-debug
omarchy-pkg-add "${PACKAGES[@]}"
source $OMARCHY_PATH/install/config/walker-elephant.sh
rm -rf ~/.config/walker/themes
omarchy-refresh-config walker/config.toml
omarchy-refresh-config elephant/calc.toml
omarchy-refresh-config elephant/desktopapplications.toml
fi
echo # Assure final success
-2
View File
@@ -1,2 +0,0 @@
echo "Install omarchy-walker meta package"
omarchy-pkg-add omarchy-walker
-10
View File
@@ -1,10 +0,0 @@
echo "Switch Elephant to run as a systemd service and walker to be autostarted on login"
pkill elephant
elephant service enable
systemctl --user start elephant.service
pkill walker
mkdir -p ~/.config/autostart/
cp $OMARCHY_PATH/default/walker/walker.desktop ~/.config/autostart/
setsid walker --gapplication-service &
-3
View File
@@ -1,3 +0,0 @@
echo "Ensure emoji menu (Super + Ctrl + E) uses single column design"
omarchy-refresh-walker
-11
View File
@@ -1,11 +0,0 @@
echo "Add emergency entry for Walker"
CONFIG_FILE="$HOME/.config/walker/config.toml"
if [[ -f $CONFIG_FILE ]] && ! grep -q 'command = "omarchy-restart-walker"' "$CONFIG_FILE"; then
cat >> "$CONFIG_FILE" << 'EOF'
[[emergencies]]
text = "Restart Walker"
command = "omarchy-restart-walker"
EOF
fi
-4
View File
@@ -1,4 +0,0 @@
echo "Enable auto-pasting for the emoji picker"
omarchy-refresh-config elephant/symbols.toml
omarchy-restart-walker
-6
View File
@@ -1,6 +0,0 @@
echo "Ensure walker service is restarted if it's killed or crashes"
mkdir -p ~/.config/systemd/user/app-walker@autostart.service.d/
cp $OMARCHY_PATH/default/walker/restart.conf ~/.config/systemd/user/app-walker@autostart.service.d/restart.conf
systemctl --user daemon-reload
-5
View File
@@ -1,5 +0,0 @@
echo "Use interactive background selector menu"
mkdir -p ~/.config/elephant/menus
ln -snf $OMARCHY_PATH/default/elephant/omarchy_background_selector.lua ~/.config/elephant/menus/omarchy_background_selector.lua
omarchy-restart-walker
-5
View File
@@ -1,5 +0,0 @@
echo "Use interactive unlock (Plymouth) selector menu"
mkdir -p ~/.config/elephant/menus
ln -snf $OMARCHY_PATH/default/elephant/omarchy_unlocks.lua ~/.config/elephant/menus/omarchy_unlocks.lua
omarchy-restart-walker
-4
View File
@@ -1,4 +0,0 @@
echo "Create Walker toggle config"
mkdir -p ~/.local/state/omarchy/toggles
[[ -f ~/.local/state/omarchy/toggles/walker.css ]] || touch ~/.local/state/omarchy/toggles/walker.css
-9
View File
@@ -1,9 +0,0 @@
echo "Migrate emoji picker shortcut to use Quickshell emoji picker"
if [[ -f ~/.config/hypr/bindings/utilities.lua ]]; then
sed -i 's/omarchy-launch-walker -m symbols/omarchy-shell-ipc shell toggle omarchy.emoji-picker "{}"/' ~/.config/hypr/bindings/utilities.lua
fi
if [[ -f ~/.config/hypr/bindings.lua ]]; then
sed -i 's/omarchy-launch-walker -m symbols/omarchy-shell-ipc shell toggle omarchy.emoji-picker "{}"/' ~/.config/hypr/bindings.lua
fi
-5
View File
@@ -1,5 +0,0 @@
echo "Migrate clipboard manager shortcut to use Quickshell clipboard picker"
if [[ -f ~/.config/hypr/bindings/clipboard.lua ]]; then
sed -i 's/walker -m clipboard/omarchy-shell-ipc shell toggle omarchy.clipboard-picker "{}"/' ~/.config/hypr/bindings/clipboard.lua
fi
+32
View File
@@ -0,0 +1,32 @@
echo "Remove Walker app-launcher services and Elephant providers"
pkill elephant 2>/dev/null || true
systemctl --user disable --now elephant.service 2>/dev/null || true
systemctl --user disable --now app-walker@autostart.service 2>/dev/null || true
rm -f ~/.config/autostart/walker.desktop
rm -rf ~/.config/elephant
rm -rf ~/.config/systemd/user/app-walker@autostart.service.d
sudo rm -f /etc/pacman.d/hooks/walker-restart.hook
for bindings_file in ~/.config/hypr/bindings/utilities.lua ~/.config/hypr/bindings.lua; do
if [[ -f $bindings_file ]]; then
sed -i 's#omarchy-launch-walker#omarchy-shell shell toggle omarchy.app-launcher \\"{}\\"#g' "$bindings_file"
fi
done
omarchy-pkg-drop \
omarchy-walker \
elephant-all \
elephant \
elephant-calc \
elephant-clipboard \
elephant-bluetooth \
elephant-desktopapplications \
elephant-files \
elephant-menus \
elephant-providerlist \
elephant-runner \
elephant-symbols \
elephant-unicode \
elephant-websearch \
elephant-todo
+1
View File
@@ -29,6 +29,7 @@ shell/
plugins/ plugins/
bar/ first-party plugins (see plugins/README.md) bar/ first-party plugins (see plugins/README.md)
settings/ settings/
app-launcher/
image-picker/ image-picker/
menu/ menu/
notifications/ notifications/
+10 -1
View File
@@ -13,6 +13,7 @@ User-installed plugins live alongside these conceptually but on disk under
|---------------|---------------------------|-----------|---------------------------------------| |---------------|---------------------------|-----------|---------------------------------------|
| Bar | `omarchy.bar` | `bar` | `bar/Bar.qml` | | Bar | `omarchy.bar` | `bar` | `bar/Bar.qml` |
| Bar settings | `omarchy.settings` | `panel` | `settings/SettingsPanel.qml` | | Bar settings | `omarchy.settings` | `panel` | `settings/SettingsPanel.qml` |
| App launcher | `omarchy.app-launcher` | `overlay` | `app-launcher/AppLauncher.qml` |
| Image picker | `omarchy.image-picker` | `overlay` | `image-picker/ImagePicker.qml` | | Image picker | `omarchy.image-picker` | `overlay` | `image-picker/ImagePicker.qml` |
| Emoji picker | `omarchy.emoji-picker` | `overlay` | `emoji-picker/EmojiPicker.qml` | | Emoji picker | `omarchy.emoji-picker` | `overlay` | `emoji-picker/EmojiPicker.qml` |
| Clipboard mgr | `omarchy.clipboard-picker`| `overlay` | `clipboard-picker/ClipboardPicker.qml`| | Clipboard mgr | `omarchy.clipboard-picker`| `overlay` | `clipboard-picker/ClipboardPicker.qml`|
@@ -43,6 +44,14 @@ Visual editor for the bar layout. Summoned by
- dynamic per-widget settings forms that write inline back to the - dynamic per-widget settings forms that write inline back to the
corresponding shell.json entry corresponding shell.json entry
## App launcher
Quickshell-powered app launcher. It uses Quickshell's native
`DesktopEntries` model for discovery/activation and renders inside the
long-running shell with the legacy launcher card dimensions, colors, row
spacing, icon sizing, and keyboard behavior. Summoned directly over shell IPC
by the `SUPER + SPACE` binding and the Omarchy menu Apps row.
## Image picker ## Image picker
Fullscreen image-grid selector overlay. Used by `omarchy-menu-images` Fullscreen image-grid selector overlay. Used by `omarchy-menu-images`
@@ -87,7 +96,7 @@ runs inside the long-lived `omarchy-shell` process, replacing the old
## Omarchy menu ## Omarchy menu
Quickshell-powered replacement for the legacy Walker-driven `omarchy-menu`. Quickshell-powered Omarchy command menu.
The menu UI lives in `menu/Menu.qml` as a first-party `menu` plugin and is The menu UI lives in `menu/Menu.qml` as a first-party `menu` plugin and is
summoned through the shell (`omarchy-shell shell summon omarchy.menu ...`), summoned through the shell (`omarchy-shell shell summon omarchy.menu ...`),
so it shares the long-running `omarchy-shell` process instead of starting a so it shares the long-running `omarchy-shell` process instead of starting a
+434
View File
@@ -0,0 +1,434 @@
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import QtQuick
import qs.Commons
Item {
id: root
property string omarchyPath: Quickshell.env("OMARCHY_PATH")
property var shell: null
property var manifest: null
property bool opened: false
property string placeholder: "\uf002 Search..."
property string filterText: ""
property int selectedIndex: 0
property bool cursorActive: true
property bool hoverArmed: false
property var filteredEntries: []
property color accent: Color.menu.selected
property color background: Color.menu.background
property color foreground: Color.menu.text
property color border: foreground
property string fontFamily: Quickshell.env("OMARCHY_MENU_FONT") || "monospace"
property int cardWidth: 644
property int cardHeight: 400
property int contentMargin: 20
property int contentSpacing: 10
property int searchHeight: 44
property int rowHeight: 50
property int iconSlotWidth: 44
property int iconSize: 24
readonly property int listHeight: cardHeight - contentMargin * 2 - searchHeight - contentSpacing
function open(payloadJson) {
var payload = ({})
try { payload = JSON.parse(payloadJson || "{}") } catch (e) { payload = ({}) }
root.placeholder = payload.placeholder || "\uf002 Search..."
root.cardWidth = Math.max(300, Number(payload.width || 644))
var requestedListHeight = Number(payload.listHeight || payload.maxHeight || 0)
root.cardHeight = requestedListHeight > 0
? root.contentMargin * 2 + root.searchHeight + root.contentSpacing + requestedListHeight
: 400
root.opened = true
root.filterText = payload.query || ""
root.selectedIndex = 0
root.cursorActive = true
root.hoverArmed = false
root.rebuildDisplay()
Qt.callLater(function() { keyCatcher.forceActiveFocus() })
}
function close() {
root.opened = false
}
function dismiss() {
root.opened = false
if (root.shell && typeof root.shell.hide === "function")
root.shell.hide((root.manifest && root.manifest.id) || "omarchy.app-launcher")
}
function withAlpha(color, alpha) {
return Qt.rgba(color.r, color.g, color.b, alpha)
}
function fileUrl(path) {
return "file://" + String(path).split("/").map(encodeURIComponent).join("/")
}
function iconSource(icon) {
var value = String(icon || "")
if (value.length === 0) return Quickshell.iconPath("application-x-executable", true)
if (value.indexOf("file://") === 0 || value.indexOf("image://") === 0) return value
if (value.charAt(0) === "/") return root.fileUrl(value)
return Quickshell.iconPath(value, true)
}
function entryName(entry) {
return String((entry && entry.name) || (entry && entry.id) || "")
}
function entrySubtext(entry) {
return String((entry && entry.genericName) || "")
}
function entrySortKey(entry) {
return String((entry && entry.id) || root.entryName(entry)).toLowerCase()
}
function entrySearchText(entry) {
if (!entry) return ""
var keywords = ""
try { keywords = (entry.keywords || []).join(" ") } catch (e) { keywords = "" }
return [entry.name, entry.genericName, entry.comment, keywords, entry.id].join(" ").toLowerCase()
}
function isHiddenEntry(entry) {
var id = String((entry && entry.id) || "")
return id === "avahi-discover" || id === "bssh" || id === "bvnc"
}
function fuzzyScore(entry, query) {
var q = String(query || "").trim().toLowerCase()
if (!q) return 0
var name = root.entryName(entry).toLowerCase()
var id = String((entry && entry.id) || "").toLowerCase()
var haystack = root.entrySearchText(entry)
var directName = name.indexOf(q)
var directId = id.indexOf(q)
if (directName === 0) return 10000 - name.length
if (directId === 0) return 9500 - id.length
if (directName > 0) return 8000 - directName * 10 - name.length
if (directId > 0) return 7600 - directId * 10 - id.length
var hayIndex = haystack.indexOf(q)
if (hayIndex >= 0) return 6000 - hayIndex
var pos = 0
var first = -1
var last = -1
for (var i = 0; i < haystack.length && pos < q.length; i++) {
if (haystack.charAt(i) !== q.charAt(pos)) continue
if (first < 0) first = i
last = i
pos++
}
if (pos !== q.length) return -1
return 3000 - (last - first) - haystack.length * 0.01
}
function sortedEntries(query) {
var values = DesktopEntries.applications.values || []
var q = String(query || "").trim()
var rows = []
for (var i = 0; i < values.length; i++) {
var entry = values[i]
if (!entry || entry.noDisplay || root.isHiddenEntry(entry)) continue
var name = root.entryName(entry)
if (!name) continue
var score = root.fuzzyScore(entry, q)
if (score < 0) continue
rows.push({ entry: entry, score: score, key: root.entrySortKey(entry), name: name.toLowerCase() })
}
rows.sort(function(a, b) {
if (q && a.score !== b.score) return b.score - a.score
if (a.key < b.key) return -1
if (a.key > b.key) return 1
if (a.name < b.name) return -1
if (a.name > b.name) return 1
return 0
})
return rows
}
function rebuildDisplay() {
displayModel.clear()
var rows = root.sortedEntries(root.filterText)
var entries = []
var count = Math.min(rows.length, 256)
for (var i = 0; i < count; i++) {
var entry = rows[i].entry
entries.push(entry)
displayModel.append({
name: root.entryName(entry),
subtext: root.entrySubtext(entry),
icon: String(entry.icon || "")
})
}
root.filteredEntries = entries
if (displayModel.count === 0) root.selectedIndex = 0
else if (root.selectedIndex >= displayModel.count) root.selectedIndex = displayModel.count - 1
else if (root.selectedIndex < 0) root.selectedIndex = 0
Qt.callLater(function() {
if (displayModel.count > 0) resultList.positionViewAtIndex(root.selectedIndex, ListView.Contain)
})
}
function setFilter(nextFilter) {
root.filterText = nextFilter
root.selectedIndex = 0
root.cursorActive = true
root.rebuildDisplay()
}
function select(delta) {
if (displayModel.count === 0) return
root.cursorActive = true
root.selectedIndex = (root.selectedIndex + delta + displayModel.count) % displayModel.count
resultList.positionViewAtIndex(root.selectedIndex, ListView.Contain)
}
function activateIndex(index) {
if (index < 0 || index >= root.filteredEntries.length) return
var entry = root.filteredEntries[index]
if (!entry) return
root.dismiss()
entry.execute()
}
ListModel { id: displayModel }
Connections {
target: DesktopEntries.applications
function onValuesChanged() {
if (root.opened) root.rebuildDisplay()
}
}
PanelWindow {
id: panel
visible: root.opened
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
WlrLayershell.namespace: "omarchy-app-launcher"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
exclusionMode: ExclusionMode.Ignore
Rectangle {
anchors.fill: parent
color: root.withAlpha(root.background, 0.5)
}
MouseArea {
anchors.fill: parent
onClicked: root.dismiss()
}
Rectangle {
id: card
width: Math.min(root.cardWidth, panel.width - Style.gapsOut * 2)
height: Math.min(root.cardHeight, panel.height - Style.gapsOut * 2)
radius: Style.cornerRadius
anchors.centerIn: parent
color: root.withAlpha(root.background, 0.95)
border.color: root.border
border.width: 2
clip: true
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.length > 0) root.setFilter("")
else root.dismiss()
event.accepted = true
} else if (event.key === Qt.Key_Backspace) {
if (root.filterText.length > 0) root.setFilter(root.filterText.slice(0, -1))
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_Home) {
if (displayModel.count > 0) {
root.cursorActive = true
root.selectedIndex = 0
resultList.positionViewAtIndex(root.selectedIndex, ListView.Contain)
}
event.accepted = true
} else if (event.key === Qt.Key_End) {
if (displayModel.count > 0) {
root.cursorActive = true
root.selectedIndex = displayModel.count - 1
resultList.positionViewAtIndex(root.selectedIndex, ListView.Contain)
}
event.accepted = true
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
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.searchHeight
radius: 0
color: root.background
Text {
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
text: root.filterText || root.placeholder
color: root.foreground
opacity: root.filterText ? 1 : 0.5
font.family: root.fontFamily
font.pixelSize: 18
elide: Text.ElideRight
}
}
Item {
width: parent.width
height: parent.height - root.searchHeight - root.contentSpacing
ListView {
id: resultList
anchors.fill: parent
model: displayModel
clip: true
spacing: 0
boundsBehavior: Flickable.StopAtBounds
delegate: Rectangle {
id: row
required property int index
required property string name
required property string subtext
required property string icon
readonly property bool hasCursor: root.cursorActive && row.index === root.selectedIndex
width: ListView.view.width
height: root.rowHeight
radius: 0
color: row.hasCursor ? root.withAlpha(root.foreground, 0.07) : "transparent"
Item {
id: iconSlot
anchors.left: parent.left
anchors.leftMargin: 14
anchors.verticalCenter: parent.verticalCenter
width: root.iconSlotWidth
height: parent.height
IconImage {
id: appIcon
anchors.centerIn: parent
implicitSize: root.iconSize
width: root.iconSize
height: root.iconSize
source: root.iconSource(row.icon)
asynchronous: true
mipmap: true
}
Text {
anchors.centerIn: parent
visible: appIcon.status === Image.Error
text: "?"
color: row.hasCursor ? root.accent : root.foreground
font.family: root.fontFamily
font.pixelSize: 18
}
}
Text {
anchors.left: iconSlot.right
anchors.leftMargin: 14
anchors.right: parent.right
anchors.rightMargin: 14
anchors.verticalCenter: parent.verticalCenter
text: row.name
color: row.hasCursor ? root.accent : root.foreground
font.family: root.fontFamily
font.pixelSize: 18
elide: Text.ElideRight
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPositionChanged: function(mouse) {
root.hoverArmed = true
root.cursorActive = true
root.selectedIndex = row.index
}
onContainsMouseChanged: if (containsMouse && root.hoverArmed) {
root.cursorActive = true
root.selectedIndex = row.index
}
onClicked: {
root.cursorActive = true
root.selectedIndex = row.index
root.activateIndex(row.index)
}
}
}
}
Text {
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: 14
visible: displayModel.count === 0
text: "No Results"
color: root.foreground
font.family: root.fontFamily
font.pixelSize: 18
}
}
}
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"schemaVersion": 1,
"id": "omarchy.app-launcher",
"name": "App launcher",
"version": "1.0.0",
"author": "Omarchy",
"description": "A Quickshell-powered application launcher",
"kinds": [
"overlay"
],
"keepLoaded": true,
"entryPoints": {
"overlay": "AppLauncher.qml"
}
}
@@ -7,12 +7,15 @@ import qs.Commons
Item { Item {
id: root id: root
property string omarchyPath: Quickshell.env("OMARCHY_PATH")
property bool opened: false property bool opened: false
property string filterText: "" property string filterText: ""
property int selectedIndex: 0 property int selectedIndex: 0
property bool cursorActive: false property bool cursorActive: false
property var items: [] property var history: []
property string historyPath: Quickshell.env("HOME") + "/.local/state/omarchy/clipboard-history.json"
property string captureScript: root.omarchyPath + "/shell/scripts/clipboard-capture.sh"
property color accent: Color.menu.selected property color accent: Color.menu.selected
property color background: Color.menu.background property color background: Color.menu.background
property color foreground: Color.menu.text property color foreground: Color.menu.text
@@ -31,12 +34,7 @@ Item {
root.filterText = "" root.filterText = ""
root.selectedIndex = 0 root.selectedIndex = 0
root.cursorActive = false root.cursorActive = false
root.rebuildDisplay()
// Trigger fetch
fetchProc.collected = ""
fetchProc.command = ["bash", "-lc", "elephant query --json 'clipboard;;100'"]
fetchProc.running = true
Qt.callLater(function() { keyCatcher.forceActiveFocus() }) Qt.callLater(function() { keyCatcher.forceActiveFocus() })
} }
@@ -53,36 +51,112 @@ Item {
return Qt.rgba(color.r, color.g, color.b, alpha) return Qt.rgba(color.r, color.g, color.b, alpha)
} }
function shellQuote(value) {
return "'" + String(value || "").replace(/'/g, "'\\''") + "'"
}
function fileUrl(path) {
return "file://" + String(path).split("/").map(encodeURIComponent).join("/")
}
function normalizeEntry(value) {
if (typeof value === "string") {
return value.length > 0 ? { type: "text", text: value } : null
}
if (!value || typeof value !== "object") return null
var type = String(value.type || value.kind || "")
if (type === "text") {
var text = String(value.text || "")
return text.length > 0 ? { type: "text", text: text } : null
}
if (type === "image") {
var path = String(value.path || "")
if (!path) return null
return {
type: "image",
path: path,
mime: String(value.mime || "image/png")
}
}
return null
}
function entryKey(entry) {
if (!entry) return ""
if (entry.type === "image") return "image:" + String(entry.path || "")
return "text:" + String(entry.text || "")
}
function loadHistory(raw) {
try {
var parsed = JSON.parse(String(raw || "[]"))
var next = []
if (Array.isArray(parsed)) {
for (var i = 0; i < parsed.length; i++) {
var entry = root.normalizeEntry(parsed[i])
if (entry) next.push(entry)
}
}
root.history = next
} catch (e) {
root.history = []
}
if (root.opened) root.rebuildDisplay()
}
function saveHistory() {
historyFile.setText(JSON.stringify(root.history.slice(0, 100), null, 2) + "\n")
}
function addClipboardEntry(entry) {
var normalized = root.normalizeEntry(entry)
if (!normalized) return
var key = root.entryKey(normalized)
var next = [normalized]
for (var i = 0; i < root.history.length && next.length < 100; i++) {
var existing = root.normalizeEntry(root.history[i])
if (!existing || root.entryKey(existing) === key) continue
next.push(existing)
}
root.history = next
root.saveHistory()
if (root.opened) root.rebuildDisplay()
}
function addClipboardJson(line) {
var raw = String(line || "").trim()
if (!raw) return
try { root.addClipboardEntry(JSON.parse(raw)) } catch (e) {}
}
function rebuildDisplay() { function rebuildDisplay() {
var query = root.filterText.trim().toLowerCase() var query = root.filterText.trim().toLowerCase()
displayModel.clear() displayModel.clear()
var outCount = 0 var outCount = 0
for (var i = 0; i < root.items.length; i++) { for (var i = 0; i < root.history.length; i++) {
var entry = root.items[i] var entry = root.normalizeEntry(root.history[i])
var isPassword = (entry.meta === "password" || entry.preview_type === "password") if (!entry) continue
var textMatch = false var isImage = entry.type === "image"
if (isPassword) { var searchable = isImage ? ("image " + String(entry.mime || "")) : String(entry.text || "")
textMatch = false // Passwords shouldn't match plain text search queries if (query && searchable.toLowerCase().indexOf(query) < 0) continue
} else {
textMatch = (entry.preview && entry.preview.toLowerCase().indexOf(query) >= 0)
}
if (!query || textMatch) {
displayModel.append({ displayModel.append({
identifier: entry.identifier, entryType: entry.type,
previewText: entry.preview_type === "text" ? entry.preview.replace(/\n/g, " ") : "", fullText: isImage ? "" : String(entry.text || ""),
previewImage: entry.preview_type === "file" ? ("file://" + entry.preview) : "", previewText: isImage ? "Image" : String(entry.text || "").replace(/\s+/g, " "),
previewType: entry.preview_type || "text", previewImage: isImage ? root.fileUrl(entry.path) : "",
isPassword: isPassword, path: isImage ? String(entry.path || "") : "",
mime: isImage ? String(entry.mime || "image/png") : "text/plain",
index: outCount index: outCount
}) })
outCount++ outCount++
if (outCount >= 50) break if (outCount >= 50) break
} }
}
if (displayModel.count === 0) selectedIndex = 0 if (displayModel.count === 0) selectedIndex = 0
else if (selectedIndex >= displayModel.count) selectedIndex = displayModel.count - 1 else if (selectedIndex >= displayModel.count) selectedIndex = displayModel.count - 1
@@ -114,40 +188,60 @@ Item {
function activateIndex(index) { function activateIndex(index) {
if (index < 0 || index >= displayModel.count) return if (index < 0 || index >= displayModel.count) return
var row = displayModel.get(index) var row = displayModel.get(index)
root.applySelected(row.identifier) root.applySelected(row)
} }
function applySelected(identifier) { function applySelected(row) {
if (!identifier) return if (!row) return
root.opened = false root.opened = false
var escId = identifier.replace(/'/g, "'\\''") if (row.entryType === "image") {
Quickshell.execDetached(["bash", "-lc", "elephant activate 'clipboard;" + escId + ";copy;;'; sleep 0.15; wtype -M shift -k Insert -m shift 2>/dev/null || true"]) Quickshell.execDetached(["bash", "-lc", "wl-copy --type " + root.shellQuote(row.mime) + " < " + root.shellQuote(row.path) + "; sleep 0.15; wtype -M shift -k Insert -m shift 2>/dev/null || true"])
} else if (row.fullText) {
Quickshell.execDetached(["bash", "-lc", "printf %s " + root.shellQuote(row.fullText) + " | wl-copy; sleep 0.15; wtype -M shift -k Insert -m shift 2>/dev/null || true"])
} }
}
Component.onCompleted: initProc.running = true
ListModel { id: displayModel } ListModel { id: displayModel }
FileView {
id: historyFile
path: root.historyPath
watchChanges: true
atomicWrites: true
printErrors: false
onLoaded: root.loadHistory(text())
onLoadFailed: root.loadHistory("[]")
onFileChanged: reload()
}
Process { Process {
id: fetchProc id: initProc
property string collected: "" command: ["bash", "-lc", "mkdir -p ~/.local/state/omarchy"]
stdout: SplitParser {
onRead: function(data) { fetchProc.collected += data + "\n" }
}
onExited: { onExited: {
var lines = fetchProc.collected.split("\n") currentProc.command = [root.captureScript]
var newItems = [] currentProc.running = true
for (var i = 0; i < lines.length; i++) { watchProc.command = ["wl-paste", "--watch", root.captureScript]
var line = lines[i].trim() watchProc.running = true
if (!line) continue
try {
var parsed = JSON.parse(line)
if (parsed && parsed.item) {
newItems.push(parsed.item)
}
} catch(e) {}
}
root.items = newItems
root.rebuildDisplay()
} }
} }
Process {
id: currentProc
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: root.addClipboardJson(text)
}
}
Process {
id: watchProc
stdout: SplitParser {
onRead: function(data) { root.addClipboardJson(data) }
}
}
PanelWindow { PanelWindow {
id: panel id: panel
visible: root.opened visible: root.opened
@@ -261,10 +355,10 @@ Item {
delegate: Rectangle { delegate: Rectangle {
required property int index required property int index
required property string identifier required property string entryType
required property string previewText required property string previewText
required property string previewType required property string fullText
required property bool isPassword required property string previewImage
readonly property bool hasCursor: root.cursorActive && index === root.selectedIndex readonly property bool hasCursor: root.cursorActive && index === root.selectedIndex
@@ -275,33 +369,33 @@ Item {
border.color: hasCursor ? Style.hoverBorderFor(root.foreground, root.accent) : "transparent" border.color: hasCursor ? Style.hoverBorderFor(root.foreground, root.accent) : "transparent"
border.width: hasCursor ? Style.hoverBorderWidth : 0 border.width: hasCursor ? Style.hoverBorderWidth : 0
Rectangle { Row {
visible: false
width: Style.space(4)
height: parent.height - Style.space(18)
radius: Math.min(root.cornerRadius, Style.space(4))
color: root.accent
anchors.left: parent.left
anchors.leftMargin: Style.space(8)
anchors.verticalCenter: parent.verticalCenter
}
Item {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Style.space(12) anchors.leftMargin: Style.space(12)
anchors.rightMargin: Style.space(12) anchors.rightMargin: Style.space(12)
anchors.topMargin: Style.space(8) anchors.topMargin: Style.space(8)
anchors.bottomMargin: Style.space(8) anchors.bottomMargin: Style.space(8)
spacing: Style.space(10)
Image {
visible: parent.parent.entryType === "image"
width: visible ? parent.height : 0
height: parent.height
source: parent.parent.previewImage
fillMode: Image.PreserveAspectFit
asynchronous: true
smooth: true
}
Text { Text {
width: parent.width width: parent.width - (parent.parent.entryType === "image" ? parent.height + parent.spacing : 0)
height: parent.height height: parent.height
text: parent.parent.isPassword ? "••••••••" : (parent.parent.previewType === "text" ? parent.parent.previewText : "Image") text: parent.parent.previewText
color: parent.parent.hasCursor ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground color: parent.parent.hasCursor ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground
font.family: root.fontFamily font.family: root.fontFamily
font.pixelSize: Style.font.title font.pixelSize: Style.font.title
font.italic: parent.parent.previewType === "file" || parent.parent.isPassword font.italic: parent.parent.entryType === "image"
opacity: (parent.parent.previewType === "file" || parent.parent.isPassword) ? 0.6 : 1.0 opacity: parent.parent.entryType === "image" ? 0.72 : 1.0
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -309,7 +403,6 @@ Item {
} }
MouseArea { MouseArea {
id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -335,13 +428,13 @@ Item {
border.width: Style.normalBorderWidth border.width: Style.normalBorderWidth
clip: true clip: true
property var activeRow: root.cursorActive && displayModel.count > 0 && root.selectedIndex >= 0 && root.selectedIndex < displayModel.count ? displayModel.get(root.selectedIndex) : null property var activeRow: displayModel.count > 0 && root.selectedIndex >= 0 && root.selectedIndex < displayModel.count ? displayModel.get(root.selectedIndex) : null
Text { Text {
visible: parent.activeRow && parent.activeRow.previewType === "text" visible: parent.activeRow && parent.activeRow.entryType === "text"
anchors.fill: parent anchors.fill: parent
anchors.margins: Style.space(16) anchors.margins: Style.space(16)
text: parent.activeRow ? (parent.activeRow.isPassword ? "••••••••" : parent.activeRow.previewText) : "" text: parent.activeRow ? parent.activeRow.fullText : ""
color: root.foreground color: root.foreground
font.family: root.fontFamily font.family: root.fontFamily
font.pixelSize: Style.font.title font.pixelSize: Style.font.title
@@ -351,11 +444,13 @@ Item {
} }
Image { Image {
visible: parent.activeRow && parent.activeRow.previewType === "file" visible: parent.activeRow && parent.activeRow.entryType === "image"
anchors.fill: parent anchors.fill: parent
anchors.margins: Style.space(16) anchors.margins: Style.space(16)
source: parent.activeRow ? parent.activeRow.previewImage : "" source: parent.activeRow ? parent.activeRow.previewImage : ""
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
asynchronous: true
smooth: true
} }
} }
} }
@@ -376,7 +471,7 @@ Item {
} }
Text { Text {
text: root.items.length === 0 ? "Clipboard is empty" : "No matches for “" + root.filterText + "”" text: root.history.length === 0 ? "Clipboard is empty" : "No matches for “" + root.filterText + "”"
color: root.foreground color: root.foreground
opacity: 0.7 opacity: 0.7
font.family: root.fontFamily font.family: root.fontFamily
+160 -8
View File
@@ -18,11 +18,15 @@ Item {
var payload = ({}) var payload = ({})
try { payload = JSON.parse(payloadJson || "{}") } catch (e) { payload = ({}) } try { payload = JSON.parse(payloadJson || "{}") } catch (e) { payload = ({}) }
root.pendingInitialMenu = payload.initialMenu || payload.menu || "root"
if (payload.fontFamily) root.fontFamily = payload.fontFamily if (payload.fontFamily) root.fontFamily = payload.fontFamily
if (payload.mode === "select" || payload.mode === "input") {
root.openDmenu(payload)
} else {
root.pendingInitialMenu = payload.initialMenu || payload.menu || "root"
root.openExistingMenu(root.pendingInitialMenu) root.openExistingMenu(root.pendingInitialMenu)
} }
}
function close() { function close() {
root.cancel() root.cancel()
@@ -37,6 +41,15 @@ Item {
property var defaultMenuItems: [] property var defaultMenuItems: []
property var userMenuItems: [] property var userMenuItems: []
property bool opened: false property bool opened: false
property string mode: "menu"
readonly property bool dmenuActive: mode === "select" || mode === "input"
property string dmenuPrompt: ""
property var dmenuOptions: []
property string selectionFile: ""
property string doneFile: ""
property int dmenuWidth: 300
property int dmenuMaxHeight: 0
property bool requestActive: false
property bool rowsLoaded: false property bool rowsLoaded: false
property string activeMenu: "root" property string activeMenu: "root"
property string filterText: "" property string filterText: ""
@@ -64,14 +77,40 @@ Item {
property int dividerHeight: Style.space(17) property int dividerHeight: Style.space(17)
property bool searchDivider: false property bool searchDivider: false
property int layoutSerial: 0 property int layoutSerial: 0
property int cardWidth: Math.min((root.activeMenu === "trigger.capture.screenrecord" || root.activeMenu === "style.font") ? Style.space(520) : Style.space(300), panel.width - Style.gapsOut * 2) property int cardWidth: Math.min(root.dmenuActive ? Style.space(root.dmenuWidth) : ((root.activeMenu === "trigger.capture.screenrecord" || root.activeMenu === "style.font") ? Style.space(520) : Style.space(300)), panel.width - Style.gapsOut * 2)
property int visibleRowsHeight: rowListHeight(layoutSerial, displayModel.count, filterText, searchDivider) property int visibleRowsHeight: root.dmenuActive ? dmenuRowListHeight(layoutSerial, displayModel.count, filterText) : rowListHeight(layoutSerial, displayModel.count, filterText, searchDivider)
property int cardHeight: Math.min(Math.max(Style.space(220), contentMargin * 2 + headerHeight + contentSpacing + visibleRowsHeight), panel.height - Style.gapsOut * 2) property int cardHeight: root.dmenuActive
? Math.min(contentMargin * 2 + headerHeight + (mode === "input" ? 0 : contentSpacing + visibleRowsHeight), panel.height - Style.gapsOut * 2)
: Math.min(Math.max(Style.space(220), contentMargin * 2 + headerHeight + contentSpacing + visibleRowsHeight), panel.height - Style.gapsOut * 2)
function withAlpha(color, alpha) { function withAlpha(color, alpha) {
return Qt.rgba(color.r, color.g, color.b, alpha) return Qt.rgba(color.r, color.g, color.b, alpha)
} }
function shellQuote(value) {
return "'" + String(value || "").replace(/'/g, "'\\''") + "'"
}
function finishRequest(selection) {
if (!root.requestActive || !root.doneFile) {
root.opened = false
return
}
var activeSelectionFile = root.selectionFile
var activeDoneFile = root.doneFile
root.requestActive = false
root.selectionFile = ""
root.doneFile = ""
if (selection === null || selection === undefined) {
resultProc.command = ["bash", "-lc", ": > " + root.shellQuote(activeDoneFile)]
} else {
resultProc.command = ["bash", "-lc", "printf '%s\\n' " + root.shellQuote(selection) + " > " + root.shellQuote(activeSelectionFile) + "; : > " + root.shellQuote(activeDoneFile)]
}
resultProc.running = true
}
function rowHeightForDetail(detail) { function rowHeightForDetail(detail) {
return root.filterText && detail ? root.detailRowHeight : root.baseRowHeight return root.filterText && detail ? root.detailRowHeight : root.baseRowHeight
} }
@@ -94,6 +133,20 @@ Item {
return total return total
} }
function dmenuRowListHeight(_serial, _count, _filter) {
if (root.mode === "input") return 0
if (displayModel.count === 0) return root.baseRowHeight
var count = Math.min(displayModel.count, 10)
var total = 0
for (var i = 0; i < count; i++) {
if (i > 0) total += root.rowSpacing
total += root.baseRowHeight
}
return root.dmenuMaxHeight > 0 ? Math.min(total, Style.space(root.dmenuMaxHeight)) : total
}
function item(id) { function item(id) {
return root.items[id] || null return root.items[id] || null
} }
@@ -458,7 +511,52 @@ Item {
} }
} }
function rebuildDmenuDisplay() {
displayModel.clear()
root.searchDivider = false
if (root.mode === "input") {
layoutSerial += 1
return
}
var query = root.filterText.trim().toLowerCase()
for (var i = 0; i < root.dmenuOptions.length; i++) {
var label = String(root.dmenuOptions[i] || "")
if (query && label.toLowerCase().indexOf(query) < 0) continue
displayModel.append({
itemId: "dmenu." + i,
kind: "dmenu",
icon: "",
label: label,
target: "",
detail: "",
path: "",
childCount: 0,
action: "",
provider: "",
score: i,
section: ""
})
}
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 rebuildDisplay() { function rebuildDisplay() {
if (root.dmenuActive) {
root.rebuildDmenuDisplay()
return
}
displayModel.clear() displayModel.clear()
if (!root.rowsLoaded) return if (!root.rowsLoaded) return
@@ -540,7 +638,7 @@ Item {
root.filterText = nextFilter root.filterText = nextFilter
root.selectedIndex = 0 root.selectedIndex = 0
root.cursorActive = false root.cursorActive = false
if (root.filterText.trim()) root.loadProvidersForSearch() if (!root.dmenuActive && root.filterText.trim()) root.loadProvidersForSearch()
root.rebuildDisplay() root.rebuildDisplay()
} }
@@ -571,6 +669,16 @@ Item {
} }
function activateIndex(index) { function activateIndex(index) {
if (root.dmenuActive) {
if (root.mode === "input") {
root.applyDmenuSelection(root.filterText)
return
}
if (index < 0 || index >= displayModel.count) return
root.applyDmenuSelection(displayModel.get(index).label)
return
}
if (index < 0 || index >= displayModel.count) return if (index < 0 || index >= displayModel.count) return
var row = displayModel.get(index) var row = displayModel.get(index)
@@ -583,6 +691,13 @@ Item {
} }
} }
function applyDmenuSelection(value) {
applySerial = requestSerial
opened = false
filterText = ""
root.finishRequest(value)
}
function applySelected(id, action) { function applySelected(id, action) {
if (!id) { cancel(); return } if (!id) { cancel(); return }
applySerial = requestSerial applySerial = requestSerial
@@ -592,12 +707,17 @@ Item {
} }
function cancel() { function cancel() {
if (root.dmenuActive) root.finishRequest(null)
opened = false opened = false
filterText = "" filterText = ""
} }
function openExistingMenu(initialMenu) { function openExistingMenu(initialMenu) {
requestSerial += 1 requestSerial += 1
mode = "menu"
requestActive = false
selectionFile = ""
doneFile = ""
activeMenu = root.item(initialMenu) ? initialMenu : "root" activeMenu = root.item(initialMenu) ? initialMenu : "root"
navStack = [] navStack = []
filterText = "" filterText = ""
@@ -610,6 +730,27 @@ Item {
Qt.callLater(function() { keyCatcher.forceActiveFocus() }) Qt.callLater(function() { keyCatcher.forceActiveFocus() })
} }
function openDmenu(payload) {
requestSerial += 1
mode = payload.mode === "input" ? "input" : "select"
dmenuPrompt = String(payload.prompt || (mode === "input" ? "Input" : "Select"))
dmenuOptions = Array.isArray(payload.options) ? payload.options : []
selectionFile = String(payload.selectionFile || "")
doneFile = String(payload.doneFile || "")
requestActive = !!doneFile
dmenuWidth = Math.max(1, Number(payload.width || 300))
dmenuMaxHeight = Math.max(0, Number(payload.maxHeight || 0))
activeMenu = "root"
navStack = []
filterText = ""
selectedIndex = 0
cursorActive = false
opened = true
rebuildDisplay()
Qt.callLater(function() { keyCatcher.forceActiveFocus() })
}
ListModel { id: displayModel } ListModel { id: displayModel }
// ----------------------------------------------------------- IPC surface // ----------------------------------------------------------- IPC surface
@@ -697,6 +838,14 @@ Item {
} }
} }
Process {
id: resultProc
onExited: {
if (root.applySerial === root.requestSerial)
root.opened = false
}
}
// The JSONC sources are watched so live edits to the default file (or the // The JSONC sources are watched so live edits to the default file (or the
// user extension at ~/.config/omarchy/extensions/omarchy-menu.jsonc) take // user extension at ~/.config/omarchy/extensions/omarchy-menu.jsonc) take
// effect without restarting the shell. // effect without restarting the shell.
@@ -840,7 +989,10 @@ Item {
if (!root.filterText) root.goBack() if (!root.filterText) root.goBack()
event.accepted = true event.accepted = true
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Right) { } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Right) {
if (root.cursorActive) root.activateIndex(root.selectedIndex) if (root.dmenuActive) {
if (root.mode === "input") root.applyDmenuSelection(root.filterText)
else if (displayModel.count > 0) root.activateIndex(root.cursorActive ? root.selectedIndex : 0)
} else if (root.cursorActive) root.activateIndex(root.selectedIndex)
else if (displayModel.count > 0) root.cursorActive = true else if (displayModel.count > 0) root.cursorActive = true
event.accepted = true 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)) { } 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)) {
@@ -866,7 +1018,7 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: root.filterText || ((root.item(root.activeMenu) ? root.item(root.activeMenu).label : "Go") + "…") text: root.filterText || (root.dmenuActive ? (root.dmenuPrompt + "…") : ((root.item(root.activeMenu) ? root.item(root.activeMenu).label : "Go") + "…"))
color: root.foreground color: root.foreground
opacity: root.filterText ? 1 : 0.58 opacity: root.filterText ? 1 : 0.58
font.family: root.fontFamily font.family: root.fontFamily
@@ -1034,7 +1186,7 @@ Item {
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.space(8) spacing: Style.space(8)
visible: displayModel.count === 0 visible: displayModel.count === 0 && root.mode !== "input"
Text { Text {
text: "󰈉" text: "󰈉"
+49
View File
@@ -0,0 +1,49 @@
#!/bin/bash
set -o pipefail
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy"
IMAGE_DIR="$STATE_DIR/clipboard-images"
mkdir -p "$IMAGE_DIR"
types=$(wl-paste --list-types 2>/dev/null || true)
emit_image() {
local mime="$1"
local ext="$2"
local tmp=""
local hash=""
local file=""
tmp=$(mktemp --tmpdir="$IMAGE_DIR" clipboard.XXXXXX) || return 0
if ! wl-paste --type "$mime" > "$tmp" 2>/dev/null || [[ ! -s $tmp ]]; then
rm -f "$tmp"
return 0
fi
hash=$(sha256sum "$tmp" | awk '{print $1}')
file="$IMAGE_DIR/$hash.$ext"
if [[ -e $file ]]; then
rm -f "$tmp"
else
mv "$tmp" "$file"
fi
jq -cn --arg mime "$mime" --arg path "$file" '{type:"image", mime:$mime, path:$path}'
}
if grep -qx 'image/png' <<<"$types"; then
emit_image 'image/png' 'png'
elif grep -qx 'image/jpeg' <<<"$types"; then
emit_image 'image/jpeg' 'jpg'
elif grep -qx 'image/webp' <<<"$types"; then
emit_image 'image/webp' 'webp'
elif grep -qx 'image/gif' <<<"$types"; then
emit_image 'image/gif' 'gif'
elif grep -qx 'image/bmp' <<<"$types"; then
emit_image 'image/bmp' 'bmp'
elif grep -qx 'image/tiff' <<<"$types"; then
emit_image 'image/tiff' 'tiff'
elif grep -q '^text/' <<<"$types" || grep -qx 'UTF8_STRING' <<<"$types" || grep -qx 'STRING' <<<"$types"; then
wl-paste --type text --no-newline 2>/dev/null | jq -Rs 'select(length > 0) | {type:"text", text:.}'
fi