Float key in the input relay, docking everything, and the plan for profiles

The input relay owns the float key now: float_toggle (Meta+Shift+F unless the
rules have their own key combinations) floats the window under the pointer, or
the active one over the wallpaper, and docks it if it floats. dock_all puts
every floating window back. Both are mappable to mouse and controller buttons,
and work without pointer mode. The KWin script no longer registers a shortcut,
and ft-floatd drops the old one. Key combinations move to Input Settings'
Keyboard page.

docs/floating-windows.md records the decisions from 2026-09-30 (the title bar
button, Launch as Standalone, profiles), and docs/profiles.md plans profiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DeeJanuzandClaude Opus 5.5 committed 2026-09-30 21:55:47 -06:00
1 parent e16b7588e2
commit 1e83f96b0f
9 files changed
+220 -69

No files matched your search

+26 -8
View File
@@ -25,13 +25,17 @@ held, the pointer stops where you look and the mouse steers it, and the release
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, key = pass through as a key, none).
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), 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. The controllers aren't input devices here, only SteamVR sees
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 <button> 1|0".
It only takes the buttons the relay tells it to ("vrbind <button>..." to @ft_pointer_helper,
sent on start, reload, and when the helper says "vrhello"), and only while no game runs,
@@ -175,7 +179,7 @@ VIRTUAL_PREFIX = "frametop virtual"
RULES_PATH = os.path.expanduser("~/.config/frametop-input.json")
ACTIONS = ("left", "right", "middle", "back", "scroll_up", "scroll_down", "dashboard", "recenter",
"pointer_toggle", "follow_toggle", "gaze_toggle", "gaze_precision", "gaze_drag", "sens_up", "sens_down",
"layout_reset", "screens_toggle", "keyboard_toggle", "key", "none")
"layout_reset", "screens_toggle", "keyboard_toggle", "float_toggle", "dock_all", "key", "none")
# Gaze mode is a mouse feature: these never come from a controller button (docs/gaze-controllers.md).
GAZE_ACTIONS = ("gaze_toggle", "gaze_precision", "gaze_drag")
# Key combinations ("key_bindings"): modifiers, each side's code folded into the left one's.
@@ -188,6 +192,11 @@ VR_BUTTONS = ("left/view", "left/dpad_up", "left/dpad_down", "left/dpad_left", "
"right/bumper", "right/trigger", "right/grip", "right/thumbstick")
VR_DEVICE = "frame_controller" # the id controller buttons have in watch events
SCREENS = "\0ft_screens"
FLOAT = "\0frametop_float" # ft-floatd, floating windows in the Frametop desktop
# Actions for ft-floatd ("float_toggle", "dock_all"): they don't need pointer mode.
FLOAT_ACTIONS = {"float_toggle": b"float pointer", "dock_all": b"dock all"}
# Key combinations a rules file without "key_bindings" gets: Meta+Shift+F floats a window.
DEFAULT_KEY_BINDINGS = {"42+125+33": "float_toggle"}
KEYS = "\0frametop_keys" # keys of keyboards grabbed for the desktop, for other readers
FT_LAYOUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "layout", "ft-layout")
DEFAULT_BUTTONS = {BTN_LEFT: "left", BTN_RIGHT: "right", BTN_MIDDLE: "middle",
@@ -295,7 +304,8 @@ def read_rules(path=RULES_PATH):
rules.setdefault("devices", {})
rules.setdefault("buttons", {})
rules.setdefault("controller_buttons", {})
rules.setdefault("key_bindings", {})
if not isinstance(rules.get("key_bindings"), dict):
rules["key_bindings"] = dict(DEFAULT_KEY_BINDINGS)
return rules
@@ -690,11 +700,19 @@ def main():
vr_keyboard("show")
def do_action(action, value, now, source="mouse"):
"""A mapped mouse or controller button, or key combination (pointer mode only)."""
"""A mapped mouse or controller button, or key combination (pointer mode only, but
for FLOAT_ACTIONS)."""
if action == "keyboard_toggle":
if value == 1 and vr_keyboard_mode() != "never":
vr_keyboard("toggle")
else:
elif action in FLOAT_ACTIONS:
if value == 1:
try:
screens_sock.sendto(FLOAT_ACTIONS[action], FLOAT)
except OSError:
pass # the Frametop desktop isn't running
log(action)
elif state["pointer"]:
state["pointer"].action(action, value, now, source)
held_modifiers = set() # on any keyboard, folded (MODIFIERS)
@@ -708,7 +726,7 @@ def main():
return False
if value == 0 and code in combos_down:
action = combos_down.pop(code)
if state["pointer"]:
if state["pointer"] or action in FLOAT_ACTIONS:
do_action(action, 0, now, "keyboard")
return True
if value != 1 or not state["rules"]["key_bindings"]:
@@ -718,7 +736,7 @@ def main():
if action not in ACTIONS or action in ("key", "none"):
return False
combos_down[code] = action
if state["pointer"]:
if state["pointer"] or action in FLOAT_ACTIONS:
do_action(action, 1, now, "keyboard")
log(f"key combination {combo}: {action}")
return True