//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // Issue #162: xdg-decoration, wp_viewporter and wp_fractional_scale_v1 are // optional, and a compositor may offer any of them or none (GNOME's Mutter // has no xdg-decoration). Device::handle_global binds only what is // advertised, but the Window constructor used all three unconditionally, so // without one it handed libwayland a null proxy and crashed. This makes a // window without each of the three, and without all of them, and checks for // each: // // - the constructor returns, with the decoration, the viewport and the // fractional scale made exactly when their globals are there; the // fractional scale also needs the viewporter, since the preferred scale // is applied through the viewport; // - without a fractional scale the scale stays 1: the surface is then as // large as its buffer, so surface-local pointer positions and configure // sizes are already physical pixels; // - the window renders, resizes, renders again and closes, and the // compositor raises no protocol error over any of it. Resize sets the // viewport's destination only when there is a viewport; the new size // itself is not checked, since Resize stores it before it does anything // else. // // The test hides the globals the compositor offers: it nulls the Device // statics between Initialize and the Window, which is the state a compositor // without them leaves. The proxies stay bound, unused. A window with every // global the compositor offers is the control. A global it does not offer // leaves the path that uses it untested, which the test reports as a SKIP // line rather than a failure: the paths without it, which are what #162 is // about, still run. // // Needs a live Wayland compositor and a Vulkan device. The validation check // only means something with CRAFTER_GRAPHICS_VALIDATION set. #include #include #include "vulkan/vulkan.h" import Crafter.Graphics; import std; using namespace Crafter; namespace { int failures = 0; void Check(bool ok, std::string_view what) { std::println("{} {}", ok ? "PASS" : "FAIL", what); if (!ok) ++failures; } // Which optional globals a window is made with. struct Case { std::string_view name; bool decoration; bool viewporter; bool fractionalScale; }; constexpr std::array kCases = { Case{ "with every global", true, true, true }, Case{ "without xdg-decoration", false, true, true }, Case{ "without wp_viewporter", true, false, true }, Case{ "without wp_fractional_scale_v1", true, true, false }, Case{ "without any of the three", false, false, false }, }; // A roundtrip fails, and the display keeps the error, once the compositor // has raised a protocol error over anything sent before it. bool NoProtocolError() { return wl_display_roundtrip(Device::display) != -1 && wl_display_get_error(Device::display) == 0; } } // namespace int main() { Device::Initialize(); Device::validationErrorCount = 0; const auto decorationManager = Device::manager; const auto viewporter = Device::wpViewporter; const auto fractionalScaleManager = Device::fractionalScaleManager; std::println("the compositor offers xdg-decoration: {}, wp_viewporter: {}, wp_fractional_scale_v1: {}", decorationManager != nullptr, viewporter != nullptr, fractionalScaleManager != nullptr); const auto skipWithout = [](bool offered, std::string_view global) { if (!offered) std::println("SKIP the compositor offers no {}, so no window here uses one", global); }; skipWithout(decorationManager != nullptr, "xdg-decoration"); skipWithout(viewporter != nullptr, "wp_viewporter"); skipWithout(fractionalScaleManager != nullptr && viewporter != nullptr, "wp_fractional_scale_v1 with wp_viewporter"); for (const Case& c : kCases) { Device::manager = c.decoration ? decorationManager : nullptr; Device::wpViewporter = c.viewporter ? viewporter : nullptr; Device::fractionalScaleManager = c.fractionalScale ? fractionalScaleManager : nullptr; const bool decorated = Device::manager != nullptr; const bool viewport = Device::wpViewporter != nullptr; const bool fractional = viewport && Device::fractionalScaleManager != nullptr; bool connected; { Window window(320, 240, "WindowOptionalGlobals test"); Check((window.decoration != nullptr) == decorated, std::format("{}: a decoration exactly when xdg-decoration is there", c.name)); Check((window.wpViewport != nullptr) == viewport, std::format("{}: a viewport exactly when wp_viewporter is there", c.name)); Check((window.wp_scale != nullptr) == fractional, std::format("{}: a fractional scale exactly when it and wp_viewporter are there", c.name)); if (!fractional) { Check(window.scale == 1.0f, std::format("{}: the scale stays 1 without a fractional scale", c.name)); } for (int i = 0; i < Window::numFrames * 2; ++i) window.Render(); window.Resize(400, 300); for (int i = 0; i < Window::numFrames * 2; ++i) window.Render(); Device::CheckVkResult(vkQueueWaitIdle(Device::queue)); connected = NoProtocolError(); Check(connected, std::format("{}: no protocol error while rendering and resizing", c.name)); } if (connected) { connected = NoProtocolError(); Check(connected, std::format("{}: no protocol error closing the window", c.name)); } // A display that raised an error is gone, and so is every later case. if (!connected) break; } Device::manager = decorationManager; Device::wpViewporter = viewporter; Device::fractionalScaleManager = fractionalScaleManager; if (std::getenv("CRAFTER_GRAPHICS_VALIDATION") != nullptr) { Check(Device::validationErrorCount == 0, std::format("no Vulkan validation errors ({} seen)", Device::validationErrorCount)); } if (failures != 0) { std::println("{} check(s) failed", failures); return EXIT_FAILURE; } std::println("all checks passed"); return EXIT_SUCCESS; }