master ohmu/fastfetch-openbsd / src / detection / displayserver / linux / xcb.c
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