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:
jenz 2026-07-03 12:10:33 +01:00
parent 2024f4cbe0
commit 53787723c1
3 changed files with 38 additions and 15 deletions

View File

@ -35,7 +35,7 @@ overlay /mnt/merged-cstrike/models overlay ro,lowerdir=/home/gameservers/css_
overlay /mnt/merged-cstrike/sound overlay ro,lowerdir=/home/gameservers/css_ze/cstrike/sound:/home/gameservers/css_mg/cstrike/sound:/home/gameservers/css_zr/cstrike/sound,nofail 0 0
## third run
docker run -d --name css-test -e SIG_URL=ws://172.17.0.1:3000 -e CONTAINER_NAME=css-test -e CF_APP_ID= -e CF_APP_TOKEN= -e CAP_FPS=60 -e CAP_W=800 -e CAP_H=600 --cpus="8.0" \
docker run -d --name css-test -e SIG_URL=ws://172.17.0.1:3000 -e CONTAINER_NAME=css-test -e CF_APP_ID= -e CF_APP_TOKEN= -e CAP_FPS=60 -e CAP_W=800 -e CAP_H=600 --cpus="10.0" \
-v /mnt/merged-cstrike/maps:/home/ubuntu/Counter-Strike\ Source/cstrike/maps:ro \
-v /mnt/merged-cstrike/materials:/home/ubuntu/Counter-Strike\ Source/cstrike/materials:ro \
-v /mnt/merged-cstrike/models:/home/ubuntu/Counter-Strike\ Source/cstrike/models:ro \
@ -64,3 +64,5 @@ css-client
## CSS DirectX9 → built-in DXVK → Vulkan → Wine Wayland driver → Weston (lavapipe/llvmpipe) → Xwayland → X11 → ximagesrc capture for gst_agent
## using DXVK cache and MESA_SHADER_CACHE which are mounted volumes
## the inbuilt DXVK version 2.3 is replaced with DXVK 3.0 downloaded from https://github.com/doitsujin/dxvk/releases/download/v3.0/dxvk-3.0.tar.gz

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 ! '

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);