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

+25 -4
View File
@@ -2,7 +2,7 @@
// loads it into the desktop's KWin over D-Bus (org.kde.kwin.Scripting) and talks to it:
// - events go to ft-floatd as JSON strings (org.frametop.Float.Event), for the windows it
// cares about: floating windows (the ones on a spare output, WL-<screens> and up), their
// popups and dialogs, new windows, and "Float in VR" requests;
// popups and dialogs, new windows, and requests to float or dock one;
// - commands come back through a long poll: the script calls NextCommand, ft-floatd
// answers when it has one (or after a while with nothing), and the script calls again.
// KWin scripts can call D-Bus but can't serve it, hence the poll. Window ids are KWin's
@@ -104,8 +104,26 @@ registerUserActionsMenu(w => {
triggered: () => requestFloat(w)
};
});
registerShortcut("Frametop Float Window", "Frametop: Float Window in VR (or put it back)", "Meta+Shift+F",
() => requestFloat(workspace.activeWindow));
// The float key is the input relay's (float_toggle, Meta+Shift+F by default): it reaches us as
// "request-pointer". No shortcut of KWin's own, so one press can't float a window and dock it again.
// The window under KWin's pointer (where the 3D mouse or a laser last was on a panel): the top
// one there, a popup or dialog standing for the window it belongs to. Null over the wallpaper
// or the taskbar.
function underPointer() {
const p = workspace.cursorPos;
const order = workspace.stackingOrder;
for (let i = order.length - 1; i >= 0; --i) {
const w = order[i];
if (w.deleted || w.minimized || w.hidden || !w.managed) continue;
const g = w.frameGeometry;
if (p.x < g.x || p.y < g.y || p.x >= g.x + g.width || p.y >= g.y + g.height) continue;
let top = w;
for (let n = 0; top.transientFor && n < 10; ++n) top = top.transientFor;
return top.normalWindow && !top.popupWindow ? top : null;
}
return null;
}
function run(c) {
const w = c.id ? byId(c.id) : null;
@@ -140,9 +158,12 @@ function run(c) {
case "info":
if (w) report("window", w);
break;
case "request-active": // ft-float float|dock active: like the shortcut
case "request-active": // ft-float float|dock active
requestFloat(workspace.activeWindow);
break;
case "request-pointer": // the float key: the window under the pointer, else the active one
requestFloat(underPointer() || workspace.activeWindow);
break;
}
}
+3
View File
@@ -2,7 +2,10 @@
"""ft-float: talk to ft-floatd (floating windows in the Frametop desktop).
ft-float float [ID|active] float a window (default: the active one; for it, a toggle)
ft-float float pointer the float key: float the window under the pointer (else the
active one), or put it back if it floats
ft-float dock [ID|active] put a floating window back on the desktop
ft-float dock all put every floating window back
ft-float close ID close a window
ft-float list the spare outputs and what floats on them
FT_FLOAT_SOCKET names ft-floatd's socket (default frametop_float).
+18 -1
View File
@@ -11,7 +11,9 @@ which window floats on which spare output and panel, and connects three parts:
- kscreen-doctor, to turn spare outputs on and off and place them in KWin's layout.
Commands and ft-screens' events arrive as datagrams on @frametop_float (ft-float is the
command-line side). Replies go to the sender:
float [ID|active] dock [ID|active] close ID list quit (ft-float)
float [ID|active|pointer] dock [ID|active|all] close ID list quit (ft-float)
("float pointer" is the float key: the window under the pointer, else the active one,
floated or docked; the input relay sends it for float_toggle, and "dock all" for dock_all)
dock N | close N | resize N W H | scale N STEPS (ft-screens, N = its screen)
Spare outputs are WL-<screens> .. WL-<screens + slots - 1>. A floating window's output is its
@@ -197,6 +199,14 @@ class Daemon:
raise RuntimeError("KWin didn't load the script")
bus.get_object("org.kde.KWin", f"/Scripting/Script{sid}").run(dbus_interface="org.kde.kwin.Script")
log(f"script loaded ({sid})")
# Older scripts registered the float key with KWin; the input relay owns it now. Drop
# that shortcut, so System Settings doesn't list one that does nothing.
try:
accel = dbus.Interface(bus.get_object("org.kde.kglobalaccel", "/kglobalaccel"), "org.kde.KGlobalAccel")
if accel.unregister("kwin", "Frametop Float Window"):
log("dropped KWin's old float shortcut")
except dbus.DBusException:
pass
# ------------------------------------------------------------ events from the script
@@ -520,6 +530,13 @@ class Daemon:
if cmd in ("float", "dock") and (not rest or rest[0] == "active"):
self.command(cmd="request-active")
return "ok"
if cmd == "float" and rest == ["pointer"]:
self.command(cmd="request-pointer")
return "ok"
if cmd == "dock" and rest == ["all"]:
for f in list(self.floats.values()):
self.dock(f)
return "ok"
if cmd in ("dock", "close", "resize", "scale") and rest and rest[0].isdigit():
f = self.by_panel(int(rest[0]))
if not f: