Commit Graph
14 Commits
Author SHA1 Message Date
Patrick McDavidandClaude Opus 5.5 6fb168a8b8 Lazy susan: Meta+Alt+Tab spins the panels around you
- ft-screens "spin next|prev|<degrees>": every unpinned screen and
  floating window turns together about a vertical axis through your
  head (0.3 s, eased), so the next panel on the right or left comes to
  straight ahead; the arrangement stays as it is. Taps during a spin
  add to it, from where the panels are headed; grabbing a panel or
  placing it (ft-layout, ft-floatd) takes it out of the spin
- when a spin settles, the panel in front gets the pointer (recenter),
  typing (as after a click), and KWin's active window: its floating
  window, or the top window on a screen (ft-floatd "front N", the KWin
  script's activate-output). KWin's outputs follow the screens'
  new places (ft-layout scale), as after a move
- the input relay: spin_next and spin_prev actions, Meta+Alt+Tab and
  Meta+Alt+Shift+Tab by default; Frametop Input Settings lists them.
  Not Meta+Tab: that's Cmd+Tab on a Mac reached through a remote
  desktop like RustDesk, and the relay would take the Mac's app
  switcher. Meta+Alt+Tab (Cmd+Option+Tab) is unused on macOS,
  Windows, and KDE

Used on the Frame (SteamOS 0.3.0 build 20260922) with one screen and
three or four floating windows, through RustDesk to a Mac.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:17:22 -06:00
Patrick McDavidandClaude Opus 5.5 1ebf3692fb ft-floatd: a launch for a missing app no longer kills the control socket (#16)
Gio.DesktopAppInfo.new() returns NULL for a desktop file that doesn't
exist, and PyGObject raises TypeError ("constructor returned NULL")
rather than returning None, so launch()'s `if info is None` never ran.
The exception escaped the control socket's GLib callback, GLib dropped
the watch, and ft-floatd stopped answering everything: the float key,
dock, Launch as Standalone, and profiles, until the desktop restarted.

Found on the Frame (2026-10-02): a profile saved with RustDesk's
Flatpak open records its window's app id, com.carriez.flutter_hbb,
which has no desktop file (the Flatpak's is com.rustdesk.RustDesk).
`ft-layout use` on that profile asked ft-floatd to launch it, and
ft-floatd went silent. With this, that launch replies "error no app
com.carriez.flutter_hbb" and the profile's other apps open.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 21:12:49 -06:00
DeeJanuzandClaude Opus 5.5 c3785bb46e Floating windows: keep KWin's placement memory out
KWin's PlacementTracker keeps each window's geometry, full screen and
maximized state per layout of the outputs, and puts windows back when a
layout it has seen comes back. A spare output resizes after its window,
so resizing a floating window back to an earlier size (or changing its
scale, or full screen) made the window and its output flip forever, and
floating or docking one window could move others onto or off a spare.

The KWin script now keeps where each window belongs, reports nothing
while KWin changes the outputs, and on screensChanged puts floating
windows back (and the screens' windows when only spares changed),
cancelling KWin's requests before the app sees them. A size asked for is
held for a second against late answers.

With that: a launched app and a profile get their remembered scale back,
scale steps keep the size in pixels, ft-floatd waits for the end of an
edge resize before resizing the output (KWin cancels the resize on any
output change), a window taken over after a restart keeps its app and
panel density, and `ft-float float ID` asks the script for the window's
current place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:50:38 -06:00
DeeJanuzandClaude Opus 5.5 a699540d95 Profiles: fixes from trying them on the live desktop
- KWin 6.2's scripts have no maximize mode: a window counts as maximized
  when it fills its output's maximize area.
- A late event for a window that just closed brought it back into
  ft-floatd's table, so a profile "found" it open, floated a window that no
  longer existed, and held a slot. The script doesn't report deleted
  windows, and ft-floatd ignores events for ids it has seen close.
- Docking a window that came from a screen that's hidden now puts it on the
  first screen that shows instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:37:50 -06:00
DeeJanuzandClaude Opus 5.5 858dbe1562 Profiles: named layouts that open apps
A profile is a named layout plus the screens it hides and its apps' windows
(docs/profiles.md). ft-layout save captures them (ft-floatd's "windows",
after the KWin script reports every window as it is now); use opens them:
the screens move, open windows of each app go to their places (on a screen,
maximized or not, or floating), and missing apps start, once and then again
for each window still missing 3 s after the first. Nothing closes.

The desktop starts in FT_PROFILE or default_profile (ft-layout start, from
the session script). Each profile gets a launcher entry (Frametop: NAME, in
SteamVR's Launch a program list) that switches to it or starts the desktop
in it. The relay's profile:NAME action and Input Settings' "Open profile"
entries put one on a key, mouse button, or controller button. Display
Settings' Layout page becomes Layout & profiles: Save as profile, Open
profile, the profile's apps, and Start in profile. Plasma's own session
restore is off in the Frametop session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:32:10 -06:00
DeeJanuzandClaude Opus 5.5 9d91ecbcaa Hide screens one at a time
ft-screens: "conceal <screen|all>" hides a screen on its own, whatever the
visibility mode or the hotkey says, until "reveal"; "concealed" lists them.
(Not "hide N": older builds read anything starting with "hide" as the hotkey.)
ft-layout keeps it per screen ("hidden" in the layout), applies it when it
arranges the screens, and has hide/show N|all and hidden. Display Settings
gets a Shown switch per screen on the Visibility tab. ft-floatd floats a new
window that opens on a hidden screen, and puts a stray window on a screen
that shows. Profiles (next) use it to show only some screens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:25:35 -06:00
DeeJanuzandClaude Opus 5.5 38843a4717 Launch apps floating, remember where they floated, and Launch as Standalone
ft-float launch APP / run COMMAND: ft-floatd starts the app and floats its
first window (matched by process or desktop file name for 30 s) where that
app last floated, or in front of you at the primary screen's density. Each
app's place (pose relative to the primary screen, size in pixels, scale) is
kept in ~/.config/frametop-float.json whenever one of its windows stops
floating; the scale isn't applied yet, since rescaling can loop (written up
in docs/floating-windows.md, Known problems).

Launch as Standalone (float/ft_apps.py): the session writes copies of the
apps' desktop files with that action and puts them first in XDG_DATA_DIRS,
so it shows in the Application Launcher's and the taskbar's right-click menus
in the Frametop desktop only. ft-floatd rewrites them when apps change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:20:36 -06:00
DeeJanuzandClaude Opus 5.5 00dfd61396 Title bar button: fixes from trying it in the live desktop
- The decoration's "bottom" property clashed with Item's (KWin refused it).
- No On All Desktops button with one virtual desktop, like Breeze.
- decoration/apply.sh finds the session's D-Bus through plasmashell (KWin's
  environment isn't readable), and installs each try under a new name: KWin
  keeps a decoration's QML by name until it restarts.
- ft-floatd starts its script with Scripting.start: after a reload, the new
  script gets the old one's id while the old one is still being deleted, so
  run() on /Scripting/Script<id> went to the old script and the new one never
  ran (a second ft-floatd start left floating windows without their script).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:11:29 -06:00
DeeJanuzandClaude Opus 5.5 f32187824a A float button in every window's title bar
Frametop's own window decoration (decoration/), a QML decoration for KWin's
Aurorae engine drawn like Breeze, puts a float button left of Close. It's the
Keep Below button with its own glyph: the KWin script floats a window when
keep-below is set and docks it when it's cleared, and keeps the flag set on
every floating window, so the button shows "back to the desktop" there. The
session script installs the decoration for the Frametop desktop only;
decoration/apply.sh switches a running desktop to it or back to Breeze.

Not yet tried in a running KWin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:03:41 -06:00
DeeJanuzandClaude Opus 5.5 1e83f96b0f 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>
2026-09-30 21:55:47 -06:00
DeeJanuzandClaude Opus 5.5 c8fc6351bb Floating windows: fix the scale crash, the click offset, and placement
- KWin's nested backend makes an output the size it's configured to times its
  scale, so after Meta+scroll every size ft-floatd sent was multiplied again,
  and an odd result disconnected KWin (buffer not divisible by its scale).
  ft-floatd now asks for sizes in the output's scaled terms (kwin_size), and
  asks again after each scale change.
- SteamVR reports mouse positions on a panel with texture bounds in the whole
  texture, not the crop, so clicks on a floating window landed up to ~200 px
  off. The mouse scale is now the buffer's size, as on a screen.
- A floated window starts 30 cm in front of its screen (was 5 cm), so it's
  easy to point at apart from the screen behind it.
- No 1 s wait before a spare turns on (a disabled output never commits), and
  the login splash on the spares isn't taken for floating windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:57:36 -06:00
DeeJanuzandClaude Opus 5.5 6122b7eb15 Floating windows: full screen, per-window scale, and drags across panels
Full screen fills the window's own panel (the margin drops to zero).
Meta+scroll over a floating window changes its scale at the same size in
pixels. Spare outputs are sized to a multiple of KWin's buffer scale, and
screens to even sizes: an odd buffer at a fractional scale is a protocol
error that disconnected KWin. The 3D mouse's drag lock now crosses onto
other Frametop panels unless the pressed one is being carried.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:31:23 -06:00
DeeJanuzandClaude Opus 5.5 c2e5e861c8 Show floating windows as panels of their own
ft-screens makes a panel for each spare output (frametop.float.N), hidden
until ft-floatd floats a window on it. The panel shows only the window's
rectangle of the buffer at the density of the screen it came from, its
popups and dialogs get small panels over it, pressing its title bar
carries it while KWin's pointer stays put, the corner tab resizes the
window in pixels, and two more buttons close it and put it back on the
desktop. The session adds FLOAT_SLOTS spare outputs to KWin and starts
ft-floatd; ft-layout arranges only the screens' outputs, and the pointer
helper treats the new panels like screens. Not yet tried in the headset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:26:20 -06:00
DeeJanuzandClaude Opus 5.5 87a402c68d Add ft-floatd and the frametop-float KWin script
ft-floatd loads the script into the desktop's KWin, which reports windows
over D-Bus and takes commands through a long poll. Floating a window (the
window menu's Float in VR, Meta+Shift+F, or ft-float) turns on a spare
output sized to the window plus a margin, moves the window onto it, and
tells ft-screens the panel's crop, density, and place; docking puts it
back and turns the spare off. Tested on the headless test desktop; the
panel side in ft-screens comes next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:20:26 -06:00