#!/bin/dash
#
# One-shot: attempt HID rebind for input event nodes that have no phase-1 marker.
# Intended to be run *after* the graphical session is up (e.g. from a small
# systemd user service or a manual admin wrapper), not from coldplug SysV.
#
# Why not a plain vdev "rescan"?  A sysfs walk does not replay the remove/add
# sequence that libinput/X typically observe; unbind/bind on the HID device node
# is closer to a synthetic hotplug for missed USB HID stacks.
#
# Markers are written by input-phase1-mark.sh (vdev action) into:
#   /run/vdev/phase1-input-seen/<encoded DEVPATH>
#
# Environment (optional):
#   REBIND_EXECUTE=1          Perform unbind/bind (default: dry-run only)
#   REBIND_USB_HID_ONLY=1     Only treat HID devices under bus hid + 0003:* (default: 1)
#   REBIND_MAX=8              Cap number of devices touched (default: 8)
#
# Limitations (read before REBIND_EXECUTE=1):
# - Devices hotplugged after boot also lack markers → false positives unless you
#   gate this script to a narrow boot window or an explicit allowlist.
# - Rebind can briefly disconnect input; keep REBIND_MAX low and exclude critical paths.
#

set -u

MARK_DIR=/run/vdev/phase1-input-seen
LOCK_DIR=/run/vdev
LOCK="$LOCK_DIR/rebind-missed-input.lock"

_encode_key() {
	printf '%s' "$1" | tr '/' '_' | tr -d '\n'
}

_hid_parent_of_event() {
	_ev="$1"
	_real=""
	_real=$(readlink -f "$_ev" 2>/dev/null) || return 1
	# .../input/inputN/eventM -> drop /eventM -> .../input/inputN
	_inp=$(dirname "$_real") || return 1
	# parent of inputN is often 0003:VID:PID.XXXX (hid device)
	_par=$(dirname "$_inp") || return 1
	printf '%s\n' "$_par"
}

_dry_run() {
	[ "${REBIND_EXECUTE:-0}" != "1" ]
}

_usb_hid_only() {
	[ "${REBIND_USB_HID_ONLY:-1}" = "1" ]
}

_max() {
	printf '%s\n' "${REBIND_MAX:-8}"
}

mkdir -p "$LOCK_DIR" 2>/dev/null || true
if ! mkdir "$LOCK" 2>/dev/null; then
	echo "rebind-missed-input: already running or stale lock ($LOCK)" >&2
	exit 0
fi
# shellcheck disable=SC2064
trap 'rmdir "$LOCK" 2>/dev/null; exit' EXIT INT TERM

_max_n=$(_max)
_done=0

for _ev in /sys/class/input/event*; do
	[ -e "$_ev/uevent" ] || continue
	_devpath=$(grep '^DEVPATH=' "$_ev/uevent" 2>/dev/null | head -n 1 | sed 's/^DEVPATH=//')
	[ -n "$_devpath" ] || continue
	_key=$(_encode_key "$_devpath")
	[ -f "$MARK_DIR/$_key" ] && continue

	_hid=$(_hid_parent_of_event "$_ev") || continue
	_base=$(basename "$_hid")
	case "$_base" in
	0003:*|0011:*|0019:*) ;;
	*) continue ;;
	esac
	if _usb_hid_only; then
		case "$_base" in
		0003:*) ;;
		*) continue ;;
		esac
	fi
	[ -L "$_hid/driver" ] || continue
	_drv=$(basename "$(readlink "$_hid/driver")")
	[ -n "$_drv" ] || continue

	if _dry_run; then
		echo "rebind-missed-input: would rebind hid=$_base driver=$_drv (no marker $_key)" >&2
	else
		# HID core uses /sys/bus/hid/drivers/<driver>/<hid-id>/
		if [ -w "/sys/bus/hid/drivers/$_drv/unbind" ] 2>/dev/null; then
			printf '%s\n' "$_base" >"/sys/bus/hid/drivers/$_drv/unbind" 2>/dev/null || true
			sleep 0.2
			printf '%s\n' "$_base" >"/sys/bus/hid/drivers/$_drv/bind" 2>/dev/null || true
		else
			echo "rebind-missed-input: no writable unbind for driver=$_drv hid=$_base" >&2
		fi
	fi
	_done=$((_done + 1))
	[ "$_done" -ge "$_max_n" ] && break
done

exit 0
