Repository navigation
Conversation
A headless session starts on an empty workspace of its own, so the windows open on the remote stay on its physical screens, out of view; and on a machine whose monitor is asleep, mirroring it shows nothing. `--move-workspaces` (server, and the client, which passes it on) moves every regular workspace onto the headless output when the session starts and shows there the one the main monitor was showing. At the end each goes back to the monitor it came from and each monitor shows what it did, before the output is removed; left to itself, Hyprland would pile them all onto one monitor. hypr-ipc gains workspace listing, each monitor's active workspace, and the two dispatchers this needs, with the Lua fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Test report: PR #81 (
|
| Check | Result |
|---|---|
Default gliff user@host creates a window-sized output |
Pass. A headless output appeared at 1752x1146 for a 876x620 window (scale 2), and at 2736x1824 with the window fullscreen. |
| Workspaces move onto the output | Pass. Workspace 1 and its windows moved. Workspace 2, auto-created on DP-1, stayed there. |
| Disconnect removes the output and restores workspaces | Pass, on four connect/disconnect cycles. The output was removed, workspace 1 returned to DP-1, and the windows were back at their original positions. No gliff-server was left running. |
| Works on an NVIDIA server | Pass. No errors, and the stream ran. The PR's own test used a 3080. |
| Window resize | The output size followed the window (876x620 window to fullscreen: 1752x1146 to 2736x1824). I did not test dragging a window to resize it. |
| Old 0.4.0 server falls back to an empty screen | Not tested |
--mirror, --keep-workspaces, DPMS keep-awake |
Not tested |
Observation: floating windows can be cropped
I saw a cropped view on the Surface: part of the remote's right and bottom edges was missing. The client wasn't cropping the frame. In fullscreen the output (1368x912 logical) was the same size as the window, and the whole frame was shown. The cause is the remote's floating windows. They keep their size when their workspace moves, so some are larger than the new output:
| Window | Right / bottom edge | Fits in 1368x912? |
|---|---|---|
| Ghostty #1 | 1132 / 797 | Yes |
| Ghostty #2 | 1292 / 873 | Yes, barely |
| Edge | 1534 / 935 | No (about 166px off the right, about 23px off the bottom) |
With a smaller Surface window (876x573 logical output), all three overflowed.
Comments
- The PR does what it describes. The crop is an edge case it doesn't claim to handle, since the test window was as big as the desktop. Tiled windows are re-laid-out by Hyprland to fit, but floating ones aren't.
- With floating windows and a small client window, parts of some windows can be out of reach. Options: shrink or reposition floating windows after the move and restore them on disconnect, document the limit and point to
--mirror, or make the output at least as large as the biggest remote window. I'd suggest at least documenting it. - Not run: the old-server compatibility case, which the PR's compatibility claim relies on, and
--mirror/--keep-workspaces.
Follow-up: tiled windows and the remaining modesSame setup as my earlier report (RTX 3090 Hyprland server, Surface client, PR head Tiled windows: no crop. With the 3090's three windows tiled (not floating), Hyprland re-laid-out the tiles to fit the window-sized output, both in the small Surface window and fullscreen. Nothing ran off the edge, and the original layout came back on disconnect. That supports the earlier finding that the crop only affects floating windows.
The other modes
Not tested: the DPMS keep-awake behaviour, and dragging a window to resize it live. |
Follow-up: live resize and DPMS keep-awakeSame setup as before (RTX 3090 Hyprland server, Surface client, PR head Live resize. I resized the Surface window through Hyprland's window-resize dispatcher, not with the mouse (no pointer-control tool on that machine). It sends the client the same size changes as a drag. Each size produced exactly the expected output on the 3090 (2 × window width, 2 × (window height − 47) in pixels). The output followed within 0.2–0.4s of the command, including the SSH round trip.
DPMS keep-awake. I turned every monitor on the 3090 off with
So the keep-awake check fixes the freeze on black, and only the session's own output is switched on, since DP-1 stayed off. "Live" here means the view was not black and the output was on. I did not check that the content kept changing. With this, everything in the PR's test plan has been run on this setup. The only open point is the floating-window crop from my first comment. |
|
This is a really cool addition! |
Connecting now creates a headless output sized and scaled to the window and moves the remote's workspaces onto it for the session, as --headless --move-workspaces did; --mirror mirrors the focused screen as before, --output a named one, and --keep-workspaces leaves the new screen empty. The client asks for the move in the handshake, as a "move-workspaces" entry in its features, rather than as a server flag, so a server without --move-workspaces still connects (with an empty screen) instead of refusing the unknown flag. Hyprland lays tiled windows out again on the new screen but keeps a floating window's size and position, so one larger than the client window runs off its edge; the README and --mirror's help say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JjGxUHRAF1wya1dVp5ScR4 Claude-Session: https://claude.ai/code/session_012Pxk3MdNTDNQ3CPySqTmfT
1a3d827 to
ad10410
Compare
|
Thanks for all three reports, and for running the 0.4.0 fallback especially, since the compatibility claim depends on it and I'd only tried it from a Mac. The DPMS run also covers #57, which this branch carries: the control against 0.4.0 is the before-and-after I didn't have. The floating-window crop is real, and as you say, it comes from Hyprland keeping floating windows at their size and position while it re-lays-out the tiles. I've pushed a note for it. The README paragraph on the default now says a floating window larger than your window runs off its edge, and to tile it or use I'd rather not shrink and move floating windows in this PR. Restoring them on disconnect means tracking each one's geometry, and if the server dies before it cleans up, the windows stay shrunk. That's worse than a crop that goes away when you disconnect. It's also a lot to add to a PR that sits on top of #57 and #58. If it's wanted, I'll do it as a follow-up on the same restore path the workspaces use. |
[Medium impact] The PR appears safe to merge; no blocking issue remains from this review. SummaryThe PR makes a window-sized remote screen the default and asks the server to move workspaces through the handshake.
|
Hyprland gives named workspaces negative IDs, like the scratchpads, so skipping every ID at or below zero left named workspaces and their windows on the physical screens. Scratchpads are now told apart by their "special" name, and named workspaces are moved and shown by "name:<name>". A workspace's home was recorded only after the move answered ok. A move whose answer timed out could still happen, and then nothing put that workspace back. The home is now recorded before the move; restoration already moves only the workspaces still on the session's output. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JauiFwn6qx2NBFZJ7FvEva
Restoration retried its snapshot but sent each move once. If IPC stalled then, the move failed silently and the output was removed anyway, leaving that workspace wherever Hyprland put it. Each move is now retried like the snapshot, and one that still fails is logged with the monitor it should have gone back to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JauiFwn6qx2NBFZJ7FvEva
A named workspace reaches the Lua dispatchers as "name:<name>", and the name went into the Lua string unescaped. A quote or backslash in it broke the call or changed the name Hyprland read, so that workspace was neither moved nor returned. Both calls now quote the selector as a Lua string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JauiFwn6qx2NBFZJ7FvEva
Main's keep-awake cancellation gave the dispatcher a third argument, the check that lets teardown skip a request. Workspace moves and focus are never cancelled, so they pass `|| true`, as set_dpms does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NWLtaPFL1Y2jfFD7qm2QjZ
Brings in current main through omacom#58. Main's session test still passed a bare VideoMode; it now passes Wants, without moving workspaces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NWLtaPFL1Y2jfFD7qm2QjZ
Showing the restored workspace now retries like the moves before it, and warns if it still fails. Special workspaces are recognized by the "special:" prefix Hyprland gives them, so a regular named workspace such as "special-mail" is moved like any other. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NWLtaPFL1Y2jfFD7qm2QjZ
Builds on #57 and #58. This branch contains their commits, so review or merge those first. The change in this PR is the last commit, ad10410.
What
gliff user@hostnow creates a headless output on the remote, sized and scaled to the window, and moves the remote's workspaces onto it for the session. This is what--headless --move-workspacesdid before. The screen follows the window as you resize it. On disconnect it is removed, and the workspaces go back to their own screens.The other modes stay available:
--mirror: mirror the focused screen, the old default.--output NAME: mirror a named screen, unchanged.--keep-workspaces: a screen of its own that starts empty.--headlessand--move-workspacesare still accepted and hidden, since they are now the default.Why
When I connect to my desktop from a laptop, I want my windows at the laptop window's size and scale. A 3440x1440 screen shrunk into a laptop window doesn't do that. The window-sized screen, holding the workspaces, also ends up the more useful mode in day-to-day use.
Compatibility
The client asks for the workspace move in the handshake, not on the server's command line. It does this with a
move-workspacesentry in itsClientCaps.features(FEATURE_MOVE_WORKSPACES). A server that predates--move-workspacesignores the entry and still connects, with an empty screen. If the client passed the flag on the command line, that server would refuse the unknown flag and the connection would fail. That is the failure I hit before making this change.Testing
scripts/check.shpasses. A new test covers which server arguments and requests each set of flags produces.🤖 Generated with Claude Code