From 12f2e844d560fc2fcad698c1a2b263c2a636b335 Mon Sep 17 00:00:00 2001 From: CuriousJ Date: Wed, 30 Sep 2026 21:06:18 -0400 Subject: [PATCH 1/2] Input relay: retry a new device until udev gives it to the input group A new /dev/input node is root:root 0600 until udev applies GROUP=input. The scan probed each new node once and marked it seen even when the open failed, so a node caught in that gap was never opened. Behind a KVM, a switch brings back a hub of devices at once: on the Frame, four nodes failed with EACCES in one switch, the keyboard was never grabbed, and its keys went to gamescope instead of the desktop screens. A node that isn't readable yet now waits for the next scan. --- input/input-relay.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/input/input-relay.py b/input/input-relay.py index 2c36788..8f6c861 100755 --- a/input/input-relay.py +++ b/input/input-relay.py @@ -1139,6 +1139,11 @@ def main(): # New here: a new device, or one that came back in the same place. for old in [n for n in nodes.values() if n.path == path]: drop(old, "replaced by a new device node") + # A new node is root's alone until udev gives it to the input group, a moment + # after it appears. Opened in that gap, it would fail and never be tried + # again: leave it for the next scan instead. + if not os.access(path, os.R_OK): + continue seen[path] = ino node = probe(path) if node and node.volume_keys and not take_volume(node): From 82d2107e1d5e97b69eadae379a4e023d52924f48 Mon Sep 17 00:00:00 2001 From: CuriousJ Date: Fri, 2 Oct 2026 09:17:16 -0400 Subject: [PATCH 2/2] Screens: take a screen's overlays from one copy in the catcher While a button pressed on a screen is held, UpdateCatcher checks every tick whether the laser is still on one of the screen's overlays. It built that list from s.All().begin() and s.All().end(), but All() returns a std::array by value: iterators into two different temporaries, which is undefined behaviour. A clang build of ft-screens got a garbage length, threw std::length_error, and aborted on the first click, taking KWin and the desktop with it. --- screens/vr.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/screens/vr.cpp b/screens/vr.cpp index 90624fa..065a869 100644 --- a/screens/vr.cpp +++ b/screens/vr.cpp @@ -1330,7 +1330,8 @@ void UpdateCatcher() { for (int k = 0; k < 3; ++k) params.vSource.v[k] = l.m[k][3], params.vDirection.v[k] = -l.m[k][2]; for (auto &[i, s] : g_screens) { if (!s.visible) continue; - std::vector parts(s.All().begin(), s.All().end()); + const auto all = s.All(); // one copy: two calls give two temporaries, not one range + std::vector parts(all.begin(), all.end()); for (const auto &[k, sub] : s.subs) parts.push_back(sub.overlay); for (auto o : parts) { vr::VROverlayIntersectionResults_t hit;