diff --git a/meson.build b/meson.build index e76b58b12..63279eccd 100644 --- a/meson.build +++ b/meson.build @@ -602,30 +602,6 @@ executable('noctalia', install: true, ) -executable('lockscreen-layer-probe', - sources: files('tools/lockscreen_layer_probe.cpp') + _protocol_sources, - dependencies: [ - wayland_client_dep, - ], - include_directories: _noctalia_inc, - cpp_args: [ - '-Wall', '-Wextra', '-Wpedantic', '-Wconversion', '-Wshadow', - ], - build_by_default: false, -) - -executable('subsurface-overflow-probe', - sources: files('tools/subsurface_overflow_probe.cpp') + _protocol_sources, - dependencies: [ - wayland_client_dep, - ], - include_directories: _noctalia_inc, - cpp_args: [ - '-Wall', '-Wextra', '-Wpedantic', '-Wconversion', '-Wshadow', - ], - build_by_default: false, -) - install_subdir('assets', install_dir: get_option('datadir') / 'noctalia', ) diff --git a/tools/lockscreen_layer_probe.cpp b/tools/lockscreen_layer_probe.cpp deleted file mode 100644 index 56e1320db..000000000 --- a/tools/lockscreen_layer_probe.cpp +++ /dev/null @@ -1,830 +0,0 @@ -#include "ext-session-lock-v1-client-protocol.h" -#include "wlr-layer-shell-unstable-v1-client-protocol.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - - volatile std::sig_atomic_t g_interrupted = 0; - - void handleSignal(int /*signal*/) { g_interrupted = 1; } - - int createMemfd(const char* name) { -#ifdef SYS_memfd_create - return static_cast(::syscall(SYS_memfd_create, name, MFD_CLOEXEC)); -#else - errno = ENOSYS; - return -1; -#endif - } - - struct ShmBuffer { - wl_buffer* buffer = nullptr; - void* data = MAP_FAILED; - std::size_t size = 0; - int fd = -1; - int width = 0; - int height = 0; - - ShmBuffer() = default; - ShmBuffer(const ShmBuffer&) = delete; - ShmBuffer& operator=(const ShmBuffer&) = delete; - - ~ShmBuffer() { reset(); } - - void reset() { - if (buffer != nullptr) { - wl_buffer_destroy(buffer); - buffer = nullptr; - } - if (data != MAP_FAILED) { - ::munmap(data, size); - data = MAP_FAILED; - } - if (fd >= 0) { - ::close(fd); - fd = -1; - } - size = 0; - width = 0; - height = 0; - } - - bool create(wl_shm* shm, int nextWidth, int nextHeight, std::uint32_t color) { - reset(); - if (shm == nullptr || nextWidth <= 0 || nextHeight <= 0) { - return false; - } - - constexpr int kBytesPerPixel = 4; - width = nextWidth; - height = nextHeight; - const int stride = width * kBytesPerPixel; - size = static_cast(stride) * static_cast(height); - - fd = createMemfd("noctalia-lockscreen-layer-probe"); - if (fd < 0) { - std::cerr << "memfd_create failed: " << std::strerror(errno) << "\n"; - reset(); - return false; - } - if (::ftruncate(fd, static_cast(size)) != 0) { - std::cerr << "ftruncate failed: " << std::strerror(errno) << "\n"; - reset(); - return false; - } - - data = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (data == MAP_FAILED) { - std::cerr << "mmap failed: " << std::strerror(errno) << "\n"; - reset(); - return false; - } - - auto* pixels = static_cast(data); - const std::size_t pixelCount = static_cast(width) * static_cast(height); - for (std::size_t i = 0; i < pixelCount; ++i) { - pixels[i] = color; - } - - wl_shm_pool* pool = wl_shm_create_pool(shm, fd, static_cast(size)); - if (pool == nullptr) { - std::cerr << "wl_shm_create_pool failed\n"; - reset(); - return false; - } - buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888); - wl_shm_pool_destroy(pool); - if (buffer == nullptr) { - std::cerr << "wl_shm_pool_create_buffer failed\n"; - reset(); - return false; - } - return true; - } - - void fillProbePattern() { - if (data == MAP_FAILED || width <= 0 || height <= 0) { - return; - } - - auto* pixels = static_cast(data); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const bool border = x < 8 || y < 8 || x >= width - 8 || y >= height - 8; - const bool diagonal = std::abs(x - y) < 4 || std::abs((width - x) - y) < 4; - const bool stripe = ((x / 18) + (y / 18)) % 2 == 0; - std::uint32_t color = stripe ? 0xff00d1ff : 0xffff2d75; - if (diagonal) { - color = 0xffffffff; - } - if (border) { - color = 0xff101014; - } - pixels[static_cast(y) * static_cast(width) + static_cast(x)] = color; - } - } - } - - void fillLockPattern() { - if (data == MAP_FAILED || width <= 0 || height <= 0) { - return; - } - - auto* pixels = static_cast(data); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const bool grid = x % 96 < 3 || y % 96 < 3; - const bool band = y > height / 2 - 64 && y < height / 2 + 64; - std::uint32_t color = 0xff101014; - if (grid) { - color = 0xff272733; - } - if (band) { - color = ((x / 32) % 2 == 0) ? 0xff2dd4bf : 0xfffb7185; - } - pixels[static_cast(y) * static_cast(width) + static_cast(x)] = color; - } - } - } - }; - - struct OutputInfo { - std::uint32_t name = 0; - wl_output* output = nullptr; - int scale = 1; - int width = 1920; - int height = 1080; - }; - - struct Client { - wl_display* display = nullptr; - wl_registry* registry = nullptr; - wl_compositor* compositor = nullptr; - wl_subcompositor* subcompositor = nullptr; - wl_shm* shm = nullptr; - zwlr_layer_shell_v1* layerShell = nullptr; - ext_session_lock_manager_v1* lockManager = nullptr; - std::deque outputs; - - bool connect() { - display = wl_display_connect(nullptr); - if (display == nullptr) { - std::cerr << "failed to connect to Wayland display\n"; - return false; - } - - registry = wl_display_get_registry(display); - if (registry == nullptr) { - std::cerr << "failed to get Wayland registry\n"; - return false; - } - wl_registry_add_listener(registry, &kRegistryListener, this); - - if (wl_display_roundtrip(display) < 0 || wl_display_roundtrip(display) < 0) { - reportDisplayError("registry roundtrip failed"); - return false; - } - return true; - } - - void disconnect() { - for (auto& output : outputs) { - if (output.output != nullptr) { - wl_output_destroy(output.output); - output.output = nullptr; - } - } - outputs.clear(); - if (lockManager != nullptr) { - ext_session_lock_manager_v1_destroy(lockManager); - lockManager = nullptr; - } - if (layerShell != nullptr) { - zwlr_layer_shell_v1_destroy(layerShell); - layerShell = nullptr; - } - if (shm != nullptr) { - wl_shm_destroy(shm); - shm = nullptr; - } - if (subcompositor != nullptr) { - wl_subcompositor_destroy(subcompositor); - subcompositor = nullptr; - } - if (compositor != nullptr) { - wl_compositor_destroy(compositor); - compositor = nullptr; - } - if (registry != nullptr) { - wl_registry_destroy(registry); - registry = nullptr; - } - if (display != nullptr) { - wl_display_disconnect(display); - display = nullptr; - } - } - - ~Client() { disconnect(); } - - [[nodiscard]] bool hasDisplayError() const { return display == nullptr || wl_display_get_error(display) != 0; } - - void reportDisplayError(std::string_view prefix) const { - if (display == nullptr) { - std::cerr << prefix << ": display is not connected\n"; - return; - } - - const int err = wl_display_get_error(display); - if (err == EPROTO) { - const wl_interface* interface = nullptr; - std::uint32_t id = 0; - const int code = wl_display_get_protocol_error(display, &interface, &id); - std::cerr << prefix << ": Wayland protocol error"; - if (interface != nullptr) { - std::cerr << " on " << interface->name << "@" << id; - } - std::cerr << " code " << code << "\n"; - return; - } - if (err != 0) { - std::cerr << prefix << ": " << std::strerror(err) << "\n"; - return; - } - std::cerr << prefix << "\n"; - } - - static void handleGlobal(void* data, wl_registry* registry, std::uint32_t name, const char* interface, - std::uint32_t version) { - auto* self = static_cast(data); - const std::string_view iface(interface != nullptr ? interface : ""); - - if (iface == wl_compositor_interface.name) { - self->compositor = static_cast( - wl_registry_bind(registry, name, &wl_compositor_interface, std::min(version, 4u))); - } else if (iface == wl_subcompositor_interface.name) { - self->subcompositor = - static_cast(wl_registry_bind(registry, name, &wl_subcompositor_interface, 1)); - } else if (iface == wl_shm_interface.name) { - self->shm = static_cast(wl_registry_bind(registry, name, &wl_shm_interface, 1)); - } else if (iface == wl_output_interface.name) { - auto* output = - static_cast(wl_registry_bind(registry, name, &wl_output_interface, std::min(version, 2u))); - self->outputs.push_back(OutputInfo{.name = name, .output = output}); - wl_output_add_listener(output, &kOutputListener, &self->outputs.back()); - } else if (iface == zwlr_layer_shell_v1_interface.name) { - self->layerShell = static_cast( - wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, std::min(version, 4u))); - } else if (iface == ext_session_lock_manager_v1_interface.name) { - self->lockManager = static_cast( - wl_registry_bind(registry, name, &ext_session_lock_manager_v1_interface, 1)); - } - } - - static void handleGlobalRemove(void* data, wl_registry* /*registry*/, std::uint32_t name) { - auto* self = static_cast(data); - std::erase_if(self->outputs, [name](const OutputInfo& output) { return output.name == name; }); - } - - static void handleOutputGeometry(void* /*data*/, wl_output* /*output*/, std::int32_t /*x*/, std::int32_t /*y*/, - std::int32_t /*physicalWidth*/, std::int32_t /*physicalHeight*/, - std::int32_t /*subpixel*/, const char* /*make*/, const char* /*model*/, - std::int32_t /*transform*/) {} - - static void handleOutputMode(void* data, wl_output* /*output*/, std::uint32_t flags, std::int32_t width, - std::int32_t height, std::int32_t /*refresh*/) { - if ((flags & WL_OUTPUT_MODE_CURRENT) == 0 || width <= 0 || height <= 0) { - return; - } - auto* out = static_cast(data); - out->width = width; - out->height = height; - } - - static void handleOutputDone(void* /*data*/, wl_output* /*output*/) {} - - static void handleOutputScale(void* data, wl_output* /*output*/, std::int32_t factor) { - auto* out = static_cast(data); - out->scale = std::max(1, factor); - } - - static void handleOutputName(void* /*data*/, wl_output* /*output*/, const char* /*name*/) {} - - static void handleOutputDescription(void* /*data*/, wl_output* /*output*/, const char* /*description*/) {} - - static constexpr wl_registry_listener kRegistryListener = { - .global = &Client::handleGlobal, - .global_remove = &Client::handleGlobalRemove, - }; - - static constexpr wl_output_listener kOutputListener = { - .geometry = &Client::handleOutputGeometry, - .mode = &Client::handleOutputMode, - .done = &Client::handleOutputDone, - .scale = &Client::handleOutputScale, - .name = &Client::handleOutputName, - .description = &Client::handleOutputDescription, - }; - }; - - template - bool dispatchUntil(Client& client, Predicate predicate, std::chrono::milliseconds timeout) { - const auto deadline = std::chrono::steady_clock::now() + timeout; - while (!g_interrupted && !predicate()) { - if (wl_display_prepare_read(client.display) != 0) { - if (wl_display_dispatch_pending(client.display) < 0) { - client.reportDisplayError("wl_display_dispatch_pending failed"); - return false; - } - continue; - } - - while (wl_display_flush(client.display) < 0 && errno == EAGAIN) { - pollfd writable{.fd = wl_display_get_fd(client.display), .events = POLLOUT, .revents = 0}; - if (::poll(&writable, 1, 100) < 0 && errno != EINTR) { - wl_display_cancel_read(client.display); - std::cerr << "poll while flushing failed: " << std::strerror(errno) << "\n"; - return false; - } - } - if (client.hasDisplayError()) { - wl_display_cancel_read(client.display); - client.reportDisplayError("wl_display_flush failed"); - return false; - } - - const auto now = std::chrono::steady_clock::now(); - if (now >= deadline) { - wl_display_cancel_read(client.display); - return predicate(); - } - - const auto remaining = std::chrono::duration_cast(deadline - now); - pollfd readable{.fd = wl_display_get_fd(client.display), .events = POLLIN, .revents = 0}; - const int pollResult = ::poll(&readable, 1, static_cast(remaining.count())); - if (pollResult < 0) { - wl_display_cancel_read(client.display); - if (errno == EINTR) { - continue; - } - std::cerr << "poll failed: " << std::strerror(errno) << "\n"; - return false; - } - if (pollResult == 0) { - wl_display_cancel_read(client.display); - return predicate(); - } - - if (wl_display_read_events(client.display) < 0) { - client.reportDisplayError("wl_display_read_events failed"); - return false; - } - if (wl_display_dispatch_pending(client.display) < 0) { - client.reportDisplayError("wl_display_dispatch_pending failed"); - return false; - } - } - - return predicate(); - } - - struct LockClient; - - struct SubsurfaceProbe { - wl_surface* surface = nullptr; - wl_subsurface* subsurface = nullptr; - ShmBuffer buffer; - bool mapped = false; - - bool create(Client& client, wl_surface* parent) { - if (client.compositor == nullptr || client.subcompositor == nullptr || client.shm == nullptr || - parent == nullptr) { - std::cerr << "missing required globals for subsurface probe: compositor=" << (client.compositor != nullptr) - << " subcompositor=" << (client.subcompositor != nullptr) << " shm=" << (client.shm != nullptr) - << "\n"; - return false; - } - - surface = wl_compositor_create_surface(client.compositor); - if (surface == nullptr) { - std::cerr << "failed to create subsurface wl_surface\n"; - return false; - } - - subsurface = wl_subcompositor_get_subsurface(client.subcompositor, surface, parent); - if (subsurface == nullptr) { - std::cerr << "failed to create wl_subsurface\n"; - return false; - } - - wl_subsurface_set_position(subsurface, 72, 72); - wl_subsurface_set_desync(subsurface); - - constexpr int kWidth = 360; - constexpr int kHeight = 180; - if (!buffer.create(client.shm, kWidth, kHeight, 0xffa3e635)) { - return false; - } - fillSubsurfacePattern(); - wl_surface_attach(surface, buffer.buffer, 0, 0); - wl_surface_damage(surface, 0, 0, kWidth, kHeight); - wl_surface_commit(surface); - mapped = wl_display_flush(client.display) >= 0; - return mapped; - } - - void destroy() { - buffer.reset(); - if (subsurface != nullptr) { - wl_subsurface_destroy(subsurface); - subsurface = nullptr; - } - if (surface != nullptr) { - wl_surface_destroy(surface); - surface = nullptr; - } - mapped = false; - } - - void fillSubsurfacePattern() { - if (buffer.data == MAP_FAILED || buffer.width <= 0 || buffer.height <= 0) { - return; - } - - auto* pixels = static_cast(buffer.data); - for (int y = 0; y < buffer.height; ++y) { - for (int x = 0; x < buffer.width; ++x) { - const bool border = x < 10 || y < 10 || x >= buffer.width - 10 || y >= buffer.height - 10; - const bool cross = std::abs(x - buffer.width / 2) < 6 || std::abs(y - buffer.height / 2) < 6; - const bool stripe = (x / 24) % 2 == 0; - std::uint32_t color = stripe ? 0xffa3e635 : 0xfffacc15; - if (cross) { - color = 0xff000000; - } - if (border) { - color = 0xffffffff; - } - pixels[static_cast(y) * static_cast(buffer.width) + static_cast(x)] = - color; - } - } - } - }; - - struct LockSurfaceProbe { - LockClient* owner = nullptr; - wl_surface* surface = nullptr; - ext_session_lock_surface_v1* lockSurface = nullptr; - std::unique_ptr subsurfaceProbe; - ShmBuffer buffer; - int fallbackWidth = 1920; - int fallbackHeight = 1080; - bool configured = false; - bool mapped = false; - - void destroy() { - if (subsurfaceProbe != nullptr) { - subsurfaceProbe->destroy(); - subsurfaceProbe.reset(); - } - buffer.reset(); - if (lockSurface != nullptr) { - ext_session_lock_surface_v1_destroy(lockSurface); - lockSurface = nullptr; - } - if (surface != nullptr) { - wl_surface_destroy(surface); - surface = nullptr; - } - } - - static void handleConfigure(void* data, ext_session_lock_surface_v1* lockSurface, std::uint32_t serial, - std::uint32_t width, std::uint32_t height); - - static constexpr ext_session_lock_surface_v1_listener kListener = { - .configure = &LockSurfaceProbe::handleConfigure, - }; - }; - - struct LockClient { - Client client; - ext_session_lock_v1* lock = nullptr; - std::vector> surfaces; - bool locked = false; - bool finished = false; - - ~LockClient() { - for (auto& surface : surfaces) { - if (surface != nullptr) { - surface->destroy(); - } - } - surfaces.clear(); - if (lock != nullptr) { - if (locked) { - ext_session_lock_v1_unlock_and_destroy(lock); - wl_display_flush(client.display); - } else { - ext_session_lock_v1_destroy(lock); - } - lock = nullptr; - } - } - - bool start() { - if (!client.connect()) { - return false; - } - if (client.compositor == nullptr || client.shm == nullptr || client.lockManager == nullptr) { - std::cerr << "missing required Wayland globals: compositor=" << (client.compositor != nullptr) - << " shm=" << (client.shm != nullptr) << " ext-session-lock=" << (client.lockManager != nullptr) - << "\n"; - return false; - } - if (client.outputs.empty()) { - std::cerr << "no Wayland outputs advertised\n"; - return false; - } - - lock = ext_session_lock_manager_v1_lock(client.lockManager); - if (lock == nullptr) { - std::cerr << "failed to create ext_session_lock_v1\n"; - return false; - } - ext_session_lock_v1_add_listener(lock, &kListener, this); - - surfaces.reserve(client.outputs.size()); - for (const auto& output : client.outputs) { - auto probe = std::make_unique(); - probe->owner = this; - probe->fallbackWidth = output.width; - probe->fallbackHeight = output.height; - probe->surface = wl_compositor_create_surface(client.compositor); - if (probe->surface == nullptr) { - std::cerr << "failed to create lock wl_surface\n"; - return false; - } - probe->lockSurface = ext_session_lock_v1_get_lock_surface(lock, probe->surface, output.output); - if (probe->lockSurface == nullptr) { - std::cerr << "failed to create ext_session_lock_surface_v1\n"; - return false; - } - ext_session_lock_surface_v1_add_listener(probe->lockSurface, &LockSurfaceProbe::kListener, probe.get()); - surfaces.push_back(std::move(probe)); - } - - return wl_display_flush(client.display) >= 0; - } - - bool waitLocked(std::chrono::milliseconds timeout) { - return dispatchUntil(client, [this]() { return locked || finished; }, timeout) && locked && !finished; - } - - bool createSubsurfaceProbes() { - bool allCreated = true; - for (auto& surface : surfaces) { - if (surface == nullptr || surface->surface == nullptr) { - allCreated = false; - continue; - } - - auto probe = std::make_unique(); - if (!probe->create(client, surface->surface)) { - allCreated = false; - continue; - } - surface->subsurfaceProbe = std::move(probe); - } - return allCreated; - } - - bool hold(std::chrono::milliseconds timeout) { - return dispatchUntil( - client, [this]() { return client.hasDisplayError() || finished; }, timeout) == false && - !client.hasDisplayError() && !finished; - } - - void unlock() { - if (lock == nullptr) { - return; - } - if (locked) { - ext_session_lock_v1_unlock_and_destroy(lock); - lock = nullptr; - locked = false; - wl_display_roundtrip(client.display); - } else { - ext_session_lock_v1_destroy(lock); - lock = nullptr; - } - } - - static void handleLocked(void* data, ext_session_lock_v1* /*lock*/) { - auto* self = static_cast(data); - self->locked = true; - } - - static void handleFinished(void* data, ext_session_lock_v1* /*lock*/) { - auto* self = static_cast(data); - self->finished = true; - } - - static constexpr ext_session_lock_v1_listener kListener = { - .locked = &LockClient::handleLocked, - .finished = &LockClient::handleFinished, - }; - }; - - void LockSurfaceProbe::handleConfigure(void* data, ext_session_lock_surface_v1* lockSurface, std::uint32_t serial, - std::uint32_t width, std::uint32_t height) { - auto* self = static_cast(data); - ext_session_lock_surface_v1_ack_configure(lockSurface, serial); - - const int bufferWidth = static_cast(width == 0 ? static_cast(self->fallbackWidth) : width); - const int bufferHeight = static_cast(height == 0 ? static_cast(self->fallbackHeight) : height); - if (self->owner == nullptr || - !self->buffer.create(self->owner->client.shm, bufferWidth, bufferHeight, 0xff101014)) { - return; - } - self->buffer.fillLockPattern(); - wl_surface_attach(self->surface, self->buffer.buffer, 0, 0); - wl_surface_damage(self->surface, 0, 0, bufferWidth, bufferHeight); - wl_surface_commit(self->surface); - self->configured = true; - self->mapped = true; - } - - struct LayerProbe { - Client client; - wl_surface* surface = nullptr; - zwlr_layer_surface_v1* layerSurface = nullptr; - ShmBuffer buffer; - bool configured = false; - bool mapped = false; - bool closed = false; - - ~LayerProbe() { - buffer.reset(); - if (layerSurface != nullptr) { - zwlr_layer_surface_v1_destroy(layerSurface); - layerSurface = nullptr; - } - if (surface != nullptr) { - wl_surface_destroy(surface); - surface = nullptr; - } - } - - bool start(std::size_t outputIndex) { - if (!client.connect()) { - return false; - } - if (client.compositor == nullptr || client.shm == nullptr || client.layerShell == nullptr) { - std::cerr << "second client missing required Wayland globals: compositor=" << (client.compositor != nullptr) - << " shm=" << (client.shm != nullptr) << " layer-shell=" << (client.layerShell != nullptr) << "\n"; - return false; - } - - wl_output* output = nullptr; - if (outputIndex < client.outputs.size()) { - output = client.outputs[outputIndex].output; - } - surface = wl_compositor_create_surface(client.compositor); - if (surface == nullptr) { - std::cerr << "failed to create probe wl_surface\n"; - return false; - } - - layerSurface = zwlr_layer_shell_v1_get_layer_surface( - client.layerShell, surface, output, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, "noctalia-lockscreen-layer-probe"); - if (layerSurface == nullptr) { - std::cerr << "failed to create probe layer surface\n"; - return false; - } - zwlr_layer_surface_v1_add_listener(layerSurface, &kListener, this); - zwlr_layer_surface_v1_set_anchor(layerSurface, - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); - zwlr_layer_surface_v1_set_size(layerSurface, 360, 180); - zwlr_layer_surface_v1_set_exclusive_zone(layerSurface, -1); - zwlr_layer_surface_v1_set_margin(layerSurface, 72, 0, 0, 500); - zwlr_layer_surface_v1_set_keyboard_interactivity(layerSurface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE); - wl_surface_commit(surface); - return wl_display_flush(client.display) >= 0; - } - - bool waitMapped(std::chrono::milliseconds timeout) { - return dispatchUntil( - client, [this]() { return mapped || closed || client.hasDisplayError(); }, timeout) && - mapped && !closed && !client.hasDisplayError(); - } - - bool hold(std::chrono::milliseconds timeout) { - return dispatchUntil( - client, [this]() { return closed || client.hasDisplayError(); }, timeout) == false && - !closed && !client.hasDisplayError(); - } - - static void handleConfigure(void* data, zwlr_layer_surface_v1* layerSurface, std::uint32_t serial, - std::uint32_t width, std::uint32_t height) { - auto* self = static_cast(data); - zwlr_layer_surface_v1_ack_configure(layerSurface, serial); - - const int bufferWidth = static_cast(width == 0 ? 360 : width); - const int bufferHeight = static_cast(height == 0 ? 180 : height); - if (!self->buffer.create(self->client.shm, bufferWidth, bufferHeight, 0xff30cfd0)) { - return; - } - self->buffer.fillProbePattern(); - wl_surface_attach(self->surface, self->buffer.buffer, 0, 0); - wl_surface_damage(self->surface, 0, 0, bufferWidth, bufferHeight); - wl_surface_commit(self->surface); - self->configured = true; - self->mapped = true; - } - - static void handleClosed(void* data, zwlr_layer_surface_v1* /*layerSurface*/) { - auto* self = static_cast(data); - self->closed = true; - } - - static constexpr zwlr_layer_surface_v1_listener kListener = { - .configure = &LayerProbe::handleConfigure, - .closed = &LayerProbe::handleClosed, - }; - }; - -} // namespace - -int main() { - std::signal(SIGINT, handleSignal); - std::signal(SIGTERM, handleSignal); - - LockClient lockClient; - if (!lockClient.start()) { - return 1; - } - - std::cout << "locking session and mapping required lock surfaces...\n"; - if (!lockClient.waitLocked(std::chrono::seconds(5))) { - if (lockClient.finished) { - std::cerr << "compositor finished the lock request instead of locking\n"; - } else { - std::cerr << "timed out waiting for ext_session_lock_v1.locked\n"; - } - return 1; - } - - std::cout << "session locked; creating widget-sized wl_subsurface markers on every lock surface...\n"; - const bool subsurfacesCreated = lockClient.createSubsurfaceProbes(); - - std::cout << "creating desktop-widget-style layer-shell markers on every advertised output...\n"; - std::vector> layerProbes; - layerProbes.reserve(lockClient.client.outputs.size()); - std::size_t mappedLayerProbes = 0; - for (std::size_t i = 0; i < lockClient.client.outputs.size(); ++i) { - auto probe = std::make_unique(); - if (probe->start(i) && probe->waitMapped(std::chrono::seconds(3))) { - ++mappedLayerProbes; - } else if (probe->client.hasDisplayError()) { - probe->client.reportDisplayError("layer probe failed"); - } else if (probe->closed) { - std::cerr << "compositor closed a layer surface before it mapped\n"; - } else { - std::cerr << "timed out waiting for a layer surface configure/map\n"; - } - layerProbes.push_back(std::move(probe)); - } - - std::cout << "mapped " << mappedLayerProbes << " layer-shell marker(s); holding for visual inspection...\n"; - const bool ok = subsurfacesCreated && lockClient.hold(std::chrono::seconds(10)); - if (lockClient.client.hasDisplayError()) { - lockClient.client.reportDisplayError("subsurface probe failed"); - } - - lockClient.unlock(); - if (!ok) { - std::cerr << "result: failed to keep widget-sized wl_subsurfaces alive on every lock surface\n"; - return 1; - } - - std::cout << "result: successfully created and committed widget-sized wl_subsurfaces while locked\n"; - return 0; -} diff --git a/tools/subsurface_overflow_probe.cpp b/tools/subsurface_overflow_probe.cpp deleted file mode 100644 index 448efe0e7..000000000 --- a/tools/subsurface_overflow_probe.cpp +++ /dev/null @@ -1,694 +0,0 @@ -#include "wlr-layer-shell-unstable-v1-client-protocol.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - - volatile std::sig_atomic_t g_interrupted = 0; - - void handleSignal(int /*signal*/) { g_interrupted = 1; } - - int createMemfd(const char* name) { -#ifdef SYS_memfd_create - return static_cast(::syscall(SYS_memfd_create, name, MFD_CLOEXEC)); -#else - errno = ENOSYS; - return -1; -#endif - } - - struct ShmBuffer { - wl_buffer* buffer = nullptr; - void* data = MAP_FAILED; - std::size_t size = 0; - int fd = -1; - int width = 0; - int height = 0; - - ShmBuffer() = default; - ShmBuffer(const ShmBuffer&) = delete; - ShmBuffer& operator=(const ShmBuffer&) = delete; - - ~ShmBuffer() { reset(); } - - void reset() { - if (buffer != nullptr) { - wl_buffer_destroy(buffer); - buffer = nullptr; - } - if (data != MAP_FAILED) { - ::munmap(data, size); - data = MAP_FAILED; - } - if (fd >= 0) { - ::close(fd); - fd = -1; - } - size = 0; - width = 0; - height = 0; - } - - bool create(wl_shm* shm, int nextWidth, int nextHeight, std::uint32_t color) { - reset(); - if (shm == nullptr || nextWidth <= 0 || nextHeight <= 0) { - return false; - } - - constexpr int kBytesPerPixel = 4; - width = nextWidth; - height = nextHeight; - const int stride = width * kBytesPerPixel; - size = static_cast(stride) * static_cast(height); - - fd = createMemfd("noctalia-subsurface-overflow-probe"); - if (fd < 0) { - std::cerr << "memfd_create failed: " << std::strerror(errno) << "\n"; - reset(); - return false; - } - if (::ftruncate(fd, static_cast(size)) != 0) { - std::cerr << "ftruncate failed: " << std::strerror(errno) << "\n"; - reset(); - return false; - } - - data = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (data == MAP_FAILED) { - std::cerr << "mmap failed: " << std::strerror(errno) << "\n"; - reset(); - return false; - } - - auto* pixels = static_cast(data); - const std::size_t pixelCount = static_cast(width) * static_cast(height); - for (std::size_t i = 0; i < pixelCount; ++i) { - pixels[i] = color; - } - - wl_shm_pool* pool = wl_shm_create_pool(shm, fd, static_cast(size)); - if (pool == nullptr) { - std::cerr << "wl_shm_create_pool failed\n"; - reset(); - return false; - } - buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888); - wl_shm_pool_destroy(pool); - if (buffer == nullptr) { - std::cerr << "wl_shm_pool_create_buffer failed\n"; - reset(); - return false; - } - return true; - } - - static std::uint32_t premul(std::uint8_t r, std::uint8_t g, std::uint8_t b, std::uint8_t a) { - const auto scale = [a](std::uint8_t c) -> std::uint8_t { - return static_cast((static_cast(c) * static_cast(a) + 127u) / 255u); - }; - return (static_cast(a) << 24u) | (static_cast(scale(r)) << 16u) | - (static_cast(scale(g)) << 8u) | static_cast(scale(b)); - } - - static std::uint32_t over(std::uint32_t src, std::uint32_t dst) { - const std::uint32_t srcA = (src >> 24u) & 0xffu; - const std::uint32_t invA = 255u - srcA; - const std::uint32_t dstA = (dst >> 24u) & 0xffu; - const std::uint32_t srcR = (src >> 16u) & 0xffu; - const std::uint32_t srcG = (src >> 8u) & 0xffu; - const std::uint32_t srcB = src & 0xffu; - const std::uint32_t dstR = (dst >> 16u) & 0xffu; - const std::uint32_t dstG = (dst >> 8u) & 0xffu; - const std::uint32_t dstB = dst & 0xffu; - const std::uint32_t outA = srcA + (dstA * invA + 127u) / 255u; - const std::uint32_t outR = srcR + (dstR * invA + 127u) / 255u; - const std::uint32_t outG = srcG + (dstG * invA + 127u) / 255u; - const std::uint32_t outB = srcB + (dstB * invA + 127u) / 255u; - return (outA << 24u) | (outR << 16u) | (outG << 8u) | outB; - } - - void blendPixel(int x, int y, std::uint32_t color) { - if (data == MAP_FAILED || x < 0 || y < 0 || x >= width || y >= height) { - return; - } - auto* pixels = static_cast(data); - auto& dst = pixels[static_cast(y) * static_cast(width) + static_cast(x)]; - dst = over(color, dst); - } - - void fillVisualPolicyParent(int barY, int barHeight, int badX, - const std::vector>& cleanMarkers, int childWidth) { - if (data == MAP_FAILED || width <= 0 || height <= 0) { - return; - } - - auto* pixels = static_cast(data); - std::fill(pixels, pixels + static_cast(width) * static_cast(height), 0x00000000u); - - const int barX = 32; - const int barW = width - 64; - const int shadowStart = barY + barHeight - 2; - for (int y = shadowStart; y < height; ++y) { - const int dy = y - shadowStart; - const std::uint8_t alpha = static_cast(std::max(0, 96 - dy * 4)); - for (int x = barX; x < barX + barW; ++x) { - const bool suppressedForCleanPolicy = - std::any_of(cleanMarkers.begin(), cleanMarkers.end(), [x, childWidth](const auto& marker) { - return x >= marker.first && x < marker.first + childWidth; - }); - if (!suppressedForCleanPolicy) { - blendPixel(x, y, premul(0, 0, 0, alpha)); - } - } - } - - const std::uint32_t bar = premul(20, 23, 31, 218); - for (int y = barY; y < barY + barHeight; ++y) { - for (int x = barX; x < barX + barW; ++x) { - blendPixel(x, y, bar); - } - } - - const auto drawChip = [&](int x, std::uint32_t color) { - for (int y = barY + 12; y < barY + barHeight - 12; ++y) { - for (int px = x; px < x + childWidth; ++px) { - if (px >= barX && px < barX + barW) { - blendPixel(px, y, color); - } - } - } - }; - drawChip(badX, premul(239, 68, 68, 96)); - for (const auto& marker : cleanMarkers) { - drawChip(marker.first, marker.second); - } - } - - void fillVisualPolicyPanel(bool cleanPolicy, int bridgeRows) { - if (data == MAP_FAILED || width <= 0 || height <= 0) { - return; - } - - auto* pixels = static_cast(data); - std::fill(pixels, pixels + static_cast(width) * static_cast(height), 0x00000000u); - - const std::uint32_t bg = cleanPolicy ? premul(20, 23, 31, 218) : premul(36, 42, 56, 214); - const std::uint32_t border = premul(255, 255, 255, 160); - const std::uint32_t line = premul(255, 255, 255, 32); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - const bool transparentCorner = (x < 12 && y < 12) || (x >= width - 12 && y < 12); - if (transparentCorner) { - continue; - } - if (cleanPolicy && y < bridgeRows) { - blendPixel(x, y, premul(20, 23, 31, 255)); - } else { - blendPixel(x, y, bg); - } - if (!cleanPolicy && (x < 2 || y < 2 || x >= width - 2 || y >= height - 2)) { - blendPixel(x, y, border); - } - if (!cleanPolicy && y < 9) { - blendPixel(x, y, premul(239, 68, 68, 180)); - } - if (y > 30 && y % 34 == 0 && x > 24 && x < width - 24) { - blendPixel(x, y, line); - } - } - } - } - }; - - struct OutputInfo { - std::uint32_t name = 0; - wl_output* output = nullptr; - }; - - struct Client { - wl_display* display = nullptr; - wl_registry* registry = nullptr; - wl_compositor* compositor = nullptr; - wl_subcompositor* subcompositor = nullptr; - wl_shm* shm = nullptr; - zwlr_layer_shell_v1* layerShell = nullptr; - std::deque outputs; - - bool connect() { - display = wl_display_connect(nullptr); - if (display == nullptr) { - std::cerr << "failed to connect to Wayland display\n"; - return false; - } - - registry = wl_display_get_registry(display); - if (registry == nullptr) { - std::cerr << "failed to get Wayland registry\n"; - return false; - } - wl_registry_add_listener(registry, &kRegistryListener, this); - - if (wl_display_roundtrip(display) < 0 || wl_display_roundtrip(display) < 0) { - reportDisplayError("registry roundtrip failed"); - return false; - } - return true; - } - - void disconnect() { - for (auto& output : outputs) { - if (output.output != nullptr) { - wl_output_destroy(output.output); - output.output = nullptr; - } - } - outputs.clear(); - if (layerShell != nullptr) { - zwlr_layer_shell_v1_destroy(layerShell); - layerShell = nullptr; - } - if (shm != nullptr) { - wl_shm_destroy(shm); - shm = nullptr; - } - if (subcompositor != nullptr) { - wl_subcompositor_destroy(subcompositor); - subcompositor = nullptr; - } - if (compositor != nullptr) { - wl_compositor_destroy(compositor); - compositor = nullptr; - } - if (registry != nullptr) { - wl_registry_destroy(registry); - registry = nullptr; - } - if (display != nullptr) { - wl_display_disconnect(display); - display = nullptr; - } - } - - ~Client() { disconnect(); } - - [[nodiscard]] bool hasDisplayError() const { return display == nullptr || wl_display_get_error(display) != 0; } - - void reportDisplayError(std::string_view prefix) const { - if (display == nullptr) { - std::cerr << prefix << ": display is not connected\n"; - return; - } - - const int err = wl_display_get_error(display); - if (err == EPROTO) { - const wl_interface* interface = nullptr; - std::uint32_t id = 0; - const int code = wl_display_get_protocol_error(display, &interface, &id); - std::cerr << prefix << ": Wayland protocol error"; - if (interface != nullptr) { - std::cerr << " on " << interface->name << "@" << id; - } - std::cerr << " code " << code << "\n"; - return; - } - if (err != 0) { - std::cerr << prefix << ": " << std::strerror(err) << "\n"; - return; - } - std::cerr << prefix << "\n"; - } - - static void handleGlobal(void* data, wl_registry* registry, std::uint32_t name, const char* interface, - std::uint32_t version) { - auto* self = static_cast(data); - const std::string_view iface(interface != nullptr ? interface : ""); - - if (iface == wl_compositor_interface.name) { - self->compositor = static_cast( - wl_registry_bind(registry, name, &wl_compositor_interface, std::min(version, 4u))); - } else if (iface == wl_subcompositor_interface.name) { - self->subcompositor = - static_cast(wl_registry_bind(registry, name, &wl_subcompositor_interface, 1)); - } else if (iface == wl_shm_interface.name) { - self->shm = static_cast(wl_registry_bind(registry, name, &wl_shm_interface, 1)); - } else if (iface == wl_output_interface.name) { - auto* output = - static_cast(wl_registry_bind(registry, name, &wl_output_interface, std::min(version, 2u))); - self->outputs.push_back(OutputInfo{.name = name, .output = output}); - } else if (iface == zwlr_layer_shell_v1_interface.name) { - self->layerShell = static_cast( - wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, std::min(version, 4u))); - } - } - - static void handleGlobalRemove(void* data, wl_registry* /*registry*/, std::uint32_t name) { - auto* self = static_cast(data); - std::erase_if(self->outputs, [name](const OutputInfo& output) { return output.name == name; }); - } - - static constexpr wl_registry_listener kRegistryListener = { - .global = &Client::handleGlobal, - .global_remove = &Client::handleGlobalRemove, - }; - }; - - template - bool dispatchUntil(Client& client, Predicate predicate, std::chrono::milliseconds timeout) { - const auto deadline = std::chrono::steady_clock::now() + timeout; - while (!g_interrupted && !predicate()) { - if (wl_display_prepare_read(client.display) != 0) { - if (wl_display_dispatch_pending(client.display) < 0) { - client.reportDisplayError("wl_display_dispatch_pending failed"); - return false; - } - continue; - } - - while (wl_display_flush(client.display) < 0 && errno == EAGAIN) { - pollfd writable{.fd = wl_display_get_fd(client.display), .events = POLLOUT, .revents = 0}; - if (::poll(&writable, 1, 100) < 0 && errno != EINTR) { - wl_display_cancel_read(client.display); - std::cerr << "poll while flushing failed: " << std::strerror(errno) << "\n"; - return false; - } - } - if (client.hasDisplayError()) { - wl_display_cancel_read(client.display); - client.reportDisplayError("wl_display_flush failed"); - return false; - } - - const auto now = std::chrono::steady_clock::now(); - if (now >= deadline) { - wl_display_cancel_read(client.display); - return predicate(); - } - - const auto remaining = std::chrono::duration_cast(deadline - now); - pollfd readable{.fd = wl_display_get_fd(client.display), .events = POLLIN, .revents = 0}; - const int pollResult = ::poll(&readable, 1, static_cast(remaining.count())); - if (pollResult < 0) { - wl_display_cancel_read(client.display); - if (errno == EINTR) { - continue; - } - std::cerr << "poll failed: " << std::strerror(errno) << "\n"; - return false; - } - if (pollResult == 0) { - wl_display_cancel_read(client.display); - return predicate(); - } - - if (wl_display_read_events(client.display) < 0) { - client.reportDisplayError("wl_display_read_events failed"); - return false; - } - if (wl_display_dispatch_pending(client.display) < 0) { - client.reportDisplayError("wl_display_dispatch_pending failed"); - return false; - } - } - - return predicate(); - } - - struct OverflowSurface { - static constexpr int kParentWidth = 1680; - static constexpr int kParentHeight = 98; - static constexpr int kBarY = 16; - static constexpr int kBarHeight = 48; - static constexpr int kChildWidth = 340; - static constexpr int kChildHeight = 260; - static constexpr int kBadChildX = 52; - static constexpr int kBadChildY = kBarY + kBarHeight - 8; - static constexpr int kCleanChildY = kBarY + kBarHeight; - static constexpr int kExactChildX = 448; - static constexpr int kGuard1ChildX = 844; - static constexpr int kGuard2ChildX = 1240; - - struct ChildPanel { - wl_surface* surface = nullptr; - wl_subsurface* subsurface = nullptr; - ShmBuffer buffer; - bool mapped = false; - bool cleanPolicy = false; - int bridgeRows = 0; - - ~ChildPanel() { destroy(); } - - void destroy() { - buffer.reset(); - if (subsurface != nullptr) { - wl_subsurface_destroy(subsurface); - subsurface = nullptr; - } - if (surface != nullptr) { - wl_surface_destroy(surface); - surface = nullptr; - } - mapped = false; - } - }; - - Client* client = nullptr; - wl_output* output = nullptr; - wl_surface* parentSurface = nullptr; - zwlr_layer_surface_v1* layerSurface = nullptr; - ShmBuffer parentBuffer; - std::vector> children; - bool configured = false; - bool closed = false; - - ~OverflowSurface() { destroy(); } - - bool start(Client& nextClient, wl_output* nextOutput) { - client = &nextClient; - output = nextOutput; - - parentSurface = wl_compositor_create_surface(client->compositor); - if (parentSurface == nullptr) { - std::cerr << "failed to create parent wl_surface\n"; - return false; - } - - layerSurface = - zwlr_layer_shell_v1_get_layer_surface(client->layerShell, parentSurface, output, - ZWLR_LAYER_SHELL_V1_LAYER_TOP, "noctalia-subsurface-overflow-probe"); - if (layerSurface == nullptr) { - std::cerr << "failed to create parent layer surface\n"; - return false; - } - - zwlr_layer_surface_v1_add_listener(layerSurface, &kListener, this); - zwlr_layer_surface_v1_set_anchor(layerSurface, - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); - zwlr_layer_surface_v1_set_size(layerSurface, kParentWidth, kParentHeight); - zwlr_layer_surface_v1_set_exclusive_zone(layerSurface, -1); - zwlr_layer_surface_v1_set_margin(layerSurface, 80, 0, 0, 120); - zwlr_layer_surface_v1_set_keyboard_interactivity(layerSurface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE); - wl_surface_commit(parentSurface); - return true; - } - - void destroy() { - for (auto& child : children) { - if (child != nullptr) { - child->destroy(); - } - } - children.clear(); - parentBuffer.reset(); - if (layerSurface != nullptr) { - zwlr_layer_surface_v1_destroy(layerSurface); - layerSurface = nullptr; - } - if (parentSurface != nullptr) { - wl_surface_destroy(parentSurface); - parentSurface = nullptr; - } - configured = false; - } - - [[nodiscard]] bool childrenMapped() const { - return children.size() == 4 && std::all_of(children.begin(), children.end(), - [](const auto& child) { return child != nullptr && child->mapped; }); - } - - void mapChild(int x, int y, bool cleanPolicy, int bridgeRows) { - if (parentSurface == nullptr || client == nullptr) { - return; - } - - auto child = std::make_unique(); - child->cleanPolicy = cleanPolicy; - child->bridgeRows = bridgeRows; - child->surface = wl_compositor_create_surface(client->compositor); - if (child->surface == nullptr) { - std::cerr << "failed to create child wl_surface\n"; - return; - } - - child->subsurface = wl_subcompositor_get_subsurface(client->subcompositor, child->surface, parentSurface); - if (child->subsurface == nullptr) { - std::cerr << "failed to create child wl_subsurface\n"; - return; - } - - wl_subsurface_set_position(child->subsurface, x, y); - wl_subsurface_place_above(child->subsurface, parentSurface); - wl_subsurface_set_desync(child->subsurface); - - if (!child->buffer.create(client->shm, kChildWidth, kChildHeight, 0x00000000)) { - return; - } - child->buffer.fillVisualPolicyPanel(cleanPolicy, bridgeRows); - wl_surface_attach(child->surface, child->buffer.buffer, 0, 0); - wl_surface_damage(child->surface, 0, 0, kChildWidth, kChildHeight); - wl_surface_commit(child->surface); - wl_surface_commit(parentSurface); - child->mapped = true; - children.push_back(std::move(child)); - } - - void mapChildren() { - if (childrenMapped()) { - return; - } - children.clear(); - mapChild(kBadChildX, kBadChildY, false, 0); - mapChild(kExactChildX, kCleanChildY, true, 0); - mapChild(kGuard1ChildX, kCleanChildY - 1, true, 1); - mapChild(kGuard2ChildX, kCleanChildY - 2, true, 2); - } - - static void handleConfigure(void* data, zwlr_layer_surface_v1* layerSurface, std::uint32_t serial, - std::uint32_t width, std::uint32_t height) { - auto* self = static_cast(data); - zwlr_layer_surface_v1_ack_configure(layerSurface, serial); - - const int parentWidth = static_cast(width == 0 ? kParentWidth : width); - const int parentHeight = static_cast(height == 0 ? kParentHeight : height); - if (self->client == nullptr || - !self->parentBuffer.create(self->client->shm, parentWidth, parentHeight, 0x00000000)) { - return; - } - const std::vector> cleanMarkers{ - {kExactChildX, ShmBuffer::premul(34, 197, 94, 64)}, - {kGuard1ChildX, ShmBuffer::premul(14, 165, 233, 64)}, - {kGuard2ChildX, ShmBuffer::premul(168, 85, 247, 64)}, - }; - self->parentBuffer.fillVisualPolicyParent(kBarY, kBarHeight, kBadChildX, cleanMarkers, kChildWidth); - wl_surface_attach(self->parentSurface, self->parentBuffer.buffer, 0, 0); - wl_surface_damage(self->parentSurface, 0, 0, parentWidth, parentHeight); - wl_surface_commit(self->parentSurface); - self->configured = true; - self->mapChildren(); - } - - static void handleClosed(void* data, zwlr_layer_surface_v1* /*layerSurface*/) { - auto* self = static_cast(data); - self->closed = true; - } - - static constexpr zwlr_layer_surface_v1_listener kListener = { - .configure = &OverflowSurface::handleConfigure, - .closed = &OverflowSurface::handleClosed, - }; - }; - -} // namespace - -int main() { - std::signal(SIGINT, handleSignal); - std::signal(SIGTERM, handleSignal); - - Client client; - if (!client.connect()) { - return 1; - } - if (client.compositor == nullptr || client.subcompositor == nullptr || client.shm == nullptr || - client.layerShell == nullptr) { - std::cerr << "missing required Wayland globals: compositor=" << (client.compositor != nullptr) - << " subcompositor=" << (client.subcompositor != nullptr) << " shm=" << (client.shm != nullptr) - << " layer-shell=" << (client.layerShell != nullptr) << "\n"; - return 1; - } - if (client.outputs.empty()) { - std::cerr << "no Wayland outputs advertised\n"; - return 1; - } - - std::vector> surfaces; - surfaces.reserve(client.outputs.size()); - for (const auto& output : client.outputs) { - auto surface = std::make_unique(); - if (!surface->start(client, output.output)) { - return 1; - } - surfaces.push_back(std::move(surface)); - } - wl_display_flush(client.display); - - const auto allMapped = [&surfaces]() { - return std::all_of(surfaces.begin(), surfaces.end(), [](const auto& surface) { - return surface != nullptr && (surface->childrenMapped() || surface->closed); - }); - }; - std::cout << "created " << surfaces.size() - << " visual-policy parent strip(s); waiting for attached panel subsurfaces...\n"; - if (!dispatchUntil(client, allMapped, std::chrono::seconds(3))) { - if (client.hasDisplayError()) { - client.reportDisplayError("subsurface overflow probe failed"); - } else { - std::cerr << "timed out waiting for attached panel subsurfaces to map\n"; - } - return 1; - } - - const std::size_t mapped = - static_cast(std::count_if(surfaces.begin(), surfaces.end(), [](const auto& surface) { - return surface != nullptr && surface->childrenMapped() && !surface->closed; - })); - std::cout << "mapped " << mapped - << " visual mockup(s); left is intentional overlap/shadow, right is flush with shadow suppressed.\n"; - std::cout << "holding for visual inspection...\n"; - - const bool closed = dispatchUntil( - client, - [&client, &surfaces]() { - return client.hasDisplayError() || std::any_of(surfaces.begin(), surfaces.end(), [](const auto& surface) { - return surface == nullptr || surface->closed; - }); - }, - std::chrono::seconds(12)); - if (closed) { - if (client.hasDisplayError()) { - client.reportDisplayError("subsurface overflow probe failed during hold"); - } else { - std::cerr << "compositor closed at least one parent layer surface during hold\n"; - } - return 1; - } - - std::cout << "result: attached panel wl_subsurfaces stayed alive for visual inspection\n"; - return 0; -}