Sharp HiDPI cursor (in-scene), compositor-offloaded rendering, focus-grab, --name - #2
Open
Phaengris wants to merge 13 commits into
Open
Sharp HiDPI cursor (in-scene), compositor-offloaded rendering, focus-grab, --name#2Phaengris wants to merge 13 commits into
Phaengris wants to merge 13 commits into
Conversation
GTK4 forbids gtk_window_set_titlebar() after the window has been realized; toggling fullscreen swapped the titlebar at runtime, which emitted Gtk-WARNINGs and crashed the viewer with SIGSEGV (both via the F11 hotkey and --fullscreen at startup). Set the titlebar once at construction and only toggle the header bar's visibility when entering/leaving fullscreen - GTK hides titlebars in fullscreen anyway, so the visible behavior is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Forward the viewer's size to the guest via the console's SetUIInfo method - the same mechanism the SPICE vdagent uses - so the guest display follows window resizes, maximization and fullscreen instead of staying at its EDID-preferred mode and getting letterboxed. Requests are debounced (350 ms) so interactive resizes send one final size, are scale-factor aware for HiDPI hosts, and are also sent on the first map so windows born fullscreen (--fullscreen) size the guest correctly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The status label was shown but never hidden, so the transient disable notice emitted during guest mode switches (or listener handover) stayed on screen for the rest of the session. Track the pending notice and clear it on the next scanout or dmabuf update from the guest; an empty status message now hides the label. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Leave the floating fullscreen toolbar and its top-edge hover hotspot unparented when requested. Useful when the guest desktop has its own panels at the screen edges: the hover hotspot otherwise fights with edge-activated guest UI (auto-hide panels, top-edge menus). Fullscreen can still be toggled via the hotkey (F11 by default) and the titlebar button in windowed mode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
|
Went through CONTRIBUTING.md and trued this PR up to the checklist: |
…resize The auto-resize triggers listened only to window property notifications (default size, fullscreened, maximized) — but when the compositor resizes the surface itself, none of those change. The common case: the monitor's resolution changes while the viewer is fullscreen; the window keeps covering the screen but the guest is never asked to adopt the new size until the viewer is reopened. Hook the GDK surface's layout signal (the ground truth for actual size changes, connected on every realize) and the window's scale-factor notify (a monitor with a different scale changes the physical pixel count without a logical resize), both feeding the existing debounce.
GTK rasterizes gdk::Cursor at logical scale (the cursor callback is invoked with scale=1 even on 200% outputs), so any cursor set through the GTK cursor API is blurry on scaled displays. Draw the guest cursor texture inside the scene instead, scaled by the same factor as the framebuffer content: wherever the display is pixel-perfect, the cursor now is too. The new CursorScene overlay widget tracks the local pointer (zero lag, same semantics as the replaced GTK cursor) and snapshots the cursor texture at hotspot-corrected coordinates. The widget cursor path is reduced to hiding the host cursor while a guest shape is defined. This mirrors what rdw does for relative mode, applied to absolute mode. In relative mode nothing is drawn, preserving existing behavior.
Wrap the picture in GtkGraphicsOffload (black-background mode) so dmabuf scanouts bypass GTK's GL compositing and are attached directly to a Wayland subsurface. At large guest resolutions (e.g. 5120x1440) that compositing pass dominated the frame budget and made fullscreen viewing sluggish regardless of how fast the guest rendered. Black-background mode keeps the subsurface below the main surface, so overlays (in-scene cursor, fullscreen bar) still draw above the video. Requires GTK 4.16 for black-background; gtk4 feature bumped v4_14->v4_16.
Keyboard forwarding was gated on the input grab, which only activated on an explicit click — so after switching to the viewer window the first click was swallowed by grab activation and keys went nowhere until then. Activate the grab when the window becomes active instead, matching how spice viewers behave: focus means input. Focus-out still releases.
Sets the program name before GTK initializes, which becomes the Wayland app_id / X11 WM class of the viewer window — the same job as spicy's --name. Lets taskbars group the viewer under a pinned launcher and lets window rules target a specific connection.
Inhibiting host shortcuts for a window that shows no guest display yet (still connecting, or an error dialog) held the user's alt-tab and screenshot hotkeys hostage. Gate the focus-time grab on the picture being visible; the click-time grab already requires pointer interaction.
Covers the auto-resize, in-scene HiDPI cursor, GraphicsOffload rendering, and focus-time keyboard grab in the viewer highlights, adds the two new flags to the options table, and records the GTK 4.16 requirement.
The default release-cursor binding (Ctrl+Alt) fired the moment the chord was completed on key PRESS — making it the prefix of every Ctrl+Alt+<key> guest shortcut: the grab dropped mid-combo, the remaining key went nowhere, and keyboard input appeared frozen until a click re-armed the grab. Modifier-only chords now arm on press and fire only when a chord key is RELEASED with no other key pressed in between (virt-viewer semantics); key+modifier bindings keep firing on press. Also add a modifier reconciliation net: when GTK's modifier state shows a modifier is up but we still track it as pressed (its release event was lost to grab churn or compositor focus flicker), release it in the guest instead of leaving it stuck repeating the last chord.
Phaengris
force-pushed
the
in-scene-cursor
branch
from
September 2, 2026 18:03
8bbdc6a to
cc3e54b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four more improvements from daily-driving qd2 as a fullscreen VM viewer on KDE Wayland at 200% scaling. Stacked on #1 — the first four commits here are that PR; the new ones are the last four.
feat(viewer): render the guest cursor in-scene (6035f25)
The guest cursor was blurry/double-size on HiDPI outputs. Root cause (measured, not guessed): GTK rasterizes
gdk::Cursorat logical scale — on Wayland the cursor callback is invoked with scale=1 even on a 200% display — so any cursor set through the GTK cursor API is upscaled by the compositor. (For reference,rdwhas the same blur in absolute-mouse mode and only avoids it in relative mode, where it draws the cursor into the scene.)Fix: a
CursorSceneoverlay widget draws the guest cursor texture inside the GL scene at the local pointer position, scaled by the same factor as the framebuffer content — wherever the display is pixel-perfect, the cursor now is too. The GTK cursor path is reduced to hiding the host cursor. Bonus: this removes thegdk::Cursor::from_textureusage entirely, sidestepping a gdk4-rs 0.10.3 ownership bug in cursor creation from textures.perf(viewer): GtkGraphicsOffload for dmabuf frames (70fbc3d)
At large guest resolutions (5120x1440 here), compositing every dmabuf frame through GTK's GL renderer dominated the frame budget and made fullscreen viewing sluggish. Wrapping the picture in
GtkGraphicsOffload(black-background mode, so overlays like the in-scene cursor and the fullscreen bar still draw above the video) hands frames to the compositor as a subsurface. Fullscreen went from noticeably sluggish to smooth. Note: raises the GTK requirement to 4.16 (forblack-background).feat(viewer): activate the input grab on window focus (7049761)
Keyboard forwarding was gated on the input grab, which only activated on an explicit click — after switching to the viewer window, the first click was swallowed by grab activation and keys went nowhere until then. Now the grab activates when the window becomes active, matching spice viewers: focus means input. Focus-out still releases.
feat(cli): add --name (bfe06a6)
Sets the program name before GTK initializes, which becomes the Wayland app_id / X11 WM class — the same job as spicy's
--name. Lets taskbars group the viewer under a pinned launcher and lets window rules target a specific connection.All 50 tests pass. Happy to split any of these out or adjust.
🤖 Generated with Claude Code