//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // SetTitle on the Wayland backend sends xdg_toplevel.set_title with exactly // the view's bytes. It handed title.data() on, which set_title reads up to // a NUL: a view into a longer string sent the whole string, and an empty // view, whose data() is null, sent a null string, which libwayland refuses // with a fatal error on the display. // // The toplevel is a proxy on a wl_display connected to one end of a // socketpair, and the test reads the requests off the other end, so no // compositor is needed. Needs the Wayland backend, hence Linux only. #include #include #include #include #include #include #include "../../lib/xdg-shell-client-protocol.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; } // What the display has written so far. std::vector Sent(wl_display* display, int peer) { wl_display_flush(display); std::vector wire; std::uint8_t chunk[4096]; for (ssize_t n; (n = recv(peer, chunk, sizeof(chunk), MSG_DONTWAIT)) > 0;) { wire.insert(wire.end(), chunk, chunk + n); } return wire; } // The titles of the set_title requests on `object` in `wire`. A request is // its object id, then its size and opcode in one word, then its arguments; // set_title's one argument is a string, its length with the NUL, then its // bytes padded to a word. std::vector Titles(const std::vector& wire, std::uint32_t object) { std::vector titles; for (std::size_t at = 0; at + 8 <= wire.size();) { std::uint32_t header[2]; std::memcpy(header, wire.data() + at, sizeof(header)); const std::uint32_t size = header[1] >> 16; if (size < 8 || at + size > wire.size()) break; if (header[0] == object && (header[1] & 0xFFFF) == XDG_TOPLEVEL_SET_TITLE && size >= 12) { std::uint32_t length; std::memcpy(&length, wire.data() + at + 8, sizeof(length)); if (length > 0 && 12 + length <= size) { titles.emplace_back(reinterpret_cast(wire.data() + at + 12), length - 1); } } at += size; } return titles; } } // namespace int main() { int fds[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds) != 0) { std::println("FAIL socketpair: {}", std::strerror(errno)); return EXIT_FAILURE; } // The display owns fds[0] from here. wl_display* display = wl_display_connect_to_fd(fds[0]); if (display == nullptr) { std::println("FAIL wl_display_connect_to_fd: {}", std::strerror(errno)); return EXIT_FAILURE; } // libwayland makes whatever proxy it is asked to bind; nothing answers. wl_registry* registry = wl_display_get_registry(display); auto* toplevel = static_cast(wl_registry_bind(registry, 1, &xdg_toplevel_interface, 1)); const std::uint32_t id = wl_proxy_get_id(reinterpret_cast(toplevel)); Sent(display, fds[1]); { // A default-constructed window, which ~Window can tear down without // a display, holding the proxy as its toplevel. Window window; window.xdgToplevel = toplevel; const std::string longer = "Crafter.Graphics"; window.SetTitle(std::string_view(longer).substr(0, 7)); const std::vector sliced = Titles(Sent(display, fds[1]), id); Check(sliced == std::vector{ "Crafter" }, "a view into a longer string: the view's bytes only"); if (sliced != std::vector{ "Crafter" }) { for (const std::string& title : sliced) std::println(" set_title \"{}\"", title); } window.SetTitle(std::string_view{}); const std::vector empty = Titles(Sent(display, fds[1]), id); const int error = wl_display_get_error(display); Check(error == 0 && empty == std::vector{ "" }, "an empty view: an empty title, not a null string"); if (error != 0) std::println(" the display failed: {}", std::strerror(error)); // ~Window destroys the toplevel. } wl_registry_destroy(registry); wl_display_disconnect(display); close(fds[1]); if (failures != 0) { std::println("{} check(s) failed", failures); return EXIT_FAILURE; } std::println("all checks passed"); return EXIT_SUCCESS; }