Skip to content

Sharp HiDPI cursor (in-scene), compositor-offloaded rendering, focus-grab, --name - #2

Open
Phaengris wants to merge 13 commits into
thelicato:mainfrom
Phaengris:in-scene-cursor
Open

Sharp HiDPI cursor (in-scene), compositor-offloaded rendering, focus-grab, --name#2
Phaengris wants to merge 13 commits into
thelicato:mainfrom
Phaengris:in-scene-cursor

Conversation

@Phaengris

Copy link
Copy Markdown

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::Cursor at 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, rdw has the same blur in absolute-mouse mode and only avoids it in relative mode, where it draws the cursor into the scene.)

Fix: a CursorScene overlay 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 the gdk::Cursor::from_texture usage 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 (for black-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

Phaengris and others added 4 commits August 14, 2026 18:55
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>
@Phaengris

Copy link
Copy Markdown
Author

Went through CONTRIBUTING.md and trued this PR up to the checklist: cargo fmt --check is clean, cargo test --offline passes (50/50), and I've added a docs commit covering the new flags (--no-fullscreen-bar from #1, --name), the viewer behavior changes (auto-resize, in-scene HiDPI cursor, subsurface offload, focus-time grab), and the GTK 4.16 requirement in the README. Happy to add before/after screenshots of the HiDPI cursor fix or split any commit out on request.

…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant