AtlatestRepositorysigil-wm

sigil-wm / tree / testprotocol-client.c

1/* SPDX-License-Identifier: BSD-3-Clause */
2/* A real xdg-shell client with immutable wl_shm pixels and frame callbacks. */
3#include <errno.h>
4#include <poll.h>
5#include <stdbool.h>
6#include <stdint.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <sys/mman.h>
11#include <time.h>
12#include <unistd.h>
13#include <wayland-client.h>
14#include <xkbcommon/xkbcommon.h>
15#include "xdg-shell-client-protocol.h"
16#include "idle-inhibit-unstable-v1-client-protocol.h"
17#include "relative-pointer-unstable-v1-client-protocol.h"
18#include "pointer-constraints-unstable-v1-client-protocol.h"
19#include "xdg-activation-v1-client-protocol.h"
21struct client {
22 struct wl_display *display;
23 struct wl_compositor *compositor;
24 struct wl_shm *shm;
25 struct xdg_wm_base *shell;
26 struct wl_surface *surface;
27 struct xdg_surface *xdg;
28 struct xdg_toplevel *toplevel;
29 struct wl_buffer *buffer;
30 struct wl_callback *frame;
31 struct wl_seat *seat;
32 /* SIGIL_WM_CLIENT_INHIBIT: an idle inhibitor on the surface. */
33 bool inhibit;
34 struct zwp_idle_inhibit_manager_v1 *inhibit_manager;
35 struct zwp_idle_inhibitor_v1 *inhibitor;
36 struct wl_keyboard *keyboard;
37 struct xkb_state *key_state;
38 struct wl_pointer *pointer;
39 struct zwp_relative_pointer_manager_v1 *relative_manager;
40 struct zwp_relative_pointer_v1 *relative_pointer;
41 bool relative_test, control_test;
42 const char *decoration_mode;
43 struct org_kde_kwin_server_decoration_manager *legacy_manager;
44 struct org_kde_kwin_server_decoration *legacy_decoration;
45 struct zxdg_decoration_manager_v1 *decoration_manager;
46 struct zxdg_toplevel_decoration_v1 *xdg_decoration;
47 bool constraint_test,activation_test;
48 struct xdg_activation_v1 *activation;
49 struct xdg_activation_token_v1 *activation_token;
50 uint32_t activation_serial;
51 struct zwp_pointer_constraints_v1 *constraints;
52 struct zwp_locked_pointer_v1 *locked;
53 struct zwp_confined_pointer_v1 *confined;
54 struct wl_data_device_manager *data_manager;
55 struct wl_data_device *data_device;
56 struct wl_data_source *data_source;
57 struct wl_data_offer *offer;
58 struct wl_surface *drag_icon;
59 struct wl_buffer *drag_buffer;
60 const char *dnd_mode;
61 bool drag_started;
62 uint32_t color;
63 int width, height;
64 bool input_test;
65 bool unmap_test;
66 unsigned frames, target;
67 bool running;
68 uint32_t xdg_version;
69 /* SIGIL_WM_CLIENT_MARGIN=left,top,right,bottom: like a GTK window
70 * with client-side shadows, the buffer extends past the window geometry
71 * by these margins, painted shadow_color, and set_window_geometry
72 * names the inner rectangle. width and height are the geometry size. */
73 int margin[4];
74 bool margin_mode;
75 /* SIGIL_WM_CLIENT_RESIZE_DELAY_MS=N: a configure with a new size
76 * is acknowledged, and the new size drawn, N ms after it arrives, as a
77 * slow client would; -1 never acknowledges one. Later configures that
78 * arrive meanwhile are folded into the delayed acknowledgement. */
79 int resize_delay_ms;
80 bool delay_pending;
81 uint32_t delayed_serial;
82 int delayed_width, delayed_height;
83 struct timespec delay_due;
84 /* SIGIL_WM_CLIENT_ALPHA=A (1-255): pixels are ARGB8888 with alpha
85 * A, premultiplied, like a translucent terminal. Unset is opaque XRGB. */
86 int alpha;
87};
89static const uint32_t shadow_color = 0xffff00ff;
91static void draw(struct client *client);
93static void buffer_release(void *data, struct wl_buffer *buffer) {
94 (void)data;
95 (void)buffer;
98static const struct wl_buffer_listener buffer_listener = {.release = buffer_release};
100static void resize_buffer(struct client *client, int width, int height) {
101 if (width <= 0 || height <= 0 || width > 4096 || height > 4096) exit(11);
102 char filename[256];
103 snprintf(filename, sizeof(filename), "%s/pixels.XXXXXX", getenv("XDG_RUNTIME_DIR"));
104 int fd = mkstemp(filename);
105 if (fd < 0) exit(5);
106 unlink(filename);
107 const int *m = client->margin;
108 const int buffer_width = width + m[0] + m[2], buffer_height = height + m[1] + m[3];
109 size_t size = (size_t)buffer_width * buffer_height * sizeof(uint32_t);
110 if (ftruncate(fd, size) < 0) exit(6);
111 uint32_t *pixels = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
112 if (pixels == MAP_FAILED) exit(7);
113 uint32_t fill = client->color;
114 if (client->alpha) {
115 /* Premultiplied: each channel scaled by alpha, rounded. */
116 const uint32_t a = (uint32_t)client->alpha;
117 fill = a << 24;
118 for (int shift = 0; shift < 24; shift += 8) {
119 fill |= ((((client->color >> shift) & 0xff) * a + 127) / 255) << shift;
120 }
121 }
122 for (int y = 0; y < buffer_height; y++) {
123 for (int x = 0; x < buffer_width; x++) {
124 bool inside = x >= m[0] && x < m[0] + width && y >= m[1] && y < m[1] + height;
125 pixels[(size_t)y * buffer_width + x] = inside ? fill : shadow_color;
126 }
127 }
128 struct wl_shm_pool *pool = wl_shm_create_pool(client->shm, fd, size);
129 struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, buffer_width, buffer_height,
130 buffer_width * 4, client->alpha ? WL_SHM_FORMAT_ARGB8888 : WL_SHM_FORMAT_XRGB8888);
131 wl_buffer_add_listener(buffer, &buffer_listener, client);
132 wl_shm_pool_destroy(pool);
133 if (client->buffer) wl_buffer_destroy(client->buffer);
134 client->buffer = buffer;
135 client->width = width;
136 client->height = height;
137 /* Each immutable backing file remains owned by the server's shm mapping. */
138 munmap(pixels, size);
139 close(fd);
142static void frame_done(void *data, struct wl_callback *callback, uint32_t time) {
143 struct client *client = data;
144 wl_callback_destroy(callback);
145 client->frame = NULL;
146 client->frames++;
147 if (client->unmap_test && client->frames == 10) {
148 /* Leave the role and wl_surface alive after a legal unmap. The next
149 * real application must not configure this uninitialized old role. */
150 wl_surface_attach(client->surface, NULL, 0, 0);
151 wl_surface_commit(client->surface);
152 puts("CLIENT_UNMAP_SENT");
153 fflush(stdout);
154 return;
155 }
156 if (client->frames >= client->target) {
157 client->running = false;
158 } else {
159 draw(client);
160 }
161 (void)time;
164static const struct wl_callback_listener frame_listener = {.done = frame_done};
166static void draw(struct client *client) {
167 if (client->frame || !client->running) {
168 return;
169 }
170 client->frame = wl_surface_frame(client->surface);
171 wl_callback_add_listener(client->frame, &frame_listener, client);
172 wl_surface_attach(client->surface, client->buffer, 0, 0);
173 if (client->margin_mode) {
174 xdg_surface_set_window_geometry(client->xdg, client->margin[0], client->margin[1],
175 client->width, client->height);
176 }
177 wl_surface_damage_buffer(client->surface, 0, 0, INT32_MAX, INT32_MAX);
178 wl_surface_commit(client->surface);
181/* Commit the current buffer now, without waiting for a pending frame
182 * callback: a client answers a resize at once, as GUI clients do, and a
183 * compositor may withhold frame callbacks while it waits for that answer. */
184static void commit_now(struct client *client) {
185 if (!client->frame) {
186 draw(client);
187 return;
188 }
189 wl_surface_attach(client->surface, client->buffer, 0, 0);
190 if (client->margin_mode) {
191 xdg_surface_set_window_geometry(client->xdg, client->margin[0], client->margin[1],
192 client->width, client->height);
193 }
194 wl_surface_damage_buffer(client->surface, 0, 0, INT32_MAX, INT32_MAX);
195 wl_surface_commit(client->surface);
198static void configure(void *data, struct xdg_surface *surface, uint32_t serial) {
199 printf("CLIENT_CONFIGURE_SERIAL=%u\n",serial); fflush(stdout);
200 struct client *client = data;
201 if (client->delay_pending) {
202 client->delayed_serial = serial;
203 return;
204 }
205 xdg_surface_ack_configure(surface, serial);
206 /* A configure is complete only once a commit follows its ack. */
207 commit_now(client);
210static int64_t ms_until(const struct timespec *due) {
211 struct timespec now;
212 clock_gettime(CLOCK_MONOTONIC, &now);
213 return (due->tv_sec - now.tv_sec) * 1000 + (due->tv_nsec - now.tv_nsec) / 1000000;
216/* The delay is over: take the newest size, acknowledge the newest
217 * configure and draw at once. */
218static void release_delayed(struct client *client) {
219 client->delay_pending = false;
220 if (client->delayed_width != client->width || client->delayed_height != client->height) {
221 resize_buffer(client, client->delayed_width, client->delayed_height);
222 }
223 xdg_surface_ack_configure(client->xdg, client->delayed_serial);
224 printf("CLIENT_RESIZED=%dx%d\n", client->width, client->height);
225 fflush(stdout);
226 commit_now(client);
229static const struct xdg_surface_listener surface_listener = {.configure = configure};
231static void toplevel_configure(void *data, struct xdg_toplevel *toplevel,
232 int32_t width, int32_t height, struct wl_array *states) {
233 struct client *client = data;
234 if (client->input_test) {
235 if (client->xdg_version >= 2) {
236 /* States as numbers: 4 activated, 5-8 tiled left/right/top/bottom,
237 * 9 suspended. */
238 printf("CLIENT_STATES=%dx%d:", width, height);
239 uint32_t *state;
240 const char *separator = "";
241 wl_array_for_each(state, states) {
242 printf("%s%u", separator, *state);
243 separator = ",";
244 }
245 printf("\n");
246 }
247 printf("CLIENT_CONFIGURE=%dx%d\n", width, height);
248 fflush(stdout);
249 bool changes = width > 0 && height > 0 &&
250 (width != client->width || height != client->height);
251 if (client->resize_delay_ms != 0 && (changes || client->delay_pending)) {
252 if (width > 0 && height > 0) {
253 client->delayed_width = width;
254 client->delayed_height = height;
255 }
256 if (!client->delay_pending) {
257 client->delay_pending = true;
258 clock_gettime(CLOCK_MONOTONIC, &client->delay_due);
259 if (client->resize_delay_ms > 0) {
260 int64_t ns = client->delay_due.tv_nsec + (int64_t)client->resize_delay_ms * 1000000;
261 client->delay_due.tv_sec += ns / 1000000000;
262 client->delay_due.tv_nsec = ns % 1000000000;
263 }
264 }
265 } else if (changes) {
266 resize_buffer(client, width, height);
267 }
268 }
269 (void)toplevel;
270 (void)width;
271 (void)height;
272 (void)states;
275static void toplevel_close(void *data, struct xdg_toplevel *toplevel) {
276 ((struct client *)data)->running = false;
277 (void)toplevel;
280static void toplevel_bounds(void *data, struct xdg_toplevel *toplevel,
281 int32_t width, int32_t height) {
282 (void)data; (void)toplevel; (void)width; (void)height;
285static void toplevel_capabilities(void *data, struct xdg_toplevel *toplevel,
286 struct wl_array *capabilities) {
287 (void)data; (void)toplevel; (void)capabilities;
290static const struct xdg_toplevel_listener toplevel_listener = {
291 .configure_bounds = toplevel_bounds,
292 .wm_capabilities = toplevel_capabilities,
293 .configure = toplevel_configure,
294 .close = toplevel_close,
295};
297static void ping(void *data, struct xdg_wm_base *shell, uint32_t serial) {
298 xdg_wm_base_pong(shell, serial);
299 (void)data;
302static const struct xdg_wm_base_listener shell_listener = {.ping = ping};
304static void shm_format(void *data, struct wl_shm *shm, uint32_t format) {
305 (void)data;
306 (void)shm;
307 (void)format;
310static const struct wl_shm_listener shm_listener = {.format = shm_format};
312static void keyboard_keymap(void *data, struct wl_keyboard *keyboard,
313 uint32_t format, int32_t fd, uint32_t size) {
314 struct client *client=data;
315 if (client->key_state) xkb_state_unref(client->key_state);
316 client->key_state=NULL;
317 if (format==WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 && size) {
318 char *text=mmap(NULL,size,PROT_READ,MAP_PRIVATE,fd,0);
319 if (text!=MAP_FAILED) {
320 struct xkb_context *context=xkb_context_new(XKB_CONTEXT_NO_FLAGS);
321 struct xkb_keymap *keymap=xkb_keymap_new_from_string(context,text,
322 XKB_KEYMAP_FORMAT_TEXT_V1,XKB_KEYMAP_COMPILE_NO_FLAGS);
323 if (keymap) {
324 client->key_state=xkb_state_new(keymap);
325 xkb_keymap_unref(keymap);
326 }
327 xkb_context_unref(context);
328 munmap(text,size);
329 }
330 }
331 close(fd);
332 (void)keyboard;
335static void keyboard_enter(void *data, struct wl_keyboard *keyboard,
336 uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {
337 puts("CLIENT_KEYBOARD_ENTER");
338 printf("CLIENT_ENTER_KEYS=%zu\n", keys->size / sizeof(uint32_t));
339 fflush(stdout);
340 (void)data; (void)keyboard; (void)serial; (void)surface; (void)keys;
343static void keyboard_leave(void *data, struct wl_keyboard *keyboard,
344 uint32_t serial, struct wl_surface *surface) {
345 puts("CLIENT_KEYBOARD_LEAVE");
346 fflush(stdout);
347 (void)data; (void)keyboard; (void)serial; (void)surface;
350static void keyboard_key(void *data, struct wl_keyboard *keyboard,
351 uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
352 if(state) ((struct client *)data)->activation_serial=serial;
353 printf("CLIENT_KEY=%u,%u\n", key, state);
354 struct xkb_state *key_state=((struct client *)data)->key_state;
355 if (key_state) {
356 printf("CLIENT_KEYSYM=%u,%u,%u\n",key,state,xkb_state_key_get_one_sym(key_state,key+8));
357 uint32_t modifiers=0;
358 const char *names[8]={"Shift","Lock","Control","Mod1","Mod2","Mod3","Mod4","Mod5"};
359 for(int bit=0;bit<8;bit++)
360 if(xkb_state_mod_name_is_active(key_state,names[bit],XKB_STATE_MODS_EFFECTIVE)>0)
361 modifiers|=1u<<bit;
362 printf("CLIENT_KEY_MODIFIERS=%u,%u,%u,%u\n",key,state,modifiers,
363 xkb_state_serialize_layout(key_state,XKB_STATE_LAYOUT_EFFECTIVE));
364 }
365 fflush(stdout);
366 if (getenv("SIGIL_WM_CLIENT_FULLSCREEN") && state == WL_KEYBOARD_KEY_STATE_PRESSED) {
367 struct client *client = data;
368 if (key == 33) xdg_toplevel_set_fullscreen(client->toplevel, NULL);
369 if (key == 22) xdg_toplevel_unset_fullscreen(client->toplevel);
370 }
371 (void)data; (void)keyboard; (void)serial; (void)time;
374static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard,
375 uint32_t serial, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) {
376 struct xkb_state *key_state=((struct client *)data)->key_state;
377 if (key_state) xkb_state_update_mask(key_state,depressed,latched,locked,0,0,group);
378 printf("CLIENT_MODIFIERS=%u,%u,%u,%u\n", depressed, latched, locked, group);
379 fflush(stdout);
380 (void)data; (void)keyboard; (void)serial; (void)depressed;
381 (void)latched; (void)locked; (void)group;
384static const struct wl_keyboard_listener keyboard_listener = {
385 .keymap = keyboard_keymap, .enter = keyboard_enter, .leave = keyboard_leave,
386 .key = keyboard_key, .modifiers = keyboard_modifiers,
387};
389static const char drag_payload[] = "drag-payload-λ";
390static const char drag_mime[] = "text/plain;charset=utf-8";
392static void source_target(void *data, struct wl_data_source *source, const char *mime) {
393 (void)data; (void)source; (void)mime;
396static void source_send(void *data, struct wl_data_source *source, const char *mime, int fd) {
397 if (strcmp(mime, drag_mime)) exit(20);
398 size_t offset = 0;
399 while (offset < sizeof(drag_payload) - 1) {
400 ssize_t count = write(fd, drag_payload + offset, sizeof(drag_payload) - 1 - offset);
401 if (count < 0 && errno == EINTR) continue;
402 if (count <= 0) exit(21);
403 offset += count;
404 }
405 close(fd);
406 puts("DND_SOURCE_SENT");
407 fflush(stdout);
408 (void)data; (void)source;
411static void source_cancelled(void *data, struct wl_data_source *source) {
412 struct client *client = data;
413 if (strcmp(client->dnd_mode, "invalid") && !getenv("SIGIL_WM_DND_EXPECT_CANCEL")) exit(22);
414 wl_data_source_destroy(source);
415 client->data_source = NULL;
416 puts(!strcmp(client->dnd_mode,"invalid") ? "DND_INVALID_SERIAL_CANCELLED" : "DND_REFUSED_CANCELLED");
417 fflush(stdout);
418 client->running = false;
419 (void)source;
422static void source_drop(void *data, struct wl_data_source *source) {
423 puts("DND_SOURCE_DROP");
424 fflush(stdout);
425 (void)data; (void)source;
428static void source_finished(void *data, struct wl_data_source *source) {
429 struct client *client = data;
430 /* Retire the proxy when the transfer completes. wlroots may release
431 * its backing source immediately afterward in the same event batch. */
432 wl_data_source_destroy(source);
433 client->data_source = NULL;
434 client->running = false;
435 puts("DND_SOURCE_FINISHED");
436 fflush(stdout);
439static void source_action(void *data, struct wl_data_source *source, uint32_t action) {
440 (void)data; (void)source; (void)action;
443static const struct wl_data_source_listener source_listener = {
444 .target = source_target, .send = source_send, .cancelled = source_cancelled,
445 .dnd_drop_performed = source_drop, .dnd_finished = source_finished, .action = source_action,
446};
448static void offer_mime(void *data, struct wl_data_offer *offer, const char *mime) {
449 (void)data; (void)offer; (void)mime;
452static void offer_source_actions(void *data, struct wl_data_offer *offer, uint32_t actions) {
453 (void)data; (void)offer; (void)actions;
456static void offer_action(void *data, struct wl_data_offer *offer, uint32_t action) {
457 if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) {
458 puts("DND_COPY_NEGOTIATED");
459 fflush(stdout);
460 }
461 (void)data; (void)offer;
464static const struct wl_data_offer_listener offer_listener = {
465 .offer = offer_mime, .source_actions = offer_source_actions, .action = offer_action,
466};
468static void device_offer(void *data, struct wl_data_device *device, struct wl_data_offer *offer) {
469 struct client *client = data;
470 if (client->offer) wl_data_offer_destroy(client->offer);
471 client->offer = offer;
472 wl_data_offer_add_listener(offer, &offer_listener, client);
473 (void)device;
476static void device_enter(void *data, struct wl_data_device *device, uint32_t serial,
477 struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *offer) {
478 struct client *client = data;
479 if (strcmp(client->dnd_mode, "target")) return;
480 if (!offer || surface != client->surface) exit(23);
481 wl_data_offer_accept(offer, serial, drag_mime);
482 wl_data_offer_set_actions(offer, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY,
483 WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
484 puts("DND_TARGET_ENTER");
485 fflush(stdout);
486 (void)device; (void)x; (void)y;
489static void device_leave(void *data, struct wl_data_device *device) {
490 (void)data; (void)device;
493static void device_motion(void *data, struct wl_data_device *device, uint32_t time,
494 wl_fixed_t x, wl_fixed_t y) {
495 (void)data; (void)device; (void)time; (void)x; (void)y;
498static void device_drop(void *data, struct wl_data_device *device) {
499 struct client *client = data;
500 if (strcmp(client->dnd_mode, "target") || !client->offer) exit(24);
501 int transfer[2];
502 if (pipe(transfer) < 0) exit(25);
503 wl_data_offer_receive(client->offer, drag_mime, transfer[1]);
504 close(transfer[1]);
505 wl_display_flush(client->display);
506 char payload[128] = {0};
507 size_t offset = 0;
508 ssize_t count;
509 while ((count = read(transfer[0], payload + offset, sizeof(payload) - 1 - offset)) != 0) {
510 if (count < 0 && errno == EINTR) continue;
511 if (count < 0 || offset + count >= sizeof(payload) - 1) exit(26);
512 offset += count;
513 }
514 close(transfer[0]);
515 if (strcmp(payload, drag_payload)) exit(27);
516 wl_data_offer_finish(client->offer);
517 wl_data_offer_destroy(client->offer);
518 client->offer = NULL;
519 puts("DND_TARGET_PAYLOAD=drag-payload-λ");
520 fflush(stdout);
521 client->running = false;
522 (void)device;
525static void device_selection(void *data, struct wl_data_device *device, struct wl_data_offer *offer) {
526 (void)data; (void)device; (void)offer;
529static const struct wl_data_device_listener device_listener = {
530 .data_offer = device_offer, .enter = device_enter, .leave = device_leave,
531 .motion = device_motion, .drop = device_drop, .selection = device_selection,
532};
534static void start_drag(struct client *client, uint32_t serial) {
535 client->drag_started = true;
536 client->data_source = wl_data_device_manager_create_data_source(client->data_manager);
537 wl_data_source_add_listener(client->data_source, &source_listener, client);
538 wl_data_source_offer(client->data_source, drag_mime);
539 wl_data_source_set_actions(client->data_source, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
540 char filename[256];
541 snprintf(filename, sizeof(filename), "%s/drag-icon.XXXXXX", getenv("XDG_RUNTIME_DIR"));
542 int fd = mkstemp(filename);
543 if (fd < 0 || ftruncate(fd, 12 * 12 * 4) < 0) exit(28);
544 unlink(filename);
545 uint32_t *pixels = mmap(NULL, 12 * 12 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
546 if (pixels == MAP_FAILED) exit(29);
547 for (int i = 0; i < 144; i++) pixels[i] = 0xffaa44dd;
548 struct wl_shm_pool *pool = wl_shm_create_pool(client->shm, fd, 12 * 12 * 4);
549 client->drag_buffer = wl_shm_pool_create_buffer(pool, 0, 12, 12, 48, WL_SHM_FORMAT_XRGB8888);
550 wl_shm_pool_destroy(pool);
551 munmap(pixels, 12 * 12 * 4);
552 close(fd);
553 client->drag_icon = wl_compositor_create_surface(client->compositor);
554 wl_data_device_start_drag(client->data_device, client->data_source, client->surface,
555 client->drag_icon, !strcmp(client->dnd_mode, "invalid") ? 0 : serial);
556 wl_surface_attach(client->drag_icon, client->drag_buffer, 0, 0);
557 wl_surface_damage_buffer(client->drag_icon, 0, 0, 12, 12);
558 wl_surface_commit(client->drag_icon);
559 if (getenv("SIGIL_WM_DND_RETIRE") && *getenv("SIGIL_WM_DND_RETIRE")) { wl_display_flush(client->display); client->running = false; }
560 puts("DND_SOURCE_REQUESTED");
561 fflush(stdout);
564static void pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial,
565 struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y) {
566 if (((struct client *)data)->constraint_test) {
567 printf("CLIENT_POINTER_ENTER=%d,%d\n", x, y);
568 fflush(stdout);
569 }
570 (void)data; (void)pointer; (void)serial; (void)surface; (void)x; (void)y;
573static void pointer_leave(void *data, struct wl_pointer *pointer,
574 uint32_t serial, struct wl_surface *surface) {
575 (void)data; (void)pointer; (void)serial; (void)surface;
578static void pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time,
579 wl_fixed_t x, wl_fixed_t y) {
580 if (((struct client *)data)->constraint_test) {
581 printf("CLIENT_POINTER_MOTION=%d,%d\n", x, y);
582 fflush(stdout);
583 }
584 (void)data; (void)pointer; (void)time; (void)x; (void)y;
587static void pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial,
588 uint32_t time, uint32_t button, uint32_t state) {
589 struct client *client = data;
590 printf("CLIENT_BUTTON=%u,%u\n", button, state);
591 fflush(stdout);
592 if (client->dnd_mode && strcmp(client->dnd_mode, "target") && !client->drag_started &&
593 button == 272 && state == WL_POINTER_BUTTON_STATE_PRESSED) start_drag(client, serial);
594 (void)data; (void)pointer; (void)serial; (void)time;
597static void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time,
598 uint32_t axis, wl_fixed_t value) {
599 (void)data; (void)pointer; (void)time; (void)axis; (void)value;
602static const struct wl_pointer_listener pointer_listener = {
603 .enter = pointer_enter, .leave = pointer_leave, .motion = pointer_motion,
604 .button = pointer_button, .axis = pointer_axis,
605};
607static void relative_motion(void *data, struct zwp_relative_pointer_v1 *pointer,
608 uint32_t hi, uint32_t lo, wl_fixed_t dx, wl_fixed_t dy,
609 wl_fixed_t raw_dx, wl_fixed_t raw_dy) {
610 printf("CLIENT_RELATIVE=%llu,%d,%d,%d,%d\n",
611 (unsigned long long)(((uint64_t)hi << 32) | lo), dx, dy, raw_dx, raw_dy);
612 fflush(stdout);
613 (void)data; (void)pointer;
616static const struct zwp_relative_pointer_v1_listener relative_listener = {
617 .relative_motion = relative_motion,
618};
620static void locked(void *data, struct zwp_locked_pointer_v1 *pointer) {
621 puts("CLIENT_LOCKED"); fflush(stdout); (void)data; (void)pointer;
623static void unlocked(void *data, struct zwp_locked_pointer_v1 *pointer) {
624 puts("CLIENT_UNLOCKED"); fflush(stdout); (void)data; (void)pointer;
626static const struct zwp_locked_pointer_v1_listener locked_listener = {
627 .locked = locked, .unlocked = unlocked,
628};
629static void confined(void *data, struct zwp_confined_pointer_v1 *pointer) {
630 puts("CLIENT_CONFINED"); fflush(stdout); (void)data; (void)pointer;
632static void unconfined(void *data, struct zwp_confined_pointer_v1 *pointer) {
633 puts("CLIENT_UNCONFINED"); fflush(stdout); (void)data; (void)pointer;
635static const struct zwp_confined_pointer_v1_listener confined_listener = {
636 .confined = confined, .unconfined = unconfined,
637};
639/* Commands are opt-in fixture controls: l/o lock persistent/one-shot,
640 * c confine, u release, h commit a hint, r shrink the committed region. */
641static void constraint_command(struct client *client, char command) {
642 if (!client->constraint_test) return;
643 if (command == 'u') {
644 if (client->locked) zwp_locked_pointer_v1_destroy(client->locked);
645 if (client->confined) zwp_confined_pointer_v1_destroy(client->confined);
646 client->locked = NULL;
647 client->confined = NULL;
648 } else if (command == 'l' || command == 'o' || command == 'c' || command == 'n') {
649 if (client->locked || client->confined) exit(35);
650 struct wl_region *region = wl_compositor_create_region(client->compositor);
651 wl_region_add(region, 0, 0, 300, 300);
652 if (command == 'c') {
653 client->confined = zwp_pointer_constraints_v1_confine_pointer(client->constraints,
654 client->surface, client->pointer, region, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
655 zwp_confined_pointer_v1_add_listener(client->confined, &confined_listener, client);
656 } else {
657 client->locked = zwp_pointer_constraints_v1_lock_pointer(client->constraints,
658 client->surface, client->pointer, command == 'n' ? NULL : region, command == 'o' ?
659 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT : ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
660 zwp_locked_pointer_v1_add_listener(client->locked, &locked_listener, client);
661 }
662 wl_region_destroy(region);
663 if (command != 'n') wl_surface_commit(client->surface);
664 } else if (command == 'h' && client->locked) {
665 zwp_locked_pointer_v1_set_cursor_position_hint(client->locked,
666 wl_fixed_from_int(140), wl_fixed_from_int(160));
667 wl_surface_commit(client->surface);
668 } else if (command == 'r' && client->confined) {
669 struct wl_region *region = wl_compositor_create_region(client->compositor);
670 wl_region_add(region, 80, 60, 60, 60);
671 zwp_confined_pointer_v1_set_region(client->confined, region);
672 wl_region_destroy(region);
673 wl_surface_commit(client->surface);
674 }
677static void create_relative_pointer(struct client *client) {
678 if (!client->relative_manager || !client->pointer || client->relative_pointer) return;
679 client->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(
680 client->relative_manager, client->pointer);
681 zwp_relative_pointer_v1_add_listener(client->relative_pointer, &relative_listener, client);
682 puts("CLIENT_RELATIVE_READY");
683 fflush(stdout);
686static void seat_capabilities(void *data, struct wl_seat *seat, uint32_t caps) {
687 struct client *client = data;
688 if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !client->keyboard) {
689 client->keyboard = wl_seat_get_keyboard(seat);
690 wl_keyboard_add_listener(client->keyboard, &keyboard_listener, client);
691 }
692 if ((caps & WL_SEAT_CAPABILITY_POINTER) && !client->pointer) {
693 client->pointer = wl_seat_get_pointer(seat);
694 wl_pointer_add_listener(client->pointer, &pointer_listener, client);
695 create_relative_pointer(client);
696 }
699static const struct wl_seat_listener seat_listener = {.capabilities = seat_capabilities};
701static void activation_done(void *data, struct xdg_activation_token_v1 *token, const char *name) {
702 struct client *client=data;
703 xdg_activation_v1_activate(client->activation,name,client->surface);
704 xdg_activation_token_v1_destroy(token);
705 client->activation_token=NULL;
706 puts("CLIENT_ACTIVATION_DONE");
707 fflush(stdout);
709static const struct xdg_activation_token_v1_listener activation_listener={.done=activation_done};
710static void activation_command(struct client *client,char command) {
711 if(!client->activation_test) return;
712 if(command=='m') {
713 xdg_toplevel_set_maximized(client->toplevel);
714 wl_surface_commit(client->surface);
715 return;
716 }
717 if(command!='a' && command!='v' && command!='i') return;
718 if(client->activation_token) exit(37);
719 client->activation_token=xdg_activation_v1_get_activation_token(client->activation);
720 xdg_activation_token_v1_add_listener(client->activation_token,&activation_listener,client);
721 if(command!='a') {
722 xdg_activation_token_v1_set_surface(client->activation_token,client->surface);
723 xdg_activation_token_v1_set_serial(client->activation_token,
724 client->activation_serial+(command=='i' ? 100000 : 0),client->seat);
725 }
726 xdg_activation_token_v1_commit(client->activation_token);
729#include "decoration-client.h"
731static void global(void *data, struct wl_registry *registry, uint32_t name,
732 const char *interface, uint32_t version) {
733 struct client *client = data;
734 if (decoration_global(client,registry,name,interface)) {
735 } else if (strcmp(interface, "wl_compositor") == 0 && version >= 4) {
736 client->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4);
737 } else if (strcmp(interface, "wl_shm") == 0) {
738 client->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
739 wl_shm_add_listener(client->shm, &shm_listener, client);
740 } else if (strcmp(interface, "xdg_wm_base") == 0) {
741 /* Version 1 unless the test asks for more, up to 6 (suspended). */
742 client->xdg_version = client->xdg_version > version ? version : client->xdg_version;
743 client->shell = wl_registry_bind(registry, name, &xdg_wm_base_interface,
744 client->xdg_version);
745 xdg_wm_base_add_listener(client->shell, &shell_listener, client);
746 } else if (client->input_test && strcmp(interface, "wl_seat") == 0) {
747 client->seat = wl_registry_bind(registry, name, &wl_seat_interface, 1);
748 wl_seat_add_listener(client->seat, &seat_listener, client);
749 } else if (client->inhibit && strcmp(interface, "zwp_idle_inhibit_manager_v1") == 0) {
750 client->inhibit_manager = wl_registry_bind(registry, name,
751 &zwp_idle_inhibit_manager_v1_interface, 1);
752 } else if (client->relative_test && !strcmp(interface, "zwp_relative_pointer_manager_v1")) {
753 client->relative_manager = wl_registry_bind(registry, name,
754 &zwp_relative_pointer_manager_v1_interface, 1);
755 create_relative_pointer(client);
756 } else if (client->activation_test && !strcmp(interface, "xdg_activation_v1")) {
757 client->activation=wl_registry_bind(registry,name,&xdg_activation_v1_interface,1);
758 } else if (client->constraint_test && !strcmp(interface, "zwp_pointer_constraints_v1")) {
759 client->constraints = wl_registry_bind(registry, name, &zwp_pointer_constraints_v1_interface, 1);
760 } else if (client->dnd_mode && strcmp(interface, "wl_data_device_manager") == 0 && version >= 3) {
761 client->data_manager = wl_registry_bind(registry, name, &wl_data_device_manager_interface, 3);
762 }
765static void global_remove(void *data, struct wl_registry *registry, uint32_t name) {
766 (void)data;
767 (void)registry;
768 (void)name;
771static const struct wl_registry_listener registry_listener = {
772 .global = global,
773 .global_remove = global_remove,
774};
776int main(int argc, char **argv) {
777 if (argc != 3 || (strcmp(argv[1], "green") && strcmp(argv[1], "red") &&
778 strcmp(argv[1], "blue") && strcmp(argv[1], "yellow") &&
779 strcmp(argv[1], "cyan") && strcmp(argv[1], "magenta"))) {
780 fprintf(stderr, "usage: pixel-client green|red|blue|yellow|cyan|magenta FRAME_COUNT\n");
781 return 2;
782 }
783 struct client client = {.target = strtoul(argv[2], NULL, 10), .running = true};
784 client.input_test = getenv("SIGIL_WM_CLIENT_INPUT") != NULL;
785 client.relative_test = getenv("SIGIL_WM_CLIENT_RELATIVE") != NULL;
786 client.control_test = getenv("SIGIL_WM_CLIENT_CONTROL") != NULL;
787 client.decoration_mode = getenv("SIGIL_WM_CLIENT_DECORATION");
788 if(client.decoration_mode) client.control_test=true;
789 client.activation_test = getenv("SIGIL_WM_CLIENT_ACTIVATION") != NULL;
790 if(client.activation_test) client.control_test=true;
791 client.constraint_test = getenv("SIGIL_WM_CLIENT_CONSTRAINTS") != NULL;
792 if (client.constraint_test) { client.relative_test = true; client.control_test = true; }
793 if (client.relative_test || client.control_test) client.input_test = true;
794 client.unmap_test = getenv("SIGIL_WM_CLIENT_UNMAP") != NULL;
795 if (client.unmap_test) client.input_test = true;
796 client.dnd_mode = getenv("SIGIL_WM_CLIENT_DND");
797 client.inhibit = getenv("SIGIL_WM_CLIENT_INHIBIT") != NULL;
798 if (client.dnd_mode) client.input_test = true;
799 client.color = strcmp(argv[1], "green") == 0 ? 0xff00cc44 :
800 strcmp(argv[1], "blue") == 0 ? 0xff1155bb :
801 strcmp(argv[1], "yellow") == 0 ? 0xffddaa11 :
802 strcmp(argv[1], "cyan") == 0 ? 0xff11bbcc :
803 strcmp(argv[1], "magenta") == 0 ? 0xffbb33aa : 0xffcc2244;
804 const char *xdg_version = getenv("SIGIL_WM_CLIENT_XDG_VERSION");
805 client.xdg_version = xdg_version ? strtoul(xdg_version, NULL, 10) : 1;
806 if (client.xdg_version < 1 || client.xdg_version > 6) return 2;
807 const char *margin = getenv("SIGIL_WM_CLIENT_MARGIN");
808 if (margin) {
809 int *m = client.margin;
810 if (sscanf(margin, "%d,%d,%d,%d", &m[0], &m[1], &m[2], &m[3]) != 4) return 2;
811 for (int i = 0; i < 4; i++) if (m[i] < 0 || m[i] > 256) return 2;
812 client.margin_mode = true;
813 printf("CLIENT_MARGIN=%d,%d,%d,%d\n", m[0], m[1], m[2], m[3]);
814 fflush(stdout);
815 }
816 const char *alpha = getenv("SIGIL_WM_CLIENT_ALPHA");
817 if (alpha) {
818 client.alpha = (int)strtol(alpha, NULL, 10);
819 if (client.alpha < 1 || client.alpha > 255) return 2;
820 printf("CLIENT_ALPHA=%d\n", client.alpha);
821 fflush(stdout);
822 }
823 const char *delay = getenv("SIGIL_WM_CLIENT_RESIZE_DELAY_MS");
824 if (delay) {
825 client.resize_delay_ms = (int)strtol(delay, NULL, 10);
826 if (client.resize_delay_ms < -1 || client.resize_delay_ms > 10000) return 2;
827 /* Sizes are taken only in input mode. */
828 client.input_test = true;
829 printf("CLIENT_RESIZE_DELAY_MS=%d\n", client.resize_delay_ms);
830 fflush(stdout);
831 }
832 if (!client.target || client.target > 36000) return 2;
833 client.display = wl_display_connect(NULL);
834 if (!client.display) return 3;
835 struct wl_registry *registry = wl_display_get_registry(client.display);
836 wl_registry_add_listener(registry, &registry_listener, &client);
837 if (wl_display_roundtrip(client.display) < 0 || !client.compositor ||
838 !client.shm || !client.shell) return 4;
839 if (client.relative_test && !client.relative_manager) return 32;
840 if (client.constraint_test && !client.constraints) return 36;
841 if (client.dnd_mode) {
842 if (!client.data_manager || !client.seat) return 30;
843 client.data_device = wl_data_device_manager_get_data_device(client.data_manager, client.seat);
844 wl_data_device_add_listener(client.data_device, &device_listener, &client);
845 }
847 resize_buffer(&client, 64, 64);
849 client.surface = wl_compositor_create_surface(client.compositor);
850 decoration_create(&client,true);
851 client.xdg = xdg_wm_base_get_xdg_surface(client.shell, client.surface);
852 xdg_surface_add_listener(client.xdg, &surface_listener, &client);
853 if (client.inhibit) {
854 if (!client.inhibit_manager) return 31;
855 client.inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(client.inhibit_manager,
856 client.surface);
857 puts("CLIENT_INHIBITOR=1");
858 fflush(stdout);
859 }
860 client.toplevel = xdg_surface_get_toplevel(client.xdg);
861 decoration_create(&client,false);
862 xdg_toplevel_add_listener(client.toplevel, &toplevel_listener, &client);
863 const char *title = getenv("SIGIL_WM_CLIENT_TITLE");
864 xdg_toplevel_set_title(client.toplevel, title ? title : "Pixel probe");
865 xdg_toplevel_set_app_id(client.toplevel, "sigil-wm-pixel-probe");
866 if (getenv("SIGIL_WM_CLIENT_INITIAL_FULLSCREEN")) {
867 xdg_toplevel_set_fullscreen(client.toplevel, NULL);
868 }
869 /* SIGIL_WM_CLIENT_FIXED: equal minimum and maximum sizes, like a
870 * fixed-size dialog, which a tiling policy floats. */
871 if (getenv("SIGIL_WM_CLIENT_FIXED")) {
872 xdg_toplevel_set_min_size(client.toplevel, 64, 64);
873 xdg_toplevel_set_max_size(client.toplevel, 64, 64);
874 puts("CLIENT_FIXED=64x64");
875 fflush(stdout);
876 }
877 if (getenv("SIGIL_WM_CLIENT_BOUNDED")) {
878 xdg_toplevel_set_min_size(client.toplevel, 160, 120);
879 xdg_toplevel_set_max_size(client.toplevel, 720, 520);
880 puts("CLIENT_BOUNDS=160x120..720x520");
881 fflush(stdout);
882 }
883 wl_surface_commit(client.surface);
884 while (client.running) {
885 /* Optional fixture control. A sync marker proves that this client
886 * has consumed every event queued before the controller's barrier,
887 * including a forbidden relative event in a negative assertion. */
888 if (client.control_test) {
889 if (wl_display_prepare_read(client.display) != 0) {
890 if (wl_display_dispatch_pending(client.display) < 0) return 8;
891 continue;
892 }
893 wl_display_flush(client.display);
894 struct pollfd fds[] = {
895 {.fd = wl_display_get_fd(client.display), .events = POLLIN},
896 {.fd = STDIN_FILENO, .events = POLLIN},
897 };
898 int ready = poll(fds, 2, -1);
899 if (ready < 0) {
900 wl_display_cancel_read(client.display);
901 if (errno == EINTR) continue;
902 return 33;
903 }
904 if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
905 wl_display_cancel_read(client.display);
906 return 8;
907 }
908 if (fds[0].revents & POLLIN) {
909 if (wl_display_read_events(client.display) < 0) return 8;
910 } else {
911 wl_display_cancel_read(client.display);
912 }
913 if (wl_display_dispatch_pending(client.display) < 0) return 8;
914 if (fds[1].revents & POLLIN) {
915 char commands[32];
916 ssize_t count = read(STDIN_FILENO, commands, sizeof(commands));
917 if (count < 0) return 34;
918 for (ssize_t i = 0; i < count; i++) {
919 if (commands[i] == 'q') { client.running = false; break; }
920 if (commands[i] == 't' && getenv("SIGIL_WM_CLIENT_NEXT_TITLE")) {
921 xdg_toplevel_set_title(client.toplevel, getenv("SIGIL_WM_CLIENT_NEXT_TITLE"));
922 wl_surface_commit(client.surface);
923 }
924 if (commands[i] == 'a' && getenv("SIGIL_WM_CLIENT_NEXT_APP_ID")) {
925 xdg_toplevel_set_app_id(client.toplevel, getenv("SIGIL_WM_CLIENT_NEXT_APP_ID"));
926 wl_surface_commit(client.surface);
927 }
928 if (commands[i] == 'u' && getenv("SIGIL_WM_CLIENT_MAP_CONTROL")) {
929 if (client.frame) wl_callback_destroy(client.frame);
930 client.frame = NULL;
931 wl_surface_attach(client.surface, NULL, 0, 0);
932 wl_surface_commit(client.surface);
933 }
934 if (commands[i] == 'r' && getenv("SIGIL_WM_CLIENT_MAP_CONTROL")) {
935 xdg_toplevel_set_title(client.toplevel, "Remapped");
936 xdg_toplevel_set_app_id(client.toplevel, "org.example.Remapped");
937 wl_surface_commit(client.surface);
938 }
939 if(commands[i]=='T' && getenv("SIGIL_WM_CLIENT_TITLE_BURST")) {
940 char title[4001];
941 for(int index=0;index<1100;index++) {
942 memset(title,'x',sizeof(title)-1);
943 char prefix[64];
944 int length=snprintf(prefix,sizeof(prefix),"Title burst %04d ",index);
945 memcpy(title,prefix,(size_t)length);
946 title[sizeof(title)-1]=0;
947 xdg_toplevel_set_title(client.toplevel,title);
948 if((index&15)==15 && wl_display_roundtrip(client.display)<0) return 8;
949 }
950 wl_surface_commit(client.surface);
951 puts("CLIENT_TITLE_BURST=1100 final=1099 bytes=4000");
952 }
953 /* Opt-in size-hint transition for real floating-drag
954 * tests. The existing fixed fixture starts at 64x64;
955 * b makes it resizable within independent bounds. */
956 if (commands[i] == 'b' && getenv("SIGIL_WM_CLIENT_SIZE_CONTROL")) {
957 xdg_toplevel_set_min_size(client.toplevel, 32, 24);
958 xdg_toplevel_set_max_size(client.toplevel, 104, 94);
959 wl_surface_commit(client.surface);
960 puts("CLIENT_BOUNDS=32x24..104x94");
961 }
962 constraint_command(&client, commands[i]);
963 activation_command(&client, commands[i]);
964 decoration_command(&client, commands[i]);
965 if (wl_display_roundtrip(client.display) < 0) return 8;
966 printf("CLIENT_SYNC=%c\n", commands[i]);
967 fflush(stdout);
968 }
969 }
970 continue;
971 }
972 if (client.delay_pending && client.resize_delay_ms > 0) {
973 /* Wait for events or the end of the delay, whichever is first. */
974 int64_t remaining = ms_until(&client.delay_due);
975 if (remaining <= 0) {
976 release_delayed(&client);
977 continue;
978 }
979 if (wl_display_prepare_read(client.display) != 0) {
980 if (wl_display_dispatch_pending(client.display) < 0) return 8;
981 continue;
982 }
983 wl_display_flush(client.display);
984 struct pollfd fd = {.fd = wl_display_get_fd(client.display), .events = POLLIN};
985 if (poll(&fd, 1, (int)remaining) > 0) {
986 if (wl_display_read_events(client.display) < 0) return 8;
987 } else {
988 wl_display_cancel_read(client.display);
989 }
990 if (wl_display_dispatch_pending(client.display) < 0) return 8;
991 continue;
992 }
993 if (wl_display_dispatch(client.display) < 0) {
994 if (!client.input_test) return 8;
995 printf("CLIENT_DISCONNECTED frames=%u\n", client.frames);
996 wl_display_disconnect(client.display);
997 return 0;
998 }
999 }
1000 if (client.frame) wl_callback_destroy(client.frame);
1001 decoration_finish(&client);
1002 if (client.activation_token) xdg_activation_token_v1_destroy(client.activation_token);
1003 if (client.activation) xdg_activation_v1_destroy(client.activation);
1004 if (client.locked) zwp_locked_pointer_v1_destroy(client.locked);
1005 if (client.confined) zwp_confined_pointer_v1_destroy(client.confined);
1006 if (client.constraints) zwp_pointer_constraints_v1_destroy(client.constraints);
1007 if (client.relative_pointer) zwp_relative_pointer_v1_destroy(client.relative_pointer);
1008 if (client.relative_manager) zwp_relative_pointer_manager_v1_destroy(client.relative_manager);
1009 if (client.pointer) wl_pointer_destroy(client.pointer);
1010 if (client.drag_icon) wl_surface_destroy(client.drag_icon);
1011 if (client.drag_buffer) wl_buffer_destroy(client.drag_buffer);
1012 if (client.data_source) wl_data_source_destroy(client.data_source);
1013 if (client.offer) wl_data_offer_destroy(client.offer);
1014 if (client.data_device) wl_data_device_release(client.data_device);
1015 if (client.data_manager) wl_data_device_manager_destroy(client.data_manager);
1016 if (client.inhibitor) zwp_idle_inhibitor_v1_destroy(client.inhibitor);
1017 if (client.inhibit_manager) zwp_idle_inhibit_manager_v1_destroy(client.inhibit_manager);
1018 wl_surface_attach(client.surface, NULL, 0, 0);
1019 wl_surface_commit(client.surface);
1020 xdg_toplevel_destroy(client.toplevel);
1021 xdg_surface_destroy(client.xdg);
1022 wl_surface_destroy(client.surface);
1023 wl_buffer_destroy(client.buffer);
1024 if (wl_display_roundtrip(client.display) < 0) return 9;
1025 xdg_wm_base_destroy(client.shell);
1026 wl_shm_destroy(client.shm);
1027 wl_compositor_destroy(client.compositor);
1028 wl_registry_destroy(registry);
1029 wl_display_flush(client.display);
1030 wl_display_disconnect(client.display);
1031 if (client.key_state) xkb_state_unref(client.key_state);
1032 printf("CLIENT_DONE color=%s frames=%u\n", argv[1], client.frames);
1033 if (client.dnd_mode || client.control_test) return 0;
1034 return client.frames == client.target ? 0 : 10;