frametop

Floating windows

Any desktop app can float in VR in a panel of its own, like SteamVR’s floating windows, while it stays part of the Frametop desktop. Drag and drop, the clipboard, and focus keep working between floating windows and the screens.

How KWin behaves underneath all this, and what the KWin script does about it, is in design.md.

Not built

These were decided (see the table) but aren’t built:

Decisions

The numbers are cited in the code, so they stay as they are. Struck-out text was replaced by a later decision.

# Question Decision
1 How many windows can float at once 8 spare outputs by default, configurable with FLOAT_SLOTS (at most 16); a change needs a desktop restart. A Display Settings control isn’t built
2 Menus and dropdowns Each floating output has a margin around the window. The panel shows only the window, and each open popup gets a small overlay of its own, cut from the same buffer
3 Tearing off Not built. Drag the title bar past a screen’s edge and let go in the air, with a small dead zone past the edge
4 Docking by dragging Not built. Push the window flush against a screen (within about 10 cm), with the landing spot highlighted, and let go
5 Visibility Floating windows follow the same rules as the screens: the hide hotkey, the visibility modes, and the games rule
6 Windows a floating app opens They float too
7 Launching floating from the headset One “Frametop Apps” launcher entry with a picker Replaced by 26 and profiles (27)
8 Build order Not kept here: it only set the order of the work
9 Show Desktop (Meta+D) Floating windows stay. Not built
10 Frametop Apps and visibility The entry starts the desktop with each screen hidden on its own, so only floating windows show Replaced: a profile can hide screens (27)
11 Window frame KWin’s title bar and border stay. Frametop’s bar, close, and “back to desktop” are extras
12 Resizing The window’s own edges and Frametop’s corner tab both change the size in pixels at the same density; the output follows
13 Margin 300 px on each side, configurable (FLOAT_MARGIN)
14 All spares in use The window stays on the screens, with a notification
15 Where a floating app’s new windows go Not built: where that app’s windows went last time, otherwise to the parent’s right, curving around you. For now, a new window that opens on a floating window’s output floats a little in front of it
16 Where the code is written Not kept here: it was about the work, not about Frametop
17 Size when docked by dragging Not built (see 4): the current floating size in pixels, shrunk to fit the screen
18 Bigger text A scale for each window (KWin’s output scale): Meta+scroll over the window. Remembered for each app. +/- buttons on its bar aren’t built
19 Switching to a window you can’t see It’s focused. Not built: a glow at the edge of your view that points to it, and a setting that moves it in front of you
20 Full screen The window fills its own panel. The margin drops to zero while it’s full screen, and the panel keeps its size and place
21 Named layouts They cover the screens only Replaced by profiles (27). Outside a profile, floating windows use the placement remembered for each app
22 The float key One toggle: it floats a window, or docks it if it already floats. The input relay owns it (float_toggle), Meta+Shift+F by default, rebindable in Frametop Input Settings and mappable to mouse and controller buttons. KWin has no shortcut of its own for it, so one press can’t fire twice
23 Which window the key acts on The window under the desktop’s pointer; the active window if there’s none there (the wallpaper, the taskbar)
24 Docking everything A dock_all action, with no default binding
25 A button on every window A float button left of Close in the title bar, from Frametop’s own QML window decoration, made to look like Breeze. It shows a dock icon on floating windows. Apps that draw their own title bar (Chromium, Electron, GTK) use the key
26 Launching one app floating “Launch as Standalone” in the right-click menu of every app in the Application Launcher and the taskbar, from copies of the apps’ desktop files that only the Frametop desktop reads. It replaces the Frametop Apps entry (7, 10)
27 Profiles Named layouts become profiles: the screens’ places, which screens show, and the apps and their windows, floating or not. See docs/profiles.md

Also: floating windows get the wrist pin, the head pin, and pass-through (pointer-ignore) like screens. Every gesture works with the controllers as well as the 3D mouse. A window launched floating uses the primary screen’s density. VNC shows only the primary screen.

The approach: each floating window gets a KWin output of its own

Drag and drop and the clipboard only work between windows of the same compositor. A Wayland window can’t move from one compositor to another. So a floating window has to stay a KWin window.

ft-screens already shows each KWin output as a panel. It sets the output’s size with an xdg_toplevel configure, and KWin resizes the output to match. So a floating window gets an output of its own, sized to fit it, and ft-screens shows that output as a panel with its own controls. To KWin this is an ordinary desktop with more monitors. Dragging between two floating windows is the same as dragging between two monitors, which KWin already handles. ft-screens moves the pointer between panels in the middle of a drag: handle_vr_event moves pointer focus to another KWin window even while a button is held.

Alternatives considered:

Where the extra outputs come from: spare outputs

KWin’s nested backend opens its outputs at start (--output-count). The session starts KWin with the screen count plus FLOAT_SLOTS outputs (default 8, at most 16). ft-floatd turns off the spares nothing floats on with kscreen-doctor once it starts. Floating a window enables a spare, and docking the window disables it again. FLOAT_SLOTS limits how many windows can float at once, and changing it means restarting the desktop. How KWin’s nested backend treats disabled outputs, and why its virtual outputs can’t be used instead, is in design.md.

ft-screens creates a screen for each toplevel in the order they appear, and indexes its settings by that order. Spares come after the screens, so they get indices SCREENS and up. Their panels are hidden while their output is disabled.

How the parts fit together

  KWin script "frametop-float"                ft-floatd (host, Python)                 ft-screens
  window events, moves, menus  ── D-Bus ──▶   window ↔ output ↔ panel table   ── @ft_screens ──▶   panels, controls,
  runs commands                ◀─ long poll ─  spare outputs (kscreen-doctor)  ◀─ @frametop_float ─  lasers, catcher

Program names stay within 15 characters (ft-floatd). Overlay keys are frametop.float.N and frametop.float.N.bar, and so on; a floating window’s popups and dialogs are frametop.float.N.sub.K.

A floating window

Tearing a window off a screen (not built)

The design for decision 3:

  1. Press a desktop window’s title bar and drag it. KWin starts a move, and the script tells ft-floatd, which tells ft-screens: move-start <output> <window> <rect>.
  2. While the button is held, the laser leaves every Frametop panel by more than a small dead zone (a few centimetres past the edge). Letting go inside the dead zone is an ordinary drop.
  3. ft-screens shows a ghost: an overlay showing the screen’s live buffer cropped to the window (SetOverlayTextureBounds, no copy). It’s at the screen’s pixel density and distance, on the laser, facing you, with the point you grabbed under the laser. The ghost takes mouse input, so SteamVR’s laser lands on it and the release comes to ft-screens.
  4. Go back onto a screen before letting go, and the ghost disappears. It’s an ordinary move again.
  5. Let go on the ghost, and ft-screens releases the button in KWin, which ends the move. It then reports the tear-off to ft-floatd, with the window, the ghost’s pose, and the density. ft-floatd enables a spare output and has ft-screens size it to the window plus the margin and put its panel at the ghost’s pose. Then it has the script move the window onto that output. The ghost stays until the new panel’s first frame at the right size arrives, so nothing blinks.

Putting it back

Getting at it: the float key and the title bar button

Decisions 22 to 25.

Launching an app floating

Drag and drop between panels

KWin handles the protocols: Wayland, X11 through Xwayland, and the portal’s file transfer. Frametop has to get the pointer right between panels.

Things that must keep working

Risks