chore: removing probe files

This commit is contained in:
Lemmy
2026-04-26 01:39:03 -04:00
parent 017caa7cbb
commit 1cc9e5b4b0
3 changed files with 0 additions and 1548 deletions
-24
View File
@@ -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',
)
-830
View File
@@ -1,830 +0,0 @@
#include "ext-session-lock-v1-client-protocol.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include <algorithm>
#include <cerrno>
#include <chrono>
#include <csignal>
#include <cstdint>
#include <cstring>
#include <deque>
#include <iostream>
#include <linux/memfd.h>
#include <memory>
#include <poll.h>
#include <string>
#include <string_view>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <vector>
#include <wayland-client.h>
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<int>(::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<std::size_t>(stride) * static_cast<std::size_t>(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<off_t>(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<std::uint32_t*>(data);
const std::size_t pixelCount = static_cast<std::size_t>(width) * static_cast<std::size_t>(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<int>(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<std::uint32_t*>(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<std::size_t>(y) * static_cast<std::size_t>(width) + static_cast<std::size_t>(x)] = color;
}
}
}
void fillLockPattern() {
if (data == MAP_FAILED || width <= 0 || height <= 0) {
return;
}
auto* pixels = static_cast<std::uint32_t*>(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<std::size_t>(y) * static_cast<std::size_t>(width) + static_cast<std::size_t>(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<OutputInfo> 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<Client*>(data);
const std::string_view iface(interface != nullptr ? interface : "");
if (iface == wl_compositor_interface.name) {
self->compositor = static_cast<wl_compositor*>(
wl_registry_bind(registry, name, &wl_compositor_interface, std::min(version, 4u)));
} else if (iface == wl_subcompositor_interface.name) {
self->subcompositor =
static_cast<wl_subcompositor*>(wl_registry_bind(registry, name, &wl_subcompositor_interface, 1));
} else if (iface == wl_shm_interface.name) {
self->shm = static_cast<wl_shm*>(wl_registry_bind(registry, name, &wl_shm_interface, 1));
} else if (iface == wl_output_interface.name) {
auto* output =
static_cast<wl_output*>(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<zwlr_layer_shell_v1*>(
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<ext_session_lock_manager_v1*>(
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<Client*>(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<OutputInfo*>(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<OutputInfo*>(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 <typename Predicate>
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<std::chrono::milliseconds>(deadline - now);
pollfd readable{.fd = wl_display_get_fd(client.display), .events = POLLIN, .revents = 0};
const int pollResult = ::poll(&readable, 1, static_cast<int>(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<std::uint32_t*>(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<std::size_t>(y) * static_cast<std::size_t>(buffer.width) + static_cast<std::size_t>(x)] =
color;
}
}
}
};
struct LockSurfaceProbe {
LockClient* owner = nullptr;
wl_surface* surface = nullptr;
ext_session_lock_surface_v1* lockSurface = nullptr;
std::unique_ptr<SubsurfaceProbe> 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<std::unique_ptr<LockSurfaceProbe>> 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<LockSurfaceProbe>();
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<SubsurfaceProbe>();
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<LockClient*>(data);
self->locked = true;
}
static void handleFinished(void* data, ext_session_lock_v1* /*lock*/) {
auto* self = static_cast<LockClient*>(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<LockSurfaceProbe*>(data);
ext_session_lock_surface_v1_ack_configure(lockSurface, serial);
const int bufferWidth = static_cast<int>(width == 0 ? static_cast<std::uint32_t>(self->fallbackWidth) : width);
const int bufferHeight = static_cast<int>(height == 0 ? static_cast<std::uint32_t>(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<LayerProbe*>(data);
zwlr_layer_surface_v1_ack_configure(layerSurface, serial);
const int bufferWidth = static_cast<int>(width == 0 ? 360 : width);
const int bufferHeight = static_cast<int>(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<LayerProbe*>(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<std::unique_ptr<LayerProbe>> 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<LayerProbe>();
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;
}
-694
View File
@@ -1,694 +0,0 @@
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include <algorithm>
#include <cerrno>
#include <chrono>
#include <csignal>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <iostream>
#include <linux/memfd.h>
#include <memory>
#include <poll.h>
#include <string_view>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <vector>
#include <wayland-client.h>
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<int>(::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<std::size_t>(stride) * static_cast<std::size_t>(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<off_t>(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<std::uint32_t*>(data);
const std::size_t pixelCount = static_cast<std::size_t>(width) * static_cast<std::size_t>(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<int>(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<std::uint8_t>((static_cast<unsigned>(c) * static_cast<unsigned>(a) + 127u) / 255u);
};
return (static_cast<std::uint32_t>(a) << 24u) | (static_cast<std::uint32_t>(scale(r)) << 16u) |
(static_cast<std::uint32_t>(scale(g)) << 8u) | static_cast<std::uint32_t>(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<std::uint32_t*>(data);
auto& dst = pixels[static_cast<std::size_t>(y) * static_cast<std::size_t>(width) + static_cast<std::size_t>(x)];
dst = over(color, dst);
}
void fillVisualPolicyParent(int barY, int barHeight, int badX,
const std::vector<std::pair<int, std::uint32_t>>& cleanMarkers, int childWidth) {
if (data == MAP_FAILED || width <= 0 || height <= 0) {
return;
}
auto* pixels = static_cast<std::uint32_t*>(data);
std::fill(pixels, pixels + static_cast<std::size_t>(width) * static_cast<std::size_t>(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::uint8_t>(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<std::uint32_t*>(data);
std::fill(pixels, pixels + static_cast<std::size_t>(width) * static_cast<std::size_t>(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<OutputInfo> 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<Client*>(data);
const std::string_view iface(interface != nullptr ? interface : "");
if (iface == wl_compositor_interface.name) {
self->compositor = static_cast<wl_compositor*>(
wl_registry_bind(registry, name, &wl_compositor_interface, std::min(version, 4u)));
} else if (iface == wl_subcompositor_interface.name) {
self->subcompositor =
static_cast<wl_subcompositor*>(wl_registry_bind(registry, name, &wl_subcompositor_interface, 1));
} else if (iface == wl_shm_interface.name) {
self->shm = static_cast<wl_shm*>(wl_registry_bind(registry, name, &wl_shm_interface, 1));
} else if (iface == wl_output_interface.name) {
auto* output =
static_cast<wl_output*>(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<zwlr_layer_shell_v1*>(
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<Client*>(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 <typename Predicate>
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<std::chrono::milliseconds>(deadline - now);
pollfd readable{.fd = wl_display_get_fd(client.display), .events = POLLIN, .revents = 0};
const int pollResult = ::poll(&readable, 1, static_cast<int>(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<std::unique_ptr<ChildPanel>> 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<ChildPanel>();
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<OverflowSurface*>(data);
zwlr_layer_surface_v1_ack_configure(layerSurface, serial);
const int parentWidth = static_cast<int>(width == 0 ? kParentWidth : width);
const int parentHeight = static_cast<int>(height == 0 ? kParentHeight : height);
if (self->client == nullptr ||
!self->parentBuffer.create(self->client->shm, parentWidth, parentHeight, 0x00000000)) {
return;
}
const std::vector<std::pair<int, std::uint32_t>> 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<OverflowSurface*>(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<std::unique_ptr<OverflowSurface>> surfaces;
surfaces.reserve(client.outputs.size());
for (const auto& output : client.outputs) {
auto surface = std::make_unique<OverflowSurface>();
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::size_t>(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;
}