added update for xdotool to prevent keyboard from going stuck after long session. explained in readme that the DXVK version is replaced

This commit is contained in:
2026-07-03 12:10:33 +01:00
parent 2024f4cbe0
commit 53787723c1
3 changed files with 38 additions and 15 deletions
+26 -14
View File
@@ -79,6 +79,20 @@ def xdotool(*args):
stderr=subprocess.DEVNULL,
)
# Track held keys so we can release them all on controller change
_held_keys = set()
def release_all_keys():
# Release all currently held keys and reset X11 modifier state
for key in list(_held_keys):
try:
xdotool('keyup', key)
except Exception:
pass
_held_keys.clear()
# Reset X11 modifier state (clears stuck shift/ctrl/alt etc.)
xdotool('key', '--clearmodifiers', 'Escape')
def handle_input(event):
t = event.get('type')
if t == 'mouse_move':
@@ -91,12 +105,18 @@ def handle_input(event):
elif t == 'mouse_up':
xdotool('mouseup', str(event.get('button', 1)))
elif t == 'key_down':
xdotool('keydown', event.get('key', ''))
key = event.get('key', '')
_held_keys.add(key)
xdotool('keydown', key)
elif t == 'key_up':
xdotool('keyup', event.get('key', ''))
key = event.get('key', '')
_held_keys.discard(key)
xdotool('keyup', key)
elif t == 'mouse_wheel':
button = '4' if event.get('direction') == 'up' else '5'
xdotool('click', button)
elif t == 'release_keys':
release_all_keys()
# ── GStreamer Pipeline ────────────────────────────────────────────────────────
class CSSStreamAgent:
@@ -125,18 +145,10 @@ class CSSStreamAgent:
f'webrtcbin. '
# Game audio: pipewiresrc → Opus
# pipewiresrc with no target follows PipeWire's default *capture*
# node (the microphone, virtual_mic) — not the default sink's
# monitor — so without explicit targeting it silently captured
# the mic into this track instead of game audio.
# game_output is a single PipeWire node with both playback ports
# (what CSS writes to) and monitor ports (the loopback read side)
# on the same node — there's no separate "game_output.monitor"
# node. stream.capture.sink=true tells PipeWire's autoconnect to
# link to that node's monitor/capture-sink ports rather than
# treating it as a regular source.
f'pipewiresrc target-object=game_output '
f'stream-properties="props,stream.capture.sink=(boolean)true" ! '
# pipewiresrc is the native PipeWire GStreamer source (Ubuntu 24+/26).
# It connects to the default audio output monitor via PipeWire directly,
# avoiding the PulseAudio compatibility layer entirely.
f'pipewiresrc ! '
f'audio/x-raw,format=F32LE,rate=48000,channels=2 ! '
f'audioconvert ! '
f'opusenc ! '
+9
View File
@@ -182,6 +182,15 @@ function releaseControl(entry, reason) {
entry.ws.send(JSON.stringify({ type: 'control_released', reason }));
}
// Tell gst_agent to release all held keys and reset X11 modifier state.
// Without this, stuck keys (shift/ctrl/w/etc.) persist across controller
// handoffs and eventually corrupt xdotool's input state, requiring a
// full container restart to recover.
const agent = [...agentSessions.values()][0];
if (agent && agent.agentWs && agent.agentWs.readyState === WebSocket.OPEN) {
agent.agentWs.send(JSON.stringify({ type: 'release_keys' }));
}
if (controlQueue.length > 0) {
const next = controlQueue.shift();
applyController(next);