Skip to content

Instantly share code, notes, and snippets.

@promovicz
Last active November 16, 2021 03:07
Show Gist options
  • Save promovicz/e0c194151670446ee671af047b0bead5 to your computer and use it in GitHub Desktop.
Save promovicz/e0c194151670446ee671af047b0bead5 to your computer and use it in GitHub Desktop.
Enhanced dmenu for use with qubes and i3
Example use:
# qubes control
bindsym $mod+u exec --no-startup-id "qubes-i3-dmenu-shell --start"
bindsym $mod+Shift+u exec --no-startup-id "qubes-i3-dmenu-shell --pause"
bindsym $mod+i exec --no-startup-id "qubes-i3-dmenu-shell --shutdown"
bindsym $mod+Shift+i exec --no-startup-id "qubes-i3-dmenu-shell --unpause"
# qubes launching
bindsym $mod+o exec --no-startup-id "qubes-i3-dmenu-shell --global"
bindsym $mod+Shift+o exec --no-startup-id "qubes-i3-dmenu-shell --global"
bindsym $mod+p exec --no-startup-id "qubes-i3-dmenu-shell --select --running"
bindsym $mod+Shift+p exec --no-startup-id "qubes-i3-dmenu-shell --select"
bindsym $mod+w exec --no-startup-id "qubes-i3-dmenu-shell --window-domain"
bindsym $mod+Shift+w exec --no-startup-id "qubes-i3-dmenu-shell --window-template"
#!/bin/sh
set -e
PROGRAM="$0"
NAME="$(basename "${PROGRAM}")"
say() {
echo "${NAME}: $*" 1>&2
}
quote() {
for s in "$@"; do
printf %q "${s}"
done
}
main() {
local nb="#d2d2d2"
local nf="#000000"
local sb="#63a0ff"
local sf="#ffffff"
local rest=""
local idx=0
while [ $# -gt 0 ]; do
case $1 in
-nb)
nb="$2"
shift 2
;;
-nf)
nf="$2"
shift 2
;;
-sb)
sb="$2"
shift 2
;;
-sf)
sf="$2"
shift 2
;;
*)
rest="${rest} $(quote "$1")"
shift 1
;;
esac
idx=$(( ${idx} + 1 ))
done
eval "dmenu -nb '${nb}' -nf '${nf}' -sb '${sb}' -sf '${sf}' ${rest}"
}
main "$@" || say "The program has failed"
#!/bin/bash
set -e
PROGRAM="$0"
NAME="$(basename "${PROGRAM}")"
run_terminal='
for t in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm gnome-terminal konsole roxterm xfce4-terminal termite lxterminal mate-terminal terminology st xterm; do
command -v "$t" > /dev/null 2>&1 && exec "$t";
done
'
# default color
#sb_default='#63a0ff'
sb_default='#2004A0'
#sb_default='#204080'
#sb_default='#080808'
# i3 focused
sb_dom0='#522702'
sb_red='#e53b27'
sb_orange='#d05f03'
sb_yellow='#999b00'
sb_green='#04af5b'
sb_gray='#8c959f'
sb_blue='#3384d6'
sb_purple='#8f5cbe'
sb_black='#595959'
# i3 unfocused
#sb_dom0='#361a01'
#sb_red='#902519'
#sb_orange='#7b3702'
#sb_yellow='#666700'
#sb_green='#02713b'
#sb_gray='#676d75'
#sb_blue='#1f5082'
#sb_purple='#5c3e78'
#sb_black='#3a3a3a'
# say <message>...
#
# Log a message to stderr.
#
say() {
echo "${NAME}: $*" 1>&2
}
# get_active_window
#
# Get the window id of the active window.
#
get_active_window() {
local id=$(xprop -root _NET_ACTIVE_WINDOW)
local rid=${id##* } # extract raw id
if [ "${rid}" != "0x0" ]; then
echo "${rid}"
fi
}
# get_window_vm <wid>
#
# Get the VM of the given window.
#
get_window_vm() {
local id="$1"
local vm=$(xprop -id "${id}" | grep '_QUBES_VMNAME(STRING)')
local vm=${vm#*\"} # extract vmname
echo ${vm%\"*} # extract vmname
}
# get_active_vm
#
# Get the active VM based on the active window.
#
#get_active_vm() {
# local win="$(get_active_window)"
# if [ -n "${win}" ]; then
# get_window_vm "${win}"
# fi
#}
# get_vm_label <vm>
#
# Get label color of VM.
#
get_vm_label() {
local vms="$@"
for vm in "${vms}"; do
qvm-ls --raw-data -O label "${vm}"
done
}
# get_vm_color <vm>
#
# Get background color for VM.
#
get_vm_color() {
local vm="$1"
local label="$(get_vm_label "${vm}")"
local color="$(eval "echo \${sb_${label}}")"
if [ -z "${color}" ]; then
color="${sb_default}"
fi
echo -n "${color}"
}
# get_vm_template <vm>
#
# Get template for VM.
#
get_vm_template() {
local vm="$1"
qvm-prefs "${vm}" template 2> /dev/null
}
# run_dmenu <options>...
#
# Run an instance of dmenu with our settings.
#
run_dmenu() {
qubes-i3-dmenu -sb "${sb_default}" "$@"
}
# run_dmenu_message <options>...
#
# Use dmenu to show a message with confirm.
#
run_dmenu_message() {
echo dismiss | run_dmenu "$@" > /dev/null
}
# run_dmenu_error <options>...
#
# Use dmenu to show an error with confirm.
#
run_dmenu_error() {
echo dismiss | run_dmenu "$@" > /dev/null
}
# run_dmenu_confirm <message> <token>...
#
# Use dmenu for a confirmation message.
#
# The given tokens will be presented as dmenu items
# to be skipped with the cursor as intent confirmation.
#
run_dmenu_confirm() {
local message="$1"
shift 1
echo "$@" "-->" "[confirm]" | tr ' ' '\n' \
| run_dmenu -p "${message}" \
| grep -q '^\[confirm\]$' > /dev/null
}
# run_dmenu_select_vm <prompt> <filter>...
#
# Run dmenu to select a VM.
#
run_dmenu_select_vm() {
local prompt="$1"
shift 1
local rules="class=*"
if [ $# -ge 1 ]; then
rules="${rules} $(echo "$*" | sed -e 's/[ ]*\-\-all \-\-exclude dom0[ ]*//' -e 's/--//g')"
fi
say "Running dmenu to select VMs $*"
qvm-ls --raw-list "$@" | run_dmenu -p "${prompt} (${rules})"
}
# run_dmenu_select_vm_class <prompt> <class> <filter>...
#
# Run dmenu to select a VM, select on class.
#
# Needed because qvm-ls can't filter on class.
#
run_dmenu_select_vm_class() {
local prompt="$1"
local class="$2"
shift 2
local rules="class=${class}"
if [ $# -ge 1 ]; then
rules="${rules} $(echo "$*" | sed -e 's/[ ]*\-\-all \-\-exclude dom0[ ]*//' -e 's/--//g')"
fi
say "Running dmenu to select VMs with ${rules}"
qvm-ls --raw-data -O NAME,CLASS "$@" \
| awk -F \| "{ if ( \$2 == \"${class}\" ) { print \$1 } }" \
| run_dmenu -p "${prompt} (${rules})"
}
# run_dmenu_desktop_global
#
# Run dmenu to select desktop files on dom0.
#
run_dmenu_desktop_global() {
say "Running dmenu for desktop files of dom0"
i3-dmenu-desktop --dmenu "qubes-i3-dmenu -p 'launch (dom0)' -sb '${sb_default}'"
}
# run_dmenu_desktop_global <vm>
#
# Run dmenu to select desktop files for a VM.
#
run_dmenu_desktop_vm() {
local vm="$1"
if [ "${vm}" = "dom0" ]; then
run_dmenu_desktop_global
return $?
fi
local color="$(get_vm_color "${vm}")"
say "Running dmenu for desktop files of ${vm}"
local label="$(get_vm_label "${vm}")"
local cmd="grep '^${vm}: ' | sed 's/^${vm}: //' | qubes-i3-dmenu -p '${vm}' -sb '${color}' | sed 's/.*/${vm}: \0/'"
i3-dmenu-desktop --dmenu "${cmd}"
}
# kill_previous
#
# Kill other instances of this tool.
#
kill_previous() {
killall -qe -o '1s' -u "${USER}" qubes-i3-dmenu-shell qubes-i3-dmenu dmenu || true
}
# main <arg>...
#
# Main program.
#
main() {
local mode=""
local modeparm=""
local rest=""
say "Started with parameters: $*"
while [ $# -gt 0 ]; do
#say "A: $1"
case "$1" in
--global|--select|--start|--shutdown|--pause|--unpause|--window-domain|--window-template)
mode="$1"
shift 1
;;
--domain|--template)
mode="$1"
modeparm="$2"
if [ -z "${modeparm}" ]; then
say "Usage: ${NAME} ${mode} <domain>"
return 1
fi
shift 2
;;
--message|--error)
mode="$1"
modeparm="$2"
if [ -u "${modeparm}" ]; then
say "Usage: ${NAME} ${mode} <message>"
fi
shift 2
;;
--system-poweroff|--system-reboot|--session-quit)
mode="$1"
shift 1
;;
*)
rest="${rest} $1"
shift 1
;;
esac
done
local exclude_dom0="--all --exclude dom0"
case "${mode}" in
--global)
run_dmenu_desktop_global
;;
--select)
run_dmenu_select_vm launch ${rest} ${exclude_dom0} | xargs -n1 ${PROGRAM} --domain
;;
--start)
run_dmenu_select_vm start --halted ${exclude_dom0} | xargs qvm-start
;;
--shutdown)
run_dmenu_select_vm shutdown --running --paused ${exclude_dom0} | xargs qvm-shutdown
;;
--pause)
run_dmenu_select_vm pause --running ${exclude_dom0} | xargs qvm-pause
;;
--unpause)
run_dmenu_select_vm unpause --paused ${exclude_dom0} | xargs qvm-unpause
;;
--domain|--template)
local vm="${modeparm}"
run_dmenu_desktop_vm "${vm}"
;;
--template)
local vm="${modeparm}"
local tm="$(get_vm_template "${vm}")"
if [ -z "${tm}" ]; then
run_dmenu_error -p "${vm} has no template"
return 1
fi
run_dmenu_desktop_vm "${tm}"
;;
--message)
run_dmenu_message -p "${modeparm}" ${rest}
;;
--error)
run_dmenu_error -p "${modeparm}" ${rest}
;;
--system-poweroff)
if run_dmenu_confirm "SYSTEM SHUTDOWN" "yes" "please" "halt" "immediately"; then
poweroff
fi
;;
--system-reboot)
if run_dmenu_confirm "SYSTEM REBOOT" "yes" "please" "reboot" "immediately"; then
reboot
fi
;;
--session-quit)
if run_dmenu_confirm "SESSION QUIT" "yes" "please" "terminate" "session"; then
i3-msg exit
fi
;;
--window-domain)
local win=""
local vm=""
local tm=""
win="$(get_active_window)"
if [ -n "${win}" ]; then
vm="$(get_window_vm "${win}")"
fi
if [ -n "${vm}" ]; then
run_dmenu_desktop_vm "${vm}"
else
run_dmenu_select_vm launch --running ${exclude_dom0} | xargs -n1 ${PROGRAM} --domain
fi
;;
--window-template)
local win=""
local vm=""
local tm=""
win="$(get_active_window)"
if [ -n "${win}" ]; then
vm="$(get_window_vm "${win}")"
if [ -n "${vm}" ]; then
tm="$(get_vm_template "${vm}")"
if [ -z "${tm}" ]; then
run_dmenu_error -p "${vm} has no template"
return 1
fi
fi
fi
if [ -n "${tm}" ]; then
run_dmenu_desktop_vm "${tm}"
else
run_dmenu_select_vm_class launch TemplateVM | xargs -n1 ${PROGRAM} --domain
fi
;;
*)
say "Unknown mode ${mode}"
return 1
;;
esac
}
# kill previous instances
kill_previous
# run main program, report errors
main "$@" || (say "The program has failed."; exit 1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment