AtlatestRepositorysigil-wm
1
/* SPDX-License-Identifier: BSD-3-Clause */2
#define _POSIX_C_SOURCE 200809L3
#include <fcntl.h>4
#include <stdbool.h>5
#include <stdint.h>6
#include <stdio.h>7
#include <stdlib.h>8
#include <string.h>9
#include <sys/mman.h>10
#include <unistd.h>11
#include <wayland-client.h>12
#include "xdg-shell-client-protocol.h"13
#include "wlr-layer-shell-unstable-v1-client-protocol.h"15
struct surface {16
struct wl_surface *wl;17
struct xdg_surface *xdg;18
struct xdg_popup *popup;19
int width, height;20
bool child;21
bool frame_pending;22
int frame_phase;23
};24
static struct wl_display *display;25
static struct wl_compositor *compositor;26
static struct wl_shm *shm;27
static struct xdg_wm_base *shell;28
static struct zwlr_layer_shell_v1 *layers;29
static struct zwlr_layer_surface_v1 *layer;30
static struct surface parent, popup;31
static int phase, configured_phase, completed_phase = -1, checks, failures;32
static bool running = true;34
static void require(bool ok, const char *message) {35
if (ok) return;36
fprintf(stderr, "popup client: %s\n", message);37
exit(2);38
}39
static struct xdg_positioner *positioner(void) {40
struct xdg_positioner *p = xdg_wm_base_create_positioner(shell);41
xdg_positioner_set_size(p, 160, 100);42
xdg_positioner_set_anchor_rect(p, phase == 2 ? 0 : 140, phase == 2 ? 0 : 140, 1, 1);43
xdg_positioner_set_anchor(p, phase == 2 ? XDG_POSITIONER_ANCHOR_TOP_LEFT : XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT);44
xdg_positioner_set_gravity(p, phase == 2 ? XDG_POSITIONER_GRAVITY_TOP_LEFT : XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);45
uint32_t adjust = XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y;46
if (phase != 1) adjust |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y;47
xdg_positioner_set_constraint_adjustment(p, adjust);48
return p;49
}50
static void open_popup(void);51
static void frame(void *data, struct wl_callback *callback, uint32_t time) {52
(void)time;53
struct surface *s = data;54
wl_callback_destroy(callback);55
s->frame_pending = false;56
if (!s->child) {57
if (!popup.wl) open_popup();58
} else if (s->frame_phase <= completed_phase) return;59
else if ((phase = (completed_phase = s->frame_phase) + 1) < 3) {60
struct xdg_positioner *p = positioner();61
xdg_popup_reposition(popup.popup, p, phase);62
xdg_positioner_destroy(p);63
} else running = false;64
}65
static const struct wl_callback_listener frame_listener = {.done = frame};66
static void draw(struct surface *s) {67
char path[] = "/tmp/sigil-popup-client.XXXXXX";68
int fd = mkstemp(path);69
unlink(path);70
require(fd >= 0, "shm file");71
int margin_x = s->child || layer ? 0 : 17;72
int margin_y = s->child || layer ? 0 : 11;73
int width = s->width + margin_x * 2, height = s->height + margin_y * 2;74
size_t size = (size_t)width * height * 4;75
require(ftruncate(fd, size) == 0, "shm size");76
uint32_t *pixels = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);77
require(pixels != MAP_FAILED, "shm map");78
for (size_t i = 0; i < size / 4; i++) pixels[i] = s->child ? 0xffeeaa22 : 0xff00cc44;79
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);80
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height, width * 4, WL_SHM_FORMAT_XRGB8888);81
wl_shm_pool_destroy(pool);82
munmap(pixels, size);83
close(fd);84
if (s->xdg) xdg_surface_set_window_geometry(s->xdg, margin_x, margin_y, s->width, s->height);85
wl_surface_attach(s->wl, buffer, 0, 0);86
wl_surface_damage_buffer(s->wl, 0, 0, width, height);87
if (!s->frame_pending && (!s->child || configured_phase > completed_phase)) {88
s->frame_pending = true;89
s->frame_phase = configured_phase;90
wl_callback_add_listener(wl_surface_frame(s->wl), &frame_listener, s);91
}92
wl_surface_commit(s->wl);93
wl_buffer_destroy(buffer);94
}95
static void configured(void *data, struct xdg_surface *xdg, uint32_t serial) {96
xdg_surface_ack_configure(xdg, serial);97
draw(data);98
}99
static const struct xdg_surface_listener surface_listener = {.configure = configured};100
static void popup_configure(void *data, struct xdg_popup *role, int32_t x, int32_t y, int32_t width, int32_t height) {101
(void)data; (void)role;102
popup.width = width; popup.height = height;103
/* Both outputs share this parent-relative box, including the nonzero output origin. */104
bool inside = x >= -480 && y >= -290 && x + width <= 160 && y + height <= 190;105
bool adjustment = configured_phase == 0 ? x == -20 && y == 40 :106
configured_phase == 1 ? x == 0 && y == 90 : x == -160 && y == -100;107
checks |= 1 << configured_phase;108
failures += !inside || !adjustment;109
printf("POPUP_%s=%s phase=%d geometry=%d,%d,%d,%d inside=%d expected-adjustment=%d\n",110
layer ? "LAYER" : "TOPLEVEL", inside && adjustment ? "PASS" : "FAIL", configured_phase,111
x, y, width, height, inside, adjustment);112
fflush(stdout);113
}114
static void popup_done(void *data, struct xdg_popup *role) {115
(void)data; (void)role;116
require(false, "unexpected dismissal");117
}118
static void repositioned(void *data, struct xdg_popup *role, uint32_t token) {119
(void)data; (void)role;120
require(token == (uint32_t)phase, "reposition token");121
configured_phase = token;122
}123
static const struct xdg_popup_listener popup_listener = {124
.configure = popup_configure, .popup_done = popup_done, .repositioned = repositioned,125
};126
static void open_popup(void) {127
popup = (struct surface){.wl = wl_compositor_create_surface(compositor), .child = true};128
popup.xdg = xdg_wm_base_get_xdg_surface(shell, popup.wl);129
xdg_surface_add_listener(popup.xdg, &surface_listener, &popup);130
struct xdg_positioner *p = positioner();131
popup.popup = xdg_surface_get_popup(popup.xdg, parent.xdg, p);132
if (layer) zwlr_layer_surface_v1_get_popup(layer, popup.popup);133
xdg_popup_add_listener(popup.popup, &popup_listener, &popup);134
xdg_positioner_destroy(p);135
wl_surface_commit(popup.wl);136
}137
static void top_configure(void *data, struct xdg_toplevel *top, int32_t width, int32_t height, struct wl_array *states) {138
(void)data; (void)top; (void)states;139
if (width) parent.width = width;140
if (height) parent.height = height;141
}142
static void top_close(void *data, struct xdg_toplevel *top) { (void)data; (void)top; running = false; }143
static const struct xdg_toplevel_listener top_listener = {.configure = top_configure, .close = top_close};144
static void layer_configure(void *data, struct zwlr_layer_surface_v1 *role, uint32_t serial, uint32_t width, uint32_t height) {145
(void)data;146
parent.width = width; parent.height = height;147
zwlr_layer_surface_v1_ack_configure(role, serial);148
draw(&parent);149
}150
static void layer_closed(void *data, struct zwlr_layer_surface_v1 *role) { (void)data; (void)role; running = false; }151
static const struct zwlr_layer_surface_v1_listener layer_listener = {.configure = layer_configure, .closed = layer_closed};152
static void ping(void *data, struct xdg_wm_base *base, uint32_t serial) { (void)data; xdg_wm_base_pong(base, serial); }153
static const struct xdg_wm_base_listener shell_listener = {.ping = ping};154
static void global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {155
(void)data;156
if (!strcmp(interface, "wl_compositor")) compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4);157
else if (!strcmp(interface, "wl_shm")) shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);158
else if (!strcmp(interface, "xdg_wm_base")) {159
shell = wl_registry_bind(registry, name, &xdg_wm_base_interface, version < 3 ? version : 3);160
xdg_wm_base_add_listener(shell, &shell_listener, NULL);161
} else if (!strcmp(interface, "zwlr_layer_shell_v1")) layers = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1);162
}163
static void removed(void *data, struct wl_registry *registry, uint32_t name) { (void)data; (void)registry; (void)name; }164
static const struct wl_registry_listener registry_listener = {.global = global, .global_remove = removed};165
int main(int argc, char **argv) {166
require(argc == 2, "usage: popup-client toplevel|layer");167
display = wl_display_connect(NULL);168
require(display != NULL, "connect");169
struct wl_registry *registry = wl_display_get_registry(display);170
wl_registry_add_listener(registry, ®istry_listener, NULL);171
require(wl_display_roundtrip(display) >= 0 && compositor && shm && shell, "registry");172
parent.wl = wl_compositor_create_surface(compositor);173
parent.width = parent.height = 150;174
struct xdg_toplevel *top = NULL;175
if (!strcmp(argv[1], "layer")) {176
require(layers != NULL, "layer shell");177
layer = zwlr_layer_shell_v1_get_layer_surface(layers, parent.wl, NULL, ZWLR_LAYER_SHELL_V1_LAYER_TOP, "popup-test");178
zwlr_layer_surface_v1_add_listener(layer, &layer_listener, NULL);179
zwlr_layer_surface_v1_set_size(layer, 150, 150);180
} else {181
require(!strcmp(argv[1], "toplevel"), "unknown role");182
parent.xdg = xdg_wm_base_get_xdg_surface(shell, parent.wl);183
xdg_surface_add_listener(parent.xdg, &surface_listener, &parent);184
top = xdg_surface_get_toplevel(parent.xdg);185
xdg_toplevel_add_listener(top, &top_listener, NULL);186
}187
wl_surface_commit(parent.wl);188
while (running && wl_display_dispatch(display) >= 0) {}189
require(checks == 7, "missing popup configurations");190
xdg_popup_destroy(popup.popup);191
xdg_surface_destroy(popup.xdg);192
wl_surface_destroy(popup.wl);193
if (top) { xdg_toplevel_destroy(top); xdg_surface_destroy(parent.xdg); }194
if (layer) zwlr_layer_surface_v1_destroy(layer);195
wl_surface_destroy(parent.wl);196
wl_display_flush(display);197
wl_display_disconnect(display);198
return failures ? 1 : 0;199
}