ohmu
·
2026-10-15
1#include "displayserver_linux.h"
2
3#ifdef FF_HAVE_XCB_RANDR
4
5 #include "common/library.h"
6 #include "common/properties.h"
7 #include "common/edidHelper.h"
8 #include "common/mallocHelper.h"
9 #include "common/strutil.h"
10
11 #include <stdlib.h>
12 #include <string.h>
13 #include <xcb/randr.h>
14 #include <xcb/xcb.h>
15
16typedef struct XcbRandrData {
17 FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current)
18 FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current_reply)
19 FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current_modes_iterator)
20 FF_LIBRARY_SYMBOL(xcb_randr_mode_info_next)
21 FF_LIBRARY_SYMBOL(xcb_randr_get_monitors)
22 FF_LIBRARY_SYMBOL(xcb_randr_get_monitors_reply)
23 FF_LIBRARY_SYMBOL(xcb_randr_get_monitors_monitors_iterator)
24 FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_next)
25 FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_outputs_length)
26 FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_outputs)
27 FF_LIBRARY_SYMBOL(xcb_randr_output_next)
28 FF_LIBRARY_SYMBOL(xcb_randr_get_output_info)
29 FF_LIBRARY_SYMBOL(xcb_randr_get_output_info_reply)
30 FF_LIBRARY_SYMBOL(xcb_randr_get_crtc_info)
31 FF_LIBRARY_SYMBOL(xcb_randr_get_crtc_info_reply)
32 FF_LIBRARY_SYMBOL(xcb_randr_get_output_property)
33 FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_reply)
34 FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_data)
35 FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_data_length)
36
37 FF_LIBRARY_SYMBOL(xcb_intern_atom)
38 FF_LIBRARY_SYMBOL(xcb_intern_atom_reply)
39 FF_LIBRARY_SYMBOL(xcb_get_property)
40 FF_LIBRARY_SYMBOL(xcb_get_property_reply)
41 FF_LIBRARY_SYMBOL(xcb_get_property_value)
42 FF_LIBRARY_SYMBOL(xcb_get_property_value_length)
43 FF_LIBRARY_SYMBOL(xcb_get_atom_name)
44 FF_LIBRARY_SYMBOL(xcb_get_atom_name_name)
45 FF_LIBRARY_SYMBOL(xcb_get_atom_name_name_length)
46 FF_LIBRARY_SYMBOL(xcb_get_atom_name_reply)
47 FF_LIBRARY_SYMBOL(xcb_get_setup)
48 FF_LIBRARY_SYMBOL(xcb_setup_vendor)
49 FF_LIBRARY_SYMBOL(xcb_setup_vendor_length)
50
51 // init once
52 xcb_connection_t* connection;
53 FFDisplayServerResult* result;
54} XcbRandrData;
55
56static void* xcbGetProperty(XcbRandrData* data, xcb_window_t window, const char* request) {
57 xcb_intern_atom_cookie_t requestAtomCookie = data->ffxcb_intern_atom(data->connection, true, (uint16_t) strlen(request), request);
58 FF_AUTO_FREE xcb_intern_atom_reply_t* requestAtomReply = data->ffxcb_intern_atom_reply(data->connection, requestAtomCookie, nullptr);
59 if (requestAtomReply == nullptr) {
60 return nullptr;
61 }
62
63 xcb_get_property_cookie_t propertyCookie = data->ffxcb_get_property(data->connection, false, window, requestAtomReply->atom, XCB_ATOM_ANY, 0, 8 * 1024);
64 FF_AUTO_FREE xcb_get_property_reply_t* propertyReply = data->ffxcb_get_property_reply(data->connection, propertyCookie, nullptr);
65 if (propertyReply == nullptr) {
66 return nullptr;
67 }
68
69 int length = data->ffxcb_get_property_value_length(propertyReply);
70 if (length <= 0) {
71 return nullptr;
72 }
73
74 // Why are xcb property strings not null terminated???
75 void* replyValue = malloc((size_t) length + 1);
76 memcpy(replyValue, data->ffxcb_get_property_value(propertyReply), (size_t) length);
77 ((char*) replyValue)[length] = '\0';
78
79 return replyValue;
80}
81
82static xcb_randr_get_output_property_reply_t* xcbRandrGetProperty(XcbRandrData* data, xcb_randr_output_t output, const char* name) {
83 xcb_intern_atom_cookie_t requestAtomCookie = data->ffxcb_intern_atom(data->connection, true, (uint16_t) strlen(name), name);
84 FF_AUTO_FREE xcb_intern_atom_reply_t* requestAtomReply = data->ffxcb_intern_atom_reply(data->connection, requestAtomCookie, nullptr);
85
86 if (requestAtomReply) {
87 xcb_randr_get_output_property_cookie_t outputPropertyCookie = data->ffxcb_randr_get_output_property(data->connection, output, requestAtomReply->atom, XCB_GET_PROPERTY_TYPE_ANY, 0, 100, false, false);
88 return data->ffxcb_randr_get_output_property_reply(data->connection, outputPropertyCookie, nullptr);
89 }
90 return nullptr;
91}
92
93static void xcbDetectWMfromEWMH(XcbRandrData* data, xcb_window_t rootWindow, FFDisplayServerResult* result) {
94 if (result->wmProcessName.length > 0 || ffStrbufEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND)) {
95 return;
96 }
97
98 FF_AUTO_FREE xcb_window_t* wmWindow = (xcb_window_t*) xcbGetProperty(data, rootWindow, "_NET_SUPPORTING_WM_CHECK");
99 if (wmWindow == nullptr) {
100 return;
101 }
102
103 FF_AUTO_FREE char* wmName = (char*) xcbGetProperty(data, *wmWindow, "WM_NAME");
104 if (!ffStrSet(wmName)) {
105 wmName = (char*) xcbGetProperty(data, *wmWindow, "_NET_WM_NAME");
106 }
107
108 if (!ffStrSet(wmName)) {
109 return;
110 }
111
112 ffStrbufSetS(&result->wmProcessName, wmName);
113}
114
115static void xcbFetchServerVendor(XcbRandrData* data, FFDisplayServerResult* result) {
116 const xcb_setup_t* setup = data->ffxcb_get_setup(data->connection);
117
118 int length = data->ffxcb_setup_vendor_length(setup);
119 if (length <= 0) {
120 return;
121 }
122
123 FF_STRBUF_AUTO_DESTROY serverVendor = ffStrbufCreateNS((uint32_t) length, data->ffxcb_setup_vendor(setup));
124
125 if (!ffStrbufEqualS(&serverVendor, "The X.Org Foundation")) // Original
126 {
127 ffStrbufDestroy(&result->wmProtocolName);
128 ffStrbufInitMove(&result->wmProtocolName, &serverVendor);
129 }
130}
131
132static bool xcbRandrHandleOutput(XcbRandrData* data, xcb_randr_output_t output, FFstrbuf* name, bool primary, FFDisplayType displayType, struct xcb_randr_get_screen_resources_current_reply_t* screenResources, uint8_t bitDepth, uint32_t dpi) {
133 xcb_randr_get_output_info_cookie_t outputInfoCookie = data->ffxcb_randr_get_output_info(data->connection, output, XCB_CURRENT_TIME);
134 FF_AUTO_FREE xcb_randr_get_output_info_reply_t* outputInfoReply = data->ffxcb_randr_get_output_info_reply(data->connection, outputInfoCookie, nullptr);
135 if (outputInfoReply == nullptr) {
136 return false;
137 }
138
139 FF_AUTO_FREE xcb_randr_get_output_property_reply_t* edidReply = xcbRandrGetProperty(data, output, "EDID");
140 uint8_t* edidData = nullptr;
141 uint32_t edidLength = 0;
142 if (edidReply) {
143 int len = data->ffxcb_randr_get_output_property_data_length(edidReply);
144 if (len >= 128) {
145 edidData = data->ffxcb_randr_get_output_property_data(edidReply);
146 edidLength = (uint32_t) len;
147 }
148 }
149
150 if (edidData) {
151 ffStrbufClear(name);
152 ffEdidGetName(edidData, name);
153 }
154
155 bool randrEmulation = false;
156 FF_AUTO_FREE xcb_randr_get_output_property_reply_t* randrEmulationReply = xcbRandrGetProperty(data, output, "RANDR Emulation");
157 if (randrEmulationReply) {
158 int len = data->ffxcb_randr_get_output_property_data_length(randrEmulationReply);
159 if (len >= 1) {
160 randrEmulation = !!data->ffxcb_randr_get_output_property_data(randrEmulationReply)[0];
161 }
162 }
163
164 xcb_randr_get_crtc_info_cookie_t crtcInfoCookie = data->ffxcb_randr_get_crtc_info(data->connection, outputInfoReply->crtc, XCB_CURRENT_TIME);
165 FF_AUTO_FREE xcb_randr_get_crtc_info_reply_t* crtcInfoReply = data->ffxcb_randr_get_crtc_info_reply(data->connection, crtcInfoCookie, nullptr);
166 if (crtcInfoReply == nullptr) {
167 return false;
168 }
169
170 uint32_t rotation;
171 switch (crtcInfoReply->rotation) {
172 case XCB_RANDR_ROTATION_ROTATE_90:
173 rotation = 90;
174 break;
175 case XCB_RANDR_ROTATION_ROTATE_180:
176 rotation = 180;
177 break;
178 case XCB_RANDR_ROTATION_ROTATE_270:
179 rotation = 270;
180 break;
181 default:
182 rotation = 0;
183 break;
184 }
185
186 xcb_randr_mode_info_t* currentMode = nullptr;
187 xcb_randr_mode_info_t* preferredMode = nullptr;
188
189 if (screenResources) {
190 xcb_randr_mode_info_iterator_t modesIterator = data->ffxcb_randr_get_screen_resources_current_modes_iterator(screenResources);
191
192 if (outputInfoReply->num_preferred > 0) {
193 preferredMode = modesIterator.data;
194 }
195
196 while (modesIterator.rem > 0) {
197 if (modesIterator.data->id == crtcInfoReply->mode) {
198 currentMode = modesIterator.data;
199 break;
200 }
201
202 data->ffxcb_randr_mode_info_next(&modesIterator);
203 }
204 }
205
206 FFDisplayResult* item = ffdsAppendDisplay(
207 data->result,
208 (uint32_t) (currentMode ? currentMode->width : crtcInfoReply->width),
209 (uint32_t) (currentMode ? currentMode->height : crtcInfoReply->height),
210 currentMode ? (double) currentMode->dot_clock / (double) ((uint32_t) currentMode->htotal * currentMode->vtotal) : 0,
211 dpi,
212 preferredMode ? (uint32_t) preferredMode->width : 0,
213 preferredMode ? (uint32_t) preferredMode->height : 0,
214 preferredMode ? (double) preferredMode->dot_clock / (double) ((uint32_t) preferredMode->htotal * preferredMode->vtotal) : 0,
215 rotation,
216 name,
217 displayType,
218 primary,
219 0,
220 (uint32_t) outputInfoReply->mm_width,
221 (uint32_t) outputInfoReply->mm_height,
222 randrEmulation
223 ? (currentMode ? "xcb-randr-emu-mode" : "xcb-randr-emu-crtc")
224 : (currentMode ? "xcb-randr-mode" : "xcb-randr-crtc")
225
226 );
227 if (item) {
228 if (edidData && edidLength >= 128) {
229 item->hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
230 ffEdidGetManufactureDate(edidData, &item->manufactureYear, &item->manufactureWeek);
231 ffEdidGetSerial(edidData, &item->serial);
232 }
233 item->bitDepth = bitDepth;
234 if ((rotation == 90 || rotation == 180) && !randrEmulation) {
235 // In XWayland mode, width / height has been swapped out of box
236 uint32_t tmp = item->width;
237 item->width = item->height;
238 item->height = tmp;
239 }
240 }
241
242 return !!item;
243}
244
245static bool xcbRandrHandleMonitor(XcbRandrData* data, xcb_randr_monitor_info_t* monitor, struct xcb_randr_get_screen_resources_current_reply_t* screenResources, uint8_t bitDepth, uint32_t dpi) {
246 // for some reasons, we have to construct this our self
247 xcb_randr_output_iterator_t outputIterator = {
248 .index = 0,
249 .data = data->ffxcb_randr_monitor_info_outputs(monitor),
250 .rem = data->ffxcb_randr_monitor_info_outputs_length(monitor)
251 };
252
253 FF_AUTO_FREE xcb_get_atom_name_reply_t* nameReply = data->ffxcb_get_atom_name_reply(
254 data->connection,
255 data->ffxcb_get_atom_name(data->connection, monitor->name),
256 nullptr);
257 FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateNS(
258 (uint32_t) data->ffxcb_get_atom_name_name_length(nameReply),
259 data->ffxcb_get_atom_name_name(nameReply));
260 const FFDisplayType displayType = ffdsGetDisplayType(name.chars);
261
262 bool foundOutput = false;
263
264 while (outputIterator.rem > 0) {
265 if (xcbRandrHandleOutput(data, *outputIterator.data, &name, monitor->primary, displayType, screenResources, bitDepth, dpi)) {
266 foundOutput = true;
267 }
268 data->ffxcb_randr_output_next(&outputIterator);
269 }
270
271 if (foundOutput) {
272 return true;
273 }
274
275 FFDisplayResult* display = ffdsAppendDisplay(
276 data->result,
277 (uint32_t) monitor->width,
278 (uint32_t) monitor->height,
279 0,
280 dpi,
281 0,
282 0,
283 0,
284 0,
285 &name,
286 displayType,
287 !!monitor->primary,
288 0,
289 (uint32_t) monitor->width_in_millimeters,
290 (uint32_t) monitor->height_in_millimeters,
291 "xcb-randr-monitor");
292 if (display) {
293 display->bitDepth = bitDepth;
294 }
295 return !!display;
296}
297
298static bool xcbRandrHandleMonitors(XcbRandrData* data, xcb_screen_t* screen) {
299 xcb_randr_get_monitors_cookie_t monitorsCookie = data->ffxcb_randr_get_monitors(data->connection, screen->root, true);
300 FF_AUTO_FREE xcb_randr_get_monitors_reply_t* monitorsReply = data->ffxcb_randr_get_monitors_reply(data->connection, monitorsCookie, nullptr);
301 if (monitorsReply == nullptr) {
302 return false;
303 }
304
305 // Init screen resources. They are used to iterate over all modes. xcbRandrHandleMode checks for " == nullptr", to fail as late as possible.
306 xcb_randr_get_screen_resources_current_cookie_t screenResourcesCookie = data->ffxcb_randr_get_screen_resources_current(data->connection, screen->root);
307 FF_AUTO_FREE struct xcb_randr_get_screen_resources_current_reply_t* screenResources = data->ffxcb_randr_get_screen_resources_current_reply(data->connection, screenResourcesCookie, nullptr);
308
309 uint32_t dpi = 0;
310 FF_AUTO_FREE const char* resourceManager = xcbGetProperty(data, screen->root, "RESOURCE_MANAGER");
311 if (resourceManager) {
312 FF_STRBUF_AUTO_DESTROY dpiStr = ffStrbufCreate();
313 if (ffParsePropLines(resourceManager, "Xft.dpi:", &dpiStr)) {
314 dpi = (uint32_t) ffStrbufToUInt(&dpiStr, 96);
315 }
316 }
317 uint8_t bitDepth = (uint8_t) (screen->root_depth / 3);
318
319 xcb_randr_monitor_info_iterator_t monitorInfoIterator = data->ffxcb_randr_get_monitors_monitors_iterator(monitorsReply);
320
321 bool foundMonitor = false;
322
323 while (monitorInfoIterator.rem > 0) {
324 if (xcbRandrHandleMonitor(data, monitorInfoIterator.data, screenResources, bitDepth, dpi)) {
325 foundMonitor = true;
326 }
327 data->ffxcb_randr_monitor_info_next(&monitorInfoIterator);
328 }
329
330 return foundMonitor;
331}
332
333static void xcbRandrHandleScreen(XcbRandrData* data, xcb_screen_t* screen) {
334 // With all the initialization done, start the detection
335 if (xcbRandrHandleMonitors(data, screen)) {
336 return;
337 }
338
339 // If detetction failed, fallback to screen = monitor, like in the libxcb.so implementation
340 ffdsAppendDisplay(
341 data->result,
342 (uint32_t) screen->width_in_pixels,
343 (uint32_t) screen->height_in_pixels,
344 0,
345 0,
346 0,
347 0,
348 0,
349 0,
350 nullptr,
351 FF_DISPLAY_TYPE_UNKNOWN,
352 false,
353 (uint64_t) screen->root,
354 (uint32_t) screen->width_in_millimeters,
355 (uint32_t) screen->height_in_millimeters,
356 "xcb-randr-screen");
357}
358
359const char* ffdsConnectXcbRandr(FFDisplayServerResult* result) {
360 FF_LIBRARY_LOAD_MESSAGE(xcbRandr, "libxcb-randr" FF_LIBRARY_EXTENSION, 1)
361 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_connect)
362 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_connection_has_error)
363 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_get_setup)
364 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_setup_roots_iterator)
365 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_screen_next)
366 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_disconnect)
367
368 XcbRandrData data;
369
370 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_intern_atom)
371 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_intern_atom_reply)
372 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property)
373 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_reply)
374 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_value)
375 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_value_length)
376 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name)
377 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_name)
378 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_name_length)
379 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_reply)
380 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_setup)
381 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_setup_vendor)
382 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_setup_vendor_length)
383
384 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current)
385 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current_reply)
386 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current_modes_iterator)
387 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_mode_info_next)
388 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors)
389 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors_reply)
390 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors_monitors_iterator)
391 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_next)
392 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_outputs_length)
393 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_outputs)
394 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_output_next)
395 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_info)
396 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_info_reply)
397 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property)
398 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_reply)
399 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_data)
400 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_data_length)
401 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_crtc_info)
402 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_crtc_info_reply)
403
404 data.connection = ffxcb_connect(nullptr, nullptr);
405 if (ffxcb_connection_has_error(data.connection) > 0) {
406 ffxcb_disconnect(data.connection);
407 return "xcb_connect() failed";
408 }
409
410 data.result = result;
411
412 xcb_screen_iterator_t iterator = ffxcb_setup_roots_iterator(ffxcb_get_setup(data.connection));
413
414 if (iterator.rem > 0) {
415 xcbDetectWMfromEWMH(&data, iterator.data->root, result);
416 xcbFetchServerVendor(&data, result);
417 }
418
419 while (iterator.rem > 0) {
420 xcbRandrHandleScreen(&data, iterator.data);
421 ffxcb_screen_next(&iterator);
422 }
423
424 ffxcb_disconnect(data.connection);
425
426 // If wayland hasn't set this, connection failed for it. So we are running only a X Server, not XWayland.
427 if (result->wmProtocolName.length == 0) {
428 ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_X11);
429 }
430
431 return nullptr;
432}
433
434#else
435
436const char* ffdsConnectXcbRandr(FFDisplayServerResult* result) {
437 // Do nothing. There are other implementations coming
438 FF_UNUSED(result)
439 return "Fastfetch was compiled without libxcb-randr support";
440}
441
442#endif