mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-01 20:28:16 +02:00
Drop walker + elephant
This commit is contained in:
@@ -61,7 +61,7 @@ Do not define:
|
||||
false flags or empty args
|
||||
|
||||
Examples:
|
||||
# omarchy:summary=Restart Walker and related user services
|
||||
# omarchy:summary=Restart the shell
|
||||
|
||||
# omarchy:summary=Take a screenshot
|
||||
# omarchy:group=capture
|
||||
|
||||
@@ -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/gdk-scale.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"
|
||||
sudo rm -f /etc/sudoers.d/first-run
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@ install_admin_webapp() {
|
||||
mkdir -p "$WEBAPP_ICON_DIR"
|
||||
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-restart-walker
|
||||
}
|
||||
|
||||
enable_hyprland_autostart() {
|
||||
|
||||
@@ -14,9 +14,6 @@ if omarchy-toggle-enabled screensaver-off && [[ $1 != "force" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Silently quit Walker on overlay
|
||||
walker -q
|
||||
|
||||
focused=$(omarchy-hyprland-monitor-focused)
|
||||
terminal=$(xdg-terminal-exec --print-id)
|
||||
|
||||
|
||||
@@ -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 "$@"
|
||||
@@ -1,15 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Pick a file with Walker
|
||||
# omarchy:summary=Pick a file from a menu
|
||||
# omarchy:group=menu
|
||||
# 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
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -22,7 +22,7 @@ IFS=: read -r -a paths <<<"$paths_arg"
|
||||
read -r -a formats <<<"$formats_arg"
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -45,4 +45,4 @@ done
|
||||
find_args+=(")" -printf '%T@\t%p\n')
|
||||
|
||||
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
@@ -1,9 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Prompt for text input with Walker
|
||||
# omarchy:summary=Prompt for text input from a menu
|
||||
# omarchy:group=menu
|
||||
# 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
|
||||
|
||||
set -euo pipefail
|
||||
@@ -13,4 +13,46 @@ if (( $# > 0 )); then
|
||||
shift
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/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
|
||||
# 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')
|
||||
menu_height=$((monitor_height * 40 / 100))
|
||||
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
|
||||
record=$(awk -F '\t' -v selection="$selection" '$1 == selection { print; exit }' <<<"$records")
|
||||
|
||||
+63
-8
@@ -1,15 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Pick one option with Walker
|
||||
# omarchy:summary=Pick one option from a menu
|
||||
# omarchy:group=menu
|
||||
# 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
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if (( $# < 2 )); then
|
||||
echo "Usage: omarchy-menu-select <prompt> <option>... [-- walker args...]" >&2
|
||||
if (( $# < 1 )); then
|
||||
echo "Usage: omarchy-menu-select <prompt> [option...] [-- menu args...]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -17,12 +17,34 @@ prompt="$1"
|
||||
shift
|
||||
|
||||
options=()
|
||||
walker_args=()
|
||||
menu_width=""
|
||||
menu_maxheight=""
|
||||
|
||||
while (( $# > 0 )); do
|
||||
if [[ $1 == "--" ]]; then
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -30,9 +52,42 @@ while (( $# > 0 )); do
|
||||
shift
|
||||
done
|
||||
|
||||
if (( ${#options[@]} == 0 )) && [[ ! -t 0 ]]; then
|
||||
mapfile -t options
|
||||
fi
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/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>]
|
||||
|
||||
print_only=false
|
||||
@@ -233,11 +233,6 @@ fi
|
||||
|
||||
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)
|
||||
|
||||
if [[ ! $monitor_height =~ ^[0-9]+$ ]] || ((monitor_height <= 0)); then
|
||||
@@ -245,4 +240,4 @@ if [[ ! $monitor_height =~ ^[0-9]+$ ]] || ((monitor_height <= 0)); then
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -74,9 +74,9 @@ perl -MCwd=abs_path -MEncode=encode,decode -MSocket -MIO::Handle \
|
||||
|
||||
my $shell_qml_arg = shift @ARGV;
|
||||
my $shell_qml = abs_path($shell_qml_arg);
|
||||
my $target = shift @ARGV;
|
||||
my $function = shift @ARGV;
|
||||
my @args = @ARGV;
|
||||
my $target = decode("UTF-8", shift @ARGV);
|
||||
my $function = decode("UTF-8", shift @ARGV);
|
||||
my @args = map { decode("UTF-8", $_) } @ARGV;
|
||||
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 @candidates;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/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:examples=omarchy style corners round | omarchy style corners sharp
|
||||
|
||||
@@ -10,11 +10,9 @@ if [[ $1 != "sharp" && $1 != "round" ]]; then
|
||||
fi
|
||||
|
||||
# The shell tracks Hyprland's `decoration:rounding` directly via
|
||||
# Quickshell's qs.Commons.Style singleton; no separate quickshell-corners
|
||||
# state file is written. The old omarchy-style-corners-quickshell shim is
|
||||
# kept as a no-op so external callers don't error.
|
||||
# Quickshell's qs.Commons.Style singleton; no separate shell state file is
|
||||
# written.
|
||||
omarchy-style-corners-hyprland "$1"
|
||||
omarchy-style-corners-walker "$1"
|
||||
|
||||
case $1 in
|
||||
sharp) omarchy-notification-send "Sharp corners enabled" -g ;;
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
# omarchy:args=[toggle|sharp|round]
|
||||
# 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
|
||||
# theme name + rounded-corners toggle files for changes. There is no
|
||||
# separate quickshell-menu.json to write anymore. This shim stays around
|
||||
# so anyone still calling the old subcommand (or the dispatcher in
|
||||
# omarchy-style-corners) doesn't error out.
|
||||
# qs.Commons.Style reads decoration:rounding from hyprctl. Theme changes
|
||||
# refresh it through shell IPC, while user corner toggles still trigger their
|
||||
# own refresh. There is no separate quickshell-menu.json to write anymore.
|
||||
# This shim stays around so anyone still calling the old subcommand (or the
|
||||
# dispatcher in omarchy-style-corners) doesn't error out.
|
||||
|
||||
case "${1:-toggle}" in
|
||||
round|sharp|toggle) exit 0 ;;
|
||||
|
||||
@@ -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
|
||||
@@ -122,10 +122,6 @@ fi
|
||||
exec 9>"$THEME_SET_LOCK"
|
||||
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)
|
||||
rm -rf "$NEXT_THEME_PATH"
|
||||
mkdir -p "$NEXT_THEME_PATH"
|
||||
@@ -154,8 +150,8 @@ mv "$NEXT_THEME_PATH" "$CURRENT_THEME_PATH"
|
||||
# Store theme name for reference
|
||||
echo "$THEME_NAME" >"$HOME/.config/omarchy/current/theme.name"
|
||||
|
||||
# Make the running shell pick up the new palette immediately instead of waiting
|
||||
# for file-watch debounce while the rest of the theme hooks run.
|
||||
# Make the running shell pick up the new palette immediately while the rest of
|
||||
# the theme hooks run.
|
||||
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")
|
||||
if [[ $OMARCHY_THEME_SKIP_BACKGROUND == "1" ]]; then
|
||||
|
||||
@@ -13,7 +13,7 @@ usage() {
|
||||
Usage:
|
||||
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.
|
||||
|
||||
Options:
|
||||
|
||||
@@ -45,4 +45,6 @@ for APP_NAME in "${APP_NAMES[@]}"; do
|
||||
echo "Removed $APP_NAME"
|
||||
done
|
||||
|
||||
omarchy-restart-walker
|
||||
if command -v update-desktop-database &>/dev/null; then
|
||||
update-desktop-database "$DESKTOP_DIR" &>/dev/null || true
|
||||
fi
|
||||
|
||||
@@ -32,6 +32,4 @@ if command -v update-desktop-database &>/dev/null; then
|
||||
update-desktop-database "$APP_DIR" &>/dev/null || true
|
||||
fi
|
||||
|
||||
omarchy-restart-walker
|
||||
|
||||
echo "Web apps removed successfully."
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
async = false
|
||||
@@ -1,3 +0,0 @@
|
||||
show_actions = false
|
||||
only_search_title = true
|
||||
history = false
|
||||
@@ -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")'
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -6,7 +6,8 @@
|
||||
hl.layer_rule({ match = { namespace = "omarchy-bar" }, 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-emoji-picker" }, no_anim = true, animation = "none" })
|
||||
hl.layer_rule({ match = { namespace = "omarchy-clipboard-picker" }, no_anim = true, animation = "none" })
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
-- Application-specific animation.
|
||||
hl.layer_rule({ match = { namespace = "walker" }, no_anim = true, animation = "none" })
|
||||
@@ -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 + 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" })
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
name: omarchy
|
||||
description: >
|
||||
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/.
|
||||
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
|
||||
settings, display config, and user-facing omarchy commands. Excludes Omarchy
|
||||
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:**
|
||||
|
||||
- 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 ANY file in `~/.config/omarchy/`
|
||||
- Window behavior, animations, opacity, blur, gaps, borders
|
||||
@@ -78,7 +78,7 @@ Omarchy is built on:
|
||||
| **Arch Linux** | Base OS | `/etc/`, `~/.config/` |
|
||||
| **Hyprland** | Wayland compositor/WM | `~/.config/hypr/` |
|
||||
| **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>/` |
|
||||
| **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` |
|
||||
| starship | `~/.config/starship.toml` |
|
||||
| git | `~/.config/git/config` |
|
||||
| walker | `~/.config/walker/config.toml` |
|
||||
|
||||
## Safe Customization Patterns
|
||||
|
||||
@@ -202,7 +201,7 @@ cp ~/.config/hypr/bindings.conf ~/.config/hypr/bindings.conf.bak.$(date +%s)
|
||||
# 4. Apply changes
|
||||
# - 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
|
||||
# - Walker: MUST restart with `omarchy restart walker`
|
||||
# - App launcher: restart with `omarchy restart shell`
|
||||
# - Terminals: MUST restart with `omarchy restart terminal`
|
||||
```
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// checked shell condition; append ✓ when it succeeds
|
||||
|
||||
// Root Menu
|
||||
"apps": {"icon":"","label":"Apps","aliases":["app","applications"],"keywords":"launch launcher","action":"omarchy-launch-walker -p 'Launch…'"},
|
||||
"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"},
|
||||
"trigger": {"icon":"","label":"Trigger","keywords":"actions capture share toggle hardware reminder"},
|
||||
"style": {"icon":"","label":"Style","keywords":"theme background font bar quickshell hyprland appearance"},
|
||||
@@ -87,7 +87,6 @@
|
||||
|
||||
// Style
|
||||
"style.theme": {"icon":"","label":"Theme","aliases":["theme","themes"],"keywords":"colors","action":"theme=$(omarchy-theme-switcher); [[ -n $theme ]] && omarchy-theme-set \"$theme\""},
|
||||
"style.unlock": {"icon":"","label":"Unlock","keywords":"themes","action":"omarchy-launch-walker -m menus:omarchyunlocks --width 800 --minheight 400"},
|
||||
"style.font": {"icon":"","label":"Font","keywords":"typeface","provider":"fonts"},
|
||||
"style.background": {"icon":"","label":"Background","aliases":["background","wallpaper"],"action":"background=$(omarchy-theme-bg-switcher); [[ -n $background ]] && omarchy-theme-bg-set \"$background\""},
|
||||
"style.bar": {"icon":"","label":"Bar","keywords":"quickshell top bottom left right settings"},
|
||||
@@ -158,12 +157,11 @@
|
||||
"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.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.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.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.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.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.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.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.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.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.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"},
|
||||
|
||||
@@ -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 }};
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=Walker
|
||||
Comment=Walker Service
|
||||
Exec=walker --gapplication-service
|
||||
StartupNotify=false
|
||||
Terminal=false
|
||||
Type=Application
|
||||
@@ -20,7 +20,6 @@ run_logged $OMARCHY_INSTALL/config/user-dirs.sh
|
||||
run_logged $OMARCHY_INSTALL/config/toggles.sh
|
||||
run_logged $OMARCHY_INSTALL/config/nautilus-python.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/unmount-fuse.sh
|
||||
run_logged $OMARCHY_INSTALL/config/sudoless-asdcontrol.sh
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
# Make sure toggles are available
|
||||
mkdir -p ~/.local/state/omarchy/toggles
|
||||
touch ~/.local/state/omarchy/toggles/walker.css
|
||||
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
elephant service enable
|
||||
systemctl --user start elephant.service
|
||||
@@ -39,6 +39,7 @@ ffmpegthumbnailer
|
||||
fontconfig
|
||||
foot
|
||||
fzf
|
||||
git
|
||||
github-cli
|
||||
gnome-calculator
|
||||
gnome-keyring
|
||||
@@ -88,7 +89,6 @@ nvim
|
||||
obs-studio
|
||||
obsidian
|
||||
omarchy-nvim
|
||||
omarchy-walker
|
||||
pamixer
|
||||
pinta
|
||||
playerctl
|
||||
|
||||
@@ -9,7 +9,6 @@ broadcom-wl
|
||||
btrfs-progs
|
||||
dkms
|
||||
egl-wayland
|
||||
git
|
||||
gst-plugin-pipewire
|
||||
gtk4-layer-shell
|
||||
libpulse
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
echo "Install omarchy-walker meta package"
|
||||
omarchy-pkg-add omarchy-walker
|
||||
@@ -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 &
|
||||
@@ -1,3 +0,0 @@
|
||||
echo "Ensure emoji menu (Super + Ctrl + E) uses single column design"
|
||||
|
||||
omarchy-refresh-walker
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
echo "Enable auto-pasting for the emoji picker"
|
||||
|
||||
omarchy-refresh-config elephant/symbols.toml
|
||||
omarchy-restart-walker
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -29,6 +29,7 @@ shell/
|
||||
plugins/
|
||||
bar/ first-party plugins (see plugins/README.md)
|
||||
settings/
|
||||
app-launcher/
|
||||
image-picker/
|
||||
menu/
|
||||
notifications/
|
||||
|
||||
+10
-1
@@ -13,6 +13,7 @@ User-installed plugins live alongside these conceptually but on disk under
|
||||
|---------------|---------------------------|-----------|---------------------------------------|
|
||||
| Bar | `omarchy.bar` | `bar` | `bar/Bar.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` |
|
||||
| Emoji picker | `omarchy.emoji-picker` | `overlay` | `emoji-picker/EmojiPicker.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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
summoned through the shell (`omarchy-shell shell summon omarchy.menu ...`),
|
||||
so it shares the long-running `omarchy-shell` process instead of starting a
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
id: root
|
||||
|
||||
property string omarchyPath: Quickshell.env("OMARCHY_PATH")
|
||||
property bool opened: false
|
||||
property string filterText: ""
|
||||
property int selectedIndex: 0
|
||||
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 background: Color.menu.background
|
||||
property color foreground: Color.menu.text
|
||||
@@ -31,12 +34,7 @@ Item {
|
||||
root.filterText = ""
|
||||
root.selectedIndex = 0
|
||||
root.cursorActive = false
|
||||
|
||||
// Trigger fetch
|
||||
fetchProc.collected = ""
|
||||
fetchProc.command = ["bash", "-lc", "elephant query --json 'clipboard;;100'"]
|
||||
fetchProc.running = true
|
||||
|
||||
root.rebuildDisplay()
|
||||
Qt.callLater(function() { keyCatcher.forceActiveFocus() })
|
||||
}
|
||||
|
||||
@@ -53,35 +51,111 @@ Item {
|
||||
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() {
|
||||
var query = root.filterText.trim().toLowerCase()
|
||||
|
||||
|
||||
displayModel.clear()
|
||||
var outCount = 0
|
||||
|
||||
for (var i = 0; i < root.items.length; i++) {
|
||||
var entry = root.items[i]
|
||||
var isPassword = (entry.meta === "password" || entry.preview_type === "password")
|
||||
|
||||
var textMatch = false
|
||||
if (isPassword) {
|
||||
textMatch = false // Passwords shouldn't match plain text search queries
|
||||
} else {
|
||||
textMatch = (entry.preview && entry.preview.toLowerCase().indexOf(query) >= 0)
|
||||
}
|
||||
|
||||
if (!query || textMatch) {
|
||||
displayModel.append({
|
||||
identifier: entry.identifier,
|
||||
previewText: entry.preview_type === "text" ? entry.preview.replace(/\n/g, " ") : "",
|
||||
previewImage: entry.preview_type === "file" ? ("file://" + entry.preview) : "",
|
||||
previewType: entry.preview_type || "text",
|
||||
isPassword: isPassword,
|
||||
index: outCount
|
||||
})
|
||||
outCount++
|
||||
if (outCount >= 50) break
|
||||
}
|
||||
|
||||
for (var i = 0; i < root.history.length; i++) {
|
||||
var entry = root.normalizeEntry(root.history[i])
|
||||
if (!entry) continue
|
||||
|
||||
var isImage = entry.type === "image"
|
||||
var searchable = isImage ? ("image " + String(entry.mime || "")) : String(entry.text || "")
|
||||
if (query && searchable.toLowerCase().indexOf(query) < 0) continue
|
||||
|
||||
displayModel.append({
|
||||
entryType: entry.type,
|
||||
fullText: isImage ? "" : String(entry.text || ""),
|
||||
previewText: isImage ? "Image" : String(entry.text || "").replace(/\s+/g, " "),
|
||||
previewImage: isImage ? root.fileUrl(entry.path) : "",
|
||||
path: isImage ? String(entry.path || "") : "",
|
||||
mime: isImage ? String(entry.mime || "image/png") : "text/plain",
|
||||
index: outCount
|
||||
})
|
||||
outCount++
|
||||
if (outCount >= 50) break
|
||||
}
|
||||
|
||||
if (displayModel.count === 0) selectedIndex = 0
|
||||
@@ -114,40 +188,60 @@ Item {
|
||||
function activateIndex(index) {
|
||||
if (index < 0 || index >= displayModel.count) return
|
||||
var row = displayModel.get(index)
|
||||
root.applySelected(row.identifier)
|
||||
root.applySelected(row)
|
||||
}
|
||||
|
||||
function applySelected(identifier) {
|
||||
if (!identifier) return
|
||||
function applySelected(row) {
|
||||
if (!row) return
|
||||
root.opened = false
|
||||
var escId = identifier.replace(/'/g, "'\\''")
|
||||
Quickshell.execDetached(["bash", "-lc", "elephant activate 'clipboard;" + escId + ";copy;;'; sleep 0.15; wtype -M shift -k Insert -m shift 2>/dev/null || true"])
|
||||
if (row.entryType === "image") {
|
||||
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 }
|
||||
|
||||
FileView {
|
||||
id: historyFile
|
||||
path: root.historyPath
|
||||
watchChanges: true
|
||||
atomicWrites: true
|
||||
printErrors: false
|
||||
onLoaded: root.loadHistory(text())
|
||||
onLoadFailed: root.loadHistory("[]")
|
||||
onFileChanged: reload()
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fetchProc
|
||||
property string collected: ""
|
||||
stdout: SplitParser {
|
||||
onRead: function(data) { fetchProc.collected += data + "\n" }
|
||||
}
|
||||
id: initProc
|
||||
command: ["bash", "-lc", "mkdir -p ~/.local/state/omarchy"]
|
||||
onExited: {
|
||||
var lines = fetchProc.collected.split("\n")
|
||||
var newItems = []
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim()
|
||||
if (!line) continue
|
||||
try {
|
||||
var parsed = JSON.parse(line)
|
||||
if (parsed && parsed.item) {
|
||||
newItems.push(parsed.item)
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
root.items = newItems
|
||||
root.rebuildDisplay()
|
||||
currentProc.command = [root.captureScript]
|
||||
currentProc.running = true
|
||||
watchProc.command = ["wl-paste", "--watch", root.captureScript]
|
||||
watchProc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: currentProc
|
||||
stdout: StdioCollector {
|
||||
waitForEnd: true
|
||||
onStreamFinished: root.addClipboardJson(text)
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: watchProc
|
||||
stdout: SplitParser {
|
||||
onRead: function(data) { root.addClipboardJson(data) }
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: panel
|
||||
visible: root.opened
|
||||
@@ -261,10 +355,10 @@ Item {
|
||||
|
||||
delegate: Rectangle {
|
||||
required property int index
|
||||
required property string identifier
|
||||
required property string entryType
|
||||
required property string previewText
|
||||
required property string previewType
|
||||
required property bool isPassword
|
||||
required property string fullText
|
||||
required property string previewImage
|
||||
|
||||
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.width: hasCursor ? Style.hoverBorderWidth : 0
|
||||
|
||||
Rectangle {
|
||||
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 {
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.space(12)
|
||||
anchors.rightMargin: Style.space(12)
|
||||
anchors.topMargin: 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 {
|
||||
width: parent.width
|
||||
width: parent.width - (parent.parent.entryType === "image" ? parent.height + parent.spacing : 0)
|
||||
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
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.title
|
||||
font.italic: parent.parent.previewType === "file" || parent.parent.isPassword
|
||||
opacity: (parent.parent.previewType === "file" || parent.parent.isPassword) ? 0.6 : 1.0
|
||||
font.italic: parent.parent.entryType === "image"
|
||||
opacity: parent.parent.entryType === "image" ? 0.72 : 1.0
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -309,7 +403,6 @@ Item {
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
@@ -335,13 +428,13 @@ Item {
|
||||
border.width: Style.normalBorderWidth
|
||||
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 {
|
||||
visible: parent.activeRow && parent.activeRow.previewType === "text"
|
||||
visible: parent.activeRow && parent.activeRow.entryType === "text"
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.space(16)
|
||||
text: parent.activeRow ? (parent.activeRow.isPassword ? "••••••••" : parent.activeRow.previewText) : ""
|
||||
text: parent.activeRow ? parent.activeRow.fullText : ""
|
||||
color: root.foreground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.title
|
||||
@@ -351,11 +444,13 @@ Item {
|
||||
}
|
||||
|
||||
Image {
|
||||
visible: parent.activeRow && parent.activeRow.previewType === "file"
|
||||
visible: parent.activeRow && parent.activeRow.entryType === "image"
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.space(16)
|
||||
source: parent.activeRow ? parent.activeRow.previewImage : ""
|
||||
fillMode: Image.PreserveAspectFit
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,7 +471,7 @@ Item {
|
||||
}
|
||||
|
||||
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
|
||||
opacity: 0.7
|
||||
font.family: root.fontFamily
|
||||
|
||||
+161
-9
@@ -18,10 +18,14 @@ Item {
|
||||
var payload = ({})
|
||||
try { payload = JSON.parse(payloadJson || "{}") } catch (e) { payload = ({}) }
|
||||
|
||||
root.pendingInitialMenu = payload.initialMenu || payload.menu || "root"
|
||||
if (payload.fontFamily) root.fontFamily = payload.fontFamily
|
||||
|
||||
root.openExistingMenu(root.pendingInitialMenu)
|
||||
if (payload.mode === "select" || payload.mode === "input") {
|
||||
root.openDmenu(payload)
|
||||
} else {
|
||||
root.pendingInitialMenu = payload.initialMenu || payload.menu || "root"
|
||||
root.openExistingMenu(root.pendingInitialMenu)
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
@@ -37,6 +41,15 @@ Item {
|
||||
property var defaultMenuItems: []
|
||||
property var userMenuItems: []
|
||||
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 string activeMenu: "root"
|
||||
property string filterText: ""
|
||||
@@ -64,14 +77,40 @@ Item {
|
||||
property int dividerHeight: Style.space(17)
|
||||
property bool searchDivider: false
|
||||
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 visibleRowsHeight: 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 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: root.dmenuActive ? dmenuRowListHeight(layoutSerial, displayModel.count, filterText) : rowListHeight(layoutSerial, displayModel.count, filterText, searchDivider)
|
||||
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) {
|
||||
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) {
|
||||
return root.filterText && detail ? root.detailRowHeight : root.baseRowHeight
|
||||
}
|
||||
@@ -94,6 +133,20 @@ Item {
|
||||
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) {
|
||||
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() {
|
||||
if (root.dmenuActive) {
|
||||
root.rebuildDmenuDisplay()
|
||||
return
|
||||
}
|
||||
|
||||
displayModel.clear()
|
||||
|
||||
if (!root.rowsLoaded) return
|
||||
@@ -540,7 +638,7 @@ Item {
|
||||
root.filterText = nextFilter
|
||||
root.selectedIndex = 0
|
||||
root.cursorActive = false
|
||||
if (root.filterText.trim()) root.loadProvidersForSearch()
|
||||
if (!root.dmenuActive && root.filterText.trim()) root.loadProvidersForSearch()
|
||||
root.rebuildDisplay()
|
||||
}
|
||||
|
||||
@@ -571,6 +669,16 @@ Item {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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) {
|
||||
if (!id) { cancel(); return }
|
||||
applySerial = requestSerial
|
||||
@@ -592,12 +707,17 @@ Item {
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
if (root.dmenuActive) root.finishRequest(null)
|
||||
opened = false
|
||||
filterText = ""
|
||||
}
|
||||
|
||||
function openExistingMenu(initialMenu) {
|
||||
requestSerial += 1
|
||||
mode = "menu"
|
||||
requestActive = false
|
||||
selectionFile = ""
|
||||
doneFile = ""
|
||||
activeMenu = root.item(initialMenu) ? initialMenu : "root"
|
||||
navStack = []
|
||||
filterText = ""
|
||||
@@ -610,6 +730,27 @@ Item {
|
||||
|
||||
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 }
|
||||
|
||||
// ----------------------------------------------------------- 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
|
||||
// user extension at ~/.config/omarchy/extensions/omarchy-menu.jsonc) take
|
||||
// effect without restarting the shell.
|
||||
@@ -840,7 +989,10 @@ Item {
|
||||
if (!root.filterText) root.goBack()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Right) {
|
||||
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
|
||||
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)) {
|
||||
@@ -866,7 +1018,7 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.filterText || ((root.item(root.activeMenu) ? root.item(root.activeMenu).label : "Go") + "…")
|
||||
text: root.filterText || (root.dmenuActive ? (root.dmenuPrompt + "…") : ((root.item(root.activeMenu) ? root.item(root.activeMenu).label : "Go") + "…"))
|
||||
color: root.foreground
|
||||
opacity: root.filterText ? 1 : 0.58
|
||||
font.family: root.fontFamily
|
||||
@@ -1034,7 +1186,7 @@ Item {
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.space(8)
|
||||
visible: displayModel.count === 0
|
||||
visible: displayModel.count === 0 && root.mode !== "input"
|
||||
|
||||
Text {
|
||||
text: ""
|
||||
|
||||
Executable
+49
@@ -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
|
||||
Reference in New Issue
Block a user