#!/usr/bin/env python3 """Input relay for the Steam Frame: stable virtual devices, the 3D pointer, device rules. SteamVR opens /dev/input/event* only when it starts and never hotplugs, so a Bluetooth mouse that sleeps and reconnects (new event nodes) stops working until SteamVR restarts. This relay creates a virtual mouse and a virtual keyboard through /dev/uinput once, before SteamVR starts, and feeds them from the physical devices as they come and go. Every USB or Bluetooth mouse and keyboard is a candidate. A physical device is identified by its Bluetooth address (EVIOCGUNIQ) or USB bus:vendor:product:name, so all its event nodes share one role (the Swiftpoint Z3 has a mouse node and a keyboard node for its extra buttons). Roles, from ~/.config/frametop-input.json (written by the Frametop Input Settings app): pointer grabbed; drives the universal 3D mouse (default for devices with a mouse node) passthrough keys go to the desktop; grabbed only while typing goes there, otherwise only observed, e.g. for the Meta dashboard shortcut (default for keyboards; the shortcut is off unless META_DASHBOARD=1 is in ~/.config/frametop.conf) ignore not grabbed, only observed for identification in the settings app Buttons and keys of pointer devices go through a per-device map to actions (left, right, middle, back, scroll_up, scroll_down, dashboard, recenter, pointer_toggle, follow_toggle = head follow on or off, gaze_toggle = gaze mode on or off (the pointer goes where you look; see pointer/helper/ft-pointer.cpp), gaze_precision = while held, the pointer stops where you look and the mouse steers it, and the release clicks there, gaze_drag = the same, but pressed at once, so it drags ("precision|gazedrag mouse|keyboard 1|0" to the helper), sens_up, sens_down, layout_reset = put the desktop screens back in their saved layout, screens_toggle = hide or show the desktop screens, keyboard_toggle = open or close Frametop's keyboard, float_toggle = float the desktop window under the pointer (else the active one) in VR, or put it back if it floats, dock_all = put every floating window back (both to ft-floatd, @frametop_float), profile:NAME = switch to that profile (ft-layout use NAME: its screens and apps; docs/profiles.md), key = pass through as a key, none). Frame controller buttons can be mapped too ("controller_buttons": {"right/a": action} in the rules file; any action but key and the gaze ones, GAZE_ACTIONS: gaze mode is a mouse feature, docs/gaze-controllers.md). So can key combinations on any keyboard ("key_bindings": {"29+56+34": action}, evdev codes joined by "+", modifiers first and left-hand codes for either side, here Ctrl+Alt+G): the combination does the action, and its last key isn't typed. A rules file without "key_bindings" gets DEFAULT_KEY_BINDINGS (Meta+Shift+F: float_toggle); one with its own, even an empty one, doesn't. The float actions work without pointer mode too. The controllers aren't input devices here, only SteamVR sees them, so the pointer helper reads them with SteamVR input and sends "vrbtn