Compare commits

...

11 commits

Author SHA1 Message Date
navi
bbf0ff2aca Merge branch 'master' into 'master'
protocol/action-binder: draft implementation.

See merge request wlroots/wlroots!4386
2025-10-27 21:31:21 +00:00
David Turner
879243e370 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.
2025-10-20 14:02:29 +01:00
Simon Ser
989cffe70d scene: add software fallback for gamma LUT 2025-10-18 20:36:01 +02:00
Simon Ser
3e08e3be4a gamma_control_v1: introduce fallback_gamma_size 2025-10-18 20:36:01 +02:00
Simon Ser
91f4890ec2 gamma_control_v1: add wlr_gamma_control_v1_get_color_transform() 2025-10-18 20:36:01 +02:00
Simon Ser
74ce6c22a5 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.
2025-10-18 20:36:01 +02:00
Simon Ser
0b58bddf13 render/color: add wlr_color_transform_pipeline
Useful to apply multiple transforms in sequence, e.g. sRGB inverse
EOTF followed by gamma LUTs.
2025-10-18 20:36:01 +02:00
Simon Ser
3d36ab9211 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.
2025-10-18 20:35:02 +02:00
Furkan Sahin
d786e07899 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
2025-10-17 17:35:51 +00:00
Simon Ser
6d63871f05 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
2025-10-17 09:05:53 +00:00
Anna (navi) Figueiredo Gomes
1495db3e16 ext-action-binder-v1: new protocol implementation
Signed-off-by: Anna (navi) Figueiredo Gomes <navi@vlhl.dev>
2024-09-13 18:44:46 +02:00
20 changed files with 994 additions and 52 deletions

View file

@ -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;

View file

@ -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);
@ -72,12 +80,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.
*/

View file

@ -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.
*/
@ -152,4 +159,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

View file

@ -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 <wayland-server-core.h>
#include <wayland-server-protocol.h>
#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

View file

@ -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
@ -49,6 +54,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

View file

@ -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;
};

View file

@ -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;

View file

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

View file

@ -0,0 +1,266 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="ext_action_binder_v1">
<copyright>
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.
</copyright>
<description summary="binds actions">
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.
</description>
<interface name="ext_action_binder_v1" version="1">
<description summary="action binder">
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.
</description>
<request name="destroy" type="destructor">
<description summary="unbind the actions">
The client no longer wants to receive events for any action.
</description>
</request>
<request name="create_binding">
<description summary="create a binding">
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.
</description>
<arg name="binding" type="new_id" interface="ext_action_binding_v1" summary="the new binding" />
</request>
<request name="commit">
<description summary="commits all created bindings">
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.
</description>
</request>
<enum name="error">
<entry name="invalid_binding" value="0" summary="the binding has no action set"/>
</enum>
</interface>
<interface name="ext_action_binding_v1" version="1">
<description summary="a binding for an action">
This interface defines an individual binding, allowing the client to register metadata about
the action and receive triggered events.
</description>
<request name="destroy" type="destructor">
<description summary="unbind the actions">
The client no longer wants to receive events for this binding.
</description>
</request>
<request name="set_name">
<description summary="sets the category and name of a 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.
</description>
<arg name="category" type="string" summary="the action category" />
<arg name="name" type="string" summary="the action name" />
</request>
<request name="set_description">
<description summary="set the human-readable description of a binding">
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.
</description>
<arg name="description" type="string" summary="a human-readable description of what the binding does" />
</request>
<request name="set_app_id">
<description summary="set the application ID">
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.
</description>
<arg name="app_id" type="string" summary="app_id of the application requesting this bind"/>
</request>
<request name="set_seat">
<description summary="sets a target seat">
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.
</description>
<arg name="seat" type="object" interface="wl_seat" summary="target seat"/>
</request>
<request name="set_keyboard_hint">
<description summary="Sets a keycombo as a trigger hint.">
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.
</description>
<arg name="keycombo" type="string" summary="the keycombo that the client would like to trigger the action"/>
</request>
<request name="set_mouse_hint">
<description summary="Sets a mouse button as a trigger hint.">
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.
</description>
<arg name="button" type="uint" summary="the mouse button that the client would like to trigger the action"/>
</request>
<enum name="gesture_type">
<description summary="Type of the hint gesture.">
</description>
<entry name="hold" value="0"/>
<entry name="swipe" value="1"/>
<entry name="pinch" value="2"/>
</enum>
<enum name="gesture_direction">
<description summary="Direction of the hint gesture.">
</description>
<entry name="none" value="0"/>
<entry name="up" value="1"/>
<entry name="down" value="2"/>
<entry name="left" value="3"/>
<entry name="right" value="4"/>
<entry name="inward" value="5"/>
<entry name="outward" value="6"/>
<entry name="clockwise" value="7"/>
<entry name="counterclockwise" value="8"/>
</enum>
<request name="set_gesture_hint">
<description summary="sets a touchpad gesture as a trigger hint">
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.
</description>
<arg name="type" type="uint" enum="gesture_type" summary="the type of trigger that was sent"/>
<arg name="direction" type="uint" enum="gesture_direction" summary="the type of trigger that was sent"/>
<arg name="fingers" type="uint" summary="the number of fingers on the gesture"/>
</request>
<event name="bound">
<description summary="the compositor bound the binding to an action">
Sent after the event was processed, and was bound to one or more of the actions set.
</description>
<arg name="trigger" type="string" summary="human-readable string describing the trigger for the action" />
</event>
<event name="rejected">
<description summary="the compositor rejected the binding">
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.
</description>
</event>
<enum name="trigger_type">
<description summary="type of binding triggered">
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.
</description>
<entry name="one_shot" value="0"
summary="a one shot action was triggered" />
<entry name="pressed" value="1"
summary="a sustained action was started" />
<entry name="released" value="2"
summary="a sustained action ended" />
</enum>
<event name="triggered">
<description summary="the action triggered">
This event is sent when actions are triggered.
</description>
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
<arg name="type" type="uint" enum="trigger_type" summary="the type of trigger that was sent"/>
</event>
<enum name="error">
<entry name="already_set" value="0" summary="property was already set"/>
</enum>
</interface>
</protocol>

View file

@ -66,6 +66,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',

View file

@ -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);
@ -108,6 +143,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 +219,38 @@ 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;
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;
}
}
void wlr_color_primaries_from_named(struct wlr_color_primaries *out,
enum wlr_color_named_primaries named) {
switch (named) {

View file

@ -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];

View file

@ -37,6 +37,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_color_management_v1.c',
'wlr_color_representation_v1.c',

View file

@ -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;
}

View file

@ -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;
}

View file

@ -0,0 +1,361 @@
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_action_binder_v1.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#include <util/time.h>
#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);
}

View file

@ -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;
@ -262,15 +265,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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}