Commitd1833561Recorded29 Oct 2026Repositorysigil-wm
Restore owned output gamma before the backend shuts down
Changed
Makefile | 3 +++
src/c/host.c | 1 +
src/c/output-gamma.h | 12 ++++++++++++
test/output-gamma-lifecycle.c | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
test/output-gamma.c | 23 ++++++++++++++++++-----
5 files changed, 153 insertions(+), 5 deletions(-)Diff
Makefilemodified
@@ -13,6 +13,9 @@ build/libsigil-wm.so: src/c/output-gamma.h
13
build/output-gamma-test: test/output-gamma.c src/c/output-gamma.h src/c/host.c 14
$(CC) $(CFLAGS) $(CPPFLAGS) $< $(LDFLAGS) $(LDLIBS) -o $@ 15
+16
build/output-gamma-lifecycle-test: test/output-gamma-lifecycle.c src/c/output-gamma.h src/c/host.c+17
$(CC) $(CFLAGS) $(CPPFLAGS) $< $(LDFLAGS) $(LDLIBS) -o $@+18
19
build/gamma-client: test/gamma-client.c build/wlr-gamma-control-unstable-v1-client-protocol.h build/wlr-gamma-control-unstable-v1-client-protocol.c 20
$(CC) $(CFLAGS) -Ibuild $(shell pkg-config --cflags wayland-client) $< build/wlr-gamma-control-unstable-v1-client-protocol.c $(shell pkg-config --libs wayland-client) -Wl,-rpath,$(shell pkg-config --variable=libdir wayland-client) -o $@ 21
src/c/host.cmodified
@@ -2379,6 +2379,7 @@ cleanup:
2379
constraints_finish(&s); 2380
output_pending_finish(&s); 2381
output_management_finish(&s);+2382
output_gamma_finish(&s); 2383
output_controls_finish(&s); 2384
desktop_finish(&s); 2385
if(s.new_layer.notify) wl_list_remove(&s.new_layer.link);src/c/output-gamma.hmodified
@@ -4,6 +4,18 @@ static struct wlr_gamma_control_v1 *output_gamma_control(struct output *o) {
4
return o->server->gamma_manager ? 5
wlr_gamma_control_manager_v1_get_control(o->server->gamma_manager,o->native) : NULL; 6
}+7
static void output_gamma_finish(struct server *s) {+8
struct output *o;+9
wl_list_for_each(o,&s->outputs,link) {+10
if(!o->native->enabled || output_gamma_control(o)) continue;+11
struct wlr_output_state state;+12
wlr_output_state_init(&state);+13
if((!wlr_output_state_set_gamma_lut(&state,0,NULL,NULL,NULL) ||+14
!wlr_output_commit_state(o->native,&state)) && wlr_output_get_gamma_size(o->native)>0)+15
wlr_log(WLR_ERROR,"failed to restore neutral gamma on output %s",o->native->name);+16
wlr_output_state_finish(&state);+17
}+18
} 19
static void output_gamma_observe(struct server *s) { 20
if(!s->gamma_manager) return; 21
struct output *o;test/output-gamma-lifecycle.cadded
@@ -0,0 +1,119 @@
+1
/* SPDX-License-Identifier: BSD-3-Clause+2
* Run the actual host and teardown, replacing only the headless backend's+3
* unsupported LUT operations with persistent synthetic hardware state. */+4
#define _GNU_SOURCE+5
#define wlr_output_get_gamma_size lifecycle_gamma_size+6
#define wlr_output_test_state lifecycle_test_state+7
#define wlr_output_commit_state lifecycle_commit_state+8
#define wlr_scene_output_build_state lifecycle_build_state+9
#include "../src/c/host.c"+10
#undef wlr_output_get_gamma_size+11
#undef wlr_output_test_state+12
#undef wlr_output_commit_state+13
#undef wlr_scene_output_build_state+14
#include <assert.h>+15
#include <sys/mman.h>+16
#include <sys/wait.h>+17
extern bool wlr_output_test_state(struct wlr_output *,const struct wlr_output_state *);+18
extern bool wlr_output_commit_state(struct wlr_output *,const struct wlr_output_state *);+19
extern bool wlr_scene_output_build_state(struct wlr_scene_output *,struct wlr_output_state *,+20
const struct wlr_scene_output_state_options *);+21
struct hardware {+22
atomic_uint size,blue,start_resets,exit_resets;+23
};+24
static struct hardware *hardware;+25
size_t lifecycle_gamma_size(struct wlr_output *output) { (void)output; return 256; }+26
bool lifecycle_test_state(struct wlr_output *output,const struct wlr_output_state *state) {+27
struct wlr_output_state plain=*state;+28
plain.committed &= ~WLR_OUTPUT_STATE_GAMMA_LUT;+29
return wlr_output_test_state(output,&plain);+30
}+31
bool lifecycle_build_state(struct wlr_scene_output *scene,struct wlr_output_state *state,+32
const struct wlr_scene_output_state_options *options) {+33
uint32_t gamma=state->committed&WLR_OUTPUT_STATE_GAMMA_LUT;+34
state->committed &= ~WLR_OUTPUT_STATE_GAMMA_LUT;+35
bool ok=wlr_scene_output_build_state(scene,state,options);+36
state->committed |= gamma;+37
return ok;+38
}+39
bool lifecycle_commit_state(struct wlr_output *output,const struct wlr_output_state *state) {+40
struct wlr_output_state plain=*state;+41
plain.committed &= ~WLR_OUTPUT_STATE_GAMMA_LUT;+42
bool ok=wlr_output_commit_state(output,&plain);+43
if(ok && (state->committed&WLR_OUTPUT_STATE_GAMMA_LUT)) {+44
atomic_store(&hardware->blue,state->gamma_lut_size ? state->gamma_lut[3*state->gamma_lut_size-1] : 65535);+45
atomic_store(&hardware->size,state->gamma_lut_size);+46
if(!state->gamma_lut_size) {+47
if(wm_state()==4) atomic_fetch_add(&hardware->exit_resets,1);+48
else atomic_fetch_add(&hardware->start_resets,1);+49
}+50
}+51
return ok;+52
}+53
static void session(bool crash) {+54
unsigned resets=atomic_load(&hardware->start_resets);+55
assert(wm_options(128,5000) && wm_start());+56
uint64_t until=now_ms()+10000,id=0;+57
while(wm_state()!=2 || !id || atomic_load(&hardware->start_resets)==resets) {+58
struct wm_message event;+59
while(wm_next(&event)) if(event.word[0]==2 && !id) {+60
id=event.word[1];+61
int64_t settings[]={id,1,0,0,1000,0,-1,1280,720,60000};+62
assert(wm_submit(2,1,settings,10));+63
}+64
if(now_ms()>=until) fprintf(stderr,"GAMMA_START_DIAGNOSTIC state=%d id=%llu resets=%u size=%u\n",+65
wm_state(),(unsigned long long)id,atomic_load(&hardware->start_resets),atomic_load(&hardware->size));+66
assert((now_ms()<until) && "GAMMA_LIFECYCLE_STARTUP_RESET");+67
usleep(1000);+68
}+69
assert((atomic_load(&hardware->size)==0 && atomic_load(&hardware->blue)==65535) &&+70
"GAMMA_LIFECYCLE_INHERITED_RESET");+71
int64_t words[2+3*256]={id,256};+72
for(size_t c=0;c<3;c++) for(size_t i=0;i<256;i++) words[2+c*256+i]=i*20000/255;+73
assert(wm_submit(16,1,words,2+3*256));+74
while(atomic_load(&hardware->size)!=256) {+75
assert((now_ms()<until) && "GAMMA_LIFECYCLE_WARM_APPLIED");+76
usleep(1000);+77
}+78
if(crash) _exit(0); /* Skip all native teardown; hardware survives below. */+79
unsigned exits=atomic_load(&hardware->exit_resets);+80
struct wm_message stop={.word={1}};+81
assert(wm_send(&stop));+82
while(wm_state()!=3) {+83
assert((now_ms()<until) && "GAMMA_LIFECYCLE_STOPPED");+84
usleep(1000);+85
}+86
assert(wm_join());+87
assert((atomic_load(&hardware->size)==0 && atomic_load(&hardware->blue)==65535 &&+88
atomic_load(&hardware->exit_resets)==exits+1) && "GAMMA_LIFECYCLE_EXIT_RESET");+89
}+90
int main(void) {+91
char directory[]="/tmp/sigil-gamma-lifecycle.XXXXXX";+92
assert(mkdtemp(directory));+93
assert(setenv("XDG_RUNTIME_DIR",directory,1)==0);+94
assert(setenv("WLR_BACKENDS","headless",1)==0);+95
assert(setenv("WLR_RENDERER","pixman",1)==0);+96
assert(setenv("WLR_HEADLESS_OUTPUTS","1",1)==0);+97
unsetenv("WAYLAND_DISPLAY"); unsetenv("DISPLAY");+98
hardware=mmap(NULL,sizeof(*hardware),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);+99
assert(hardware!=MAP_FAILED);+100
atomic_init(&hardware->size,256); atomic_init(&hardware->blue,20000);+101
atomic_init(&hardware->start_resets,0); atomic_init(&hardware->exit_resets,0);+102
for(int arm=0;arm<3;arm++) {+103
pid_t child=fork(); assert(child>=0);+104
if(!child) { session(arm==1); _exit(0); }+105
int status; assert(waitpid(child,&status,0)==child);+106
assert((WIFEXITED(status) && WEXITSTATUS(status)==0) && "GAMMA_LIFECYCLE_CHILD");+107
if(arm==1) assert((atomic_load(&hardware->size)==256 && atomic_load(&hardware->blue)==20000) &&+108
"GAMMA_LIFECYCLE_CRASH_PRESERVES_HARDWARE");+109
}+110
puts("GAMMA_LIFECYCLE=PASS clean-exit crash-inherited-ramp fresh-start-neutral");+111
munmap(hardware,sizeof(*hardware));+112
for(int index=0;index<4;index++) {+113
char path[128];+114
snprintf(path,sizeof(path),"%s/wayland-%d",directory,index); unlink(path);+115
snprintf(path,sizeof(path),"%s/wayland-%d.lock",directory,index); unlink(path);+116
}+117
assert(rmdir(directory)==0);+118
return 0;+119
}test/output-gamma.cmodified
@@ -57,12 +57,12 @@ int main(void) {
57
native.data=&o; wl_list_insert(&s.outputs,&o.link); 58
applied_size=99; 59
output_commit_gamma(&o);−60
assert(commits==1 && applied_size==0 && !o.gamma_pending);+60
assert((commits==1 && applied_size==0 && !o.gamma_pending) && "GAMMA_CHECK_TAKEOVER_NEUTRAL"); 61
puts("GAMMA_NEUTRAL=PASS reset-zero-table"); 62
int64_t words[]={7,4,0,100,200,300,0,90,180,270,0,80,160,240}; 63
assert(output_set_gamma(&s,23,words,14)); 64
words[3]=999; /* Payload is copied before the caller can change it. */−65
assert(o.gamma_table[1]==100 && o.gamma_token==23);+65
assert((o.gamma_table[1]==100 && o.gamma_token==23) && "GAMMA_CHECK_COPIED_RAMP"); 66
native.frame_pending=false; 67
output_commit_gamma(&o); 68
assert(commits==2 && applied_size==4 && applied[1]==100 && applied[11]==240);@@ -74,8 +74,8 @@ int main(void) {
74
unsigned before=commits; 75
unsigned scheduled_before=scheduled; 76
output_commit_gamma(&o);−77
assert(commits==before+1 && plain==0 && o.gamma_pending && !native.frame_pending);−78
assert(scheduled==scheduled_before && o.gamma_retry_at>now_ms());+77
assert((commits==before+1 && plain==0 && o.gamma_pending && !native.frame_pending) && "GAMMA_CHECK_BUSY_SINGLE_COMMIT");+78
assert((scheduled==scheduled_before && o.gamma_retry_at>now_ms()) && "GAMMA_CHECK_BOUNDED_RETRY"); 79
assert(native_deadline(&s,now_ms())==o.gamma_retry_at); 80
output_gamma_present(&o.gamma_present,NULL); 81
assert(!o.gamma_retry_at && scheduled==scheduled_before+1);@@ -101,7 +101,7 @@ int main(void) {
101
output_gamma_observe(&s); 102
assert(o.gamma_external && !o.gamma_pending); 103
assert(output_set_gamma(&s,24,words,14));−104
assert(!o.gamma_pending); /* A stored base never displaces an owner. */+104
assert(!o.gamma_pending && "GAMMA_CHECK_EXTERNAL_OWNER"); 105
control.table=external; o.gamma_pending=true; native.frame_pending=false; 106
output_commit_gamma(&o); 107
assert(applied[1]==101 && applied[11]==243);@@ -121,6 +121,19 @@ int main(void) {
121
output_commit_gamma(&o); 122
assert(failed==1 && commits==before+1 && plain==0); 123
puts("GAMMA_REJECT=PASS scene-preserved single-commit");+124
native.frame_pending=false; reject_gamma=false;+125
before=commits;+126
output_gamma_finish(&s);+127
assert((commits==before+1 && applied_size==0) && "GAMMA_CHECK_EXIT_NEUTRAL");+128
native.frame_pending=false;+129
wl_list_insert(&manager.controls,&control.link);+130
output_gamma_finish(&s);+131
assert(commits==before+1 && "GAMMA_CHECK_EXIT_EXTERNAL_OWNER");+132
wl_list_remove(&control.link);+133
native.enabled=false;+134
output_gamma_finish(&s);+135
assert(commits==before+1 && "GAMMA_CHECK_EXIT_DISABLED_OUTPUT");+136
puts("GAMMA_EXIT=PASS neutral owned-externally-disabled-skipped"); 137
free(o.gamma_table); 138
close(bridge.event_fd); 139
puts("OUTPUT_GAMMA=PASS");