Commit2157d1d1Recorded29 Oct 2026Repositorysigil-wm
Constrain popups and retry failed client frame presentation
Changed
Makefile | 15 +++++++++++++
README.md | 13 +++++++++++
scripts/test-foot-blink | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
scripts/test-popup-constraints | 23 +++++++++++++++++++
scripts/test-surface-frames | 39 ++++++++++++++++++++++++++++++++
src/c/deadlines.h | 5 ++++-
src/c/host.c | 36 ++++++++++++++++++++++++++---
src/c/output-gamma.h | 10 ++++-----
test/damage-client.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
test/deadlines.c | 1 +
test/frame-fault.c | 41 +++++++++++++++++++++++++++++++++
test/popup-client.c | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
test/popup-constraints.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
test/surface-frames.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14 files changed, 821 insertions(+), 9 deletions(-)Diff
Makefilemodified
@@ -219,6 +219,21 @@ build/backend-pointer.so: test/backend-pointer.c
219
build/geometry-client: test/geometry-client.c build/xdg-shell-client-protocol.h build/xdg-shell-client-protocol.c build/wlr-layer-shell-unstable-v1-client-protocol.h build/wlr-layer-shell-unstable-v1-client-protocol.c 220
$(CC) $(CFLAGS) -Ibuild $(shell pkg-config --cflags wayland-client) $< build/xdg-shell-client-protocol.c build/wlr-layer-shell-unstable-v1-client-protocol.c $(shell pkg-config --libs wayland-client) -Wl,-rpath,$(shell pkg-config --variable=libdir wayland-client) -o $@ 221
+222
build/popup-client: test/popup-client.c build/xdg-shell-client-protocol.h build/xdg-shell-client-protocol.c build/wlr-layer-shell-unstable-v1-client-protocol.h build/wlr-layer-shell-unstable-v1-client-protocol.c+223
$(CC) $(CFLAGS) -Ibuild $(shell pkg-config --cflags wayland-client) $< build/xdg-shell-client-protocol.c build/wlr-layer-shell-unstable-v1-client-protocol.c $(shell pkg-config --libs wayland-client) -Wl,-rpath,$(shell pkg-config --variable=libdir wayland-client) -o $@+224
+225
build/popup-constraints: test/popup-constraints.c build/libsigil-wm.so+226
$(CC) $(CFLAGS) -Isrc/c $< -Lbuild -lsigil-wm -Wl,-rpath,'$$ORIGIN' -o $@+227
+228
build/damage-client: test/damage-client.c build/xdg-shell-client-protocol.h build/xdg-shell-client-protocol.c+229
$(CC) $(CFLAGS) -Ibuild $(shell pkg-config --cflags wayland-client) $< build/xdg-shell-client-protocol.c $(shell pkg-config --libs wayland-client) -Wl,-rpath,$(shell pkg-config --variable=libdir wayland-client) -o $@+230
+231
build/surface-frames: test/surface-frames.c build/libsigil-wm.so+232
$(CC) $(CFLAGS) -Isrc/c $< -Lbuild -lsigil-wm -Wl,-rpath,'$$ORIGIN' -o $@+233
+234
build/frame-fault.so: test/frame-fault.c+235
$(CC) $(CFLAGS) $(CPPFLAGS) -fPIC -shared $< $(LDFLAGS) $(LDLIBS) -ldl -o $@+236
237
build/security-context-v1-client-protocol.h: 238
mkdir -p build 239
wayland-scanner client-header $(PROTOCOLS)/staging/security-context/security-context-v1.xml $@README.mdmodified
@@ -61,6 +61,19 @@ frame or client events. `scripts/test-deadlines` checks every deadline class,
61
independent repeating timers, real resource expiry, lock isolation, repeated 62
key streams, coalesced motion and zero blanked-host maintenance ticks. 63
+64
Popups use their root parent's output as the constraint area, including layer+65
surfaces and nested popups. Initial configuration, repositioning and policy+66
configuration apply the client's flip, slide and resize rules in root-surface+67
coordinates. `scripts/test-popup-constraints` checks delivered geometry against+68
two adjacent outputs with nonzero layout and window-geometry origins.+69
+70
Failed scene commits retain damage and client frame callbacks until an output+71
commit succeeds. Ordinary scene failures retry after 16 ms through the native+72
deadline timer; gamma failures retain their existing ownership and retry rules.+73
`scripts/test-surface-frames` checks tiny redraws, callbacks and actual presented+74
pixel phases, including one rejected commit. `scripts/test-foot-blink` samples+75
cursor phases in a nested compositor, directly and through a private tmux.+76
77
Run the stacking example from a Wayland session: 78
79
```shscripts/test-foot-blinkadded
@@ -0,0 +1,68 @@
+1
#!/usr/bin/env bash+2
set -euo pipefail+3
cd "$(dirname "$0")/.."+4
if [[ ${1:-} != --inside ]]; then+5
exec guix time-machine -C channels.scm -- shell -m draw-manifest.scm foot -- bash scripts/test-foot-blink --inside+6
fi+7
make -j2 all build/surface-frames build/frame-fault.so+8
unset TMUX TMUX_PANE+9
unset SIGIL_WM_FOOT_MODERN+10
foot_version=$(foot --version)+11
if [[ $foot_version =~ ([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then+12
if (( BASH_REMATCH[1] > 1 || (BASH_REMATCH[1] == 1 && BASH_REMATCH[2] >= 28) )); then+13
export SIGIL_WM_FOOT_MODERN=1+14
fi+15
else+16
printf 'unrecognized foot version: %s\n' "$foot_version" >&2+17
exit 2+18
fi+19
out=$PWD/build/receipts/foot-blink+20
mkdir -p "$out"+21
parent_runtime=$(mktemp -d /tmp/sigil-foot-parent.XXXXXXXX)+22
child_runtime=$(mktemp -d /tmp/sigil-foot-child.XXXXXXXX)+23
chmod 700 "$parent_runtime" "$child_runtime"+24
parent_pid=+25
cleanup() {+26
if [[ -n $parent_pid ]]; then kill "$parent_pid" 2>/dev/null || true; wait "$parent_pid" 2>/dev/null || true; fi+27
rm -rf "$parent_runtime" "$child_runtime"+28
}+29
trap cleanup EXIT+30
XDG_RUNTIME_DIR=$parent_runtime WLR_BACKENDS=headless WLR_RENDERER=pixman WLR_HEADLESS_OUTPUTS=1 \+31
SIGIL_WM_REPL=$parent_runtime/repl timeout 60 bash scripts/run-sigil -L src -L examples eval -f examples/tiling.sgl > "$out/parent.log" 2>&1 &+32
parent_pid=$!+33
for ((attempt=0;attempt<400;attempt++)); do+34
if grep -q '^WAYLAND_SOCKET=' "$out/parent.log"; then break; fi+35
sleep 0.025+36
done+37
parent_display=$(sed -n 's/^WAYLAND_SOCKET=//p' "$out/parent.log" | head -1)+38
[[ -n $parent_display ]] || { cat "$out/parent.log"; exit 2; }+39
for terminal in direct tmux; do+40
for clip in 0 1; do+41
for failure in ${SIGIL_WM_FOOT_FAILURES:-clean}; do+42
unset SIGIL_WM_FRAMES_TMUX+43
if [[ $terminal == tmux ]]; then export SIGIL_WM_FRAMES_TMUX=1; fi+44
unset SIGIL_WM_FRAME_FAILURE+45
rm -f "$child_runtime/fail"+46
if [[ $failure == busy ]]; then export SIGIL_WM_FRAME_FAILURE=$child_runtime/fail; fi+47
rc=0+48
XDG_RUNTIME_DIR=$child_runtime WAYLAND_DISPLAY=$parent_runtime/$parent_display WLR_BACKENDS=wayland WLR_RENDERER=pixman \+49
SIGIL_WM_FRAMES_FOOT=1 LD_PRELOAD=$PWD/build/frame-fault.so \+50
timeout 20 build/surface-frames "$out/$terminal-$clip-$failure.samples" "$clip" > "$out/$terminal-$clip-$failure.log" 2>&1 || rc=$?+51
cat "$out/$terminal-$clip-$failure.log"+52
test "$rc" == 0+53
if grep -q '^ err:' "$out/$terminal-$clip-$failure.log"; then exit 2; fi+54
if [[ $failure == busy ]]; then+55
awk '+56
/^FRAME_COMMIT_EBUSY / { pending=1; failures++ }+57
/^FRAME_COMMIT_ACCEPTED / { pending=0 }+58
/^FRAME_CALLBACK / && pending { print "FOOT_CALLBACK_BEFORE_ACCEPTED=FAIL"; failed=1 }+59
END { if (failures != 1) failed=1; exit failed }+60
' "$out/$terminal-$clip-$failure.log"+61
fi+62
done+63
done+64
done+65
XDG_RUNTIME_DIR=$parent_runtime bash scripts/msg "$parent_runtime/repl" '(stop-host! host)' > "$out/quit.log"+66
wait "$parent_pid"+67
parent_pid=+68
printf 'FOOT_NESTED_BLINK=PASS clipped-and-unclipped sampled-on-off-phases\n'scripts/test-popup-constraintsadded
@@ -0,0 +1,23 @@
+1
#!/usr/bin/env bash+2
set -euo pipefail+3
cd "$(dirname "$0")/.."+4
if [[ ${1:-} != --inside ]]; then+5
exec guix time-machine -C channels.scm -- shell -m manifest.scm -- bash scripts/test-popup-constraints --inside+6
fi+7
make -j2 all build/popup-client build/popup-constraints+8
mkdir -p build/receipts/popup-constraints+9
runtime=$(mktemp -d /tmp/sigil-popup-constraints.XXXXXXXX)+10
chmod 700 "$runtime"+11
trap 'rm -rf "$runtime"' EXIT+12
export XDG_RUNTIME_DIR=$runtime WLR_BACKENDS=headless WLR_RENDERER=pixman WLR_HEADLESS_OUTPUTS=2+13
unset WAYLAND_DISPLAY DISPLAY+14
result=0+15
for role in toplevel layer; do+16
for output in 0 1; do+17
rc=0+18
timeout 30 build/popup-constraints "$role" "$output" > "build/receipts/popup-constraints/$role-$output.log" 2>&1 || rc=$?+19
cat "build/receipts/popup-constraints/$role-$output.log"+20
if ((rc)); then result=$rc; fi+21
done+22
done+23
exit "$result"scripts/test-surface-framesadded
@@ -0,0 +1,39 @@
+1
#!/usr/bin/env bash+2
set -euo pipefail+3
cd "$(dirname "$0")/.."+4
if [[ ${1:-} != --inside ]]; then+5
exec guix time-machine -C channels.scm -- shell -m manifest.scm -- bash scripts/test-surface-frames --inside+6
fi+7
make -j2 all build/damage-client build/surface-frames build/frame-fault.so+8
out=$PWD/build/receipts/surface-frames+9
mkdir -p "$out"+10
runtime=$(mktemp -d /tmp/sigil-surface-frames.XXXXXXXX)+11
chmod 700 "$runtime"+12
trap 'rm -rf "$runtime"' EXIT+13
export XDG_RUNTIME_DIR=$runtime WLR_BACKENDS=headless WLR_RENDERER=pixman WLR_HEADLESS_OUTPUTS=1+14
unset WAYLAND_DISPLAY DISPLAY+15
result=0+16
for clip in 0 1; do+17
for failure in clean busy; do+18
unset SIGIL_WM_FRAME_FAILURE+19
if [[ $failure == busy ]]; then export SIGIL_WM_FRAME_FAILURE=$runtime/fail; fi+20
rc=0+21
LD_PRELOAD=$PWD/build/frame-fault.so timeout 20 build/surface-frames "$out/$clip-$failure.samples" "$clip" > "$out/$clip-$failure.log" 2>&1 || rc=$?+22
cat "$out/$clip-$failure.log"+23
awk '+24
/^FRAME_PRESENT / && /presented=1/ { split($3, item, "="); presented[item[2]]=1 }+25
/^DAMAGE_PRESENT_PHASE=/ { split($2, item, "="); phases[item[2]]=1 }+26
END {+27
for (sequence in phases) if (!presented[sequence]) {+28
print "DAMAGE_UNPRESENTED_SEQUENCE=" sequence; failed=1+29
}+30
exit failed+31
}+32
' "$out/$clip-$failure.log" || rc=2+33
if [[ $failure == busy ]]; then+34
test "$(grep -c '^FRAME_COMMIT_EBUSY ' "$out/$clip-$failure.log")" == 1 || rc=2+35
fi+36
if ((rc)); then result=$rc; fi+37
done+38
done+39
exit "$result"src/c/deadlines.hmodified
@@ -10,7 +10,10 @@ static uint64_t native_deadline(struct server *s,uint64_t now) {
10
if(atomic_load(&bridge.input_resync)) earlier_deadline(&next,now); 11
if(s->plan) earlier_deadline(&next,s->plan_deadline); 12
struct output *output;−13
wl_list_for_each(output,&s->outputs,link) earlier_deadline(&next,output->gamma_retry_at);+13
wl_list_for_each(output,&s->outputs,link) {+14
earlier_deadline(&next,output->gamma_retry_at);+15
earlier_deadline(&next,output->scene_retry_at);+16
} 17
earlier_deadline(&next,s->scene_update_deadline); 18
if(s->lock_state==1) earlier_deadline(&next,s->lock_deadline); 19
if(s->has_pending_motion) earlier_deadline(&next,(uint64_t)s->motion_last+s->motion_period);src/c/host.cmodified
@@ -160,7 +160,7 @@ struct output {
160
bool configured_enabled,gamma_pending,gamma_external,nearest_filter; 161
uint16_t *gamma_table; 162
size_t gamma_size;−163
uint64_t gamma_token,gamma_retry_at;+163
uint64_t gamma_token,gamma_retry_at,scene_retry_at; 164
int x,y; 165
uint64_t configuration_generation; 166
};@@ -1296,7 +1296,24 @@ struct popup {
1296
struct wlr_scene_tree *tree; 1297
struct wl_listener commit,destroy,reposition; 1298
};+1299
static void constrain_popup(struct popup *p) {+1300
struct server *s=p->server;+1301
struct view *root=find_view(s,window_for_surface(s,p->native->parent));+1302
if(!root) return;+1303
int x,y;+1304
wlr_scene_node_coords(&root->tree->node,&x,&y);+1305
struct wlr_output *output=root->layer ? root->layer->output :+1306
wlr_output_layout_output_at(s->layout,x+root->committed.width/2.0,y+root->committed.height/2.0);+1307
if(!output) return;+1308
struct wlr_box box;+1309
wlr_output_layout_get_box(s->layout,output,&box);+1310
/* wlroots resolves nested popup offsets from the root surface origin. */+1311
box.x-=x-root->committed.x;+1312
box.y-=y-root->committed.y;+1313
wlr_xdg_popup_unconstrain_from_box(p->native,&box);+1314
} 1315
static void popup_request(struct popup *p) {+1316
constrain_popup(p); 1317
struct wlr_box box=p->native->scheduled.geometry; 1318
p->deadline=now_ms()+bridge.request_deadline; 1319
EVENT(80,p->id,p->parent,box.x,box.y,box.width,box.height);@@ -1354,6 +1371,7 @@ static bool configure_popup(struct server *s,int64_t *w) {
1371
wl_list_for_each(p,&s->popups,link) { 1372
if(p->id!=(uint64_t)w[1] || !p->native->base->initialized) continue; 1373
p->native->scheduled.geometry=(struct wlr_box){w[2],w[3],w[4],w[5]};+1374
constrain_popup(p); 1375
wlr_xdg_surface_schedule_configure(p->native->base); 1376
p->deadline=0; 1377
return true;@@ -1555,8 +1573,14 @@ static void output_frame(struct wl_listener *listener,void *data) {
1573
} 1574
wlr_scene_output_for_each_buffer(o->scene,output_buffer_filter,o); 1575
lock_arm(o);−1558
if(o->gamma_pending) output_commit_gamma(o);−1559
else wlr_scene_output_commit(o->scene,NULL);+1576
bool committed=o->gamma_pending ? output_commit_gamma(o) : wlr_scene_output_commit(o->scene,NULL);+1577
if(!committed) {+1578
/* Retain damage and callbacks until a replacement frame is accepted. */+1579
wlr_damage_ring_add_whole(&o->scene->damage_ring);+1580
if(!o->gamma_pending) o->scene_retry_at=now_ms()+16;+1581
return;+1582
}+1583
o->scene_retry_at=0; 1584
struct timespec now; 1585
clock_gettime(CLOCK_MONOTONIC,&now); 1586
wlr_scene_output_send_frame_done(o->scene,&now);@@ -2214,6 +2238,12 @@ static int timer_tick(void *data) {
2238
data_expire(s); 2239
output_controls_expire(s); 2240
output_gamma_expire(s);+2241
struct output *output;+2242
wl_list_for_each(output,&s->outputs,link) {+2243
if(!output->scene_retry_at || now_ms()<output->scene_retry_at) continue;+2244
output->scene_retry_at=0;+2245
if(output->native->enabled) wlr_output_schedule_frame(output->native);+2246
} 2247
output_requests_expire(s); 2248
pointer_hints_expire(s); 2249
struct popup *p,*next;src/c/output-gamma.hmodified
@@ -68,16 +68,15 @@ static void output_gamma_expire(struct server *s) {
68
if(o->native->enabled) wlr_output_schedule_frame(o->native); 69
} 70
}−71
static void output_commit_gamma(struct output *o) {+71
static bool output_commit_gamma(struct output *o) { 72
/* A page flip must finish before another buffer/gamma commit. Never issue 73
* a second commit from this frame callback after a runtime failure. */−74
if(o->native->frame_pending) return;+74
if(o->native->frame_pending) return false; 75
o->gamma_retry_at=0; 76
struct wlr_gamma_control_v1 *control=output_gamma_control(o); 77
if(control && !control->table) { 78
o->gamma_pending=false;−79
wlr_scene_output_commit(o->scene,NULL);−80
return;+79
return wlr_scene_output_commit(o->scene,NULL); 80
} 81
struct wlr_output_state state; 82
wlr_output_state_init(&state);@@ -93,7 +92,7 @@ static void output_commit_gamma(struct output *o) {
92
* or revoke a client's ownership for an unrelated backend fault. */ 93
wlr_output_state_finish(&state); 94
o->gamma_retry_at=now_ms()+16;−96
return;+95
return false; 96
} 97
o->gamma_pending=false; 98
if(control) wlr_gamma_control_v1_send_failed_and_destroy(control);@@ -109,4 +108,5 @@ static void output_commit_gamma(struct output *o) {
108
* Retry on the next backend frame rather than racing the page flip. */ 109
o->gamma_retry_at=now_ms()+16; 110
}+111
return committed; 112
}test/damage-client.cadded
@@ -0,0 +1,134 @@
+1
/* SPDX-License-Identifier: BSD-3-Clause */+2
#define _POSIX_C_SOURCE 200809L+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 "xdg-shell-client-protocol.h"+15
+16
static struct wl_display *display;+17
static struct wl_compositor *compositor;+18
static struct wl_shm *shm;+19
static struct xdg_wm_base *shell;+20
static struct wl_surface *surface;+21
static int width = 150, height = 150;+22
static unsigned commits, callbacks;+23
static bool configured, failed;+24
static double milliseconds(void) {+25
struct timespec now;+26
clock_gettime(CLOCK_MONOTONIC, &now);+27
return now.tv_sec * 1000.0 + now.tv_nsec / 1e6;+28
}+29
static void require(bool ok, const char *message) {+30
if (ok) return;+31
fprintf(stderr, "damage client: %s\n", message);+32
exit(2);+33
}+34
static void done(void *data, struct wl_callback *callback, uint32_t time) {+35
(void)time;+36
unsigned sequence = (uintptr_t)data;+37
wl_callback_destroy(callback);+38
callbacks++;+39
if (sequence != callbacks) failed = true;+40
printf("DAMAGE_FRAME_DONE=%u\n", sequence);+41
fflush(stdout);+42
}+43
static const struct wl_callback_listener frame_listener = {.done = done};+44
static void draw(bool initial) {+45
char path[] = "/tmp/sigil-damage-client.XXXXXX";+46
int fd = mkstemp(path);+47
unlink(path);+48
require(fd >= 0, "shm file");+49
size_t size = (size_t)width * height * 4;+50
require(ftruncate(fd, size) == 0, "shm size");+51
uint32_t *pixels = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);+52
require(pixels != MAP_FAILED, "shm map");+53
uint32_t colour = commits % 2 ? 0xffeeeeee : 0xff222222;+54
for (int y = 0; y < height; y++)+55
for (int x = 0; x < width; x++)+56
pixels[y * width + x] = x >= 20 && x < 28 && y >= 20 && y < 36 ? colour : 0xff222222;+57
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);+58
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height, width * 4, WL_SHM_FORMAT_XRGB8888);+59
wl_shm_pool_destroy(pool);+60
munmap(pixels, size); close(fd);+61
if (!initial && commits == 3) {+62
const char *barrier = getenv("SIGIL_WM_FRAME_FAILURE");+63
if (barrier) { FILE *file = fopen(barrier, "w"); require(file != NULL, "failure barrier"); fclose(file); }+64
}+65
wl_surface_attach(surface, buffer, 0, 0);+66
if (initial) wl_surface_damage_buffer(surface, 0, 0, width, height);+67
else wl_surface_damage_buffer(surface, 20, 20, 8, 16);+68
commits++;+69
wl_callback_add_listener(wl_surface_frame(surface), &frame_listener, (void *)(uintptr_t)commits);+70
wl_surface_commit(surface);+71
wl_buffer_destroy(buffer);+72
printf("DAMAGE_COMMIT=%u colour=%06x damage=%s\n", commits, colour & 0xffffff, initial ? "whole" : "20,20,8,16");+73
fflush(stdout);+74
}+75
static void configure(void *data, struct xdg_surface *xdg, uint32_t serial) {+76
(void)data;+77
xdg_surface_ack_configure(xdg, serial);+78
if (!configured) { configured = true; draw(true); }+79
else wl_surface_commit(surface);+80
}+81
static const struct xdg_surface_listener surface_listener = {.configure = configure};+82
static void top_configure(void *data, struct xdg_toplevel *top, int32_t w, int32_t h, struct wl_array *states) {+83
(void)data; (void)top; (void)states;+84
if (w) width = w;+85
if (h) height = h;+86
}+87
static void close_top(void *data, struct xdg_toplevel *top) { (void)data; (void)top; failed = true; }+88
static const struct xdg_toplevel_listener top_listener = {.configure = top_configure, .close = close_top};+89
static void ping(void *data, struct xdg_wm_base *base, uint32_t serial) { (void)data; xdg_wm_base_pong(base, serial); }+90
static const struct xdg_wm_base_listener shell_listener = {.ping = ping};+91
static void global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {+92
(void)data; (void)version;+93
if (!strcmp(interface, "wl_compositor")) compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4);+94
else if (!strcmp(interface, "wl_shm")) shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);+95
else if (!strcmp(interface, "xdg_wm_base")) {+96
shell = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);+97
xdg_wm_base_add_listener(shell, &shell_listener, NULL);+98
}+99
}+100
static void removed(void *data, struct wl_registry *registry, uint32_t name) { (void)data; (void)registry; (void)name; }+101
static const struct wl_registry_listener registry_listener = {.global = global, .global_remove = removed};+102
int main(void) {+103
setvbuf(stdout, NULL, _IOLBF, 0);+104
display = wl_display_connect(NULL);+105
require(display != NULL, "connect");+106
struct wl_registry *registry = wl_display_get_registry(display);+107
wl_registry_add_listener(registry, ®istry_listener, NULL);+108
require(wl_display_roundtrip(display) >= 0 && compositor && shm && shell, "registry");+109
surface = wl_compositor_create_surface(compositor);+110
struct xdg_surface *xdg = xdg_wm_base_get_xdg_surface(shell, surface);+111
xdg_surface_add_listener(xdg, &surface_listener, NULL);+112
struct xdg_toplevel *top = xdg_surface_get_toplevel(xdg);+113
xdg_toplevel_add_listener(top, &top_listener, NULL);+114
wl_surface_commit(surface);+115
double end = milliseconds() + 5000, next = milliseconds() + 200;+116
while (!failed && milliseconds() < end && callbacks < 12) {+117
require(wl_display_dispatch_pending(display) >= 0, "dispatch pending");+118
require(wl_display_flush(display) >= 0 || errno == EAGAIN, "flush");+119
struct pollfd fd = {.fd = wl_display_get_fd(display), .events = POLLIN};+120
int result = poll(&fd, 1, 10);+121
require(result >= 0 || errno == EINTR, "poll");+122
if (fd.revents & POLLIN) require(wl_display_dispatch(display) >= 0, "dispatch");+123
if (configured && commits < 12 && milliseconds() >= next) {+124
if (callbacks != commits) { fprintf(stderr, "DAMAGE_MISSING_CALLBACK commit=%u callbacks=%u\n", commits, callbacks); failed = true; }+125
else draw(false);+126
next = milliseconds() + 120;+127
}+128
}+129
bool passed = !failed && commits == 12 && callbacks == commits;+130
xdg_toplevel_destroy(top); xdg_surface_destroy(xdg); wl_surface_destroy(surface);+131
wl_display_flush(display); wl_display_disconnect(display);+132
printf("DAMAGE_CALLBACKS=%s commits=%u callbacks=%u\n", passed ? "PASS" : "FAIL", commits, callbacks);+133
return passed ? 0 : 1;+134
}test/deadlines.cmodified
@@ -52,6 +52,7 @@ static void selection(void) {
52
struct data_request d={.deadline=1011}; wl_list_insert(&s.data_requests,&d.link); 53
assert(native_deadline(&s,now)==1011); wl_list_remove(&d.link); 54
struct output o={.id=7}; wl_list_insert(&s.outputs,&o.link);+55
o.scene_retry_at=1012; assert(native_deadline(&s,now)==1012); o.scene_retry_at=0; 56
struct output_control_request c={.output=7,.deadline=1012}; wl_list_insert(&s.output_controls,&c.link); 57
assert(native_deadline(&s,now)==1012); c.resolved=true; 58
assert(native_deadline(&s,now)==0); wl_list_remove(&c.link); wl_list_remove(&o.link);test/frame-fault.cadded
@@ -0,0 +1,41 @@
+1
/* SPDX-License-Identifier: BSD-3-Clause */+2
#define _GNU_SOURCE+3
#include <dlfcn.h>+4
#include <errno.h>+5
#include <stdio.h>+6
#include <stdlib.h>+7
#include <unistd.h>+8
#include <wlr/interfaces/wlr_output.h>+9
#include <wlr/types/wlr_compositor.h>+10
+11
bool wlr_output_commit_state(struct wlr_output *output, const struct wlr_output_state *state) {+12
const char *barrier = getenv("SIGIL_WM_FRAME_FAILURE");+13
if ((state->committed & WLR_OUTPUT_STATE_BUFFER) && barrier && unlink(barrier) == 0) {+14
fprintf(stderr, "FRAME_COMMIT_EBUSY output=%s sequence=%u\n", output->name, output->commit_seq + 1);+15
errno = EBUSY;+16
return false;+17
}+18
typedef bool (*commit_fn)(struct wlr_output *, const struct wlr_output_state *);+19
commit_fn commit = (commit_fn)dlsym(RTLD_NEXT, "wlr_output_commit_state");+20
if (!commit) abort();+21
bool accepted = commit(output, state);+22
if (accepted && (state->committed & WLR_OUTPUT_STATE_BUFFER))+23
fprintf(stderr, "FRAME_COMMIT_ACCEPTED output=%s sequence=%u\n", output->name, output->commit_seq);+24
return accepted;+25
}+26
void wlr_output_send_present(struct wlr_output *output, struct wlr_output_event_present *event) {+27
fprintf(stderr, "FRAME_PRESENT output=%s sequence=%u presented=%d\n", output->name, event->commit_seq, event->presented);+28
typedef void (*present_fn)(struct wlr_output *, struct wlr_output_event_present *);+29
present_fn present = (present_fn)dlsym(RTLD_NEXT, "wlr_output_send_present");+30
if (!present) abort();+31
present(output, event);+32
}+33
void wlr_surface_send_frame_done(struct wlr_surface *surface, const struct timespec *when) {+34
if (!wl_list_empty(&surface->current.frame_callback_list))+35
fprintf(stderr, "FRAME_CALLBACK sequence=%u callbacks=%d\n", surface->current.seq,+36
wl_list_length(&surface->current.frame_callback_list));+37
typedef void (*done_fn)(struct wlr_surface *, const struct timespec *);+38
done_fn done = (done_fn)dlsym(RTLD_NEXT, "wlr_surface_send_frame_done");+39
if (!done) abort();+40
done(surface, when);+41
}test/popup-client.cadded
@@ -0,0 +1,199 @@
+1
/* SPDX-License-Identifier: BSD-3-Clause */+2
#define _POSIX_C_SOURCE 200809L+3
#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"+14
+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;+33
+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
}test/popup-constraints.cadded
@@ -0,0 +1,109 @@
+1
/* SPDX-License-Identifier: BSD-3-Clause */+2
#include <errno.h>+3
#include <poll.h>+4
#include <signal.h>+5
#include <stdbool.h>+6
#include <stdint.h>+7
#include <stdio.h>+8
#include <stdlib.h>+9
#include <string.h>+10
#include <sys/wait.h>+11
#include <time.h>+12
#include <unistd.h>+13
#include "api.h"+14
+15
static pid_t child;+16
static void cleanup(void) {+17
if (child > 0) { kill(child, SIGKILL); waitpid(child, NULL, 0); }+18
}+19
static void require(bool ok, const char *message) {+20
if (ok) return;+21
fprintf(stderr, "popup constraints: %s\n", message);+22
exit(2);+23
}+24
static void send_words(int64_t *words, size_t count) {+25
struct wm_message command = {0};+26
require(count <= 16, "command size");+27
memcpy(command.word, words, count * sizeof(*words));+28
require(wm_send(&command) == 1, "command refused");+29
}+30
#define SEND(...) send_words((int64_t[]){__VA_ARGS__}, sizeof((int64_t[]){__VA_ARGS__}) / sizeof(int64_t))+31
static double seconds(void) {+32
struct timespec now;+33
clock_gettime(CLOCK_MONOTONIC, &now);+34
return now.tv_sec + now.tv_nsec / 1e9;+35
}+36
int main(int argc, char **argv) {+37
require(argc == 3, "usage: popup-constraints toplevel|layer 0|1");+38
int selected = atoi(argv[2]);+39
require(selected == 0 || selected == 1, "output selection");+40
atexit(cleanup);+41
setvbuf(stdout, NULL, _IOLBF, 0);+42
require(wm_options(32, 2000) == 1 && wm_start() == 1, "start");+43
int outputs = 0, configured = 0, result = 2;+44
int64_t chosen = 0;+45
bool running = true, layer_configured = false;+46
int64_t admitted_popup = 0;+47
double deadline = seconds() + 20;+48
while (running && seconds() < deadline) {+49
struct pollfd fd = {.fd = wm_event_fd(), .events = POLLIN};+50
require(poll(&fd, 1, 50) >= 0 || errno == EINTR, "event poll");+51
struct wm_message event;+52
while (wm_next(&event)) {+53
int64_t *w = event.word;+54
switch (w[0]) {+55
case 2: {+56
if (outputs == selected) chosen = w[1];+57
int64_t output[] = {w[1], 1, 100 + outputs * 640, 60, 1000, 0, -1, 640, 480, 60000};+58
require(wm_submit(2, ++outputs, output, 10) == 1, "output configuration");+59
break;+60
}+61
case 24:+62
require(w[3] == 1, "output rejected");+63
if (++configured == 2) {+64
require(outputs == 2 && chosen, "two outputs");+65
SEND(25, chosen);+66
require(setenv("WAYLAND_DISPLAY", wm_socket(), 1) == 0, "socket name");+67
child = fork();+68
require(child >= 0, "fork");+69
if (!child) { execl("build/popup-client", "popup-client", argv[1], NULL); _exit(127); }+70
}+71
break;+72
case 14: SEND(2, w[1], 150, 150); break;+73
case 19:+74
if (w[14] && !layer_configured) {+75
layer_configured = true;+76
SEND(24, w[1], chosen, 150, 150);+77
}+78
break;+79
case 11: {+80
int64_t plan[] = {1, 1, w[1], 200, w[1], 580 + selected * 640, 350, 150, 150, 1, 1, 0, 0, 0};+81
require(wm_submit(1, 10, plan, 14) == 1, "parent placement");+82
break;+83
}+84
case 80:+85
if (admitted_popup != w[1]) {+86
admitted_popup = w[1];+87
SEND(20, w[1], w[3], w[4], w[5], w[6]);+88
}+89
break;+90
case 13: {+91
int status;+92
require(child > 0 && waitpid(child, &status, 0) == child, "client wait");+93
child = 0;+94
result = WIFEXITED(status) ? WEXITSTATUS(status) : 2;+95
SEND(1);+96
break;+97
}+98
case 7: require(w[2] == 0, "native failure"); running = false; break;+99
case 23: require(false, "plan rejected"); break;+100
case 98: require(false, "command rejected"); break;+101
}+102
}+103
}+104
require(!running, "timeout waiting for client");+105
require(wm_join() == 1, "join");+106
require(wm_stat(1) == 0 && wm_stat(2) == 0 && wm_stat(3) == 0 && wm_stat(7) == 0, "retained resources");+107
printf("POPUP_CONSTRAINTS=%s role=%s output=%d\n", result == 0 ? "PASS" : "FAIL", argv[1], selected);+108
return result;+109
}test/surface-frames.cadded
@@ -0,0 +1,137 @@
+1
/* SPDX-License-Identifier: BSD-3-Clause */+2
#include <errno.h>+3
#include <poll.h>+4
#include <signal.h>+5
#include <stdbool.h>+6
#include <stdint.h>+7
#include <stdio.h>+8
#include <stdlib.h>+9
#include <string.h>+10
#include <sys/wait.h>+11
#include <time.h>+12
#include <unistd.h>+13
#include "api.h"+14
+15
static pid_t child;+16
static void cleanup(void) { if (child > 0) { kill(child, SIGKILL); waitpid(child, NULL, 0); } }+17
static void require(bool ok, const char *message) {+18
if (ok) return;+19
fprintf(stderr, "surface frames: %s\n", message);+20
exit(2);+21
}+22
static void command(int kind, int64_t id, int64_t a, int64_t b) {+23
struct wm_message m = {.word = {kind, id, a, b}};+24
require(wm_send(&m) == 1, "command refused");+25
}+26
static double seconds(void) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return now.tv_sec + now.tv_nsec / 1e9; }+27
int main(int argc, char **argv) {+28
require(argc == 3, "usage: surface-frames SAMPLE-PATH CLIP");+29
bool foot = getenv("SIGIL_WM_FRAMES_FOOT") != NULL;+30
bool modern = getenv("SIGIL_WM_FOOT_MODERN") != NULL;+31
setvbuf(stdout, NULL, _IOLBF, 0);+32
atexit(cleanup);+33
require(wm_options(foot ? 8 : 0, 2000) == 1 && wm_decoration_default(2) == 1 && wm_start() == 1, "start");+34
bool running = true, injected = false;+35
int result = 2;+36
double deadline = seconds() + 12;+37
while (running && seconds() < deadline) {+38
struct pollfd fd = {.fd = wm_event_fd(), .events = POLLIN};+39
require(poll(&fd, 1, 50) >= 0 || errno == EINTR, "event poll");+40
struct wm_message event;+41
while (wm_next(&event)) {+42
int64_t *w = event.word;+43
switch (w[0]) {+44
case 1: {+45
int64_t keyboard[] = {1, 25, 600, 2, 'u', 's'};+46
require(wm_submit(3, 3, keyboard, 6) == 1, "keyboard configuration");+47
command(14, 3, 0, 0);+48
break;+49
}+50
case 2: {+51
int64_t output[] = {w[1], 1, 0, 0, 1000, 0, -1, 320, 240, w[5] ? 0 : 60000};+52
require(wm_submit(2, 1, output, 10) == 1, "output configuration");+53
break;+54
}+55
case 4: {+56
int64_t output[] = {w[1], 1, 0, 0, 1000, 0, -1, w[2], w[3], 0};+57
require(wm_submit(2, 1, output, 10) == 1, "nested output configuration");+58
break;+59
}+60
case 6:+61
if (!child) {+62
size_t length = strlen(argv[1]);+63
require(length < 1024, "sample path");+64
int64_t samples[1028] = {60, 1, 30, 30};+65
if (foot) { samples[0] = 120; samples[2] = 15; samples[3] = 18; }+66
for (size_t i = 0; i < length; i++) samples[i + 4] = (unsigned char)argv[1][i];+67
require(wm_submit(15, w[1], samples, length + 4) == 1, "sample recording");+68
require(setenv("WAYLAND_DISPLAY", wm_socket(), 1) == 0, "socket name");+69
child = fork();+70
require(child >= 0, "fork");+71
if (!child) {+72
if (foot) execlp("foot", "foot", "--config=/dev/null",+73
"--override=cursor.blink=yes", "--override=cursor.blink-rate=120",+74
"--override=pad=0x0", "--override=font=JetBrains Mono:size=12",+75
modern ? "--override=colors-dark.background=222222" : "--override=colors.background=222222",+76
modern ? "--override=colors-dark.foreground=eeeeee" : "--override=colors.foreground=eeeeee",+77
modern ? "--override=colors-dark.cursor=222222 eeeeee" : "--override=cursor.color=222222 eeeeee",+78
"--", "sh", "-c",+79
"if [ -n \"$SIGIL_WM_FRAMES_TMUX\" ]; then exec tmux -S \"$XDG_RUNTIME_DIR/blink-tmux\" -f /dev/null new-session \"printf '\\033[2J\\033[H\\033[?25h'; sleep 3\"; else printf '\033[2J\033[H\033[?25h'; sleep 3; fi", NULL);+80
else execl("build/damage-client", "damage-client", NULL);+81
_exit(127);+82
}+83
}+84
break;+85
case 14: command(2, w[1], 150, 150); break;+86
case 75: command(18, w[1], 2, 0); break;+87
case 22:+88
if (foot && !injected && getenv("SIGIL_WM_FRAME_FAILURE")) {+89
FILE *barrier = fopen(getenv("SIGIL_WM_FRAME_FAILURE"), "w");+90
require(barrier != NULL, "foot failure barrier");+91
fclose(barrier);+92
injected = true;+93
}+94
break;+95
case 11: {+96
int64_t plan[] = {1, 1, w[1], 200, w[1], 10, 10, 150, 150, 1, 1, 0, 15, atoi(argv[2])};+97
require(wm_submit(1, 2, plan, 14) == 1, "placement");+98
break;+99
}+100
case 13: {+101
int status;+102
require(child > 0 && waitpid(child, &status, 0) == child, "wait client");+103
child = 0;+104
result = WIFEXITED(status) ? WEXITSTATUS(status) : 2;+105
command(1, 0, 0, 0);+106
break;+107
}+108
case 7: require(w[2] == 0, "native failure"); running = false; break;+109
case 23: require(false, "plan rejected"); break;+110
case 24: require(w[3] == 1, "submission rejected"); break;+111
case 98: require(false, "command rejected"); break;+112
}+113
}+114
}+115
require(!running && wm_join() == 1, "timeout or join");+116
FILE *file = fopen(argv[1], "r");+117
require(file != NULL, "missing sample log");+118
char line[256];+119
unsigned phases = 0, previous = 0, colour, sequence, index, width, height;+120
while (fgets(line, sizeof(line), file)) {+121
char status[8];+122
require(sscanf(line, "FRAME %u %u %u %u %x %7s", &index, &sequence, &width, &height, &colour, status) == 6 &&+123
!strcmp(status, "PASS"), "bad sample");+124
if (colour != 0x222222 && colour != 0xeeeeee) continue;+125
if (colour == previous) continue;+126
if (!foot) require(colour == (phases % 2 ? 0xeeeeee : 0x222222), "phase order");+127
previous = colour;+128
phases++;+129
printf("DAMAGE_PRESENT_PHASE=%u output-sequence=%u colour=%06x\n", phases, sequence, colour);+130
}+131
fclose(file);+132
bool passed = !result && (foot ? phases >= 4 : phases == 12);+133
printf("%s=%s phases=%u expected=%s\n", foot ? "FOOT_BLINK" : "DAMAGE_PRESENTATION",+134
passed ? "PASS" : "FAIL", phases, foot ? "at-least-4" : "12");+135
require(wm_stat(1) == 0 && wm_stat(2) == 0 && wm_stat(3) == 0 && wm_stat(7) == 0, "retained resources");+136
return result ? result : passed ? 0 : 1;+137
}