Advanced Uses and Special Set Ups - felixfung/skippy-xd GitHub Wiki
Skippy-xd is easy to use, but there is depth in plenty of advanced uses and customizations, including invocation method, scripting, dashboard set up, multimonitor set up, config file options.
Skippy-xd offers many options, configured through a config file.
Most default values in the config file works well. For full details of the skippy-xd config file, see https://github.com/felixfung/skippy-xd/wiki/Config.
Invoking skippy-xd activation consists simply of launching skippy-xd with different parameters. With all the great tools in the Linux world, this allows myriad possibilities to use skippy-xd.
Skippy-xd operations always allow both keyboard and mouse. Here are some ideas how to activate skippy-xd with either keyboard or mouse:
For simplest set up, simply use your favourite hot key app for these bindings:
| hot key | metaphor | command |
|---|---|---|
| Alt Tab | switch | skippy-xd --switch --next |
| Shift Alt Tab | switch | skippy-xd --switch --prev |
| F11 | expose | skippy-xd --expose |
| F12 | paging | skippy-xd --paging |
There are plenty of apps to set up hot keys in Linux. See for example https://wiki.archlinux.org/title/keyboard_shortcuts#Customization.
Of these, xbindkeys (https://wiki.archlinux.org/title/Xbindkeys) allows lisp/guile programming, and hence unlimited flexibility in skippy-xd invocation. A simple .xbindkeysrc would be
"skippy-xd --switch --next"
Alt + Tab
"skippy-xd --switch --prev"
Alt + Shift + Tab
"skippy-xd --expose"
F11
"skippy-xd --paging"
F12
Further ideas include key combinations and key double tapping to invoke skippy-xd.
If you are heavy on mouse operation, you may find efficiency in activating skippy-xd expose and paging modes with mouse:
-
Set up hot corner to trigger skippy-xd.
xdotoolis one simple tool to achieve this. -
Panel applets is one way to trigger skippy-xd from your favourite panel.
-
Touchpad swipe and pinch gestures to invoke skippy-xd, for example https://wiki.archlinux.org/title/Touchegg.
-
Mouse pivoting: https://github.com/felixfung/skippy-xd/wiki/Customizations,-tips-and-special-set-ups#mouse-pivoting
Skippy-xd is activated in one of two modes: toggle mode or pivot mode.
In toggle mode, skippy-xd is simply activated. A subsequent invocation of skippy-xd leads de-activation without window selection. Hence, invocations toggle skippy-xd on/off.
To invoke with toggle mode, use the --toggle command line argument. For example:
skippy-xd --expose --toggle
skippy-xd --paging --toggle
In pivot mode, a pivot key has to be held for skippy-xd to remain activated. For example, during Alt-Tab, Alt is the pivot key, and when it is released, skippy-xd selects the currently highlighted window.
To specify pivot mode, add --pivot command line argument and specify the pivot key. Hence:
skippy-xd --switch --pivot Alt_L --next
skippy-xd --switch --pivot Alt_L --prev
When pivot or toggle modes are not specified, switch defaults to pivot with Alt_L pivot key, while expose and paging defaults to toggle mode. Hence these shortened commands are identical to those above:
skippy-xd --switch --next
skippy-xd --switch --prev
skippy-xd --expose
skippy-xd --paging
Expose and paging can be invoked in pivot mode, and thus behave similar to Alt-Tab. For example with xbindkeys and .xbindkeysrc:
"skippy-xd --expose --pivot Super_L --next"
Mod4 + Tab
"skippy-xd --paging --pivot Control_L --next"
Control + Tab
The feeling of this usage is very different from the toggling usage. Try it out!
You can also invoke expose and paging with a single key, so that pressing and holding the key keeps skippy-xd activated.
For this, you will need this patched version of xbindkeys at https://github.com/felixfung/xbindkeys3, which has xbind-skippy in place of xbindkey, and skippy-runner in place of run-command. Then in .xbindkeys.scm, do:
(xbindkey
'(Alt Tab)
"skippy-xd --switch --next")
(xbindkey
'(Shift Alt Tab)
"skippy-xd --switch --prev")
(xbind-skippy
'(Super_L)
"skippy-xd --expose \
--pivot Super_L")
(xbind-skippy
'(Super_R)
"skippy-xd --paging \
--pivot Super_R")
If your pivot key is not a modifier (Shift, Control, etc), then it will fire continuously leading to 100% CPU usage. Use xset -r keycode to disable key repetition; the keycode can be found from xev.
This set up has quickly become my personal favourite. Try it out!
Hot corner (e.g. with xdotool) is a good way to use skippy-xd with the mouse. There is another way if your mouse has additional buttons (e.g. thumb buttons).
The syntax is the same as usual pivoting, where the pivot key is now b:x, with x being the mouse button, with ids 6, 7, 8, 9 being common. For example
skippy-xd --expose --pivot b:6
An example .xbindkeysrc.scm using https://github.com/felixfung/xbindkeys3:
(xbind-skippy
'(b:9)
"skippy-xd --expose \
--pivot b:9")
(xbind-skippy
'(b:8)
"skippy-xd --paging \
--pivot b:8")
Now, you can get a quick expose and paging overview of your desktop with the hold of your mouse button, hover your mouse on the window you want to select, and release the mouse button to select. Click at the desktop to cancel. In the whole operation, your hand needs minimal movement!
This has been transformative for me. Try it out!
Skippy-xd offers many possibilities to filter windows, so that you choose what want to display and/or select!
The filtering is specified via command-line interface, offering the largest flexibility and maneuverability.
You can also combine different filtering options for pin point window selection.
Starting version 0.9.0, skippy-xd allows showing only certain window classes!
Trigger skippy-xd with the --wm-class command line option. For example, this would show only XTerm windows, filtering away all other windows:
skippy-xd --expose --wm-class XTerm
One simple use is to use xdotool to filter for currently highlighted window class:
skippy-xd --expose --wm-class $(xdotool getwindowclassname $(xdotool getactivewindow))
Another use is to couple with yad or similar to achieve a window filter and selector:
skippy-xd --expose --wm-class $(yad --entry)
The filtering allows expressions such as * for wildcard characters, and ! for negation. Some example:
Partial match XT to match class XTerm,
skippy-xd --expose --wm-class XT
Use * for wildcard characters,
skippy-xd --expose --wm-class 'X*rm'
Match anything but XTerm.
skippy-xd --expose --wm-class '!XTerm'
Of course you can combine above expressions for sophisticated filtering, for example
skippy-xd --expose --wm-class 'f*fox' --wm-class '!XT'
Starting version 0.10.4, skippy-xd allows showing only certain window titles!
Trigger skippy-xd with the --wm-title command line option. For example, this would show only firefox windows, filtering away all other windows:
skippy-xd --expose --wm-title Firefox
Similar to window class filtering, title filtering allows partial matching, wildcard expressions, and negative filtering. For example:
skippy-xd --expose --wm-title '!F*fox'
Starting version 0.10.3, skippy-xd allows showing only certain window status!
Trigger skippy-xd with the --wm-status command line option.
Available options are sticky, shaded, minimized, float, maximized_vert, maximized_horz, maximized.
You can select multiple options separated by comma ,.
For example, this command would display only minimized or floating (those that are not sticky, shaded, minimized, maximized) windows:
skippy-xd --expose --wm-status minimized,float
This command would display maximized or horizontally maximized windows, but not vertically maximized windows:
skippy-xd --expose --wm-status maximized --wm-status maximized_horz
You can also use ! to specify not displaying specific window status:
skippy-xd --expose --wm-status '!minimized'
Starting version 0.10.4, skippy-xd allows filtering via virtual desktop!
Trigger skippy-xd with the --desktop command line option. For example, this command would show windows on desktop 2 and 3:
skippy-xd --expose --desktop 2,3
On the contrary, when the --desktop command line option is not used, then only windows on the current virtual desktop is displayed.
Sticky windows will always be shown.
You can select multiple options separated by comma ,. You can also use -1 to display windows from all virtual desktops.
You can also use ! to specify not displaying windows from certain virtual desktops:
skippy-xd --expose --desktop '!1,!2'
Try development branch digimon!
Skippy-xd is script-able!
Skippy-xd is script-able by design, with these important characteristics:
- Invoking skippy-xd is always through the simple skippy-xd command.
- If the skippy-xd command activated skippy-xd, it is guaranteed to return after the completion of skippy-xd selection, with the window ID (in case of switch and expose) or virtual desktop ID (in case of paging).
- If the skippy-xd command did not activate skippy-xd, e.g. with toggling off of expose, or
--nextinvocations of switch, then the command will return immediately with-1.
- In addition, there is multi select mode. See below section for more detail.
Below are some ideas and scripts for your skippy-xd use. These are by no means the limitations of what scripts of skippy-xd can do, rather these are ideas and building blocks of what skippy-xd can do for YOU.
You can set up skippy-xd with your choice of panels and windows to create your own dashboard! Then, during skippy-xd activation, your choice of informational windows will be displayed as well.
A simple example of dashboard setup consisting of wallpaper and conky:
#!/bin/bash
conky &
load-wallpaper dashboard.jpg
sleep 0.05 # add small time delay for conky to load
skippy-xd --expose # you can even add --config path for invoke with special config option
killall conky
load-wallpaper wallpaper.jpg
This deceptively simple looking script works because skippy-xd --expose returns only after skippy-xd selection is complete, so that cleaning up (in this case killing conky and reloading wallpaper) is not performed prematurely.
Show/hide panel and desktop windows as part of your dashboard. Show/hide them before/after skippy-xd invocation in your script. Panel windows have window type _NET_WM_WINDOW_TYPE_DOCK; desktop windows have window type _NET_WM_WINDOW_TYPE_DESKTOP. See also https://github.com/felixfung/skippy-xd/wiki/Config.
To set a class of windows as panel window, do:
xprop -id $(xdotool search --class window_class) -f _NET_WM_WINDOW_TYPE 32a -set _NET_WM_WINDOW_TYPE _NET_WM_WINDOW_TYPE_DOCK
To set a class of windows as desktop window, do:
xprop -id $(xdotool search --class window_class) -f _NET_WM_WINDOW_TYPE 32a -set _NET_WM_WINDOW_TYPE _NET_WM_WINDOW_TYPE_DESKTOP
To show conky as a panel window, set in .conkyrc
own_window = true,
own_window_type = 'panel'
To show conky in the desktop window, set in .conkyrc
own_window = true,
own_window_type = 'desktop'
Unfortunately, it is technically impossible to click or send events to the panels.
This script detect skippy-xd activation state, by using xwininfo to look for mapping state of skippy-xd windows:
#!/bin/bash
wids=`xdotool search --class skippy-xd`
for wid in $wids; do
mapstate=`xwininfo -id $wid | grep Map`
if [[ "$mapstate" =~ IsViewable ]]; then
echo "active"
exit
fi
done
echo "inactive"
With the ability to detect whether skippy-xd is currently active, more sophisticated uses can be built.
When skippy-xd is not active, this script simply switches to the next virtual desktop.
When skippy-xd expose is already active, this script terminates skippy-xd, switches to the next virtual desktop, and activates skippy-xd expose again. In effect, this script "preserves" expose while switches virtual desktop.
#!/bin/bash
activating=0
wids=`xdotool search --class skippy-xd`
for wid in $wids; do
mapstate=`xwininfo -id $wid | grep Map`
if [[ "$mapstate" =~ IsViewable ]]; then
activating=1
fi
done
if [ $activating -eq 1 ]; then
skippy-xd --expose
fi
xdotool set_desktop $(( $((`xdotool get_desktop` + `xdotool get_num_desktops` + 1)) % `xdotool get_num_desktops`))
if [ $activating -eq 1 ]; then
skippy-xd --expose
fi
As a bonus, when skippy-xd is activated via pivot mode, this script has no effect ;)
#!/bin/bash
wid0=`xdotool getactivewindow`
wid1=`skippy-xd --expose`
xdotool windowfocus $wid0
echo $wid1
By modifying the above script, we can perform window action on the window selected by skippy-xd.
For example, the following toggles the selected window fullscreen:
#!/bin/bash
wid0=`xdotool getactivewindow`
wid1=`skippy-xd --expose`
xdotool windowfocus $wid0
xdotool windowstate -toggle FULLSCREEN $wid1
One interesting possibility is to perform expose on windows that are currently playing music/sound, and mute the window selected by skippy-xd. This, of course, depends on your particular audio set up. See https://github.com/felixfung/skippy-xd/wiki/Advanced-Uses-and-Special-Set-Ups#advanced-script-skippy-audio for an example script using pulseaudio and pactl.
This is my personal favourite: use paging to transfer current window to a new virtual desktop:
#!/bin/bash
wid0=`xdotool getactivewindow`
did=`skippy-xd --paging`
xdotool set_desktop_for_window $wid0 $did
xdotool windowfocus $wid0
Some other advanced use idea is to send key events or clicks to the selected window.
Starting version 0.10.2, there is multi-select mode!
Invoke skippy-xd with --multi-select parameter. Then, you see that the highlight tint is different (default smart blue, controlled by multiselect section in the config file).
From here, toggle window selection status with the usual mouse click or enter/space. Terminate skippy-xd activation by pressing escape or clicking on a non-window area.
Then, skippy-xd returns all the selected window or virtual desktop IDs, which can be parsed for scripting.
This is a script to use multi-select to take a bunch of windows to a selected virtual desktop:
#!/bin/bash
wids=`skippy-xd --expose --multi-select`
did=`skippy-xd --paging`
for wid in $wids; do
xdotool set_desktop_for_window $wid $did
xdotool windowfocus $wid
done
Display all windows playing audio/music; select to toggle mute of the window:
#!/usr/bin/env bash
set -u
log() {
printf '[skippy-audio] %s\n' "$*" >&2
}
die() {
log "ERROR: $*"
exit 1
}
for cmd in pactl wmctrl xdotool awk ps sort base64 skippy-xd; do
command -v "$cmd" >/dev/null || die "missing dependency: $cmd"
done
marker="SKIPPY_AUDIO_CANDIDATE_$$"
restore_file="$(mktemp)"
restore_titles() {
while IFS=$'\t' read -r wid title_b64; do
[ -z "${wid:-}" ] && continue
title="$(printf '%s' "$title_b64" | base64 -d 2>/dev/null || true)"
xdotool set_window --name "$title" "$wid" 2>/dev/null || true
done < "$restore_file"
rm -f "$restore_file"
}
trap restore_titles EXIT INT TERM
streams="$(
pactl list sink-inputs | awk '
function flush() {
if (id != "" && pid != "" && corked != "yes")
print id "\t" pid "\t" mute "\t" app "\t" media
}
/^Sink Input #/ {
flush()
id = $3
sub("#", "", id)
pid = ""
mute = ""
corked = ""
app = ""
media = ""
next
}
/^[[:space:]]*Mute:/ {
mute = $2
next
}
/^[[:space:]]*Corked:/ {
corked = $2
next
}
/application\.process\.id =/ {
line = $0
sub(/.*application\.process\.id = "/, "", line)
sub(/".*/, "", line)
pid = line
next
}
/application\.name =/ {
line = $0
sub(/.*application\.name = "/, "", line)
sub(/".*/, "", line)
app = line
next
}
/media\.name =/ {
line = $0
sub(/.*media\.name = "/, "", line)
sub(/".*/, "", line)
media = line
next
}
END {
flush()
}
'
)"
if [ -z "$streams" ]; then
log "no active sink-inputs found"
log "play audio or check pavucontrol Playback tab"
exit 0
fi
nearest_windows_for_pid() {
local pid="$1"
local p="$pid"
local matches=""
while [[ "$p" =~ ^[0-9]+$ ]] && [ "$p" -gt 1 ]; do
matches="$(wmctrl -lp | awk -v p="$p" '$3 == p { print $1 }' | sort -u)"
if [ -n "$matches" ]; then
printf '%s\n' "$matches"
return 0
fi
p="$(ps -o ppid= -p "$p" 2>/dev/null | tr -d ' ' || true)"
[ -z "$p" ] && break
done
return 1
}
declare -A window_inputs
declare -A window_desc
while IFS=$'\t' read -r input pid mute app media; do
[ -z "${input:-}" ] && continue
[ -z "${pid:-}" ] && continue
wins="$(nearest_windows_for_pid "$pid" || true)"
if [ -z "$wins" ]; then
log "sink-input #$input pid=$pid mute=$mute app=[$app] media=[$media] has no matching X11 window"
continue
fi
while read -r wid_hex; do
[ -z "$wid_hex" ] && continue
wid_dec="$((wid_hex))"
title="$(xdotool getwindowname "$wid_dec" 2>/dev/null || true)"
log "candidate: sink-input #$input mute=$mute pid=$pid window=$wid_hex title=[$title] app=[$app] media=[$media]"
window_inputs["$wid_dec"]+="$input "
window_desc["$wid_dec"]+="sink-input #$input mute=$mute app=[$app] media=[$media]; "
done <<< "$wins"
done <<< "$streams"
if [ "${#window_inputs[@]}" -eq 0 ]; then
log "audio streams exist, but none mapped to X11 top-level windows"
exit 0
fi
for wid in "${!window_inputs[@]}"; do
title="$(xdotool getwindowname "$wid" 2>/dev/null || true)"
title_b64="$(printf '%s' "$title" | base64 -w0)"
printf '%s\t%s\n' "$wid" "$title_b64" >> "$restore_file"
xdotool set_window --name "🔊 $title $marker" "$wid" 2>/dev/null || true
done
log "showing audio candidate windows in skippy-xd..."
selected="$(skippy-xd --expose --desktop -1 --wm-title "$marker" || true)"
if [ -z "${selected:-}" ] || [ "$selected" = "-1" ]; then
log "no window selected"
exit 0
fi
selected_dec="$((selected))"
inputs="${window_inputs[$selected_dec]:-}"
desc="${window_desc[$selected_dec]:-}"
if [ -z "$inputs" ]; then
log "selected window has no mapped sink-input"
exit 1
fi
log "selected window: $selected_dec"
log "mapped stream(s): $desc"
log "toggling ONLY sink-input(s): $inputs"
for input in $inputs; do
pactl set-sink-input-mute "$input" toggle
done
log "done"
Usage: skippy-cpu {cpu_threshold}
Display windows which is taking more CPU cycles % than the cpu_threshold; select to close the window
#!/usr/bin/env bash
set -u
threshold="${1:-20}"
log() {
printf '[skippy-cpu-close] %s\n' "$*" >&2
}
die() {
log "ERROR: $*"
exit 1
}
for cmd in ps wmctrl xdotool awk sort base64 skippy-xd; do
command -v "$cmd" >/dev/null || die "missing dependency: $cmd"
done
marker="SKIPPY_CPU_CANDIDATE_$$"
restore_file="$(mktemp)"
restore_titles() {
while IFS=$'\t' read -r wid title_b64; do
[ -z "${wid:-}" ] && continue
title="$(printf '%s' "$title_b64" | base64 -d 2>/dev/null || true)"
xdotool set_window --name "$title" "$wid" 2>/dev/null || true
done < "$restore_file"
rm -f "$restore_file"
}
trap restore_titles EXIT INT TERM
declare -A pid_windows
declare -A window_cpu
declare -A window_desc
declare -A window_hex
while read -r wid_hex desktop pid host title; do
[ -z "${wid_hex:-}" ] && continue
[ "${pid:-0}" = "0" ] && continue
wid_dec="$((wid_hex))"
pid_windows["$pid"]+="$wid_dec "
window_hex["$wid_dec"]="$wid_hex"
done < <(wmctrl -lp)
nearest_window_pids_for_process() {
local p="$1"
local wins=""
while [[ "$p" =~ ^[0-9]+$ ]] && [ "$p" -gt 1 ]; do
wins="${pid_windows[$p]:-}"
if [ -n "$wins" ]; then
printf '%s\n' "$wins"
return 0
fi
p="$(ps -o ppid= -p "$p" 2>/dev/null | tr -d ' ' || true)"
[ -z "$p" ] && break
done
return 1
}
while read -r cpu pid comm; do
over="$(awk -v cpu="$cpu" -v threshold="$threshold" 'BEGIN { print cpu >= threshold ? 1 : 0 }')"
[ "$over" = "1" ] || continue
wins="$(nearest_window_pids_for_process "$pid" || true)"
[ -z "$wins" ] && continue
for wid in $wins; do
old="${window_cpu[$wid]:-0}"
sum="$(awk -v a="$old" -v b="$cpu" 'BEGIN { printf "%.1f", a + b }')"
window_cpu["$wid"]="$sum"
window_desc["$wid"]+="$comm:$cpu% "
done
done < <(ps -u "$USER" -o pcpu=,pid=,comm= | sort -nr)
if [ "${#window_cpu[@]}" -eq 0 ]; then
log "no X11 windows associated with processes above ${threshold}% CPU"
exit 0
fi
for wid in "${!window_cpu[@]}"; do
title="$(xdotool getwindowname "$wid" 2>/dev/null || true)"
title_b64="$(printf '%s' "$title" | base64 -w0)"
printf '%s\t%s\n' "$wid" "$title_b64" >> "$restore_file"
cpu="${window_cpu[$wid]}"
desc="${window_desc[$wid]}"
xdotool set_window --name "🔥 ${cpu}% CPU — $title $marker" "$wid" 2>/dev/null || true
done
selected="$(skippy-xd --expose --desktop -1 --wm-title "$marker" || true)"
if [ -z "${selected:-}" ] || [ "$selected" = "-1" ]; then
exit 0
fi
selected_dec="$((selected))"
selected_hex="${window_hex[$selected_dec]:-}"
if [ -z "$selected_hex" ]; then
log "selected window was not in candidate set"
exit 1
fi
cpu="${window_cpu[$selected_dec]:-?}"
desc="${window_desc[$selected_dec]:-}"
restore_titles
trap - EXIT INT TERM
log "closing selected high-CPU window: ${cpu}% CPU; processes: $desc"
xdotool windowclose "$selected_dec" 2>/dev/null || wmctrl -ic "$selected_hex"
Skippy-xd is designed to work with other X11 windowing tools.
Simply match skippy-xd windows based on these properties:
| Skippy-xd window | Window title |
|---|---|
| Fullscreen window | "skippy-xd fullscreen window" |
| Preview windows | "skippy-xd preview window 0xXXXX" |
| Paging windows | "skippy-xd page [virtual desktop name]" |
| Label windows | "skippy-xd label" |
| Skippy-xd window | Window class |
|---|---|
| Fullscreen window | "skippy-xd" |
| Preview windows | "skippy-xd" |
| Paging windows | "skippy-xd" |
| Label windows | "skippy-xd" |
Integrate picom with skippy-xd for exotic visual effects. Thank you musqz for the contributions!
Youtube videos with various picom effects: https://www.youtube.com/@musboule
See also https://github.com/felixfung/skippy-xd/discussions/335.
In $HOME/.config/picom/config/picom.conf, include skippy-xd.conf:
@include "skippy-xd.conf"And in $HOME/.config/picom/include/skippy-xd.conf,
match = "class_i = 'skippy-xd'";
blur-background = false;
corner-radius = 8;
opacity = 1.0;
dim = 0.3;
shadow = false;
animations = (
{
triggers = ["close", "hide"];
opacity = 1.0;
preset = "fly-out";
direction = "left";
delay = 0.12;
duration = 0.8;
},
{
triggers = ["open", "show"];
opacity = 1.0;
preset = "fly-in";
delay = 3;
direction = "left";
duration = 0.2;
},
);Use keyd to remap keys in app-specific manner. This is particularly useful if the keys within skippy-xd clash with those in the rest of the environment.
In ~/.config/keyd/app.conf, do
[skippy-xd]
<new key> = <old key>
and similar to get new key to behave like old key only within skippy-xd.
Use conky to display informational dashboard.
See https://github.com/felixfung/skippy-xd/wiki/Advanced-Uses-and-Special-Set-Ups#dashboard.
xdotool is the swiss army knife of window management. See https://github.com/felixfung/skippy-xd/wiki/Advanced-Uses-and-Special-Set-Ups#scripting for example on combining xdotool and skippy-xd.
Very similar to xdotool.
Further reading in https://github.com/felixfung/skippy-xd/wiki/Skippy-xd-philosophy, https://github.com/felixfung/skippy-xd/wiki/Cosmos.
For more tips, questions, idea exchanges, go to https://github.com/felixfung/skippy-xd/discussions