adaptive sync
This commit is contained in:
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
# Freeze / resume the focused window's process tree (SIGSTOP / SIGCONT).
|
||||
pid=$(xdotool getactivewindow getwindowpid 2>/dev/null) || exit 1
|
||||
|
||||
# collect pid plus all descendants (Wine/Proton games spawn several)
|
||||
descendants() {
|
||||
local p
|
||||
for p in $(pgrep -P "$1"); do
|
||||
echo "$p"
|
||||
descendants "$p"
|
||||
done
|
||||
}
|
||||
pids="$pid $(descendants "$pid")"
|
||||
name=$(ps -o comm= -p "$pid")
|
||||
|
||||
case "$(ps -o state= -p "$pid")" in
|
||||
T*) kill -CONT $pids && notify-send -t 1500 "Resumed" "$name ($pid)" ;;
|
||||
*) kill -STOP $pids && notify-send -t 1500 "Frozen" "$name ($pid)" ;;
|
||||
esac
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
# Freeze/resume the focused window on the controller's Home/guide button (BTN_MODE).
|
||||
# Stdlib only: reads raw evdev events. Gamepads are readable by the seat user.
|
||||
import glob, os, select, signal, struct, subprocess, time
|
||||
|
||||
EV_KEY, BTN_MODE = 1, 0x13c
|
||||
EVENT = struct.Struct('llHHi') # struct input_event
|
||||
LONG_BITS = struct.calcsize('l') * 8
|
||||
CMD = [os.path.expanduser('~/.config/i3/scripts/freeze-toggle')]
|
||||
|
||||
signal.signal(signal.SIGCHLD, signal.SIG_IGN) # auto-reap child processes
|
||||
|
||||
def has_btn_mode(path):
|
||||
try:
|
||||
with open(f'/sys/class/input/{os.path.basename(path)}/device/capabilities/key') as f:
|
||||
words = f.read().split()
|
||||
except OSError:
|
||||
return False
|
||||
bits = 0
|
||||
for w in words:
|
||||
bits = bits << LONG_BITS | int(w, 16)
|
||||
return bits >> BTN_MODE & 1
|
||||
|
||||
fds = {} # fd -> device path
|
||||
last = 0.0
|
||||
rescan = 0.0
|
||||
while True:
|
||||
if time.monotonic() >= rescan: # pick up hotplugged controllers
|
||||
for path in glob.glob('/dev/input/event*'):
|
||||
if path not in fds.values() and has_btn_mode(path):
|
||||
try:
|
||||
fds[os.open(path, os.O_RDONLY)] = path
|
||||
except OSError:
|
||||
pass
|
||||
rescan = time.monotonic() + 2
|
||||
ready, _, _ = select.select(list(fds), [], [], 2)
|
||||
for fd in ready:
|
||||
try:
|
||||
data = os.read(fd, EVENT.size * 64)
|
||||
except OSError: # controller unplugged
|
||||
os.close(fd)
|
||||
del fds[fd]
|
||||
continue
|
||||
for sec, usec, type_, code, value in EVENT.iter_unpack(data):
|
||||
if type_ == EV_KEY and code == BTN_MODE and value == 1:
|
||||
now = sec + usec / 1e6
|
||||
if now - last < 0.4: # GuliKit's Home button double-fires ~10ms apart
|
||||
continue
|
||||
last = now
|
||||
subprocess.Popen(CMD, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
Reference in New Issue
Block a user