170 lines
4.3 KiB
Bash
170 lines
4.3 KiB
Bash
#!/usr/bin/env bash
|
|
# tablet-mode — run commands as the machine is folded into, and out of, a tablet.
|
|
#
|
|
# sway has `bindswitch tablet:on`, but att_wm has none and river's init is a
|
|
# startup script rather than an IPC surface, so there is nothing to bind there.
|
|
# Reading SW_TABLET_MODE off evdev is the one mechanism that works in both
|
|
# sessions, which is why it is the only one used here — sway's bindswitch is
|
|
# left gating rotation and nothing else.
|
|
#
|
|
# evtest does the reading: without --grab it only listens, so the compositor
|
|
# still gets the switch, and its opening dump reports the *current* state. That
|
|
# last part is the reason it is evtest and not `libinput debug-events` — a
|
|
# session that starts already folded has no transition to react to.
|
|
|
|
set -uo pipefail
|
|
|
|
DEVICE="${TABLET_MODE_DEVICE:-}"
|
|
ON_CMD=""
|
|
OFF_CMD=""
|
|
VERBOSE=0
|
|
|
|
usage() {
|
|
cat <<-EOF
|
|
usage: tablet-mode [-d DEVICE] [--on CMD] [--off CMD] [-v]
|
|
|
|
-d, --device DEVICE evdev node to watch (default: found by capability)
|
|
--on CMD run CMD when the machine enters tablet mode
|
|
--off CMD run CMD when it leaves
|
|
-v, --verbose log every line evtest prints
|
|
|
|
Both commands run through \`bash -c\` with \$TABLET_MODE set to 1 or 0.
|
|
EOF
|
|
}
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-d | --device)
|
|
DEVICE="${2:?--device needs a value}"
|
|
shift 2
|
|
;;
|
|
--on)
|
|
ON_CMD="${2:?--on needs a value}"
|
|
shift 2
|
|
;;
|
|
--off)
|
|
OFF_CMD="${2:?--off needs a value}"
|
|
shift 2
|
|
;;
|
|
-v | --verbose)
|
|
VERBOSE=1
|
|
shift
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
printf 'tablet-mode: unknown argument: %s\n' "$1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
command -v evtest >/dev/null || {
|
|
printf 'tablet-mode: evtest not found in PATH\n' >&2
|
|
exit 1
|
|
}
|
|
|
|
log() { printf '[tablet-mode] %s\n' "$*"; }
|
|
vlog() {
|
|
[ "$VERBOSE" = 1 ] && log "$@"
|
|
return 0
|
|
}
|
|
|
|
# Find the switch by capability rather than by name or by-path: on fuji it is an
|
|
# intel-hid device that is registered after boot, so neither its event number
|
|
# nor its presence at our start time can be counted on. Not finding it is a
|
|
# non-zero exit, which is what makes systemd try us again a few seconds later.
|
|
#
|
|
# In /proc/bus/input/devices each device ends with its capability bitmaps, and
|
|
# SW_TABLET_MODE is bit 1 of the switch one. `H:` precedes `B:` in every record,
|
|
# so the handler list is always already in hand by the time the bitmap arrives.
|
|
find_device() {
|
|
local line handlers="" bitmap handler
|
|
while IFS= read -r line; do
|
|
case "$line" in
|
|
"H: Handlers="*)
|
|
handlers="${line#H: Handlers=}"
|
|
;;
|
|
"B: SW="*)
|
|
# A bitmap is hex words, most significant first; bit 1
|
|
# lives in the last of them.
|
|
bitmap="${line#B: SW=}"
|
|
bitmap="${bitmap##* }"
|
|
[ -n "$bitmap" ] || continue
|
|
(((0x$bitmap & 2) == 0)) && continue
|
|
for handler in $handlers; do
|
|
case "$handler" in
|
|
event*)
|
|
printf '/dev/input/%s' "$handler"
|
|
return 0
|
|
;;
|
|
esac
|
|
done
|
|
;;
|
|
esac
|
|
done </proc/bus/input/devices
|
|
return 1
|
|
}
|
|
|
|
if [ -z "$DEVICE" ]; then
|
|
DEVICE="$(find_device)" || {
|
|
printf 'tablet-mode: no input device reports SW_TABLET_MODE\n' >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
[ -r "$DEVICE" ] || {
|
|
printf 'tablet-mode: cannot read %s\n' "$DEVICE" >&2
|
|
exit 1
|
|
}
|
|
log "watching ${DEVICE}"
|
|
|
|
state="" # last seen switch state, "" until evtest reports the first one
|
|
|
|
apply() {
|
|
local cmd
|
|
case "$1" in
|
|
1) cmd="$ON_CMD" ;;
|
|
0) cmd="$OFF_CMD" ;;
|
|
*) return 0 ;;
|
|
esac
|
|
[ -n "$cmd" ] || return 0
|
|
TABLET_MODE="$1" bash -c "$cmd" || log "hook failed: ${cmd}"
|
|
}
|
|
|
|
exec 3< <(stdbuf -oL evtest "$DEVICE" 2>&1)
|
|
evtest_pid=$!
|
|
# Only kill the child here — an `exit` in an EXIT trap would overwrite the
|
|
# status below, and systemd needs to see a failure to restart us.
|
|
trap 'kill "$evtest_pid" 2>/dev/null' EXIT
|
|
trap 'exit 0' INT TERM
|
|
|
|
while IFS= read -r line <&3; do
|
|
case "$line" in
|
|
# A transition:
|
|
# Event: time ..., type 5 (EV_SW), code 1 (SW_TABLET_MODE), value 1
|
|
*"(SW_TABLET_MODE), value "*)
|
|
new="${line##*, value }"
|
|
;;
|
|
# The opening dump, which is where the state at startup comes from:
|
|
# Event code 1 (SW_TABLET_MODE) state 0
|
|
*"(SW_TABLET_MODE) state "*)
|
|
new="${line##* state }"
|
|
;;
|
|
*)
|
|
vlog "$line"
|
|
continue
|
|
;;
|
|
esac
|
|
|
|
[ "$new" = "$state" ] && continue
|
|
state="$new"
|
|
if [ "$state" = 1 ]; then log "tablet mode on"; else log "tablet mode off"; fi
|
|
apply "$state"
|
|
done
|
|
|
|
log "evtest exited"
|
|
exit 1
|