fix(wayland): open windows on compositors without xdg-decoration, viewporter or fractional scale

Window
- The constructor made a fractional scale, a toplevel decoration and a
  viewport from Device::fractionalScaleManager, Device::manager and
  Device::wpViewporter without looking at them. Device::handle_global
  binds those globals only when the compositor advertises them, and
  Initialize requires only wl_shm, wl_compositor and xdg_wm_base, so on a
  compositor without one of the three (GNOME's Mutter has no
  xdg-decoration) the constructor handed libwayland a null proxy and
  crashed before the window existed (#162). Each object is now made only
  when its global is there, which Resize and the destructor already
  assumed.
- The fractional scale needs the viewporter too: the preferred scale is
  applied through the viewport, which shows the buffer's physical pixels at
  width/scale x height/scale. A surface without a viewport is as large as
  its buffer, so there the scale stays 1, which keeps pointer and touch
  positions, configure sizes and the text-input caret, all converted by
  it, in physical pixels; the compositor scales the window up on a HiDPI
  output. A scale that changes after the constructor is still applied only
  by the next resize, which is #165.
- Without xdg-decoration the window has no title bar, since the library
  draws no client-side decorations.

Device
- The optional globals say they are optional, and that a request on a
  null proxy crashes.

Tests
- WindowOptionalGlobals: hides xdg-decoration, wp_viewporter and
  wp_fractional_scale_v1 from the Window one at a time and all at once,
  by nulling the Device statics after Initialize, and renders, resizes
  and closes a window each time without a protocol error. Before the fix
  it crashed on the window without xdg-decoration. A global the compositor
  does not offer is reported as a SKIP line, since the path that uses it
  goes untested there.

README
- Which Wayland protocols the window needs, and what it does without the
  optional ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 08:28:48 +01:00
commit 670df31cc6
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
5 changed files with 184 additions and 7 deletions

View file

@ -364,18 +364,36 @@ Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height
xdg_toplevel_add_listener(xdgToplevel, &xdg_toplevel_listener, this);
wl_surface_commit(surface);
wp_scale = wp_fractional_scale_manager_v1_get_fractional_scale(Device::fractionalScaleManager, surface);
wp_fractional_scale_v1_add_listener(wp_scale, &wp_fractional_scale_v1_listener, this);
// The fractional scale, the decoration and the viewport come from
// optional protocols: Device::handle_global binds a global only when the
// compositor advertises it, and a request on the null proxy it leaves
// otherwise crashes in libwayland (#162). The preferred scale is applied
// through the viewport, which shows the width x height physical pixels of
// the buffer at width/scale x height/scale, so without a viewporter there
// is no fractional scale either: scale stays 1, the surface is as large as
// its buffer, and the compositor scales it up on a HiDPI output. A scale
// that changes after this is applied only by the next resize (#165).
if (Device::fractionalScaleManager != nullptr && Device::wpViewporter != nullptr) {
wp_scale = wp_fractional_scale_manager_v1_get_fractional_scale(Device::fractionalScaleManager, surface);
wp_fractional_scale_v1_add_listener(wp_scale, &wp_fractional_scale_v1_listener, this);
}
while (wl_display_dispatch(Device::display) != -1 && !configured) {}
wl_surface_commit(surface);
decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(Device::manager, xdgToplevel);
zxdg_toplevel_decoration_v1_set_mode(decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
// GNOME's Mutter has no xdg-decoration: it leaves decorations to the
// client, and the library draws none, so there the window has no title
// bar.
if (Device::manager != nullptr) {
decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(Device::manager, xdgToplevel);
zxdg_toplevel_decoration_v1_set_mode(decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
}
wpViewport = wp_viewporter_get_viewport(Device::wpViewporter, surface);
wp_viewport_set_destination(wpViewport, std::ceil(width/scale), std::ceil(height/scale));
if (Device::wpViewporter != nullptr) {
wpViewport = wp_viewporter_get_viewport(Device::wpViewporter, surface);
wp_viewport_set_destination(wpViewport, std::ceil(width/scale), std::ceil(height/scale));
}
wl_surface_commit(surface);
#endif