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:
@@ -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 ! '
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user