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");