Commit10170740Recorded29 Oct 2026Repositorysigil-wm
Merge output gamma control and lifecycle restoration
Message
Resolve the Makefile insertion conflict by retaining the independent input queue gate and gamma test targets, plus the gamma header dependency. Both regression suites remain available on the combined native host.
Changed
Makefile | 11 +++++++++++
UPSTREAM.md | 6 ++++++
protocol/wlr-gamma-control-unstable-v1.xml | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/c/deadlines.h | 6 +++++-
src/c/event-stream.h | 4 ++--
src/c/host.c | 44 +++++++++++++++++++++++++++-----------------
src/c/output-controls.h | 10 ++++++++++
src/c/output-gamma.h | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/c/output-pending.h | 3 +--
src/sigil/wm/host.sgl | 11 ++++++++++-
src/sigil/wm/runtime.sgl | 4 +++-
test/gamma-client.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
test/output-gamma-lifecycle.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
test/output-gamma.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14 files changed, 662 insertions(+), 24 deletions(-)Diff
A merge. Shown against its first parent, so this is the effect of merging rather than the work of the branch.
Makefilemodified
@@ -11,6 +11,17 @@ all: build/libsigil-wm.so build/environment-exec
11
build/input-queue-gate.so: test/input-queue-gate.c build/libsigil-wm.so 12
$(CC) $(CFLAGS) $(CPPFLAGS) -fPIC -shared $< $(LDFLAGS) $(LDLIBS) -o $@ 13
+14
build/libsigil-wm.so: src/c/output-gamma.h+15
+16
build/output-gamma-test: test/output-gamma.c src/c/output-gamma.h src/c/host.c+17
$(CC) $(CFLAGS) $(CPPFLAGS) $< $(LDFLAGS) $(LDLIBS) -o $@+18
+19
build/output-gamma-lifecycle-test: test/output-gamma-lifecycle.c src/c/output-gamma.h src/c/host.c+20
$(CC) $(CFLAGS) $(CPPFLAGS) $< $(LDFLAGS) $(LDLIBS) -o $@+21
+22
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+23
$(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 $@+24
25
build/trace-records-test: test/trace-records.c src/c/trace-records.h 26
mkdir -p build 27
$(CC) -std=c11 -O2 -Wall -Wextra -Werror -D_POSIX_C_SOURCE=200809L $< -o $@UPSTREAM.mdmodified
@@ -23,3 +23,9 @@ from wlroots 0.19.3, retaining Ilia Bozhinov's original permission notice. Its
23
generated client bindings are used by the taskbar fixture; production uses the 24
wlroots public foreign-toplevel APIs. The ext list client bindings are generated 25
from the installed wayland-protocols staging XML.+26
+27
`protocol/wlr-gamma-control-unstable-v1.xml` is copied unchanged from+28
gammastep v2.0.11's `src/gamma-control.xml`, with Giulio Camuffo and Simon Ser's+29
permission notice retained. The source is+30
https://gitlab.com/chinstrap/gammastep/-/blob/v2.0.11/src/gamma-control.xml.+31
Its generated bindings are used only by the one-shot gamma protocol fixture.protocol/wlr-gamma-control-unstable-v1.xmladded
@@ -0,0 +1,126 @@
+1
<?xml version="1.0" encoding="UTF-8"?>+2
<protocol name="wlr_gamma_control_unstable_v1">+3
<copyright>+4
Copyright © 2015 Giulio camuffo+5
Copyright © 2018 Simon Ser+6
+7
Permission to use, copy, modify, distribute, and sell this+8
software and its documentation for any purpose is hereby granted+9
without fee, provided that the above copyright notice appear in+10
all copies and that both that copyright notice and this permission+11
notice appear in supporting documentation, and that the name of+12
the copyright holders not be used in advertising or publicity+13
pertaining to distribution of the software without specific,+14
written prior permission. The copyright holders make no+15
representations about the suitability of this software for any+16
purpose. It is provided "as is" without express or implied+17
warranty.+18
+19
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS+20
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND+21
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY+22
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+23
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN+24
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,+25
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF+26
THIS SOFTWARE.+27
</copyright>+28
+29
<description summary="manage gamma tables of outputs">+30
This protocol allows a privileged client to set the gamma tables for+31
outputs.+32
+33
Warning! The protocol described in this file is experimental and+34
backward incompatible changes may be made. Backward compatible changes+35
may be added together with the corresponding interface version bump.+36
Backward incompatible changes are done by bumping the version number in+37
the protocol and interface names and resetting the interface version.+38
Once the protocol is to be declared stable, the 'z' prefix and the+39
version number in the protocol and interface names are removed and the+40
interface version number is reset.+41
</description>+42
+43
<interface name="zwlr_gamma_control_manager_v1" version="1">+44
<description summary="manager to create per-output gamma controls">+45
This interface is a manager that allows creating per-output gamma+46
controls.+47
</description>+48
+49
<request name="get_gamma_control">+50
<description summary="get a gamma control for an output">+51
Create a gamma control that can be used to adjust gamma tables for the+52
provided output.+53
</description>+54
<arg name="id" type="new_id" interface="zwlr_gamma_control_v1"/>+55
<arg name="output" type="object" interface="wl_output"/>+56
</request>+57
+58
<request name="destroy" type="destructor">+59
<description summary="destroy the manager">+60
All objects created by the manager will still remain valid, until their+61
appropriate destroy request has been called.+62
</description>+63
</request>+64
</interface>+65
+66
<interface name="zwlr_gamma_control_v1" version="1">+67
<description summary="adjust gamma tables for an output">+68
This interface allows a client to adjust gamma tables for a particular+69
output.+70
+71
The client will receive the gamma size, and will then be able to set gamma+72
tables. At any time the compositor can send a failed event indicating that+73
this object is no longer valid.+74
+75
There must always be at most one gamma control object per output, which+76
has exclusive access to this particular output. When the gamma control+77
object is destroyed, the gamma table is restored to its original value.+78
</description>+79
+80
<event name="gamma_size">+81
<description summary="size of gamma ramps">+82
Advertise the size of each gamma ramp.+83
+84
This event is sent immediately when the gamma control object is created.+85
</description>+86
<arg name="size" type="uint"/>+87
</event>+88
+89
<enum name="error">+90
<entry name="invalid_gamma" value="1" summary="invalid gamma tables"/>+91
</enum>+92
+93
<request name="set_gamma">+94
<description summary="set the gamma table">+95
Set the gamma table. The file descriptor can be memory-mapped to provide+96
the raw gamma table, which contains successive gamma ramps for the red,+97
green and blue channels. Each gamma ramp is an array of 16-byte unsigned+98
integers which has the same length as the gamma size.+99
+100
The file descriptor data must have the same length as three times the+101
gamma size.+102
</description>+103
<arg name="fd" type="fd" summary="gamma table file descriptor"/>+104
</request>+105
+106
<event name="failed">+107
<description summary="object no longer valid">+108
This event indicates that the gamma control is no longer valid. This+109
can happen for a number of reasons, including:+110
- The output doesn't support gamma tables+111
- Setting the gamma tables failed+112
- Another client already has exclusive gamma control for this output+113
- The compositor has transfered gamma control to another client+114
+115
Upon receiving this event, the client should destroy this object.+116
</description>+117
</event>+118
+119
<request name="destroy" type="destructor">+120
<description summary="destroy this control">+121
Destroys the gamma control object. If the object is still valid, this+122
restores the original gamma tables.+123
</description>+124
</request>+125
</interface>+126
</protocol>src/c/deadlines.hmodified
@@ -9,6 +9,8 @@ static uint64_t native_deadline(struct server *s,uint64_t now) {
9
uint64_t next=UINT64_MAX; 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); 14
earlier_deadline(&next,s->scene_update_deadline); 15
if(s->lock_state==1) earlier_deadline(&next,s->lock_deadline); 16
if(s->has_pending_motion) earlier_deadline(&next,(uint64_t)s->motion_last+s->motion_period);@@ -61,5 +63,7 @@ static int dispatch_native_once(struct server *s,int timeout) {
63
if(s->stopping) return 0; 64
if(rearm_deadline(s,now_ms())<0) return -1; 65
wl_display_flush_clients(s->display);−64
return wl_event_loop_dispatch(loop,timeout);+66
int result=wl_event_loop_dispatch(loop,timeout);+67
output_gamma_observe(s);+68
return result; 69
}src/c/event-stream.hmodified
@@ -41,7 +41,7 @@ static int fact_kind(int kind) {
41
case 16: case 18: case 19: case 25: case 26: case 27: case 28: 42
case 37: case 40: case 41: case 43: case 45: case 46: case 61: 43
case 63: case 64: case 76: case 93: case 105: case 106:−44
case 107: case 116: case 123: case 128: return kind;+44
case 107: case 116: case 123: case 128: case 130: case 131: return kind; 45
case 11: case 12: return 11; 46
default: return 0; 47
}@@ -51,7 +51,7 @@ static bool fact_global(int kind) {
51
} 52
static bool fact_collection(int kind,int removed) { 53
switch(removed) {−54
case 3: return kind==2 || kind==5 || kind==6 || kind==8 || kind==46 || kind==93;+54
case 3: return kind==2 || kind==5 || kind==6 || kind==8 || kind==46 || kind==93 || kind==130 || kind==131; 55
case 13: return kind==10 || kind==11 || kind==14 || kind==15 || kind==16 || 56
kind==18 || kind==19 || kind==26 || kind==27 || kind==123 || kind==128; 57
case 42: return kind==28 || kind==40 || kind==41 || kind==43;src/c/host.cmodified
@@ -147,7 +147,7 @@ struct output {
147
char capture[PATH_MAX]; 148
unsigned sample_frames, sample_index, sample_count; 149
int sample_points[128];−150
struct wl_listener lock_present,wake_present;+150
struct wl_listener lock_present,wake_present,gamma_present; 151
uint64_t wake_requested,wake_commit_time; 152
uint32_t wake_commit; 153
bool wake_armed;@@ -157,7 +157,10 @@ struct output {
157
uint64_t lock_generation; 158
uint32_t lock_commit; 159
bool lock_presented, lock_armed;−160
bool configured_enabled,gamma_pending,nearest_filter;+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; 164
int x,y; 165
uint64_t configuration_generation; 166
};@@ -1531,6 +1534,7 @@ static void publish_output_management(struct server *s);
1534
static void queue_output_flush(struct server *s); 1535
#include "output-atomic.h" 1536
#include "output-controls.h"+1537
#include "output-gamma.h" 1538
static void output_buffer_filter(struct wlr_scene_buffer *buffer,int x,int y,void *data) { 1539
(void)x; (void)y; 1540
struct output *output=data;@@ -1551,20 +1555,8 @@ static void output_frame(struct wl_listener *listener,void *data) {
1555
} 1556
wlr_scene_output_for_each_buffer(o->scene,output_buffer_filter,o); 1557
lock_arm(o);−1554
if(o->gamma_pending) {−1555
o->gamma_pending=false;−1556
struct wlr_gamma_control_v1 *control=−1557
wlr_gamma_control_manager_v1_get_control(o->server->gamma_manager,o->native);−1558
struct wlr_output_state state;−1559
wlr_output_state_init(&state);−1560
bool committed=wlr_scene_output_build_state(o->scene,&state,NULL) &&−1561
wlr_gamma_control_v1_apply(control,&state) && wlr_output_commit_state(o->native,&state);−1562
wlr_output_state_finish(&state);−1563
if(!committed) {−1564
if(control) wlr_gamma_control_v1_send_failed_and_destroy(control);−1565
wlr_scene_output_commit(o->scene,NULL);−1566
}−1567
} else wlr_scene_output_commit(o->scene,NULL);+1558
if(o->gamma_pending) output_commit_gamma(o);+1559
else wlr_scene_output_commit(o->scene,NULL); 1560
struct timespec now; 1561
clock_gettime(CLOCK_MONOTONIC,&now); 1562
wlr_scene_output_send_frame_done(o->scene,&now);@@ -1578,6 +1570,12 @@ static void output_readback(struct wl_listener *listener,void *data) {
1570
struct wlr_output_event_commit *e=data; 1571
if(e->state->committed & (WLR_OUTPUT_STATE_ENABLED|WLR_OUTPUT_STATE_MODE| 1572
WLR_OUTPUT_STATE_SCALE|WLR_OUTPUT_STATE_TRANSFORM)) inventory_schedule(o->server);+1573
if((e->state->committed&WLR_OUTPUT_STATE_ENABLED) && o->native->enabled) {+1574
o->gamma_pending=true;+1575
wlr_output_schedule_frame(o->native);+1576
}+1577
if(e->state->committed&(WLR_OUTPUT_STATE_ENABLED|WLR_OUTPUT_STATE_MODE))+1578
EVENT(130,o->id,wlr_output_get_gamma_size(o->native),output_gamma_control(o)!=NULL); 1579
if (e->state->committed & WLR_OUTPUT_STATE_BUFFER) { 1580
if(trace_output_commit(&o->trace,o->native->commit_seq,o->id,e->when)) wake(bridge.event_fd); 1581
}@@ -1672,11 +1670,14 @@ static void output_destroy(struct wl_listener *listener,void *data) {
1670
wl_list_remove(&o->request.link); wl_list_remove(&o->readback.link); 1671
wl_list_remove(&o->lock_present.link); 1672
wl_list_remove(&o->wake_present.link);+1673
wl_list_remove(&o->gamma_present.link); 1674
if(o->lock_surface) o->lock_surface->output=NULL; 1675
if(o->lock_fill && !o->server->stopping) wlr_scene_node_destroy(&o->lock_fill->node); 1676
wl_list_remove(&o->link); 1677
inventory_schedule(o->server); 1678
publish_output_management(o->server);+1679
o->native->data=NULL;+1680
free(o->gamma_table); 1681
free(o); 1682
} 1683
static void output_request(struct wl_listener *listener,void *data) {@@ -1693,6 +1694,7 @@ static void new_output(struct wl_listener *listener,void *data) {
1694
if(!o) return; 1695
o->server=s; o->native=native; o->id=++s->next_id; 1696
o->wake_requested=now_ms();+1697
o->gamma_pending=true; 1698
native->data=o; 1699
o->scene=wlr_scene_output_create(s->scene,native); 1700
if(!o->scene) { free(o); return; }@@ -1703,6 +1705,7 @@ static void new_output(struct wl_listener *listener,void *data) {
1705
listen(&native->events.commit,&o->readback,output_readback); 1706
listen(&native->events.present,&o->lock_present,lock_present); 1707
listen(&native->events.present,&o->wake_present,output_wake_present);+1708
listen(&native->events.present,&o->gamma_present,output_gamma_present); 1709
if(s->lock_state) lock_configure(o); 1710
struct wlr_output_mode *mode; 1711
int index=0,preferred=-1;@@ -1712,6 +1715,7 @@ static void new_output(struct wl_listener *listener,void *data) {
1715
} 1716
EVENT(2,o->id,native->width,native->height,preferred,wlr_output_is_wl(native)); 1717
object_text(o->id,8,native->name);+1718
EVENT(130,o->id,wlr_output_get_gamma_size(native),false); 1719
s->output_revision++; 1720
inventory_schedule(s); 1721
publish_output_management(s);@@ -2121,6 +2125,10 @@ static bool apply_command(struct server *s,struct wm_message m) {
2125
case 6: ok=configure_keyboard(s,data,w[4]); break; 2126
case 7: ok=add_headless_output(s,data,w[4]); break; 2127
case 15: ok=record_output_samples(s,w[2],data,w[4]); break;+2128
case 16:+2129
ok=output_set_gamma(s,w[2],data,w[4]);+2130
if(!ok && w[4]>=1) EVENT(131,data[0],w[2],false);+2131
break; 2132
case 8: ok=restack_scene(s,data,w[4]); break; 2133
case 9: ok=configure_pointer(s,w[2],data,w[4]); break; 2134
case 10: output_batch(s,w[2],data,w[4]); free(data); return true;@@ -2205,6 +2213,7 @@ static int timer_tick(void *data) {
2213
permissions_expire(s); 2214
data_expire(s); 2215
output_controls_expire(s);+2216
output_gamma_expire(s); 2217
output_requests_expire(s); 2218
pointer_hints_expire(s); 2219
struct popup *p,*next;@@ -2370,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);@@ -2508,7 +2518,7 @@ int wm_input_reply(const struct wm_message *m,int kind) {
2518
return result; 2519
} 2520
int wm_submit_traced(int64_t kind,int64_t token,const int64_t *words,int64_t count,const uint64_t *causes,int trace_count) {−2511
if(!words || count<1 || count>65536 || kind<1 || kind>15) return 0;+2521
if(!words || count<1 || count>65536 || kind<1 || kind>16) return 0; 2522
if(trace_count<0 || trace_count>TRACE_CAUSES || (trace_count && !causes)) return 0; 2523
int64_t *copy=malloc((count+trace_count)*sizeof(int64_t)); 2524
if(!copy) return 0;src/c/output-controls.hmodified
@@ -102,6 +102,16 @@ static void output_gamma_request(struct wl_listener *listener,void *data) {
102
output_control_remove(previous); 103
} 104
}+105
if(!event->control) {+106
/* A destroyed control cannot authorize a ramp. Restore the latest+107
* base ramp without waiting for a policy reply from the dead client. */+108
if(output && !s->stopping) {+109
output->gamma_pending=true;+110
wlr_output_schedule_frame(output->native);+111
}+112
return;+113
}+114
if(output) output->gamma_pending=false; 115
struct output_control_request *request=output_control_new(s,output,1); 116
if(!request) { 117
if(event->control) wlr_gamma_control_v1_send_failed_and_destroy(event->control);src/c/output-gamma.hadded
@@ -0,0 +1,112 @@
+1
/* SPDX-License-Identifier: BSD-3-Clause+2
* Copied base ramps and external ownership share the frame commit. */+3
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;+22
wl_list_for_each(o,&s->outputs,link) {+23
struct wlr_gamma_control_v1 *control=output_gamma_control(o);+24
bool external=control!=NULL;+25
if(external==o->gamma_external) continue;+26
o->gamma_external=external;+27
o->gamma_retry_at=0;+28
/* Acquisition alone owns gamma, even before the first table arrives. */+29
if(!external) o->gamma_pending=true;+30
else if(!control->table) o->gamma_pending=false;+31
EVENT(130,o->id,wlr_output_get_gamma_size(o->native),external);+32
if(!external) wlr_output_schedule_frame(o->native);+33
}+34
}+35
static bool output_set_gamma(struct server *s,uint64_t token,const int64_t *words,size_t count) {+36
if(count<2 || words[1]<2 || words[1]>16384 || count!=2+3*(size_t)words[1]) return false;+37
struct output *o=find_output(s,words[0]);+38
if(!o || (size_t)words[1]!=wlr_output_get_gamma_size(o->native)) return false;+39
for(size_t i=2;i<count;i++) if(words[i]<0 || words[i]>UINT16_MAX) return false;+40
uint16_t *table=malloc((count-2)*sizeof(*table));+41
if(!table) return false;+42
for(size_t i=2;i<count;i++) table[i-2]=words[i];+43
free(o->gamma_table);+44
o->gamma_table=table; o->gamma_size=words[1]; o->gamma_token=token;+45
if(!output_gamma_control(o)) {+46
o->gamma_pending=true;+47
wlr_output_schedule_frame(o->native);+48
}+49
return true;+50
}+51
static bool output_base_gamma(struct output *o,struct wlr_output_state *state) {+52
if(!o->gamma_table) return wlr_output_state_set_gamma_lut(state,0,NULL,NULL,NULL);+53
return wlr_output_state_set_gamma_lut(state,o->gamma_size,+54
o->gamma_table,o->gamma_table+o->gamma_size,o->gamma_table+2*o->gamma_size);+55
}+56
static void output_gamma_present(struct wl_listener *listener,void *data) {+57
(void)data;+58
struct output *o=wl_container_of(listener,o,gamma_present);+59
if(!o->gamma_retry_at) return;+60
o->gamma_retry_at=0;+61
wlr_output_schedule_frame(o->native);+62
}+63
static void output_gamma_expire(struct server *s) {+64
struct output *o;+65
wl_list_for_each(o,&s->outputs,link) {+66
if(!o->gamma_retry_at || now_ms()<o->gamma_retry_at) continue;+67
o->gamma_retry_at=0;+68
if(o->native->enabled) wlr_output_schedule_frame(o->native);+69
}+70
}+71
static void 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;+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;+81
}+82
struct wlr_output_state state;+83
wlr_output_state_init(&state);+84
bool gamma=control ? wlr_gamma_control_v1_apply(control,&state) : output_base_gamma(o,&state);+85
bool built=gamma && wlr_scene_output_build_state(o->scene,&state,NULL);+86
bool supported=built && wlr_output_test_state(o->native,&state);+87
if(built && !supported) {+88
/* Invalid LUTs cannot prevent the same scene's normal presentation.+89
* Keep the rendered buffer and remove just the rejected LUT field. */+90
state.committed &= ~WLR_OUTPUT_STATE_GAMMA_LUT;+91
if(!wlr_output_test_state(o->native,&state)) {+92
/* The scene itself is temporarily invalid. Do not blame the LUT+93
* or revoke a client's ownership for an unrelated backend fault. */+94
wlr_output_state_finish(&state);+95
o->gamma_retry_at=now_ms()+16;+96
return;+97
}+98
o->gamma_pending=false;+99
if(control) wlr_gamma_control_v1_send_failed_and_destroy(control);+100
else EVENT(131,o->id,o->gamma_token,false);+101
}+102
bool committed=built && wlr_output_commit_state(o->native,&state);+103
wlr_output_state_finish(&state);+104
if(committed && supported) {+105
o->gamma_pending=false;+106
if(!control) EVENT(131,o->id,o->gamma_token,true);+107
} else if(!committed) {+108
/* EBUSY and other runtime failures retain the LUT and scene damage.+109
* Retry on the next backend frame rather than racing the page flip. */+110
o->gamma_retry_at=now_ms()+16;+111
}+112
}src/c/output-pending.hmodified
@@ -79,8 +79,7 @@ static void output_flush(void *data) {
79
output->pending_power=false; 80
if(success) { 81
if(output->native->enabled) {−82
output->gamma_pending=s->gamma_manager &&−83
wlr_gamma_control_manager_v1_get_control(s->gamma_manager,output->native);+82
output->gamma_pending=true; 83
wlr_output_schedule_frame(output->native); 84
} 85
EVENT(93,output->id,output->native->enabled);src/sigil/wm/host.sglmodified
@@ -10,7 +10,7 @@
10
set-pointer-route! focus-window! close-window! capture-output! record-output-samples! stop-output-samples! focus-lock! set-lock-background! reply-request! 11
set-decoration-mode! set-cursor! set-idle-state! configure-popup! begin-scene-update! 12
configure-layer! set-default-layer-output! configure-x11! observe-pointer! add-headless-output! remove-headless-output!−13
reply-data-request! delegate-client-cursor! reply-output-control! set-output-power! set-output-filter! set-active-pointer-constraint! reply-pointer-hint!+13
reply-data-request! delegate-client-cursor! reply-output-control! set-output-power! set-output-filter! set-output-gamma! set-active-pointer-constraint! reply-pointer-hint! 14
query-pointer! query-window! delegate-relative-pointer! acknowledge-configure! configure-session-switches!) 15
(begin 16
(define (latency-mark! stage (opts: (token 0)))@@ -99,6 +99,15 @@
99
100
(define (reply-output-control! host token accept?) 101
(host-send! host (list 32 token (if accept? 1 0))))+102
(define (set-output-gamma! host output ramps)+103
(unless (and (list? ramps) (= (length ramps) 3)+104
(every list? ramps) (<= 2 (length (car ramps)) 16384)+105
(every (lambda (channel) (= (length channel) (length (car ramps)))) ramps)+106
(every (lambda (channel)+107
(every (lambda (n) (and (exact-integer? n) (<= 0 n 65535))) channel)) ramps))+108
(error "gamma requires three equally sized lists of 16-bit samples"))+109
(let ((token (host-next-token! host)))+110
(and (host-submit! host 16 token (append (list output (length (car ramps))) (apply append ramps))) token))) 111
(define (set-output-filter! host output filter) 112
(host-send! host 113
(list 34 output (case filter ((linear) 0) ((nearest) 1)src/sigil/wm/runtime.sglmodified
@@ -135,6 +135,8 @@
135
((91) '(output-gamma-request (output: 2) (ramp-size: 3) (reset?: 4) (generation: 5))) 136
((92) '(output-control-result (control-kind: 2) (output: 3) (success?: 4))) 137
((93) '(output-power-changed (on?: 2)))+138
((130) '(output-gamma-state (ramp-size: 2) (external?: 3)))+139
((131) '(output-gamma-applied (operation: 2) (success?: 3))) 140
((98) '(command-rejected (command: 1) (object: 2))) 141
((99) '(input-fault (reason: 2))) 142
((124) '(snapshot-resync-begin (overflow-count: 2)))@@ -183,7 +185,7 @@
185
;; Reconstruct the exact integer from bounded components. 186
(trace-monotonic-ns raw)) 187
((eq? field scale:) (/ value 1000.0))−186
((memq field '(presented?: requested-suspended?: focused?: activated?: suspended?: fullscreen?: xdg-state?: session-available?: nested?: preferred?: timed-out?: success?: enabled?: powered?: adaptive-sync?: mapped?: accepted?: initialized?: override-redirect?: modal?: dialog?: moved?: source?: valid-serial?: icon?: submitted?: virtual?: libinput?: on?: reset?: apply?: current?: persistent?: seat?: pointer-focused?: inside?: visible?: active?:))+188
((memq field '(external?: presented?: requested-suspended?: focused?: activated?: suspended?: fullscreen?: xdg-state?: session-available?: nested?: preferred?: timed-out?: success?: enabled?: powered?: adaptive-sync?: mapped?: accepted?: initialized?: override-redirect?: modal?: dialog?: moved?: source?: valid-serial?: icon?: submitted?: virtual?: libinput?: on?: reset?: apply?: current?: persistent?: seat?: pointer-focused?: inside?: visible?: active?:)) 189
(not (= value 0))) 190
(else value))))))))) 191
test/gamma-client.cadded
@@ -0,0 +1,88 @@
+1
/* SPDX-License-Identifier: BSD-3-Clause+2
* Complete a neutral gamma request and release it, with sync barriers. */+3
#include <errno.h>+4
#include <stdbool.h>+5
#include <stdint.h>+6
#include <stdio.h>+7
#include <stdlib.h>+8
#include <string.h>+9
#include <unistd.h>+10
#include <wayland-client.h>+11
#include "wlr-gamma-control-unstable-v1-client-protocol.h"+12
struct gamma_output { struct wl_output *output; struct zwlr_gamma_control_v1 *control; uint32_t size; };+13
static struct gamma_output outputs[64];+14
static size_t count;+15
static struct zwlr_gamma_control_manager_v1 *manager;+16
static void geometry(void *data,struct wl_output *o,int32_t x,int32_t y,int32_t pw,int32_t ph,+17
int32_t sub,const char *make,const char *model,int32_t transform) {+18
(void)data; (void)o; (void)x; (void)y; (void)pw; (void)ph; (void)sub;+19
(void)make; (void)model; (void)transform;+20
}+21
static void mode(void *data,struct wl_output *o,uint32_t flags,int32_t w,int32_t h,int32_t refresh) {+22
(void)data; (void)o; (void)flags; (void)w; (void)h; (void)refresh;+23
}+24
static const struct wl_output_listener output_listener={.geometry=geometry,.mode=mode};+25
static void global(void *data,struct wl_registry *registry,uint32_t name,const char *interface,uint32_t version) {+26
(void)data; (void)version;+27
if(!strcmp(interface,"zwlr_gamma_control_manager_v1"))+28
manager=wl_registry_bind(registry,name,&zwlr_gamma_control_manager_v1_interface,1);+29
else if(!strcmp(interface,"wl_output") && count<64) {+30
outputs[count].output=wl_registry_bind(registry,name,&wl_output_interface,1);+31
wl_output_add_listener(outputs[count].output,&output_listener,NULL);+32
count++;+33
}+34
}+35
static void removed(void *data,struct wl_registry *registry,uint32_t name) {+36
(void)data; (void)registry; (void)name;+37
}+38
static const struct wl_registry_listener registry_listener={.global=global,.global_remove=removed};+39
static void gamma_size(void *data,struct zwlr_gamma_control_v1 *control,uint32_t size) {+40
(void)control; ((struct gamma_output *)data)->size=size;+41
}+42
static void gamma_failed(void *data,struct zwlr_gamma_control_v1 *control) {+43
(void)control; ((struct gamma_output *)data)->size=0;+44
}+45
static const struct zwlr_gamma_control_v1_listener gamma_listener={.gamma_size=gamma_size,.failed=gamma_failed};+46
static void require(bool value,const char *message) {+47
if(!value) { fprintf(stderr,"gamma-client: %s\n",message); exit(1); }+48
}+49
int main(void) {+50
struct wl_display *display=wl_display_connect(NULL);+51
require(display!=NULL,"connect");+52
struct wl_registry *registry=wl_display_get_registry(display);+53
wl_registry_add_listener(registry,®istry_listener,NULL);+54
require(wl_display_roundtrip(display)>=0 && manager && count,"registry roundtrip");+55
for(size_t i=0;i<count;i++) {+56
outputs[i].control=zwlr_gamma_control_manager_v1_get_gamma_control(manager,outputs[i].output);+57
zwlr_gamma_control_v1_add_listener(outputs[i].control,&gamma_listener,&outputs[i]);+58
}+59
require(wl_display_roundtrip(display)>=0,"gamma-size roundtrip");+60
size_t supported=0;+61
for(size_t i=0;i<count;i++) {+62
size_t size=outputs[i].size;+63
if(!size) continue;+64
require(size>=2 && size<=16384,"bounded table");+65
supported++;+66
uint16_t *ramp=calloc(3*size,sizeof(*ramp));+67
require(ramp!=NULL,"allocate ramp");+68
for(size_t c=0;c<3;c++) for(size_t j=0;j<size;j++) ramp[c*size+j]=(uint64_t)j*65535/(size-1);+69
char path[]="/tmp/sigil-gamma-client.XXXXXX";+70
int fd=mkstemp(path); require(fd>=0,"create table"); unlink(path);+71
size_t bytes=3*size*sizeof(*ramp),written=0;+72
while(written<bytes) {+73
ssize_t result=write(fd,(char *)ramp+written,bytes-written);+74
if(result<0 && errno==EINTR) continue;+75
require(result>0,"write table"); written+=result;+76
}+77
zwlr_gamma_control_v1_set_gamma(outputs[i].control,fd);+78
close(fd); free(ramp);+79
}+80
require(wl_display_roundtrip(display)>=0,"set-gamma sync roundtrip");+81
for(size_t i=0;i<count;i++) zwlr_gamma_control_v1_destroy(outputs[i].control);+82
require(wl_display_roundtrip(display)>=0,"release sync roundtrip");+83
for(size_t i=0;i<count;i++) wl_output_destroy(outputs[i].output);+84
zwlr_gamma_control_manager_v1_destroy(manager); wl_registry_destroy(registry);+85
wl_display_disconnect(display);+86
printf("GAMMA_ROUNDTRIP=PASS outputs=%zu supported=%zu\n",count,supported);+87
return 0;+88
}test/output-gamma-lifecycle.cadded
@@ -0,0 +1,120 @@
+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++)+73
words[2+c*256+i]=i*(c==0 ? 50000 : c==1 ? 35000 : 20000)/255;+74
assert(wm_submit(16,1,words,2+3*256));+75
while(atomic_load(&hardware->size)!=256) {+76
assert((now_ms()<until) && "GAMMA_LIFECYCLE_WARM_APPLIED");+77
usleep(1000);+78
}+79
if(crash) _exit(0); /* Skip all native teardown; hardware survives below. */+80
unsigned exits=atomic_load(&hardware->exit_resets);+81
struct wm_message stop={.word={1}};+82
assert(wm_send(&stop));+83
while(wm_state()!=3) {+84
assert((now_ms()<until) && "GAMMA_LIFECYCLE_STOPPED");+85
usleep(1000);+86
}+87
assert(wm_join());+88
assert((atomic_load(&hardware->size)==0 && atomic_load(&hardware->blue)==65535 &&+89
atomic_load(&hardware->exit_resets)==exits+1) && "GAMMA_LIFECYCLE_EXIT_RESET");+90
}+91
int main(void) {+92
char directory[]="/tmp/sigil-gamma-lifecycle.XXXXXX";+93
assert(mkdtemp(directory));+94
assert(setenv("XDG_RUNTIME_DIR",directory,1)==0);+95
assert(setenv("WLR_BACKENDS","headless",1)==0);+96
assert(setenv("WLR_RENDERER","pixman",1)==0);+97
assert(setenv("WLR_HEADLESS_OUTPUTS","1",1)==0);+98
unsetenv("WAYLAND_DISPLAY"); unsetenv("DISPLAY");+99
hardware=mmap(NULL,sizeof(*hardware),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);+100
assert(hardware!=MAP_FAILED);+101
atomic_init(&hardware->size,256); atomic_init(&hardware->blue,20000);+102
atomic_init(&hardware->start_resets,0); atomic_init(&hardware->exit_resets,0);+103
for(int arm=0;arm<3;arm++) {+104
pid_t child=fork(); assert(child>=0);+105
if(!child) { session(arm==1); _exit(0); }+106
int status; assert(waitpid(child,&status,0)==child);+107
assert((WIFEXITED(status) && WEXITSTATUS(status)==0) && "GAMMA_LIFECYCLE_CHILD");+108
if(arm==1) assert((atomic_load(&hardware->size)==256 && atomic_load(&hardware->blue)==20000) &&+109
"GAMMA_LIFECYCLE_CRASH_PRESERVES_HARDWARE");+110
}+111
puts("GAMMA_LIFECYCLE=PASS clean-exit crash-inherited-ramp fresh-start-neutral");+112
munmap(hardware,sizeof(*hardware));+113
for(int index=0;index<4;index++) {+114
char path[128];+115
snprintf(path,sizeof(path),"%s/wayland-%d",directory,index); unlink(path);+116
snprintf(path,sizeof(path),"%s/wayland-%d.lock",directory,index); unlink(path);+117
}+118
assert(rmdir(directory)==0);+119
return 0;+120
}test/output-gamma.cadded
@@ -0,0 +1,141 @@
+1
/* SPDX-License-Identifier: BSD-3-Clause+2
* Real state allocation/ownership with deterministic backend commit faults. */+3
#define listen socket_listen+4
#include <sys/socket.h>+5
#undef listen+6
#define wlr_scene_output_build_state fixture_build_state+7
#define wlr_scene_output_commit fixture_scene_commit+8
#define wlr_output_test_state fixture_test_state+9
#define wlr_output_commit_state fixture_commit_state+10
#define wlr_output_schedule_frame fixture_schedule_frame+11
#define wlr_output_get_gamma_size fixture_gamma_size+12
#define wlr_gamma_control_v1_send_failed_and_destroy fixture_gamma_failed+13
#include "../src/c/host.c"+14
#include <assert.h>+15
static unsigned commits,scheduled,plain,failed;+16
static bool reject_gamma,reject_scene,fail_commit;+17
static uint16_t applied[12];+18
static size_t applied_size;+19
bool fixture_build_state(struct wlr_scene_output *scene,struct wlr_output_state *state,+20
const struct wlr_scene_output_state_options *options) {+21
(void)scene; (void)state; (void)options; return true;+22
}+23
bool fixture_scene_commit(struct wlr_scene_output *scene,const struct wlr_scene_output_state_options *options) {+24
(void)scene; (void)options; plain++; return true;+25
}+26
bool fixture_test_state(struct wlr_output *output,const struct wlr_output_state *state) {+27
assert(!output->frame_pending);+28
return !reject_scene && (!reject_gamma || !(state->committed&WLR_OUTPUT_STATE_GAMMA_LUT));+29
}+30
bool fixture_commit_state(struct wlr_output *output,const struct wlr_output_state *state) {+31
assert(!output->frame_pending);+32
commits++;+33
if(fail_commit) { errno=EBUSY; return false; }+34
if(state->committed&WLR_OUTPUT_STATE_GAMMA_LUT) {+35
applied_size=state->gamma_lut_size;+36
if(applied_size) memcpy(applied,state->gamma_lut,applied_size*3*sizeof(uint16_t));+37
}+38
output->frame_pending=true;+39
return true;+40
}+41
void fixture_schedule_frame(struct wlr_output *output) { (void)output; scheduled++; }+42
size_t fixture_gamma_size(struct wlr_output *output) { (void)output; return 4; }+43
void fixture_gamma_failed(struct wlr_gamma_control_v1 *control) {+44
failed++; wl_list_remove(&control->link);+45
}+46
int main(void) {+47
bridge.event_fd=eventfd(0,EFD_CLOEXEC|EFD_NONBLOCK);+48
assert(bridge.event_fd>=0);+49
struct server s={0};+50
wl_list_init(&s.outputs); wl_list_init(&s.output_controls);+51
wl_list_init(&s.permissions); wl_list_init(&s.data_requests);+52
wl_list_init(&s.output_requests); wl_list_init(&s.pointer_hints); wl_list_init(&s.popups);+53
struct wlr_gamma_control_manager_v1 manager={0};+54
wl_list_init(&manager.controls); s.gamma_manager=&manager;+55
struct wlr_output native={.enabled=true};+56
struct output o={.server=&s,.native=&native,.id=7,.gamma_pending=true};+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) && "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) && "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);+69
assert(!o.gamma_pending);+70
o.gamma_pending=true;+71
output_commit_gamma(&o); /* An outstanding flip cannot be raced. */+72
assert(commits==2 && o.gamma_pending);+73
native.frame_pending=false; fail_commit=true;+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) && "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);+82
fail_commit=false;+83
output_commit_gamma(&o);+84
assert(commits==before+2 && !o.gamma_pending && !o.gamma_retry_at);+85
o.gamma_retry_at=now_ms();+86
output_gamma_expire(&s);+87
assert(!o.gamma_retry_at && scheduled==scheduled_before+2);+88
puts("GAMMA_FRAMES=PASS one-commit busy-retry pending-flip");+89
native.frame_pending=false; o.gamma_pending=true; reject_scene=true;+90
before=commits;+91
output_commit_gamma(&o);+92
assert(commits==before && o.gamma_pending && o.gamma_retry_at>now_ms());+93
reject_scene=false;+94
output_commit_gamma(&o);+95
assert(commits==before+1 && !o.gamma_pending);+96
puts("GAMMA_SCENE_RETRY=PASS backend-test-failure-retains-ramp");+97
uint16_t external[12]={0,101,202,303,0,91,182,273,0,81,162,243};+98
struct wlr_gamma_control_v1 control={.output=&native,.table=NULL,.ramp_size=4};+99
wl_list_insert(&manager.controls,&control.link);+100
o.gamma_pending=true;+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 && "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);+108
wl_list_remove(&control.link);+109
struct wlr_gamma_control_manager_v1_set_gamma_event release={.output=&native};+110
output_gamma_request(&s.gamma_request,&release);+111
assert(o.gamma_pending && s.output_control_count==0);+112
output_gamma_observe(&s);+113
assert(!o.gamma_external && o.gamma_pending);+114
native.frame_pending=false;+115
output_commit_gamma(&o);+116
assert(applied[1]==999 && o.gamma_token==24);+117
puts("GAMMA_OWNERSHIP=PASS acquire-before-table external release-latest-base");+118
wl_list_insert(&manager.controls,&control.link);+119
o.gamma_pending=true; native.frame_pending=false; reject_gamma=true;+120
before=commits;+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");+140
return 0;+141
}