mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 01:00:06 +02:00
Merge PR #8 fixes 1 and 2 into experimental
From 0x1f6's robustness PR: survive a malformed control datagram (adapted to wrap experimental's textfield command too) and plan a layout when the rows setting exceeds what the screens fill. The PR's third commit, the volume-key swap rollback, is left out: rolling back a partial remap leaves every real volume key exposed to gamescope instead of some, and on restore it turns already-restored keys back into stand-ins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
46e5c59cc0
2 files changed
+52
-42
No files matched your search
+48
-42
@@ -910,48 +910,54 @@ def main():
|
|||||||
return
|
return
|
||||||
words = data.decode(errors="replace").split()
|
words = data.decode(errors="replace").split()
|
||||||
cmd = words[0] if words else ""
|
cmd = words[0] if words else ""
|
||||||
if cmd == "keyboard":
|
# One malformed datagram must not end the relay: it would drop every grab,
|
||||||
# From ft-screens (unbound, no reply): where typing goes, repeated every second.
|
# including the volume keys that keep gamescope from aborting. The control
|
||||||
desktop = len(words) > 1 and words[1] == "desktop"
|
# socket is an abstract socket, so any local process can send to it.
|
||||||
state["desktop_until"] = now + 3.0 if desktop else 0.0
|
try:
|
||||||
continue
|
if cmd == "keyboard":
|
||||||
if cmd == "vrbtn" and len(words) == 3 and words[1] in VR_BUTTONS and words[2] in ("0", "1"):
|
# From ft-screens (unbound, no reply): where typing goes, repeated every second.
|
||||||
vr_button(words[1], int(words[2]), now)
|
desktop = len(words) > 1 and words[1] == "desktop"
|
||||||
continue
|
state["desktop_until"] = now + 3.0 if desktop else 0.0
|
||||||
if cmd == "vrhello":
|
continue
|
||||||
vr_bind(now)
|
if cmd == "vrbtn" and len(words) == 3 and words[1] in VR_BUTTONS and words[2] in ("0", "1"):
|
||||||
continue
|
vr_button(words[1], int(words[2]), now)
|
||||||
if cmd == "textfield" and len(words) == 2:
|
continue
|
||||||
text_field(words[1] == "1")
|
if cmd == "vrhello":
|
||||||
continue
|
vr_bind(now)
|
||||||
if cmd == "gazeawake" and len(words) == 2:
|
continue
|
||||||
if state["pointer"]:
|
if cmd == "textfield" and len(words) == 2:
|
||||||
state["pointer"].gaze_awake_until = now + 12.0 if words[1] == "1" else 0.0
|
text_field(words[1] == "1")
|
||||||
continue
|
continue
|
||||||
if not addr:
|
if cmd == "gazeawake" and len(words) == 2:
|
||||||
continue # unbound sender, nowhere to reply
|
if state["pointer"]:
|
||||||
if cmd == "devices":
|
state["pointer"].gaze_awake_until = now + 12.0 if words[1] == "1" else 0.0
|
||||||
reply(addr, {"t": "devices", "pointer_mode": state["pointer"] is not None,
|
continue
|
||||||
"actions": ACTIONS,
|
if not addr:
|
||||||
"nodes": [n.describe() for n in nodes.values() if n.candidate]})
|
continue # unbound sender, nowhere to reply
|
||||||
elif cmd == "watch":
|
if cmd == "devices":
|
||||||
seconds = float(words[1]) if len(words) > 1 else 30
|
reply(addr, {"t": "devices", "pointer_mode": state["pointer"] is not None,
|
||||||
watchers[addr] = now + min(seconds, 600)
|
"actions": ACTIONS,
|
||||||
reply(addr, {"t": "watching", "seconds": seconds})
|
"nodes": [n.describe() for n in nodes.values() if n.candidate]})
|
||||||
elif cmd == "reload":
|
elif cmd == "watch":
|
||||||
load_config()
|
seconds = float(words[1]) if len(words) > 1 else 30
|
||||||
apply_roles()
|
watchers[addr] = now + min(seconds, 600)
|
||||||
if state["pointer"]:
|
reply(addr, {"t": "watching", "seconds": seconds})
|
||||||
state["pointer"].send("reload")
|
elif cmd == "reload":
|
||||||
vr_bind(now)
|
load_config()
|
||||||
reply(addr, {"t": "reloaded"})
|
apply_roles()
|
||||||
elif cmd == "vrcapture":
|
if state["pointer"]:
|
||||||
seconds = float(words[1]) if len(words) > 1 else 30
|
state["pointer"].send("reload")
|
||||||
state["vr_capture_until"] = now + min(seconds, 120) if seconds > 0 else 0.0
|
vr_bind(now)
|
||||||
vr_bind(now)
|
reply(addr, {"t": "reloaded"})
|
||||||
reply(addr, {"t": "vrcapture", "seconds": seconds})
|
elif cmd == "vrcapture":
|
||||||
else:
|
seconds = float(words[1]) if len(words) > 1 else 30
|
||||||
reply(addr, {"t": "error", "error": f"unknown command {cmd!r}"})
|
state["vr_capture_until"] = now + min(seconds, 120) if seconds > 0 else 0.0
|
||||||
|
vr_bind(now)
|
||||||
|
reply(addr, {"t": "vrcapture", "seconds": seconds})
|
||||||
|
else:
|
||||||
|
reply(addr, {"t": "error", "error": f"unknown command {cmd!r}"})
|
||||||
|
except Exception as e:
|
||||||
|
log(f"bad control datagram {data!r}: {e!r}")
|
||||||
|
|
||||||
def broadcast(node, etype, code, value, now):
|
def broadcast(node, etype, code, value, now):
|
||||||
if not watchers or not node.candidate:
|
if not watchers or not node.candidate:
|
||||||
|
|||||||
@@ -318,6 +318,10 @@ def plan(layout, count, panel_size=None):
|
|||||||
p = layout["preset"]
|
p = layout["preset"]
|
||||||
rows = max(1, min(int(p.get("rows", 1)), count))
|
rows = max(1, min(int(p.get("rows", 1)), count))
|
||||||
cols = math.ceil(count / rows)
|
cols = math.ceil(count / rows)
|
||||||
|
# A row setting above what the screens fill leaves empty grid rows (4 screens,
|
||||||
|
# 3 rows -> cols 2 -> a third row with nothing in it), and max() over an empty
|
||||||
|
# row crashes plan(). Trim rows to what the screens actually fill.
|
||||||
|
rows = max(1, math.ceil(count / cols))
|
||||||
d = max(0.3, float(p.get("distance", 2.0)))
|
d = max(0.3, float(p.get("distance", 2.0)))
|
||||||
gap = max(0.0, float(p.get("gap", 0.05)))
|
gap = max(0.0, float(p.get("gap", 0.05)))
|
||||||
height = float(p.get("height", 0.0))
|
height = float(p.get("height", 0.0))
|
||||||
|
|||||||
Reference in new issue
Block a user