ohmu
·
2026-10-15
1#ifdef FF_HAVE_WAYLAND
2
3 #include "wayland.h"
4 #include "common/strutil.h"
5 #include "xdg-output-unstable-v1-client-protocol.h"
6 #include "wp-color-management-v1-client-protocol.h"
7
8static void waylandOutputModeListener(void* data, [[maybe_unused]] struct wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refreshRate) {
9 WaylandDisplay* display = data;
10
11 if (flags & WL_OUTPUT_MODE_CURRENT) {
12 display->width = width;
13 display->height = height;
14 display->refreshRate = refreshRate;
15 }
16 if (flags & WL_OUTPUT_MODE_PREFERRED) {
17 display->preferredWidth = width;
18 display->preferredHeight = height;
19 display->preferredRefreshRate = refreshRate;
20 }
21}
22
23static void waylandOutputScaleListener(void* data, [[maybe_unused]] struct wl_output* output, int32_t scale) {
24 WaylandDisplay* display = data;
25 display->dpi = 96 * (uint32_t) scale;
26}
27
28static void waylandOutputDoneListener(void* data, [[maybe_unused]] struct wl_output* output) {
29 WaylandDisplay* display = data;
30 display->done = true;
31}
32
33static void waylandOutputGeometryListener(void* data,
34 [[maybe_unused]] struct wl_output* output,
35 [[maybe_unused]] int32_t x,
36 [[maybe_unused]] int32_t y,
37 int32_t physical_width,
38 int32_t physical_height,
39 [[maybe_unused]] int32_t subpixel,
40 [[maybe_unused]] const char* make,
41 [[maybe_unused]] const char* model,
42 int32_t transform) {
43 WaylandDisplay* display = data;
44 display->physicalWidth = physical_width;
45 display->physicalHeight = physical_height;
46 display->transform = (enum wl_output_transform) transform;
47}
48
49static void handleXdgLogicalSize(void* data, [[maybe_unused]] struct zxdg_output_v1* _, int32_t width, [[maybe_unused]] int32_t height) {
50 WaylandDisplay* display = data;
51 // Seems the values are only useful when ractional scale is enabled
52 if (width < display->width) {
53 display->dpi = (uint32_t) (display->width * 96 / width);
54 }
55}
56
57static struct wl_output_listener outputListener = {
58 .geometry = waylandOutputGeometryListener,
59 .mode = waylandOutputModeListener,
60 .done = waylandOutputDoneListener,
61 .scale = waylandOutputScaleListener,
62 .name = (void*) ffWaylandOutputNameListener,
63 .description = (void*) ffWaylandOutputDescriptionListener,
64};
65
66static struct zxdg_output_v1_listener zxdgOutputListener = {
67 .logical_position = (void*) ffUnused,
68 .logical_size = handleXdgLogicalSize,
69 .done = (void*) ffUnused,
70 .name = (void*) ffWaylandOutputNameListener,
71 .description = (void*) ffWaylandOutputDescriptionListener,
72};
73
74static void handleWpTfNamed(void *data, [[maybe_unused]] struct wp_image_description_info_v1* wp_image_description_info_v1, uint32_t tf) {
75 // KDE reports `gamma 2.2` even in HDR mode, but it should be handled in KDE specific path
76 WaylandDisplay* display = data;
77 switch (tf) {
78 case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ:
79 case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG:
80 display->hdrInfoAvailable = true;
81 display->hdrEnabled = true;
82 break;
83 }
84}
85
86static const struct wp_image_description_info_v1_listener wpImageDescInfoListener = {
87 .done = (void*) ffUnused,
88 .icc_file = (void*) ffUnused,
89 .primaries = (void*) ffUnused,
90 .primaries_named = (void*) ffUnused,
91 .tf_power = (void*) ffUnused,
92 .tf_named = (void*) handleWpTfNamed,
93 .luminances = (void*) ffUnused,
94 .target_primaries = (void*) ffUnused,
95 .target_luminance = (void*) ffUnused,
96 .target_max_cll = (void*) ffUnused,
97 .target_max_fall = (void*) ffUnused,
98};
99
100const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
101 const char* api = "wayland-base";
102 uint32_t bindVersion = min(version, WL_OUTPUT_DESCRIPTION_SINCE_VERSION);
103 struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &wl_output_interface, bindVersion, name, wl_output_interface.name, bindVersion, nullptr);
104 if (output == nullptr) {
105 return "Failed to create wl_output";
106 }
107
108 WaylandDisplay display = {
109 .parent = wldata,
110 .transform = WL_OUTPUT_TRANSFORM_NORMAL,
111 .type = FF_DISPLAY_TYPE_UNKNOWN,
112 .name = ffStrbufCreate(),
113 .description = ffStrbufCreate(),
114 .edidName = ffStrbufCreate(),
115 };
116
117 if (wldata->ffwl_proxy_add_listener(output, (void (**)(void)) &outputListener, &display) < 0) {
118 wldata->ffwl_proxy_destroy(output);
119 return "Failed to add listener to wl_output";
120 }
121 if (wldata->ffwl_display_roundtrip(wldata->display) < 0) {
122 wldata->ffwl_proxy_destroy(output);
123 return "Failed to roundtrip wl_output";
124 }
125 if (bindVersion >= WL_OUTPUT_DONE_SINCE_VERSION && !display.done) {
126 const char* error = ffWaylandWaitForDone(&display);
127 if (error) {
128 wldata->ffwl_proxy_destroy(output);
129 return error;
130 }
131 }
132
133 if (wldata->zxdgOutputManager) {
134 uint32_t bindVersion = min(version, ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION);
135 struct wl_proxy* zxdgOutput = wldata->ffwl_proxy_marshal_constructor_versioned(wldata->zxdgOutputManager, ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, bindVersion, nullptr, output);
136
137 if (zxdgOutput) {
138 wldata->ffwl_proxy_add_listener(zxdgOutput, (void (**)(void)) &zxdgOutputListener, &display);
139 wldata->ffwl_display_roundtrip(wldata->display);
140 wldata->ffwl_proxy_destroy(zxdgOutput);
141 api = "wayland-zxdg";
142 }
143 }
144
145 if (wldata->wpColorManager) {
146 uint32_t bindVersion = min(version, WP_COLOR_MANAGER_V1_GET_OUTPUT_SINCE_VERSION);
147 struct wl_proxy* wpColorOutput = wldata->ffwl_proxy_marshal_constructor_versioned(wldata->wpColorManager, WP_COLOR_MANAGER_V1_GET_OUTPUT, &wp_color_management_output_v1_interface, bindVersion, nullptr, output);
148
149 if (wpColorOutput) {
150 struct wl_proxy* wpImageDesc = wldata->ffwl_proxy_marshal_constructor_versioned(wpColorOutput, WP_COLOR_MANAGEMENT_OUTPUT_V1_GET_IMAGE_DESCRIPTION, &wp_image_description_v1_interface, bindVersion, nullptr);
151 if (wpImageDesc) {
152 struct wl_proxy* wpImageDescInfo = wldata->ffwl_proxy_marshal_constructor_versioned(wpImageDesc, WP_IMAGE_DESCRIPTION_V1_GET_INFORMATION, &wp_image_description_info_v1_interface, bindVersion, nullptr);
153 if (wpImageDescInfo) {
154 wldata->ffwl_proxy_add_listener(wpImageDescInfo, (void (**)(void)) &wpImageDescInfoListener, &display);
155 wldata->ffwl_display_roundtrip(wldata->display);
156 wldata->ffwl_proxy_destroy(wpImageDescInfo);
157 }
158 wldata->ffwl_proxy_destroy(wpImageDesc);
159 }
160 wldata->ffwl_proxy_destroy(wpColorOutput);
161 wldata->ffwl_display_roundtrip(wldata->display);
162 api = api[strlen("wayland-")] == 'z' ? "wayland-zxdg+wpcolor" : "wayland-wpcolor";
163 }
164 }
165
166 wldata->ffwl_proxy_destroy(output);
167
168 if (display.width <= 0 || display.height <= 0) {
169 return "Failed to get display information from wl_output";
170 }
171
172 uint32_t rotation = ffWaylandHandleRotation(&display);
173
174 FFDisplayResult* item = ffdsAppendDisplay(wldata->result,
175 (uint32_t) display.width,
176 (uint32_t) display.height,
177 display.refreshRate / 1000.0,
178 display.dpi,
179 (uint32_t) display.preferredWidth,
180 (uint32_t) display.preferredHeight,
181 display.preferredRefreshRate / 1000.0,
182 rotation,
183 display.edidName.length
184 ? &display.edidName
185 // Try ignoring `eDP-1-unknown`, where `unknown` is localized
186 : display.description.length && !ffStrbufContain(&display.description, &display.name)
187 ? &display.description
188 : &display.name,
189 display.type,
190 false,
191 display.id,
192 (uint32_t) display.physicalWidth,
193 (uint32_t) display.physicalHeight,
194 api);
195 if (item) {
196 if (display.hdrEnabled) {
197 item->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;
198 } else if (display.hdrSupported) {
199 item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;
200 } else if (display.hdrInfoAvailable) {
201 item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
202 } else {
203 item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;
204 }
205
206 item->manufactureYear = display.myear;
207 item->manufactureWeek = display.mweek;
208 ffStrbufInitMove(&item->serial, &display.serial);
209 }
210
211 ffStrbufDestroy(&display.description);
212 ffStrbufDestroy(&display.name);
213 ffStrbufDestroy(&display.edidName);
214
215 return nullptr;
216}
217
218const char* ffWaylandHandleZxdgOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
219 uint32_t bindVersion = min(version, ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION);
220 struct wl_proxy* manager = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &zxdg_output_manager_v1_interface, bindVersion, name, zxdg_output_manager_v1_interface.name, bindVersion, nullptr);
221 if (manager == nullptr) {
222 return "Failed to create zxdg_output_manager_v1";
223 }
224
225 wldata->zxdgOutputManager = manager;
226
227 return nullptr;
228}
229
230const char* ffWaylandHandleWpColor(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
231 uint32_t bindVersion = min(version, WP_COLOR_MANAGER_V1_GET_OUTPUT_SINCE_VERSION);
232 struct wl_proxy* manager = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &wp_color_manager_v1_interface, bindVersion, name, wp_color_manager_v1_interface.name, bindVersion, nullptr);
233 if (manager == nullptr) {
234 return "Failed to create wp_color_manager_v1";
235 }
236
237 wldata->wpColorManager = manager;
238
239 return nullptr;
240}
241
242#endif