143 lines
6.3 KiB
C++
143 lines
6.3 KiB
C++
|
|
//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 <wayland-client.h>
|
||
|
|
#include <cstdlib>
|
||
|
|
#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;
|
||
|
|
}
|