From 1495db3e163163ce8c44afe1b836354e71b568b0 Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Mon, 9 Oct 2023 22:12:46 +0100 Subject: [PATCH 01/10] ext-action-binder-v1: new protocol implementation Signed-off-by: Anna (navi) Figueiredo Gomes --- include/wlr/types/wlr_action_binder_v1.h | 82 +++++ protocol/ext-action-binder-v1.xml | 266 +++++++++++++++++ protocol/meson.build | 1 + types/meson.build | 1 + types/wlr_action_binder_v1.c | 361 +++++++++++++++++++++++ 5 files changed, 711 insertions(+) create mode 100644 include/wlr/types/wlr_action_binder_v1.h create mode 100644 protocol/ext-action-binder-v1.xml create mode 100644 types/wlr_action_binder_v1.c diff --git a/include/wlr/types/wlr_action_binder_v1.h b/include/wlr/types/wlr_action_binder_v1.h new file mode 100644 index 000000000..f43f19c33 --- /dev/null +++ b/include/wlr/types/wlr_action_binder_v1.h @@ -0,0 +1,82 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_ACTION_BINDER_V1_H +#define WLR_TYPES_WLR_ACTION_BINDER_V1_H + +#include +#include +#include "ext-action-binder-v1-protocol.h" + +struct wlr_action_binder_v1 { + struct wl_global *global; + struct wl_list states; // wlr_action_binder_v1_state.link + struct wl_listener display_destroy; + + struct { + struct wl_signal bind; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_action_binder_v1_state { + struct wl_list binds; // wlr_action_binding_v1.link + struct wl_list bind_queue; // wlr_action_binding_v1.link + struct wlr_action_binder_v1 *binder; + struct wl_resource *resource; + + struct wl_list link; +}; + +struct wlr_action_binding_hint_v1 { + enum { + WLR_ACTION_BINDING_HINT_V1_NONE, + WLR_ACTION_BINDING_HINT_V1_KEYBOARD, + WLR_ACTION_BINDING_HINT_V1_MOUSE, + WLR_ACTION_BINDING_HINT_V1_GESTURE, + } type; + + union { + char *keycombo; // WLR_ACTION_BINDING_HINT_V1_KEYBOARD + uint32_t mouse_button; // WLR_ACTION_BINDING_HINT_V1_MOUSE + struct { + enum ext_action_binding_v1_gesture_type type; + enum ext_action_binding_v1_gesture_direction direction; + uint32_t fingers; + } gesture; // WLR_ACTION_BINDING_HINT_V1_GESTURE + }; +}; + +struct wlr_action_binding_v1 { + struct wl_resource *resource; + struct wlr_action_binder_v1_state *state; + + char *category, *name; + char *description; // may be NULL when the client doesn't set a description + + struct wlr_action_binding_hint_v1 hint; + char *app_id; // may be NULL when the client doesn't set an app_id + struct wlr_seat *seat; // may be NULL when the client doesn't set a seat + struct wl_listener seat_destroy; + + struct { + struct wl_signal destroy; + } events; + + bool bound; + struct wl_list link; +}; + +struct wlr_action_binder_v1 *wlr_action_binder_v1_create(struct wl_display *display); +void wlr_action_binding_v1_bind(struct wlr_action_binding_v1 *bind, const char *trigger); +void wlr_action_binding_v1_reject(struct wlr_action_binding_v1 *bind); +void wlr_action_binding_v1_trigger(struct wlr_action_binding_v1 *binding, uint32_t trigger_type, uint32_t time_msec); + +#endif diff --git a/protocol/ext-action-binder-v1.xml b/protocol/ext-action-binder-v1.xml new file mode 100644 index 000000000..b572b92ed --- /dev/null +++ b/protocol/ext-action-binder-v1.xml @@ -0,0 +1,266 @@ + + + + Copyright © 2023-2024 Anna "navi" Figueiredo Gomes + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol allows clients to register "actions" as a set of triggers + and metadata, and get notified when those actions are triggered by the user. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + The key words "must", "must not", "required", "shall", "shall not", "should", + "should not", "recommended", "may", and "optional" in this document are to + be interpreted as described in IETF RFC 2119. + + + + + This interface is designed to allow any application to bind an action. + + It is left to the compositor to determine which client will get events. + The choice can be based on policy, heuristic, user configuration, or any + other mechanism that may be relevant. + Here are some examples of dispatching choice: all applications, last + focused, user-defined preference order, latest fullscreened application. + + This interface is exposed as global. + + + + + The client no longer wants to receive events for any action. + + + + + + Creates a binding. + + After setting the metadata on all bindings created, the client must + call ext_action_binder_v1.commit for the binding to take effect. + + + + + + + Commits all bindings created from this interface. + This request may be called again if new bindings are created, + already bound bindings are unaffected. + + After calling bind, either the "bound" or "rejected" event shall + be sent for each binding created. + + If no action has been set for any binding, the error "invalid_binding" is raised. + + + + + + + + + + + This interface defines an individual binding, allowing the client to register metadata about + the action and receive triggered events. + + + + + The client no longer wants to receive events for this binding. + + + + + + Sets an arbitrary couple of a category and a name describing the + wanted behaviour. Some categories are well-known and shared by + applications while each application can have its own category + for internal actions. + + This name may be used by the compositor to render a ui for bindings. + + A name must be set before attempting to commit a binding. + + Attempting to send this request twice or after the binding was bound raises an already set error. + + + + + + + + This setting is optional. + + This description may be used by the compositor to render a ui for bindings. + + Attempting to send this request twice or after the binding was bound raises an already_set error. + + + + + + + This setting is optional. + + The app ID identifies the general class of applications to which + the binding belongs. The compositor may use this to select which + client will receive an event. + + Attempting to send this request twice or after the binding was bound raises an already_set error. + + + + + + + This setting is optional. + + If set, the actions shall only be triggered by the specific seat. + + Attempting to send this request twice or after the binding was bound raises an already_set error. + + + + + + + This setting is optional. + + This hint is a suggestion to the compositor, and the action must not rely + on being set to this specific trigger. + + The keycombo must follow the XDG Shortcut standard. + + Only a maximum of one hint shall be set per action. + + + + + + + This setting is optional. + + This hint is a suggestion to the compositor, and the action must not rely + on being set to this specific trigger. + + Button is a value mapped to their x11 values (1=left, 2=middle, 3=right, + 4=scroll up, 5=scroll down, 6=scroll left, 7=scroll right, 8=back, 9=forward). + + Only a maximum of one hint shall be set per action. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This setting is optional. + + This hint is a suggestion to the compositor, and the action must not rely + on being set to this specific trigger. + + Only a maximum of one trigger hint may be set per action. + + + + + + + + + Sent after the event was processed, and was bound to one or more of the actions set. + + + + + + + Sent after the event was processed and got rejected, + or at any time should the compositor want to remove the binding. + The compositor must not send further events after this event. + The client should destroy the object at this point. + + + + + + Depending on the user configuration, an action can be either one_shot or + sustained. The client must handle all the three event types and either make + sense of them or ignore them properly. + + one_shot actions are for events that don't have a defined "end", like a laptop + lid closing, or a gesture. The client should not expect to recieve a released or + ending event for that action. + + sustained actions have a start and an end. after a 'pressed' event is sent, a + 'released' event should eventually be sent as well. + + + + + + + + + This event is sent when actions are triggered. + + + + + + + + + diff --git a/protocol/meson.build b/protocol/meson.build index a4476918b..4e4e4caae 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -54,6 +54,7 @@ protocols = { 'drm': 'drm.xml', 'input-method-unstable-v2': 'input-method-unstable-v2.xml', 'kde-server-decoration': 'server-decoration.xml', + 'ext-action-binder-v1': 'ext-action-binder-v1.xml', 'virtual-keyboard-unstable-v1': 'virtual-keyboard-unstable-v1.xml', 'wlr-data-control-unstable-v1': 'wlr-data-control-unstable-v1.xml', 'wlr-export-dmabuf-unstable-v1': 'wlr-export-dmabuf-unstable-v1.xml', diff --git a/types/meson.build b/types/meson.build index ec70d4b7c..08c6faaf9 100644 --- a/types/meson.build +++ b/types/meson.build @@ -33,6 +33,7 @@ wlr_files += files( 'buffer/dmabuf.c', 'buffer/readonly_data.c', 'buffer/resource.c', + 'wlr_action_binder_v1.c', 'wlr_alpha_modifier_v1.c', 'wlr_compositor.c', 'wlr_content_type_v1.c', diff --git a/types/wlr_action_binder_v1.c b/types/wlr_action_binder_v1.c new file mode 100644 index 000000000..0f00c1b74 --- /dev/null +++ b/types/wlr_action_binder_v1.c @@ -0,0 +1,361 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ext-action-binder-v1-protocol.h" + +static void resource_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct ext_action_binder_v1_interface ext_action_binder_v1_implementation; +static struct wlr_action_binder_v1_state *wlr_action_binder_v1_state_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &ext_action_binder_v1_interface, &ext_action_binder_v1_implementation)); + return wl_resource_get_user_data(resource); +} + +static const struct ext_action_binding_v1_interface ext_action_binding_v1_implementation; +static struct wlr_action_binding_v1 *wlr_action_binding_v1_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &ext_action_binding_v1_interface, &ext_action_binding_v1_implementation)); + return wl_resource_get_user_data(resource); +} + +static void destroy_binding(struct wlr_action_binding_v1 *binding) { + if (!binding) { + return; + } + + free(binding->category); + free(binding->name); + + if (binding->hint.type == WLR_ACTION_BINDING_HINT_V1_KEYBOARD) { + free(binding->hint.keycombo); + } + + free(binding->description); + + free(binding->app_id); + + wl_list_remove(&binding->link); + wl_list_remove(&binding->seat_destroy.link); + + wl_resource_set_user_data(binding->resource, NULL); // make resource inert + + free(binding); +} + +static void handle_seat_destroy(struct wl_listener *listener, void *data) { + struct wlr_action_binding_v1 *binding = wl_container_of(listener, binding, seat_destroy); + wl_list_remove(&binding->seat_destroy.link); + binding->seat = NULL; + wlr_action_binding_v1_reject(binding); +} + +static void action_binding_destroy(struct wl_resource *resource) { + struct wlr_action_binding_v1 *binding = wlr_action_binding_v1_from_resource(resource); + destroy_binding(binding); +} + +static void action_binding_set_name(struct wl_client *client, + struct wl_resource *resource, const char *category, const char *name) { + struct wlr_action_binding_v1 *binding = wlr_action_binding_v1_from_resource(resource); + if (!binding) { + return; + } + + if (binding->bound || binding->name || binding->category) { + wl_resource_post_error(binding->resource, + EXT_ACTION_BINDING_V1_ERROR_ALREADY_SET, + "attempted to set a binding property twice"); + return; + } + + binding->name = strdup(name); + if (binding->name == NULL) { + wl_client_post_no_memory(client); + } + + binding->category = strdup(category); + if (binding->category == NULL) { + wl_client_post_no_memory(client); + } +} + +static struct wlr_action_binding_hint_v1 *action_binding_hint_from_resource(struct wl_resource *resource) { + struct wlr_action_binding_v1 *binding = wlr_action_binding_v1_from_resource(resource); + if (!binding) { + return NULL; + } + if (binding->bound || binding->hint.type != WLR_ACTION_BINDING_HINT_V1_NONE) { + wl_resource_post_error(binding->resource, + EXT_ACTION_BINDING_V1_ERROR_ALREADY_SET, "attempted to set a hint twice"); + return NULL; + } + + return &binding->hint; +} + +static void action_binding_set_mouse_hint(struct wl_client *client, + struct wl_resource *resource, uint32_t button) { + struct wlr_action_binding_hint_v1 *hint = action_binding_hint_from_resource(resource); + if (!hint) { + return; + } + + hint->type = WLR_ACTION_BINDING_HINT_V1_MOUSE; + hint->mouse_button = button; +} + +static void action_binding_set_keyboard_hint(struct wl_client *client, + struct wl_resource *resource, const char *keycombo) { + struct wlr_action_binding_hint_v1 *hint = action_binding_hint_from_resource(resource); + if (!hint) { + return; + } + + hint->type = WLR_ACTION_BINDING_HINT_V1_KEYBOARD; + hint->keycombo = strdup(keycombo); +} + +static void action_binding_set_gesture_hint(struct wl_client *client, + struct wl_resource *resource, uint32_t type, uint32_t direction, uint32_t fingers) { + struct wlr_action_binding_hint_v1 *hint = action_binding_hint_from_resource(resource); + if (!hint) { + return; + } + + hint->type = WLR_ACTION_BINDING_HINT_V1_GESTURE; + hint->gesture.type = type; + hint->gesture.direction = direction; + hint->gesture.fingers = fingers; +} + +static void action_binding_set_desc(struct wl_client *client, + struct wl_resource *resource, const char *description) { + struct wlr_action_binding_v1 *binding = wlr_action_binding_v1_from_resource(resource); + if (!binding) { + return; + } + if (binding->bound || binding->description) { + wl_resource_post_error(binding->resource, + EXT_ACTION_BINDING_V1_ERROR_ALREADY_SET, + "attempted to set a binding property twice"); + return; + } + + binding->description = strdup(description); + if (binding->description == NULL) { + wl_client_post_no_memory(client); + } +} + +static void action_binding_set_app_id(struct wl_client *client, + struct wl_resource *resource, const char *app_id) { + struct wlr_action_binding_v1 *binding = wlr_action_binding_v1_from_resource(resource); + if (!binding) { + return; + } + if (binding->bound || binding->app_id) { + wl_resource_post_error(binding->resource, + EXT_ACTION_BINDING_V1_ERROR_ALREADY_SET, + "attempted to set a binding property twice"); + return; + } + + binding->app_id = strdup(app_id); + if (binding->app_id == NULL) { + wl_client_post_no_memory(client); + } +} + +static void action_binding_set_seat(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource) { + struct wlr_action_binding_v1 *binding = wlr_action_binding_v1_from_resource(resource); + if (!binding) { + return; + } + + if (binding->bound || binding->seat) { + wl_resource_post_error(binding->resource, + EXT_ACTION_BINDING_V1_ERROR_ALREADY_SET, + "attempted to set a binding property twice"); + return; + } + + struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat_resource); + binding->seat = seat_client ? seat_client->seat : NULL; + if (binding->seat) { + wl_signal_add(&binding->seat->events.destroy, &binding->seat_destroy); + } +} + +static const struct ext_action_binding_v1_interface ext_action_binding_v1_implementation = { + .destroy = resource_handle_destroy, + .set_mouse_hint = action_binding_set_mouse_hint, + .set_keyboard_hint = action_binding_set_keyboard_hint, + .set_gesture_hint = action_binding_set_gesture_hint, + .set_description = action_binding_set_desc, + .set_name = action_binding_set_name, + .set_app_id = action_binding_set_app_id, + .set_seat = action_binding_set_seat, +}; + +static void action_binder_create_binding(struct wl_client *client, + struct wl_resource *resource, uint32_t binding) { + struct wlr_action_binder_v1_state *state = wlr_action_binder_v1_state_from_resource(resource); + + struct wl_resource *bind_resource = wl_resource_create(client, + &ext_action_binding_v1_interface, ext_action_binding_v1_interface.version, binding); + if (bind_resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + struct wlr_action_binding_v1 *bind = calloc(1, sizeof(*bind)); + if (bind == NULL) { + wl_client_post_no_memory(client); + wl_resource_destroy(bind_resource); + return; + } + bind->resource = bind_resource; + bind->state = state; + bind->bound = false; + + wl_list_init(&bind->link); + wl_signal_init(&bind->events.destroy); + wl_list_insert(&state->bind_queue, &bind->link); + + wl_list_init(&bind->seat_destroy.link); + bind->seat_destroy.notify = handle_seat_destroy; + + wl_resource_set_implementation(bind_resource, + &ext_action_binding_v1_implementation, bind, action_binding_destroy); +} + +static void action_binder_commit(struct wl_client *client, struct wl_resource *resource) { + struct wlr_action_binder_v1_state *state = wlr_action_binder_v1_state_from_resource(resource); + struct wlr_action_binding_v1 *binding = NULL; + + wl_list_for_each(binding, &state->bind_queue, link) { + if (!binding->category || !binding->name) { + wl_resource_post_error(binding->resource, + EXT_ACTION_BINDER_V1_ERROR_INVALID_BINDING, + "attempted to bind a unactionable binding"); + return; + } + } + + wl_signal_emit_mutable(&state->binder->events.bind, state); +} + +static const struct ext_action_binder_v1_interface ext_action_binder_v1_implementation = { + .create_binding = action_binder_create_binding, + .commit = action_binder_commit, + .destroy = resource_handle_destroy, +}; + +static void action_binder_destroy(struct wl_resource *resource) { + struct wlr_action_binder_v1_state *state = wlr_action_binder_v1_state_from_resource(resource); + struct wlr_action_binding_v1 *binding = NULL, *tmp = NULL; + + wl_list_for_each_safe(binding, tmp, &state->binds, link) { + wl_signal_emit(&binding->events.destroy, NULL); + destroy_binding(binding); + } + + wl_list_for_each_safe(binding, tmp, &state->bind_queue, link) { + destroy_binding(binding); + } + + wl_list_remove(&state->link); + + free(state); +} + +static void action_binder_bind(struct wl_client *wl_client, + void *data, uint32_t version, uint32_t id) { + struct wlr_action_binder_v1 *binder = data; + + struct wl_resource *resource = wl_resource_create(wl_client, + &ext_action_binder_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + struct wlr_action_binder_v1_state *state = calloc(1, sizeof(*state)); + if (state == NULL) { + wl_client_post_no_memory(wl_client); + wl_resource_destroy(resource); + return; + } + + wl_list_init(&state->binds); + wl_list_init(&state->bind_queue); + state->binder = binder; + state->resource = resource; + + wl_list_insert(&binder->states, &state->link); + + wl_resource_set_implementation(resource, + &ext_action_binder_v1_implementation, state, action_binder_destroy); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_action_binder_v1 *binder = wl_container_of(listener, binder, display_destroy); + wl_signal_emit(&binder->events.destroy, NULL); + wl_list_remove(&binder->display_destroy.link); + wl_global_destroy(binder->global); + free(binder); +} + +struct wlr_action_binder_v1 *wlr_action_binder_v1_create(struct wl_display *display) { + struct wlr_action_binder_v1 *action_binder = calloc(1, sizeof(*action_binder)); + if (!action_binder) { + return NULL; + } + + struct wl_global *global = wl_global_create(display, + &ext_action_binder_v1_interface, 1, action_binder, action_binder_bind); + if (!global) { + free(action_binder); + return NULL; + } + action_binder->global = global; + + wl_signal_init(&action_binder->events.bind); + wl_signal_init(&action_binder->events.destroy); + wl_list_init(&action_binder->states); + + action_binder->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &action_binder->display_destroy); + + return action_binder; +} + +void wlr_action_binding_v1_bind(struct wlr_action_binding_v1 *binding, const char *trigger) { + assert(!binding->bound); + binding->bound = true; + wl_list_remove(&binding->link); + wl_list_insert(&binding->state->binds, &binding->link); + + ext_action_binding_v1_send_bound(binding->resource, trigger); +} + +void wlr_action_binding_v1_reject(struct wlr_action_binding_v1 *binding) { + ext_action_binding_v1_send_rejected(binding->resource); + wl_signal_emit(&binding->events.destroy, NULL); + destroy_binding(binding); +} + +void wlr_action_binding_v1_trigger(struct wlr_action_binding_v1 *binding, uint32_t trigger_type, uint32_t time_msec) { + ext_action_binding_v1_send_triggered(binding->resource, time_msec, trigger_type); +} From 6d63871f059192b15d2fa0dfacfb391709f4952d Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 16 Oct 2025 10:42:20 +0200 Subject: [PATCH 02/10] linux_drm_syncobj_v1: fix use-after-free in surface_commit_destroy() surface_commit_destroy() accesses a field from struct wlr_linux_drm_syncobj_surface_v1, however that struct may have been free'd earlier: ==1103==ERROR: AddressSanitizer: heap-use-after-free on address 0x7cdef7a6e288 at pc 0x7feefaac335a bp 0x7ffc4de8f570 sp 0x7ffc4de8f560 READ of size 8 at 0x7cdef7a6e288 thread T0 #0 0x7feefaac3359 in surface_commit_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:195 #1 0x7feefaac34cd in surface_commit_handle_surface_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:211 #2 0x7feefbd194cf in wl_signal_emit_mutable (/usr/lib/libwayland-server.so.0+0x84cf) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #3 0x7feefaa52b22 in surface_handle_resource_destroy ../subprojects/wlroots/types/wlr_compositor.c:730 #4 0x7feefbd1bb9f (/usr/lib/libwayland-server.so.0+0xab9f) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #5 0x7feefaa46a18 in surface_handle_destroy ../subprojects/wlroots/types/wlr_compositor.c:65 #6 0x7feef89afac5 (/usr/lib/libffi.so.8+0x7ac5) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90) #7 0x7feef89ac76a (/usr/lib/libffi.so.8+0x476a) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90) #8 0x7feef89af06d in ffi_call (/usr/lib/libffi.so.8+0x706d) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90) #9 0x7feefbd17531 (/usr/lib/libwayland-server.so.0+0x6531) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #10 0x7feefbd1cd2f (/usr/lib/libwayland-server.so.0+0xbd2f) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #11 0x7feefbd1b181 in wl_event_loop_dispatch (/usr/lib/libwayland-server.so.0+0xa181) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #12 0x7feefbd1d296 in wl_display_run (/usr/lib/libwayland-server.so.0+0xc296) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #13 0x555bf0a55a40 in server_run ../sway/server.c:615 #14 0x555bf0a4a654 in main ../sway/main.c:376 #15 0x7feef9227674 (/usr/lib/libc.so.6+0x27674) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e) #16 0x7feef9227728 in __libc_start_main (/usr/lib/libc.so.6+0x27728) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e) #17 0x555bf0a03f54 in _start (/home/leo/code/stuff/sway/build/sway/sway+0x390f54) (BuildId: e3d4e653af1aa0885f0426c403e16fc87c086d33) 0x7cdef7a6e288 is located 8 bytes inside of 176-byte region [0x7cdef7a6e280,0x7cdef7a6e330) freed by thread T0 here: #0 0x7feefb71f79d in free /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:51 #1 0x7feefaac29f1 in surface_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:84 #2 0x7feefaac2e47 in surface_handle_resource_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:143 #3 0x7feefbd1bb9f (/usr/lib/libwayland-server.so.0+0xab9f) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #4 0x7feefaac2a12 in surface_handle_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:89 #5 0x7feef89afac5 (/usr/lib/libffi.so.8+0x7ac5) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90) previously allocated by thread T0 here: #0 0x7feefb7205dd in calloc /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:74 #1 0x7feefaac4abd in manager_handle_get_surface ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:313 #2 0x7feef89afac5 (/usr/lib/libffi.so.8+0x7ac5) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90) Fix this by storing the struct wlr_surface in the field. Closes: https://github.com/swaywm/sway/issues/8917 --- types/wlr_linux_drm_syncobj_v1.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/types/wlr_linux_drm_syncobj_v1.c b/types/wlr_linux_drm_syncobj_v1.c index 7873a09c4..988d44e01 100644 --- a/types/wlr_linux_drm_syncobj_v1.c +++ b/types/wlr_linux_drm_syncobj_v1.c @@ -26,7 +26,7 @@ struct wlr_linux_drm_syncobj_surface_v1 { }; struct wlr_linux_drm_syncobj_surface_v1_commit { - struct wlr_linux_drm_syncobj_surface_v1 *surface; + struct wlr_surface *surface; struct wlr_drm_syncobj_timeline_waiter waiter; uint32_t cached_seq; @@ -192,7 +192,7 @@ static struct wlr_linux_drm_syncobj_surface_v1 *surface_from_wlr_surface( } static void surface_commit_destroy(struct wlr_linux_drm_syncobj_surface_v1_commit *commit) { - wlr_surface_unlock_cached(commit->surface->surface, commit->cached_seq); + wlr_surface_unlock_cached(commit->surface, commit->cached_seq); wl_list_remove(&commit->surface_destroy.link); wlr_drm_syncobj_timeline_waiter_finish(&commit->waiter); free(commit); @@ -237,7 +237,7 @@ static bool lock_surface_commit(struct wlr_linux_drm_syncobj_surface_v1 *surface return false; } - commit->surface = surface; + commit->surface = surface->surface; commit->cached_seq = wlr_surface_lock_pending(surface->surface); commit->surface_destroy.notify = surface_commit_handle_surface_destroy; From d786e07899481dd970025ffef09a18eb726cd41d Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Tue, 14 Oct 2025 17:46:50 -0400 Subject: [PATCH 03/10] backend/session: use device `boot_display` shouldn't need to check for `boot_vga` if newer, more general sysfs `boot_display` is set. closes https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/4016 --- backend/session/session.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/backend/session/session.c b/backend/session/session.c index 48f4ab187..32522fb42 100644 --- a/backend/session/session.c +++ b/backend/session/session.c @@ -519,8 +519,6 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, break; } - bool is_boot_vga = false; - const char *path = udev_list_entry_get_name(entry); struct udev_device *dev = udev_device_new_from_syspath(session->udev, path); if (!dev) { @@ -536,14 +534,20 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, continue; } - // This is owned by 'dev', so we don't need to free it - struct udev_device *pci = - udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); + bool is_primary = false; + const char *boot_display = udev_device_get_sysattr_value(dev, "boot_display"); + if (boot_display && strcmp(boot_display, "1") == 0) { + is_primary = true; + } else { + // This is owned by 'dev', so we don't need to free it + struct udev_device *pci = + udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); - if (pci) { - const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); - if (id && strcmp(id, "1") == 0) { - is_boot_vga = true; + if (pci) { + const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); + if (id && strcmp(id, "1") == 0) { + is_primary = true; + } } } @@ -557,7 +561,7 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, udev_device_unref(dev); ret[i] = wlr_dev; - if (is_boot_vga) { + if (is_primary) { struct wlr_device *tmp = ret[0]; ret[0] = ret[i]; ret[i] = tmp; From 3d36ab921114e17f2538d8260876c62786115b1a Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 15 Jun 2025 15:00:21 +0200 Subject: [PATCH 04/10] render/color: add wlr_color_transform_eval() Makes it so the Vulkan renderer can handle arbitrary color transforms, and doesn't need to be updated each time a new one is added. --- include/render/color.h | 6 --- include/wlr/render/color.h | 6 +++ render/color.c | 76 +++++++++++++++++++++++++++++++++++++- render/vulkan/pass.c | 19 +--------- 4 files changed, 82 insertions(+), 25 deletions(-) diff --git a/include/render/color.h b/include/render/color.h index 8730ac6e9..5dc6481ab 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -72,12 +72,6 @@ struct wlr_color_transform_inverse_eotf *wlr_color_transform_inverse_eotf_from_b struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( struct wlr_color_transform *tr); -/** - * Evaluate a 3x1D LUT color transform for a given RGB triplet. - */ -void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr, - float out[static 3], const float in[static 3]); - /** * Obtain primaries values from a well-known primaries name. */ diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index d906e3425..5f2ad36b5 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -152,4 +152,10 @@ struct wlr_color_transform *wlr_color_transform_ref(struct wlr_color_transform * */ void wlr_color_transform_unref(struct wlr_color_transform *tr); +/** + * Evaluate a color transform for a given RGB triplet. + */ +void wlr_color_transform_eval(struct wlr_color_transform *tr, + float out[static 3], const float in[static 3]); + #endif diff --git a/render/color.c b/render/color.c index da2f938f8..287cda76f 100644 --- a/render/color.c +++ b/render/color.c @@ -108,6 +108,65 @@ struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( return lut_3x1d; } +static float srgb_eval_inverse_eotf(float x) { + // See https://www.w3.org/Graphics/Color/srgb + if (x <= 0.0031308) { + return 12.92 * x; + } else { + return 1.055 * powf(x, 1.0 / 2.4) - 0.055; + } +} + +static float st2084_pq_eval_inverse_eotf(float x) { + // H.273 TransferCharacteristics code point 16 + float c1 = 0.8359375; + float c2 = 18.8515625; + float c3 = 18.6875; + float m = 78.84375; + float n = 0.1593017578125; + if (x < 0) { + x = 0; + } + if (x > 1) { + x = 1; + } + float pow_n = powf(x, n); + return powf((c1 + c2 * pow_n) / (1 + c3 * pow_n), m); +} + +static float bt1886_eval_inverse_eotf(float x) { + float lb = powf(0.0001, 1.0 / 2.4); + float lw = powf(1.0, 1.0 / 2.4); + float a = powf(lw - lb, 2.4); + float b = lb / (lw - lb); + return powf(x / a, 1.0 / 2.4) - b; +} + +static float transfer_function_eval_inverse_eotf( + enum wlr_color_transfer_function tf, float x) { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + return srgb_eval_inverse_eotf(x); + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + return st2084_pq_eval_inverse_eotf(x); + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + return x; + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + return powf(x, 1.0 / 2.2); + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + return bt1886_eval_inverse_eotf(x); + } + abort(); // unreachable +} + +static void color_transform_inverse_eotf_eval( + struct wlr_color_transform_inverse_eotf *tr, + float out[static 3], const float in[static 3]) { + for (size_t i = 0; i < 3; i++) { + out[i] = transfer_function_eval_inverse_eotf(tr->tf, in[i]); + } +} + static float lut_1d_get(const uint16_t *lut, size_t len, size_t i) { if (i >= len) { i = len - 1; @@ -125,13 +184,28 @@ static float lut_1d_eval(const uint16_t *lut, size_t len, float x) { return a * (1 - frac_part) + b * frac_part; } -void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr, +static void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr, float out[static 3], const float in[static 3]) { for (size_t i = 0; i < 3; i++) { out[i] = lut_1d_eval(&tr->lut_3x1d[tr->dim * i], tr->dim, in[i]); } } +void wlr_color_transform_eval(struct wlr_color_transform *tr, + float out[static 3], const float in[static 3]) { + switch (tr->type) { + case COLOR_TRANSFORM_INVERSE_EOTF: + color_transform_inverse_eotf_eval(wlr_color_transform_inverse_eotf_from_base(tr), out, in); + break; + case COLOR_TRANSFORM_LCMS2: + color_transform_lcms2_eval(color_transform_lcms2_from_base(tr), out, in); + break; + case COLOR_TRANSFORM_LUT_3X1D: + color_transform_lut_3x1d_eval(color_transform_lut_3x1d_from_base(tr), out, in); + break; + } +} + void wlr_color_primaries_from_named(struct wlr_color_primaries *out, enum wlr_color_named_primaries named) { switch (named) { diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 9e212aacc..398ee2104 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -964,19 +964,6 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, *ds = VK_NULL_HANDLE; *ds_pool = NULL; - struct wlr_color_transform_lcms2 *tr_lcms2 = NULL; - struct wlr_color_transform_lut_3x1d *tr_lut_3x1d = NULL; - switch (tr->type) { - case COLOR_TRANSFORM_INVERSE_EOTF: - abort(); // unreachable - case COLOR_TRANSFORM_LCMS2: - tr_lcms2 = color_transform_lcms2_from_base(tr); - break; - case COLOR_TRANSFORM_LUT_3X1D: - tr_lut_3x1d = color_transform_lut_3x1d_from_base(tr); - break; - } - // R32G32B32 is not a required Vulkan format // TODO: use it when available VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT; @@ -1074,11 +1061,7 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, b_index * sample_range, }; float rgb_out[3]; - if (tr_lcms2 != NULL) { - color_transform_lcms2_eval(tr_lcms2, rgb_out, rgb_in); - } else { - color_transform_lut_3x1d_eval(tr_lut_3x1d, rgb_out, rgb_in); - } + wlr_color_transform_eval(tr, rgb_out, rgb_in); dst[dst_offset] = rgb_out[0]; dst[dst_offset + 1] = rgb_out[1]; From 0b58bddf1370a173d53bc2ed51c7b46294252c5c Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 15 Jun 2025 15:03:18 +0200 Subject: [PATCH 05/10] render/color: add wlr_color_transform_pipeline Useful to apply multiple transforms in sequence, e.g. sRGB inverse EOTF followed by gamma LUTs. --- include/render/color.h | 8 +++++++ include/wlr/render/color.h | 7 ++++++ render/color.c | 45 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/include/render/color.h b/include/render/color.h index 5dc6481ab..128b345e1 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -9,6 +9,7 @@ enum wlr_color_transform_type { COLOR_TRANSFORM_INVERSE_EOTF, COLOR_TRANSFORM_LCMS2, COLOR_TRANSFORM_LUT_3X1D, + COLOR_TRANSFORM_PIPELINE, }; struct wlr_color_transform { @@ -39,6 +40,13 @@ struct wlr_color_transform_lut_3x1d { size_t dim; }; +struct wlr_color_transform_pipeline { + struct wlr_color_transform base; + + struct wlr_color_transform **transforms; + size_t len; +}; + void wlr_color_transform_init(struct wlr_color_transform *tr, enum wlr_color_transform_type type); diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index 5f2ad36b5..bc3baf181 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -141,6 +141,13 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_inverse_eotf( struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, const uint16_t *r, const uint16_t *g, const uint16_t *b); +/** + * Initialize a color transformation to apply a sequence of color transforms + * one after another. + */ +struct wlr_color_transform *wlr_color_transform_init_pipeline( + struct wlr_color_transform **transforms, size_t len); + /** * Increase the reference count of the color transform by 1. */ diff --git a/render/color.c b/render/color.c index 287cda76f..0a1a67be3 100644 --- a/render/color.c +++ b/render/color.c @@ -62,6 +62,33 @@ struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, return &tx->base; } +struct wlr_color_transform *wlr_color_transform_init_pipeline( + struct wlr_color_transform **transforms, size_t len) { + assert(len > 0); + + struct wlr_color_transform **copy = calloc(len, sizeof(copy[0])); + if (copy == NULL) { + return NULL; + } + + struct wlr_color_transform_pipeline *tx = calloc(1, sizeof(*tx)); + if (!tx) { + free(copy); + return NULL; + } + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_PIPELINE); + + // TODO: flatten nested pipeline transforms + for (size_t i = 0; i < len; i++) { + copy[i] = wlr_color_transform_ref(transforms[i]); + } + + tx->transforms = copy; + tx->len = len; + + return &tx->base; +} + static void color_transform_destroy(struct wlr_color_transform *tr) { switch (tr->type) { case COLOR_TRANSFORM_INVERSE_EOTF: @@ -73,6 +100,14 @@ static void color_transform_destroy(struct wlr_color_transform *tr) { struct wlr_color_transform_lut_3x1d *lut_3x1d = color_transform_lut_3x1d_from_base(tr); free(lut_3x1d->lut_3x1d); break; + case COLOR_TRANSFORM_PIPELINE:; + struct wlr_color_transform_pipeline *pipeline = + wl_container_of(tr, pipeline, base); + for (size_t i = 0; i < pipeline->len; i++) { + wlr_color_transform_unref(pipeline->transforms[i]); + } + free(pipeline->transforms); + break; } wlr_addon_set_finish(&tr->addons); free(tr); @@ -203,6 +238,16 @@ void wlr_color_transform_eval(struct wlr_color_transform *tr, case COLOR_TRANSFORM_LUT_3X1D: color_transform_lut_3x1d_eval(color_transform_lut_3x1d_from_base(tr), out, in); break; + case COLOR_TRANSFORM_PIPELINE:; + struct wlr_color_transform_pipeline *pipeline = + wl_container_of(tr, pipeline, base); + float color[3]; + memcpy(color, in, sizeof(color)); + for (size_t i = 0; i < pipeline->len; i++) { + wlr_color_transform_eval(pipeline->transforms[i], color, color); + } + memcpy(out, color, sizeof(color)); + break; } } From 74ce6c22a54f28abcaaef743d739da78fb853e85 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 23 Jun 2025 09:15:10 +0200 Subject: [PATCH 06/10] output: check for color transform no-op changes This allows callers to always set this state and not care whether the output currently has the same color transform applied. --- include/wlr/types/wlr_output.h | 1 + types/output/output.c | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 6a0dd0455..2ae11a4d3 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -267,6 +267,7 @@ struct wlr_output { struct { struct wl_listener display_destroy; struct wlr_output_image_description image_description_value; + struct wlr_color_transform *color_transform; } WLR_PRIVATE; }; diff --git a/types/output/output.c b/types/output/output.c index 1fb0347a0..ca2e55538 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -242,6 +242,15 @@ static void output_apply_state(struct wlr_output *output, } } + if (state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { + wlr_color_transform_unref(output->color_transform); + if (state->color_transform != NULL) { + output->color_transform = wlr_color_transform_ref(state->color_transform); + } else { + output->color_transform = NULL; + } + } + bool geometry_updated = state->committed & (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_TRANSFORM | WLR_OUTPUT_STATE_SUBPIXEL); @@ -407,6 +416,7 @@ void wlr_output_finish(struct wlr_output *output) { wlr_swapchain_destroy(output->cursor_swapchain); wlr_buffer_unlock(output->cursor_front_buffer); + wlr_color_transform_unref(output->color_transform); wlr_swapchain_destroy(output->swapchain); @@ -515,8 +525,7 @@ const struct wlr_output_image_description *output_pending_image_description( * Returns a bitfield of the unchanged fields. * * Some fields are not checked: damage always changes in-between frames, the - * gamma LUT is too expensive to check, the contents of the buffer might have - * changed, etc. + * contents of the buffer might have changed, etc. */ static uint32_t output_compare_state(struct wlr_output *output, const struct wlr_output_state *state) { @@ -562,6 +571,10 @@ static uint32_t output_compare_state(struct wlr_output *output, output->subpixel == state->subpixel) { fields |= WLR_OUTPUT_STATE_SUBPIXEL; } + if ((state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) && + output->color_transform == state->color_transform) { + fields |= WLR_OUTPUT_STATE_COLOR_TRANSFORM; + } return fields; } From 91f4890ec27baab6f0df598e842cdf127cc6304c Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 2 Jul 2025 18:28:06 +0200 Subject: [PATCH 07/10] gamma_control_v1: add wlr_gamma_control_v1_get_color_transform() --- include/wlr/types/wlr_gamma_control_v1.h | 2 ++ types/wlr_gamma_control_v1.c | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/include/wlr/types/wlr_gamma_control_v1.h b/include/wlr/types/wlr_gamma_control_v1.h index b3a70f11e..4b0948964 100644 --- a/include/wlr/types/wlr_gamma_control_v1.h +++ b/include/wlr/types/wlr_gamma_control_v1.h @@ -49,6 +49,8 @@ struct wlr_gamma_control_v1 *wlr_gamma_control_manager_v1_get_control( struct wlr_gamma_control_manager_v1 *manager, struct wlr_output *output); bool wlr_gamma_control_v1_apply(struct wlr_gamma_control_v1 *gamma_control, struct wlr_output_state *output_state); +struct wlr_color_transform *wlr_gamma_control_v1_get_color_transform( + struct wlr_gamma_control_v1 *gamma_control); void wlr_gamma_control_v1_send_failed_and_destroy(struct wlr_gamma_control_v1 *gamma_control); #endif diff --git a/types/wlr_gamma_control_v1.c b/types/wlr_gamma_control_v1.c index 207d1977f..04d73c2e5 100644 --- a/types/wlr_gamma_control_v1.c +++ b/types/wlr_gamma_control_v1.c @@ -262,15 +262,24 @@ struct wlr_gamma_control_v1 *wlr_gamma_control_manager_v1_get_control( return NULL; } +struct wlr_color_transform *wlr_gamma_control_v1_get_color_transform( + struct wlr_gamma_control_v1 *gamma_control) { + if (gamma_control == NULL || gamma_control->table == NULL) { + return NULL; + } + + const uint16_t *r = gamma_control->table; + const uint16_t *g = gamma_control->table + gamma_control->ramp_size; + const uint16_t *b = gamma_control->table + 2 * gamma_control->ramp_size; + + return wlr_color_transform_init_lut_3x1d(gamma_control->ramp_size, r, g, b); +} + bool wlr_gamma_control_v1_apply(struct wlr_gamma_control_v1 *gamma_control, struct wlr_output_state *output_state) { struct wlr_color_transform *tr = NULL; if (gamma_control != NULL && gamma_control->table != NULL) { - const uint16_t *r = gamma_control->table; - const uint16_t *g = gamma_control->table + gamma_control->ramp_size; - const uint16_t *b = gamma_control->table + 2 * gamma_control->ramp_size; - - tr = wlr_color_transform_init_lut_3x1d(gamma_control->ramp_size, r, g, b); + tr = wlr_gamma_control_v1_get_color_transform(gamma_control); if (tr == NULL) { return false; } From 3e08e3be4a02122dd3b0bebe965ab0d410f08a01 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 5 Oct 2025 21:22:07 +0200 Subject: [PATCH 08/10] gamma_control_v1: introduce fallback_gamma_size --- include/wlr/types/wlr_gamma_control_v1.h | 5 +++++ types/wlr_gamma_control_v1.c | 3 +++ 2 files changed, 8 insertions(+) diff --git a/include/wlr/types/wlr_gamma_control_v1.h b/include/wlr/types/wlr_gamma_control_v1.h index 4b0948964..7a6df98a9 100644 --- a/include/wlr/types/wlr_gamma_control_v1.h +++ b/include/wlr/types/wlr_gamma_control_v1.h @@ -10,6 +10,11 @@ struct wlr_gamma_control_manager_v1 { struct wl_global *global; struct wl_list controls; // wlr_gamma_control_v1.link + // Fallback to use when an struct wlr_output doesn't support gamma LUTs. + // Can be used to apply gamma LUTs via a struct wlr_renderer. Leave zero to + // indicate that the fallback is unsupported. + size_t fallback_gamma_size; + struct { struct wl_signal destroy; struct wl_signal set_gamma; // struct wlr_gamma_control_manager_v1_set_gamma_event diff --git a/types/wlr_gamma_control_v1.c b/types/wlr_gamma_control_v1.c index 04d73c2e5..42d14799d 100644 --- a/types/wlr_gamma_control_v1.c +++ b/types/wlr_gamma_control_v1.c @@ -157,6 +157,9 @@ static void gamma_control_manager_get_gamma_control(struct wl_client *client, } size_t gamma_size = wlr_output_get_gamma_size(output); + if (gamma_size == 0) { + gamma_size = manager->fallback_gamma_size; + } if (gamma_size == 0) { zwlr_gamma_control_v1_send_failed(resource); return; From 989cffe70d32db9fc2b5a776abd4767aabe430f6 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 5 Oct 2025 21:21:32 +0200 Subject: [PATCH 09/10] scene: add software fallback for gamma LUT --- include/wlr/types/wlr_scene.h | 5 +++ types/scene/wlr_scene.c | 79 +++++++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 58794e8db..9658a02b4 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -252,6 +252,11 @@ struct wlr_scene_output { bool gamma_lut_changed; struct wlr_gamma_control_v1 *gamma_lut; + struct wlr_color_transform *gamma_lut_color_transform; + + struct wlr_color_transform *prev_gamma_lut_color_transform; + struct wlr_color_transform *prev_supplied_color_transform; + struct wlr_color_transform *prev_combined_color_transform; struct wl_listener output_commit; struct wl_listener output_damage; diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index a06b641e3..c5cbcd29f 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1530,6 +1530,8 @@ static void scene_handle_gamma_control_manager_v1_set_gamma(struct wl_listener * output->gamma_lut_changed = true; output->gamma_lut = event->control; + wlr_color_transform_unref(output->gamma_lut_color_transform); + output->gamma_lut_color_transform = wlr_gamma_control_v1_get_color_transform(event->control); wlr_output_schedule_frame(output->output); } @@ -1547,6 +1549,8 @@ static void scene_handle_gamma_control_manager_v1_destroy(struct wl_listener *li wl_list_for_each(output, &scene->outputs, link) { output->gamma_lut_changed = false; output->gamma_lut = NULL; + wlr_color_transform_unref(output->gamma_lut_color_transform); + output->gamma_lut_color_transform = NULL; } } @@ -1766,6 +1770,10 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { wl_list_remove(&scene_output->output_damage.link); wl_list_remove(&scene_output->output_needs_frame.link); wlr_drm_syncobj_timeline_unref(scene_output->in_timeline); + wlr_color_transform_unref(scene_output->gamma_lut_color_transform); + wlr_color_transform_unref(scene_output->prev_gamma_lut_color_transform); + wlr_color_transform_unref(scene_output->prev_supplied_color_transform); + wlr_color_transform_unref(scene_output->prev_combined_color_transform); wl_array_release(&scene_output->render_list); free(scene_output); } @@ -2104,16 +2112,15 @@ static void scene_output_state_attempt_gamma(struct wlr_scene_output *scene_outp return; } - if (!wlr_gamma_control_v1_apply(scene_output->gamma_lut, &gamma_pending)) { - wlr_output_state_finish(&gamma_pending); - return; - } - + wlr_output_state_set_color_transform(&gamma_pending, scene_output->gamma_lut_color_transform); scene_output->gamma_lut_changed = false; + if (!wlr_output_test_state(scene_output->output, &gamma_pending)) { wlr_gamma_control_v1_send_failed_and_destroy(scene_output->gamma_lut); scene_output->gamma_lut = NULL; + wlr_color_transform_unref(scene_output->gamma_lut_color_transform); + scene_output->gamma_lut_color_transform = NULL; wlr_output_state_finish(&gamma_pending); return; } @@ -2122,6 +2129,41 @@ static void scene_output_state_attempt_gamma(struct wlr_scene_output *scene_outp wlr_output_state_finish(&gamma_pending); } +static struct wlr_color_transform *scene_output_combine_color_transforms( + struct wlr_scene_output *scene_output, struct wlr_color_transform *supplied) { + struct wlr_color_transform *gamma_lut = scene_output->gamma_lut_color_transform; + assert(gamma_lut != NULL); + + if (gamma_lut == scene_output->prev_gamma_lut_color_transform && + supplied == scene_output->prev_supplied_color_transform) { + return wlr_color_transform_ref(scene_output->prev_combined_color_transform); + } + + struct wlr_color_transform *combined; + if (supplied == NULL) { + combined = wlr_color_transform_ref(gamma_lut); + } else { + struct wlr_color_transform *transforms[] = { + gamma_lut, + supplied, + }; + size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]); + combined = wlr_color_transform_init_pipeline(transforms, transforms_len); + if (combined == NULL) { + return NULL; + } + } + + wlr_color_transform_unref(scene_output->prev_gamma_lut_color_transform); + scene_output->prev_gamma_lut_color_transform = wlr_color_transform_ref(gamma_lut); + wlr_color_transform_unref(scene_output->prev_supplied_color_transform); + scene_output->prev_supplied_color_transform = supplied ? wlr_color_transform_ref(supplied) : NULL; + wlr_color_transform_unref(scene_output->prev_combined_color_transform); + scene_output->prev_combined_color_transform = wlr_color_transform_ref(combined); + + return combined; +} + bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, struct wlr_output_state *state, const struct wlr_scene_output_state_options *options) { struct wlr_scene_output_state_options default_options = {0}; @@ -2145,6 +2187,16 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, enum wlr_scene_debug_damage_option debug_damage = scene_output->scene->debug_damage_option; + bool render_gamma_lut = false; + if (wlr_output_get_gamma_size(output) == 0 && output->renderer->features.output_color_transform) { + if (scene_output->gamma_lut_color_transform != scene_output->prev_gamma_lut_color_transform) { + scene_output_damage_whole(scene_output); + } + if (scene_output->gamma_lut_color_transform != NULL) { + render_gamma_lut = true; + } + } + struct render_data render_data = { .transform = output->transform, .scale = output->scale, @@ -2245,7 +2297,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, // - There are no color transforms that need to be applied // - Damage highlight debugging is not enabled enum scene_direct_scanout_result scanout_result = SCANOUT_INELIGIBLE; - if (options->color_transform == NULL && list_len == 1 + if (options->color_transform == NULL && !render_gamma_lut && list_len == 1 && debug_damage != WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) { scanout_result = scene_entry_try_direct_scanout(&list_data[0], state, &render_data); } @@ -2319,6 +2371,17 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, color_transform = wlr_color_transform_ref(options->color_transform); } + if (render_gamma_lut) { + struct wlr_color_transform *combined = + scene_output_combine_color_transforms(scene_output, color_transform); + wlr_color_transform_unref(color_transform); + if (combined == NULL) { + wlr_buffer_unlock(buffer); + return false; + } + color_transform = combined; + } + scene_output->in_point++; struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer, &(struct wlr_buffer_pass_options){ @@ -2441,7 +2504,9 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, scene_output->in_point); } - scene_output_state_attempt_gamma(scene_output, state); + if (!render_gamma_lut) { + scene_output_state_attempt_gamma(scene_output, state); + } return true; } From 879243e370de6167d2c49510396f937b1a93fab5 Mon Sep 17 00:00:00 2001 From: David Turner Date: Mon, 20 Oct 2025 13:55:00 +0100 Subject: [PATCH 10/10] xwm: Fix double-close When an FD is passed to xcb_connect_to_fd(), xcb takes ownership of that FD and is responsible for closing it, which it does when xcb_disconnect() is called. But the xwayland handler code also keeps a copy of the FD and closes it via safe_close() in server_finish_process(). This double-close can cause all sorts of problems if another part of wlroots allocates another FD between the two closes - the latter close will close the wrong FD and things go horribly wrong (in my case leading to use-after-free and segfaults). Fix this by setting wm_fd[0]=-1 after calling xwm_create(), and ensuring that xwm_create() closes the FD if startup errors occur. --- include/xwayland/xwm.h | 1 + xwayland/xwayland.c | 3 +++ xwayland/xwm.c | 3 +++ 3 files changed, 7 insertions(+) diff --git a/include/xwayland/xwm.h b/include/xwayland/xwm.h index e460bbb63..73f440d29 100644 --- a/include/xwayland/xwm.h +++ b/include/xwayland/xwm.h @@ -164,6 +164,7 @@ struct wlr_xwm { struct wl_listener drop_focus_destroy; }; +// xwm_create takes ownership of wm_fd and will close it under all circumstances. struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland, int wm_fd); void xwm_destroy(struct wlr_xwm *xwm); diff --git a/xwayland/xwayland.c b/xwayland/xwayland.c index 5d51df074..3aa47bac2 100644 --- a/xwayland/xwayland.c +++ b/xwayland/xwayland.c @@ -42,6 +42,9 @@ static void handle_server_start(struct wl_listener *listener, void *data) { static void xwayland_mark_ready(struct wlr_xwayland *xwayland) { assert(xwayland->server->wm_fd[0] >= 0); xwayland->xwm = xwm_create(xwayland, xwayland->server->wm_fd[0]); + // xwm_create takes ownership of wm_fd[0] under all circumstances + xwayland->server->wm_fd[0] = -1; + if (!xwayland->xwm) { return; } diff --git a/xwayland/xwm.c b/xwayland/xwm.c index a82e8b145..2bb4e4c64 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -2530,6 +2530,7 @@ void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride, struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { struct wlr_xwm *xwm = calloc(1, sizeof(*xwm)); if (xwm == NULL) { + close(wm_fd); return NULL; } @@ -2544,11 +2545,13 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { xwm->ping_timeout = 10000; + // xcb_connect_to_fd takes ownership of the FD regardless of success/failure xwm->xcb_conn = xcb_connect_to_fd(wm_fd, NULL); int rc = xcb_connection_has_error(xwm->xcb_conn); if (rc) { wlr_log(WLR_ERROR, "xcb connect failed: %d", rc); + xcb_disconnect(xwm->xcb_conn); free(xwm); return NULL; }