nix-files/hosts/common/programs/sane-input-handler/sane-input-handler

245 lines
6.2 KiB
Plaintext
Executable File

#!/usr/bin/env nix-shell
#!nix-shell -i bash -p coreutils -p killall -p playerctl -p procps -p sane-open-desktop -p sway -p util-linux -p wireplumber
# input map considerations
# - using compound actions causes delays.
# e.g. if volup->volup is a distinct action from volup, then single-volup action is forced to wait the maximum button delay.
# - actions which are to be responsive should therefore have a dedicated key.
# - a dedicated "kill" combo is important for unresponsive fullscreen apps, because appmenu doesn't show in those
# - although better may be to force appmenu to show over FS apps
# - bonsai mappings are static, so buttons can't benefit from non-compounding unless they're mapped accordingly for all lock states
# - this limitation could be removed, but with work
#
# example of a design which considers these things:
# - when unlocked:
# - volup tap -> app menu
# - volup hold -> file browser
# - voldown press -> keyboard
# - voldown hold -> terminal
# - power x2 -> screenoff
# - power tap->hold -> kill app
# - power,volup -> screen rotate CCW
# - power,voldown -> screen rotate CW
# - power+volup -> screenshot
# - power+voldown -> camera
# - when locked:
# - volup tap -> volume up
# - volup hold -> media seek forward
# - voldown tap -> volume down
# - voldown hold -> media seek backward
# - power tap -> screen on
# - power hold -> play/pause media
# limitations/downsides:
# - voldown hold is over eager: easy to open terminals when phone is slow.
# - remap to voldown tap->hold ?
#
# EXAMPLE EVENT FIRINGS:
# - double-tap voldown:
# - voldown_start
# - voldown_tap_1
# - voldown_tap_2
# - hold voldown:
# - voldown_start
# - voldown_hold_1
# - voldown_hold_2
# - voldown_hold_3
# - hold power:
# - power_hold (notice: it doesn't fire power_start)
# - double-tap power:
# - power_tap_1
# - power_tap_2
# - power tap-then-hold:
# - power_tap_1
# - power_tap_1_hold
# increments to use for volume adjustment (in %)
VOL_INCR=5
KEYBOARD="${KEYBOARD:-wvkbd-mobintl}"
action="$1"
isTouchOn() {
# success if all touch inputs have their events enabled
swaymsg -t get_inputs --raw \
| jq --exit-status '. | map(select(.type == "touch")) | all(.libinput.send_events == "enabled")' \
> /dev/null
}
isScreenOn() {
# success if all outputs have power
swaymsg -t get_outputs --raw \
| jq --exit-status '. | all(.power)' \
> /dev/null
}
isAllOn() {
isTouchOn && isScreenOn
}
isInhibited() {
pidof rofi
}
ignore() {
true
}
inhibited() {
true
}
unmapped() {
true
}
allOn() {
swaymsg -- output '*' power true
swaymsg -- input type:touch events enabled
}
allOff() {
swaymsg -- output '*' power false
swaymsg -- input type:touch events disabled
}
toggleKeyboard() {
local kbpid=$(pidof "$KEYBOARD")
if [ -z "$kbpid" ] || ! ( env kill -s RTMIN+0 "$kbpid" ); then
echo "sane-input-handler: failed to toggle keyboard: $KEYBOARD"
fi
}
handleWith() {
state=
if [ -n "$_isInhibited" ]; then
state="inhibited+"
fi
if [ -n "$_isAllOn" ]; then
state="${state}on"
else
state="${state}off"
fi
echo "sane-input-handler: state=$state action=$action: handleWith: $@"
"$@"
exit 0
}
dispatchDefault() {
case "$action" in
"power_tap_2")
# power twice => screenoff
handleWith allOff
;;
"power_hold")
# power twice => toggle media player
handleWith playerctl play-pause
;;
volup_tap*)
handleWith wpctl set-volume @DEFAULT_AUDIO_SINK@ "$VOL_INCR"%+
;;
voldown_tap*)
handleWith wpctl set-volume @DEFAULT_AUDIO_SINK@ "$VOL_INCR"%-
;;
esac
}
dispatchOff() {
case "$action" in
"power_tap_1")
# power once => unlock
handleWith allOn
;;
volup_hold*)
handleWith playerctl position 30+
;;
voldown_hold*)
handleWith playerctl position 10-
;;
esac
}
dispatchOn() {
case "$action" in
# power_tap_1: intentional default to no-op (it's important this be unmapped, because events can be misordered with power_tap_1 arriving *after* power_tap_2)
# power_tap_2: intentional default to screenoff
"power_tap_1_hold")
# power tap->hold: kill active window
# TODO: disable this if locked (with e.g. schlock, swaylock, etc)
handleWith swaymsg kill
;;
"power_and_volup")
# power (hold) -> volup: take screenshot
handleWith sane-open-desktop sane-screenshot.desktop
;;
"power_and_voldown")
# power (hold) -> voldown: open camera
handleWith sane-open-desktop org.postmarketos.Megapixels.desktop
;;
"power_then_volup")
# power (tap) -> volup: rotate CCW
handleWith swaymsg -- output '-' transform 90 anticlockwise
;;
"power_then_voldown")
# power (tap) -> voldown: rotate CW
handleWith swaymsg -- output '-' transform 90 clockwise
;;
"volup_tap_1")
# volume up once: system menu
handleWith sane-open-desktop rofi.desktop
;;
"volup_hold_1")
# volume up hold: just browse files
handleWith sane-open-desktop rofi-filebrowser.desktop
;;
"voldown_start")
# volume down once: toggle keyboard
handleWith toggleKeyboard
;;
"voldown_hold_1")
# hold voldown to launch terminal
# note we already triggered the keyboard; that's fine: usually keyboard + terminal go together :)
handleWith sane-open-desktop xdg-terminal-exec.desktop
;;
"voldown_tap_1")
# swallow, to prevent keyboard from also triggering media controls
handleWith ignore
;;
voldown_hold_*)
# swallow, to prevent terminal from also triggering media controls
handleWith ignore
;;
esac
}
dispatchInhibited() {
case "$action" in
"power_tap_1_hold")
# power hold: escape hatch in case rofi has hung
handleWith killall -9 rofi
;;
*)
# eat everything else (and let rofi consume it)
handleWith inhibited
;;
esac
}
_isAllOn="$(isAllOn && echo 1 || true)"
if [ -z "$_isAllOn" ]; then
dispatchOff
else
_isInhibited="$(isInhibited && echo 1 || true)"
if [ -n "$_isInhibited" ]; then
dispatchInhibited
else
dispatchOn
fi
fi
dispatchDefault
handleWith unmapped