AtlatestRepositorysigil-wm
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"21
struct 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 window70
* with client-side shadows, the buffer extends past the window geometry71
* by these margins, painted shadow_color, and set_window_geometry72
* 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 size76
* is acknowledged, and the new size drawn, N ms after it arrives, as a77
* slow client would; -1 never acknowledges one. Later configures that78
* 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 alpha85
* A, premultiplied, like a translucent terminal. Unset is opaque XRGB. */86
int alpha;87
};89
static const uint32_t shadow_color = 0xffff00ff;91
static void draw(struct client *client);93
static void buffer_release(void *data, struct wl_buffer *buffer) {94
(void)data;95
(void)buffer;96
}98
static const struct wl_buffer_listener buffer_listener = {.release = buffer_release};100
static 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);140
}142
static 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 next149
* 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;162
}164
static const struct wl_callback_listener frame_listener = {.done = frame_done};166
static 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);179
}181
/* Commit the current buffer now, without waiting for a pending frame182
* callback: a client answers a resize at once, as GUI clients do, and a183
* compositor may withhold frame callbacks while it waits for that answer. */184
static 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);196
}198
static 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);208
}210
static 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;214
}216
/* The delay is over: take the newest size, acknowledge the newest217
* configure and draw at once. */218
static 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);227
}229
static const struct xdg_surface_listener surface_listener = {.configure = configure};231
static 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;273
}275
static void toplevel_close(void *data, struct xdg_toplevel *toplevel) {276
((struct client *)data)->running = false;277
(void)toplevel;278
}280
static 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;283
}285
static void toplevel_capabilities(void *data, struct xdg_toplevel *toplevel,286
struct wl_array *capabilities) {287
(void)data; (void)toplevel; (void)capabilities;288
}290
static 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
};297
static void ping(void *data, struct xdg_wm_base *shell, uint32_t serial) {298
xdg_wm_base_pong(shell, serial);299
(void)data;300
}302
static const struct xdg_wm_base_listener shell_listener = {.ping = ping};304
static void shm_format(void *data, struct wl_shm *shm, uint32_t format) {305
(void)data;306
(void)shm;307
(void)format;308
}310
static const struct wl_shm_listener shm_listener = {.format = shm_format};312
static 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;333
}335
static 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;341
}343
static 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;348
}350
static 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;372
}374
static 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;382
}384
static 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
};389
static const char drag_payload[] = "drag-payload-λ";390
static const char drag_mime[] = "text/plain;charset=utf-8";392
static void source_target(void *data, struct wl_data_source *source, const char *mime) {393
(void)data; (void)source; (void)mime;394
}396
static 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;409
}411
static 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;420
}422
static void source_drop(void *data, struct wl_data_source *source) {423
puts("DND_SOURCE_DROP");424
fflush(stdout);425
(void)data; (void)source;426
}428
static 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 release431
* 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);437
}439
static void source_action(void *data, struct wl_data_source *source, uint32_t action) {440
(void)data; (void)source; (void)action;441
}443
static 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
};448
static void offer_mime(void *data, struct wl_data_offer *offer, const char *mime) {449
(void)data; (void)offer; (void)mime;450
}452
static void offer_source_actions(void *data, struct wl_data_offer *offer, uint32_t actions) {453
(void)data; (void)offer; (void)actions;454
}456
static 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;462
}464
static const struct wl_data_offer_listener offer_listener = {465
.offer = offer_mime, .source_actions = offer_source_actions, .action = offer_action,466
};468
static 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;474
}476
static 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;487
}489
static void device_leave(void *data, struct wl_data_device *device) {490
(void)data; (void)device;491
}493
static 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;496
}498
static 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;523
}525
static void device_selection(void *data, struct wl_data_device *device, struct wl_data_offer *offer) {526
(void)data; (void)device; (void)offer;527
}529
static 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
};534
static 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);562
}564
static 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;571
}573
static 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;576
}578
static 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;585
}587
static 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;595
}597
static 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;600
}602
static 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
};607
static 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;614
}616
static const struct zwp_relative_pointer_v1_listener relative_listener = {617
.relative_motion = relative_motion,618
};620
static void locked(void *data, struct zwp_locked_pointer_v1 *pointer) {621
puts("CLIENT_LOCKED"); fflush(stdout); (void)data; (void)pointer;622
}623
static void unlocked(void *data, struct zwp_locked_pointer_v1 *pointer) {624
puts("CLIENT_UNLOCKED"); fflush(stdout); (void)data; (void)pointer;625
}626
static const struct zwp_locked_pointer_v1_listener locked_listener = {627
.locked = locked, .unlocked = unlocked,628
};629
static void confined(void *data, struct zwp_confined_pointer_v1 *pointer) {630
puts("CLIENT_CONFINED"); fflush(stdout); (void)data; (void)pointer;631
}632
static void unconfined(void *data, struct zwp_confined_pointer_v1 *pointer) {633
puts("CLIENT_UNCONFINED"); fflush(stdout); (void)data; (void)pointer;634
}635
static 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. */641
static 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
}675
}677
static 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);684
}686
static 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
}697
}699
static const struct wl_seat_listener seat_listener = {.capabilities = seat_capabilities};701
static 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);708
}709
static const struct xdg_activation_token_v1_listener activation_listener={.done=activation_done};710
static 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);727
}729
#include "decoration-client.h"731
static 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
}763
}765
static void global_remove(void *data, struct wl_registry *registry, uint32_t name) {766
(void)data;767
(void)registry;768
(void)name;769
}771
static const struct wl_registry_listener registry_listener = {772
.global = global,773
.global_remove = global_remove,774
};776
int 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, ®istry_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 a870
* 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 client886
* 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-drag954
* 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;1035
}