ohmu
·
2026-10-15
1#ifdef FF_HAVE_WAYLAND
2
3 #include "wayland.h"
4 #include "kde-output-device-v2-client-protocol.h"
5 #include "common/edidHelper.h"
6 #include "common/base64.h"
7
8typedef struct WaylandKdeMode {
9 int32_t width;
10 int32_t height;
11 int32_t refreshRate;
12 bool preferred;
13 struct kde_output_device_mode_v2* pMode;
14} WaylandKdeMode;
15
16static void waylandKdeModeSizeListener(void* data, [[maybe_unused]] struct kde_output_device_mode_v2* _, int32_t width, int32_t height) {
17 WaylandKdeMode* mode = (WaylandKdeMode*) data;
18 mode->width = width;
19 mode->height = height;
20}
21
22static void waylandKdeModeRefreshListener(void* data, [[maybe_unused]] struct kde_output_device_mode_v2* _, int32_t rate) {
23 WaylandKdeMode* mode = (WaylandKdeMode*) data;
24 mode->refreshRate = rate;
25}
26
27static void waylandKdeModePreferredListener(void* data, [[maybe_unused]] struct kde_output_device_mode_v2* _) {
28 WaylandKdeMode* mode = (WaylandKdeMode*) data;
29 mode->preferred = true;
30}
31
32static const struct kde_output_device_mode_v2_listener modeListener = {
33 .size = waylandKdeModeSizeListener,
34 .refresh = waylandKdeModeRefreshListener,
35 .preferred = waylandKdeModePreferredListener,
36 .removed = (void*) ffUnused,
37 .flags = (void*) ffUnused,
38};
39
40static void waylandKdeModeListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, struct kde_output_device_mode_v2* mode) {
41 WaylandDisplay* wldata = (WaylandDisplay*) data;
42 if (!wldata->internal) {
43 return;
44 }
45
46 WaylandKdeMode* newMode = FF_LIST_ADD(WaylandKdeMode, *(FFlist*) wldata->internal);
47 *newMode = (WaylandKdeMode) { .pMode = mode };
48
49 // Strangely, the listener is called only in this function, but not in `waylandKdeCurrentModeListener`
50 wldata->parent->ffwl_proxy_add_listener((struct wl_proxy*) mode, (void (**)(void)) &modeListener, newMode);
51}
52
53static void waylandKdeCurrentModeListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, struct kde_output_device_mode_v2* mode) {
54 // waylandKdeModeListener is always run before this
55 WaylandDisplay* wldata = (WaylandDisplay*) data;
56 if (!wldata->internal) {
57 return;
58 }
59
60 bool foundCurrent = false, foundPreferred = false;
61 FF_LIST_FOR_EACH (WaylandKdeMode, m, *(FFlist*) wldata->internal) {
62 if (!foundCurrent && m->pMode == mode) {
63 wldata->width = m->width;
64 wldata->height = m->height;
65 wldata->refreshRate = m->refreshRate;
66 foundCurrent = true;
67 }
68 if (!foundPreferred && m->preferred) {
69 wldata->preferredWidth = m->width;
70 wldata->preferredHeight = m->height;
71 wldata->preferredRefreshRate = m->refreshRate;
72 foundPreferred = true;
73 }
74 if (foundCurrent && foundPreferred) {
75 break;
76 }
77 }
78}
79
80static void waylandKdeScaleListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, wl_fixed_t scale) {
81 WaylandDisplay* wldata = (WaylandDisplay*) data;
82 wldata->dpi = (uint32_t) scale * 3 / 8; // wl_fixed_to_double(scale) * 96;
83}
84
85static void waylandKdeEdidListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, const char* raw) {
86 if (!*raw) {
87 return;
88 }
89 WaylandDisplay* wldata = (WaylandDisplay*) data;
90 FF_STRBUF_AUTO_DESTROY b64 = ffStrbufCreateStatic(raw);
91 FF_STRBUF_AUTO_DESTROY edid = ffBase64DecodeStrbuf(&b64);
92 if (edid.length < 128) {
93 return;
94 }
95 ffEdidGetName((const uint8_t*) edid.chars, &wldata->edidName);
96 wldata->hdrSupported = ffEdidGetHdrCompatible((const uint8_t*) edid.chars, edid.length);
97 ffEdidGetManufactureDate((const uint8_t*) edid.chars, &wldata->myear, &wldata->mweek);
98 ffEdidGetSerial((const uint8_t*) edid.chars, &wldata->serial);
99 wldata->hdrInfoAvailable = true;
100}
101
102static void waylandKdeEnabledListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, int32_t enabled) {
103 WaylandDisplay* wldata = (WaylandDisplay*) data;
104 if (!enabled) {
105 wldata->internal = nullptr;
106 }
107}
108
109static void waylandKdeGeometryListener(void* data,
110 [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2,
111 [[maybe_unused]] int32_t x,
112 [[maybe_unused]] int32_t y,
113 int32_t physical_width,
114 int32_t physical_height,
115 [[maybe_unused]] int32_t subpixel,
116 [[maybe_unused]] const char* make,
117 [[maybe_unused]] const char* model,
118 int32_t transform) {
119 WaylandDisplay* display = data;
120 display->physicalWidth = physical_width;
121 display->physicalHeight = physical_height;
122 display->transform = (enum wl_output_transform) transform;
123}
124
125static void waylandKdeNameListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2, const char* name) {
126 WaylandDisplay* display = data;
127 display->type = ffdsGetDisplayType(name);
128 // As display->id is used as an internal identifier, we don't need it to be NUL terminated
129 strncpy((char*) &display->id, name, sizeof(display->id));
130
131 ffStrbufAppendS(&display->name, name);
132}
133
134static void waylandKdeHdrListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2, uint32_t hdr_enabled) {
135 WaylandDisplay* display = data;
136 display->hdrEnabled = !!hdr_enabled;
137}
138
139static void waylandKdeMaxBitsPerColorListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2, uint32_t max_bpc) {
140 WaylandDisplay* display = data;
141 display->bitDepth = (uint8_t) max_bpc;
142}
143
144static void waylandKdePriorityListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2, uint32_t priority) {
145 WaylandDisplay* display = data;
146 display->primary = priority == 1;
147}
148
149static void waylandKdeDoneListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2) {
150 WaylandDisplay* display = data;
151 display->done = true;
152}
153
154static struct kde_output_device_v2_listener outputListener = {
155 .geometry = waylandKdeGeometryListener,
156 .current_mode = waylandKdeCurrentModeListener,
157 .mode = waylandKdeModeListener,
158 .done = waylandKdeDoneListener,
159 .scale = waylandKdeScaleListener,
160 .edid = waylandKdeEdidListener,
161 .enabled = waylandKdeEnabledListener,
162 .uuid = (void*) ffUnused,
163 .serial_number = (void*) ffUnused,
164 .eisa_id = (void*) ffUnused,
165 .capabilities = (void*) ffUnused,
166 .overscan = (void*) ffUnused,
167 .vrr_policy = (void*) ffUnused,
168 .rgb_range = (void*) ffUnused,
169 .name = waylandKdeNameListener,
170 .high_dynamic_range = waylandKdeHdrListener,
171 .sdr_brightness = (void*) ffUnused,
172 .wide_color_gamut = (void*) ffUnused,
173 .auto_rotate_policy = (void*) ffUnused,
174 .icc_profile_path = (void*) ffUnused,
175 .brightness_metadata = (void*) ffUnused,
176 .brightness_overrides = (void*) ffUnused,
177 .sdr_gamut_wideness = (void*) ffUnused,
178 .color_profile_source = (void*) ffUnused,
179 .brightness = (void*) ffUnused,
180 .color_power_tradeoff = (void*) ffUnused,
181 .dimming = (void*) ffUnused,
182 .replication_source = (void*) ffUnused,
183 .ddc_ci_allowed = (void*) ffUnused,
184 .max_bits_per_color = (void*) waylandKdeMaxBitsPerColorListener,
185 .max_bits_per_color_range = (void*) ffUnused,
186 .automatic_max_bits_per_color_limit = (void*) ffUnused,
187 .edr_policy = (void*) ffUnused,
188 .sharpness = (void*) ffUnused,
189 .priority = waylandKdePriorityListener,
190 .auto_brightness = (void*) ffUnused,
191 .removed = (void*) ffUnused,
192 .hdr_icc_profile_path = (void*) ffUnused,
193 .hdr_color_profile_source = (void*) ffUnused,
194 .abm_level = (void*) ffUnused,
195};
196
197static const char* waylandKdeHandleOutput(WaylandData* wldata, struct wl_proxy* output) {
198 FF_LIST_AUTO_DESTROY modes = ffListCreate();
199 WaylandDisplay display = {
200 .parent = wldata,
201 .transform = WL_OUTPUT_TRANSFORM_NORMAL,
202 .type = FF_DISPLAY_TYPE_UNKNOWN,
203 .name = ffStrbufCreate(),
204 .description = ffStrbufCreate(),
205 .edidName = ffStrbufCreate(),
206 .internal = &modes,
207 };
208
209 if (wldata->ffwl_proxy_add_listener(output, (void (**)(void)) &outputListener, &display) < 0) {
210 wldata->ffwl_proxy_destroy(output);
211 return "Failed to add listener to kde_output_device_v2";
212 }
213
214 if (wldata->ffwl_display_roundtrip(wldata->display) < 0) {
215 wldata->ffwl_proxy_destroy(output);
216 return "Failed to roundtrip kde_output_device_v2";
217 }
218 if (!display.done) {
219 const char* error = ffWaylandWaitForDone(&display);
220 if (error) {
221 wldata->ffwl_proxy_destroy(output);
222 return error;
223 }
224 }
225 // Destroy any mode proxies that were created during the listeners.
226 // wl proxies created for modes are not automatically freed by destroying
227 // the parent output proxy, so destroy them explicitly to avoid leaks.
228 FF_LIST_FOR_EACH (WaylandKdeMode, m, modes) {
229 if (m->pMode) {
230 wldata->ffwl_proxy_destroy((struct wl_proxy*) m->pMode);
231 m->pMode = nullptr;
232 }
233 }
234
235 wldata->ffwl_proxy_destroy(output);
236
237 if (display.width <= 0 || display.height <= 0 || !display.internal) {
238 return "Failed to get display information from kde_output_device_v2";
239 }
240
241 uint32_t rotation = ffWaylandHandleRotation(&display);
242
243 FFDisplayResult* item = ffdsAppendDisplay(wldata->result,
244 (uint32_t) display.width,
245 (uint32_t) display.height,
246 display.refreshRate / 1000.0,
247 display.dpi,
248 (uint32_t) display.preferredWidth,
249 (uint32_t) display.preferredHeight,
250 display.preferredRefreshRate / 1000.0,
251 rotation,
252 display.edidName.length
253 ? &display.edidName
254 : &display.name,
255 display.type,
256 display.primary,
257 display.id,
258 (uint32_t) display.physicalWidth,
259 (uint32_t) display.physicalHeight,
260 "wayland-kde");
261 if (item) {
262 if (display.hdrEnabled) {
263 item->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;
264 } else if (display.hdrSupported) {
265 item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;
266 } else if (display.hdrInfoAvailable) {
267 item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
268 } else {
269 item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;
270 }
271
272 item->manufactureYear = display.myear;
273 item->manufactureWeek = display.mweek;
274 ffStrbufInitMove(&item->serial, &display.serial);
275 item->bitDepth = display.bitDepth;
276 }
277
278 ffStrbufDestroy(&display.description);
279 ffStrbufDestroy(&display.name);
280 ffStrbufDestroy(&display.edidName);
281
282 return nullptr;
283}
284
285const char* ffWaylandHandleKdeOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
286 // TODO: remove this in future versions
287 uint32_t bindVersion = min(version, KDE_OUTPUT_DEVICE_V2_PRIORITY_SINCE_VERSION);
288 struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_device_v2_interface, bindVersion, name, kde_output_device_v2_interface.name, bindVersion, nullptr);
289 if (output == nullptr) {
290 return "Failed to create kde_output_device_v2";
291 }
292
293 return waylandKdeHandleOutput(wldata, output);
294}
295
296static void waylandKdeOutputListener(void* data, [[maybe_unused]] struct kde_output_device_registry_v2* kde_output_device_registry_v2, struct kde_output_device_v2* output) {
297 waylandKdeHandleOutput((WaylandData*) data, (struct wl_proxy*) output);
298}
299
300static struct kde_output_device_registry_v2_listener registryListener = {
301 .output = waylandKdeOutputListener,
302 .finished = (void*) ffUnused,
303};
304
305const char* ffWaylandHandleKdeOutputRegistry(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
306 uint32_t bindVersion = min(version, KDE_OUTPUT_DEVICE_REGISTRY_V2_OUTPUT_SINCE_VERSION);
307 struct wl_proxy* outputRegistry = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_device_registry_v2_interface, bindVersion, name, kde_output_device_registry_v2_interface.name, bindVersion, nullptr);
308 if (outputRegistry == nullptr) {
309 return "Failed to create kde_output_device_registry_v2";
310 }
311 if (wldata->ffwl_proxy_add_listener(outputRegistry, (void (**)(void)) ®istryListener, wldata) < 0) {
312 wldata->ffwl_proxy_destroy(outputRegistry);
313 return "Failed to add listener to kde_output_device_registry_v2";
314 }
315 if (wldata->ffwl_display_roundtrip(wldata->display) < 0) {
316 wldata->ffwl_proxy_destroy(outputRegistry);
317 return "Failed to roundtrip kde_output_device_registry_v2";
318 }
319
320 wldata->ffwl_proxy_destroy(outputRegistry);
321 return nullptr;
322}
323
324#endif