input_handler
Server-side input, clipboard, cursor, and gamepad injection for Selkies.
Both transports (WebSockets and WebRTC) feed client messages into one WebRTCInput handler, so input authority and fallback behavior stay identical across modes. Injection follows fallback ladders that resolve the newest mechanism first and degrade rung by rung, with cooldowns that re-probe the top rung rather than latching into a fallback:
- Wayland keyboard: the compositor seat keymap (
_WaylandKeymapOwner), then the in-process zwp_virtual_keyboard client, then a data-control clipboard paste. The Wayland path is subprocess-free by design; never reintroduce wtype, wl-copy or similar forks where the in-process pixelflux harness exists. - X11 keyboard: in-process XTEST (
_XTestKeyboard, with a dynamic spare-keycode overlay for unmapped keysyms and an XKB group lock for keysyms a later layout group carries), then an xdotool fork. - Clipboard: native event-driven monitors (XFixes on X11, compositor callbacks / data-control on Wayland), with xclip polling as the X11 fallback.
Under a nested app compositor the seat rung only carries keysyms the base
layout resolves, so the keyboard worker routes character-bearing keysyms the
base lacks onto the virtual-keyboard text batch by classification, not by
failure. That batch and the selection are aimed at the auto-detected app
compositor: the live wayland-\<N> socket beside the capture compositor's,
never a differently named relay; aiming at the capture compositor instead
silently kills every non-ASCII key.
Input authority is enforced here, shared by both transports, so a modified
client cannot inject input a controller did not grant: a read-only viewer
peer (shared/#player co-op) may send only VIEWER_ALLOWED_PREFIXES; a viewer
holding the active mk token while enable_collab is on (a read-write
collaborator) may additionally send VIEWER_COLLAB_EXTRA_PREFIXES — the
keyboard, mouse and clipboard set, including co, because IME commits and
atomic typing arrive that way. cmd and every settings-mutating message stay
controller-only. Blur/visibility lifecycle noise (VIEWER_SILENT_DROP_PREFIXES)
from a read-only viewer is normal operation and is dropped without a warning.
Blocking X and compositor work runs on worker threads or dedicated event threads; the asyncio loop only queues, dispatches, and awaits, so input never stalls behind a slow display server.
attributelibxkb= ctypes.CDLL('libxkbcommon.so.0')attributeX11_LIBS_AVAILABLE= TrueattributeVIEWER_ALLOWED_PREFIXES= ('SETTINGS,', 'START_VIDEO', 'STOP_VIDEO', 'REQUEST_KEYFRAME', 'js,')attributeVIEWER_COLLAB_EXTRA_PREFIXES= ('kd', 'ku', 'kh', 'kr', 'm', 'm2', 'co,', 'cws', 'cbs', 'cwd', 'cbd', 'cwe', 'cbe', 'cw', 'cb', 'cr', 'REQUEST_CLIPBOARD')attributeVIEWER_SILENT_DROP_PREFIXES= ('kr', 'cr')attributelogger_webrtc_input= logging.getLogger('webrtc_input')attributelogger_selkies_gamepad= logging.getLogger('selkies_gamepad')attributeMULTIPART_CLIPBOARD_MAX_SIZE= 64 * 1024 * 1024attributeINPUT_X_REPLY_TIMEOUT_S= 20.0attributeINPUT_X_EVENT_POLL_S= 0.02attributeEV_SYN= 0attributeEV_KEY= 1attributeEV_REL= 2attributeEV_ABS= 3attributeEV_MSC= 4attributeSYN_REPORT= 0attributeBTN_MOUSE= 272attributeBTN_LEFT= 272attributeBTN_RIGHT= 273attributeBTN_MIDDLE= 274attributeBTN_SIDE= 275attributeBTN_EXTRA= 276attributeBTN_A= 304attributeBTN_B= 305attributeBTN_C= 306attributeBTN_X= 307attributeBTN_Y= 308attributeBTN_Z= 309attributeBTN_TL= 310attributeBTN_TR= 311attributeBTN_SELECT= 314attributeBTN_START= 315attributeBTN_MODE= 316attributeBTN_THUMBL= 317attributeBTN_THUMBR= 318attributeABS_X= 0attributeABS_Y= 1attributeABS_Z= 2attributeABS_RX= 3attributeABS_RY= 4attributeABS_RZ= 5attributeABS_HAT0X= 16attributeABS_HAT0Y= 17attributeJS_EVENT_BUTTON= 1attributeJS_EVENT_AXIS= 2attributeJS_EVENT_INIT= 128attributeINTERPOSER_MAX_BTNS= 512attributeINTERPOSER_MAX_AXES= 64attributeCONTROLLER_NAME_MAX_LEN= 255attributeC_INTERPOSER_STRUCT_SIZE= 1360attributeBULK_DRAIN_TIMEOUT_S= 15.0attributeCLIPBOARD_CHUNK_SIZE= min((WS_MAX_MESSAGE_BYTES - 4096) * 3 // 4, (1024 * 1024 - 512) * 3 // 4)attributeKEYSYM_ALT_L= 65513attributeKEYSYM_LEFT_ARROW= 65361attributeKEYSYM_RIGHT_ARROW= 65363attributeCYRILLIC_TO_QWERTY_KEYSYM= {1738: 113, 1731: 119, 1749: 101, 1739: 114, 1733: 116, 1742: 121, 1735: 117, 1755: 105, 1757: 111, 1754: 112, 1734: 97, 1753: 115, 1751: 100, 1729: 102, 1744: 103, 1746: 104, 1743: 106, 1740: 107, 1732: 108, 1745: 122, 1758: 120, 1747: 99, 1741: 118, 1737: 98, 1748: 110, 1752: 109}attributeEXPECTED_C_STRUCT_SIZEint= ctypes.sizeof(JsConfigCtypes)attributeABS_MIN_VAL= -32767attributeABS_MAX_VAL= 32767attributeABS_TRIGGER_MIN_VAL= 0attributeABS_TRIGGER_MAX_VAL= 255attributeABS_HAT_MIN_VAL= -1attributeABS_HAT_MAX_VAL= 1attributeSTANDARD_XPAD_CONFIG= {'name': 'Microsoft X-Box 360 pad', 'vendor_id': 1118, 'product_id': 654, 'version': 276, 'btn_map': [BTN_A, BTN_B, BTN_X, BTN_Y, BTN_TL, BTN_TR, BTN_SELECT, BTN_START, BTN_MODE, BTN_THUMBL, BTN_THUMBR], 'axes_map': [ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ, ABS_HAT0X, ABS_HAT0Y], 'mapping': {'btns': {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 8: 6, 9: 7, 10: 9, 11: 10, 16: 8}, 'axes': {0: 0, 1: 1, 2: 3, 3: 4}, 'client_btns_to_internal_axes': {6: 2, 7: 5}, 'dpad_to_hat': {12: (7, -1), 13: (7, 1), 14: (6, -1), 15: (6, 1)}, 'trigger_internal_abstract_axis_indices': [2, 5], 'hat_internal_abstract_axis_indices': [6, 7]}}attributeUINPUT_PATH= '/dev/uinput'attributeUINPUT_SYSFS_BASE= '/sys/devices/virtual/input'attributeUINPUT_MAX_NAME_SIZE= 80attributeBUS_USB= 3attributeUINPUT_SETUP_FMT= '=HHHH80sI'attributeUINPUT_ABS_SETUP_FMT= '=H2x6i'attributeUINPUT_SYSNAME_LEN= 64attributeUI_DEV_CREATE= _uinput_ioc(0, 1, 0)attributeUI_DEV_DESTROY= _uinput_ioc(0, 2, 0)attributeUI_DEV_SETUP= _uinput_ioc(_IOC_WRITE, 3, struct.calcsize(UINPUT_SETUP_FMT))attributeUI_ABS_SETUP= _uinput_ioc(_IOC_WRITE, 4, struct.calcsize(UINPUT_ABS_SETUP_FMT))attributeUI_SET_EVBIT= _uinput_ioc(_IOC_WRITE, 100, 4)attributeUI_SET_KEYBIT= _uinput_ioc(_IOC_WRITE, 101, 4)attributeUI_SET_ABSBIT= _uinput_ioc(_IOC_WRITE, 103, 4)attributeUI_GET_SYSNAME= _uinput_ioc(_IOC_READ, 44, UINPUT_SYSNAME_LEN)attributeUINPUT_ABS_INFO_DEFAULT= (ABS_MIN_VAL, ABS_MAX_VAL, 16, 128, 1)attributeUINPUT_ABS_INFO= {ABS_HAT0X: (ABS_HAT_MIN_VAL, ABS_HAT_MAX_VAL, 0, 0, 0), ABS_HAT0Y: (ABS_HAT_MIN_VAL, ABS_HAT_MAX_VAL, 0, 0, 0)}attributeLOCAL_ARCH_BITS= 64 if struct.calcsize('P') == 8 else 32attributeX11_APP_TERMINALS= ('st', 'xterm -e')attributeWAYLAND_APP_TERMINALS= ('foot', 'st', 'xterm -e')attributeAPP_RUNNER= 'selkies-proot'attributeAPP_RUNNER_CHECK_TIMEOUT_S= 10.0attributeSESSION_ENV_ADOPTED= ('DBUS_SESSION_BUS_ADDRESS', 'XAUTHORITY', 'XDG_CURRENT_DESKTOP', 'DESKTOP_SESSION', 'XDG_SESSION_DESKTOP', 'XDG_MENU_PREFIX', 'XDG_SESSION_CLASS', 'XDG_SESSION_ID')attributeCOMMAND_TAIL_LINES= 20attributeCOMMAND_LINE_CHARS= 4096attributeCOMMAND_REASON_CHARS= 200attributeMOUSE_POSITION= 10attributeMOUSE_MOVE= 11attributeMOUSE_SCROLL_UP= 20attributeMOUSE_SCROLL_DOWN= 21attributeMOUSE_SCROLL_LEFT= 22attributeMOUSE_SCROLL_RIGHT= 23attributeMOUSE_BUTTON_PRESS= 30attributeMOUSE_BUTTON_RELEASE= 31attributeMOUSE_BUTTON= 40attributeMOUSE_BUTTON_LEFT_ID= 41attributeMOUSE_BUTTON_MIDDLE_ID= 42attributeMOUSE_BUTTON_RIGHT_ID= 43attributeMOUSE_MASK_BIT_PRIMARY= 1 << 0attributeMOUSE_MASK_BIT_ERASER= 1 << 5attributeUINPUT_BTN_LEFT= (EV_KEY, BTN_LEFT)attributeUINPUT_BTN_MIDDLE= (EV_KEY, BTN_MIDDLE)attributeUINPUT_BTN_RIGHT= (EV_KEY, BTN_RIGHT)attributeUINPUT_REL_X= (EV_REL, 0)attributeUINPUT_REL_Y= (EV_REL, 1)attributeUINPUT_REL_HWHEEL= (EV_REL, 6)attributeUINPUT_REL_WHEEL= (EV_REL, 8)attributeXBUTTON_LEFT= 1attributeXBUTTON_MIDDLE= 2attributeXBUTTON_RIGHT= 3attributeMOUSE_BUTTON_MAP= {MOUSE_BUTTON_LEFT_ID: {'uinput': UINPUT_BTN_LEFT, 'x11': XBUTTON_LEFT}, MOUSE_BUTTON_MIDDLE_ID: {'uinput': UINPUT_BTN_MIDDLE, 'x11': XBUTTON_MIDDLE}, MOUSE_BUTTON_RIGHT_ID: {'uinput': UINPUT_BTN_RIGHT, 'x11': XBUTTON_RIGHT}}