diff --git a/client.html b/client.html
index a08ba14..42b2487 100644
--- a/client.html
+++ b/client.html
@@ -1,7 +1,7 @@
-
+
-
CSS Stream
@@ -598,7 +597,11 @@
if (e.key === '"') { e.preventDefault(); return; }
if (e.key === 'F11') { e.preventDefault(); return; }
if (e.key === 'Escape') {
- if (isControlling) {
+ // If pointer is locked, Escape releases pointer lock — don't also
+ // forward it to the game or it opens the in-game escape menu.
+ // Only forward Escape when pointer is already unlocked (user is
+ // navigating the in-game menu with mouse released).
+ if (isControlling && !pointerLocked) {
sendInput({ type: 'key_down', key: 'Escape' });
sendInput({ type: 'key_up', key: 'Escape' });
}
diff --git a/streaming-agent/gst_agent.py b/streaming-agent/gst_agent.py
index c9b0627..051539a 100755
--- a/streaming-agent/gst_agent.py
+++ b/streaming-agent/gst_agent.py
@@ -90,8 +90,9 @@ def release_all_keys():
except Exception:
pass
_held_keys.clear()
- # Reset X11 modifier state (clears stuck shift/ctrl/alt etc.)
- xdotool('key', '--clearmodifiers', 'Escape')
+ # Reset X11 modifier state using a neutral key that has no in-game effect
+ # Escape was previously used here but it toggles the in-game menu
+ xdotool('key', '--clearmodifiers', 'ctrl')
def handle_input(event):
t = event.get('type')
@@ -106,10 +107,16 @@ def handle_input(event):
xdotool('mouseup', str(event.get('button', 1)))
elif t == 'key_down':
key = event.get('key', '')
+ # Block characters that can be used for console command injection
+ # via chat. ";" splits commands and '"' opens/closes command strings.
+ if key in (';', 'semicolon', '"', 'quotedbl', 'quote'):
+ return
_held_keys.add(key)
xdotool('keydown', key)
elif t == 'key_up':
key = event.get('key', '')
+ if key in (';', 'semicolon', '"', 'quotedbl', 'quote'):
+ return
_held_keys.discard(key)
xdotool('keyup', key)
elif t == 'mouse_wheel':