From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/detection/displayserver/linux/xcb.c | 441 ++++++++++++++++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 src/detection/displayserver/linux/xcb.c (limited to 'src/detection/displayserver/linux/xcb.c') diff --git a/src/detection/displayserver/linux/xcb.c b/src/detection/displayserver/linux/xcb.c new file mode 100644 index 0000000..8620d71 --- /dev/null +++ b/src/detection/displayserver/linux/xcb.c @@ -0,0 +1,441 @@ +#include "displayserver_linux.h" + +#ifdef FF_HAVE_XCB_RANDR + + #include "common/library.h" + #include "common/properties.h" + #include "common/edidHelper.h" + #include "common/mallocHelper.h" + #include "common/strutil.h" + + #include + #include + #include + #include + +typedef struct XcbRandrData { + FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current) + FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current_reply) + FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current_modes_iterator) + FF_LIBRARY_SYMBOL(xcb_randr_mode_info_next) + FF_LIBRARY_SYMBOL(xcb_randr_get_monitors) + FF_LIBRARY_SYMBOL(xcb_randr_get_monitors_reply) + FF_LIBRARY_SYMBOL(xcb_randr_get_monitors_monitors_iterator) + FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_next) + FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_outputs_length) + FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_outputs) + FF_LIBRARY_SYMBOL(xcb_randr_output_next) + FF_LIBRARY_SYMBOL(xcb_randr_get_output_info) + FF_LIBRARY_SYMBOL(xcb_randr_get_output_info_reply) + FF_LIBRARY_SYMBOL(xcb_randr_get_crtc_info) + FF_LIBRARY_SYMBOL(xcb_randr_get_crtc_info_reply) + FF_LIBRARY_SYMBOL(xcb_randr_get_output_property) + FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_reply) + FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_data) + FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_data_length) + + FF_LIBRARY_SYMBOL(xcb_intern_atom) + FF_LIBRARY_SYMBOL(xcb_intern_atom_reply) + FF_LIBRARY_SYMBOL(xcb_get_property) + FF_LIBRARY_SYMBOL(xcb_get_property_reply) + FF_LIBRARY_SYMBOL(xcb_get_property_value) + FF_LIBRARY_SYMBOL(xcb_get_property_value_length) + FF_LIBRARY_SYMBOL(xcb_get_atom_name) + FF_LIBRARY_SYMBOL(xcb_get_atom_name_name) + FF_LIBRARY_SYMBOL(xcb_get_atom_name_name_length) + FF_LIBRARY_SYMBOL(xcb_get_atom_name_reply) + FF_LIBRARY_SYMBOL(xcb_get_setup) + FF_LIBRARY_SYMBOL(xcb_setup_vendor) + FF_LIBRARY_SYMBOL(xcb_setup_vendor_length) + + // init once + xcb_connection_t* connection; + FFDisplayServerResult* result; +} XcbRandrData; + +static void* xcbGetProperty(XcbRandrData* data, xcb_window_t window, const char* request) { + xcb_intern_atom_cookie_t requestAtomCookie = data->ffxcb_intern_atom(data->connection, true, (uint16_t) strlen(request), request); + FF_AUTO_FREE xcb_intern_atom_reply_t* requestAtomReply = data->ffxcb_intern_atom_reply(data->connection, requestAtomCookie, NULL); + if (requestAtomReply == NULL) { + return NULL; + } + + xcb_get_property_cookie_t propertyCookie = data->ffxcb_get_property(data->connection, false, window, requestAtomReply->atom, XCB_ATOM_ANY, 0, 8 * 1024); + FF_AUTO_FREE xcb_get_property_reply_t* propertyReply = data->ffxcb_get_property_reply(data->connection, propertyCookie, NULL); + if (propertyReply == NULL) { + return NULL; + } + + int length = data->ffxcb_get_property_value_length(propertyReply); + if (length <= 0) { + return NULL; + } + + // Why are xcb property strings not null terminated??? + void* replyValue = malloc((size_t) length + 1); + memcpy(replyValue, data->ffxcb_get_property_value(propertyReply), (size_t) length); + ((char*) replyValue)[length] = '\0'; + + return replyValue; +} + +static xcb_randr_get_output_property_reply_t* xcbRandrGetProperty(XcbRandrData* data, xcb_randr_output_t output, const char* name) { + xcb_intern_atom_cookie_t requestAtomCookie = data->ffxcb_intern_atom(data->connection, true, (uint16_t) strlen(name), name); + FF_AUTO_FREE xcb_intern_atom_reply_t* requestAtomReply = data->ffxcb_intern_atom_reply(data->connection, requestAtomCookie, NULL); + + if (requestAtomReply) { + 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); + return data->ffxcb_randr_get_output_property_reply(data->connection, outputPropertyCookie, NULL); + } + return NULL; +} + +static void xcbDetectWMfromEWMH(XcbRandrData* data, xcb_window_t rootWindow, FFDisplayServerResult* result) { + if (result->wmProcessName.length > 0 || ffStrbufEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND)) { + return; + } + + FF_AUTO_FREE xcb_window_t* wmWindow = (xcb_window_t*) xcbGetProperty(data, rootWindow, "_NET_SUPPORTING_WM_CHECK"); + if (wmWindow == NULL) { + return; + } + + FF_AUTO_FREE char* wmName = (char*) xcbGetProperty(data, *wmWindow, "WM_NAME"); + if (!ffStrSet(wmName)) { + wmName = (char*) xcbGetProperty(data, *wmWindow, "_NET_WM_NAME"); + } + + if (!ffStrSet(wmName)) { + return; + } + + ffStrbufSetS(&result->wmProcessName, wmName); +} + +static void xcbFetchServerVendor(XcbRandrData* data, FFDisplayServerResult* result) { + const xcb_setup_t* setup = data->ffxcb_get_setup(data->connection); + + int length = data->ffxcb_setup_vendor_length(setup); + if (length <= 0) { + return; + } + + FF_STRBUF_AUTO_DESTROY serverVendor = ffStrbufCreateNS((uint32_t) length, data->ffxcb_setup_vendor(setup)); + + if (!ffStrbufEqualS(&serverVendor, "The X.Org Foundation")) // Original + { + ffStrbufDestroy(&result->wmProtocolName); + ffStrbufInitMove(&result->wmProtocolName, &serverVendor); + } +} + +static 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) { + xcb_randr_get_output_info_cookie_t outputInfoCookie = data->ffxcb_randr_get_output_info(data->connection, output, XCB_CURRENT_TIME); + FF_AUTO_FREE xcb_randr_get_output_info_reply_t* outputInfoReply = data->ffxcb_randr_get_output_info_reply(data->connection, outputInfoCookie, NULL); + if (outputInfoReply == NULL) { + return false; + } + + FF_AUTO_FREE xcb_randr_get_output_property_reply_t* edidReply = xcbRandrGetProperty(data, output, "EDID"); + uint8_t* edidData = NULL; + uint32_t edidLength = 0; + if (edidReply) { + int len = data->ffxcb_randr_get_output_property_data_length(edidReply); + if (len >= 128) { + edidData = data->ffxcb_randr_get_output_property_data(edidReply); + edidLength = (uint32_t) len; + } + } + + if (edidData) { + ffStrbufClear(name); + ffEdidGetName(edidData, name); + } + + bool randrEmulation = false; + FF_AUTO_FREE xcb_randr_get_output_property_reply_t* randrEmulationReply = xcbRandrGetProperty(data, output, "RANDR Emulation"); + if (randrEmulationReply) { + int len = data->ffxcb_randr_get_output_property_data_length(randrEmulationReply); + if (len >= 1) { + randrEmulation = !!data->ffxcb_randr_get_output_property_data(randrEmulationReply)[0]; + } + } + + xcb_randr_get_crtc_info_cookie_t crtcInfoCookie = data->ffxcb_randr_get_crtc_info(data->connection, outputInfoReply->crtc, XCB_CURRENT_TIME); + FF_AUTO_FREE xcb_randr_get_crtc_info_reply_t* crtcInfoReply = data->ffxcb_randr_get_crtc_info_reply(data->connection, crtcInfoCookie, NULL); + if (crtcInfoReply == NULL) { + return false; + } + + uint32_t rotation; + switch (crtcInfoReply->rotation) { + case XCB_RANDR_ROTATION_ROTATE_90: + rotation = 90; + break; + case XCB_RANDR_ROTATION_ROTATE_180: + rotation = 180; + break; + case XCB_RANDR_ROTATION_ROTATE_270: + rotation = 270; + break; + default: + rotation = 0; + break; + } + + xcb_randr_mode_info_t* currentMode = NULL; + xcb_randr_mode_info_t* preferredMode = NULL; + + if (screenResources) { + xcb_randr_mode_info_iterator_t modesIterator = data->ffxcb_randr_get_screen_resources_current_modes_iterator(screenResources); + + if (outputInfoReply->num_preferred > 0) { + preferredMode = modesIterator.data; + } + + while (modesIterator.rem > 0) { + if (modesIterator.data->id == crtcInfoReply->mode) { + currentMode = modesIterator.data; + break; + } + + data->ffxcb_randr_mode_info_next(&modesIterator); + } + } + + FFDisplayResult* item = ffdsAppendDisplay( + data->result, + (uint32_t) (currentMode ? currentMode->width : crtcInfoReply->width), + (uint32_t) (currentMode ? currentMode->height : crtcInfoReply->height), + currentMode ? (double) currentMode->dot_clock / (double) ((uint32_t) currentMode->htotal * currentMode->vtotal) : 0, + dpi, + preferredMode ? (uint32_t) preferredMode->width : 0, + preferredMode ? (uint32_t) preferredMode->height : 0, + preferredMode ? (double) preferredMode->dot_clock / (double) ((uint32_t) preferredMode->htotal * preferredMode->vtotal) : 0, + rotation, + name, + displayType, + primary, + 0, + (uint32_t) outputInfoReply->mm_width, + (uint32_t) outputInfoReply->mm_height, + randrEmulation + ? (currentMode ? "xcb-randr-emu-mode" : "xcb-randr-emu-crtc") + : (currentMode ? "xcb-randr-mode" : "xcb-randr-crtc") + + ); + if (item) { + if (edidData && edidLength >= 128) { + item->hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED; + ffEdidGetSerialAndManufactureDate(edidData, &item->serial, &item->manufactureYear, &item->manufactureWeek); + } + item->bitDepth = bitDepth; + if ((rotation == 90 || rotation == 180) && !randrEmulation) { + // In XWayland mode, width / height has been swapped out of box + uint32_t tmp = item->width; + item->width = item->height; + item->height = tmp; + } + } + + return !!item; +} + +static 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) { + // for some reasons, we have to construct this our self + xcb_randr_output_iterator_t outputIterator = { + .index = 0, + .data = data->ffxcb_randr_monitor_info_outputs(monitor), + .rem = data->ffxcb_randr_monitor_info_outputs_length(monitor) + }; + + FF_AUTO_FREE xcb_get_atom_name_reply_t* nameReply = data->ffxcb_get_atom_name_reply( + data->connection, + data->ffxcb_get_atom_name(data->connection, monitor->name), + NULL); + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateNS( + (uint32_t) data->ffxcb_get_atom_name_name_length(nameReply), + data->ffxcb_get_atom_name_name(nameReply)); + const FFDisplayType displayType = ffdsGetDisplayType(name.chars); + + bool foundOutput = false; + + while (outputIterator.rem > 0) { + if (xcbRandrHandleOutput(data, *outputIterator.data, &name, monitor->primary, displayType, screenResources, bitDepth, dpi)) { + foundOutput = true; + } + data->ffxcb_randr_output_next(&outputIterator); + } + + if (foundOutput) { + return true; + } + + FFDisplayResult* display = ffdsAppendDisplay( + data->result, + (uint32_t) monitor->width, + (uint32_t) monitor->height, + 0, + dpi, + 0, + 0, + 0, + 0, + &name, + displayType, + !!monitor->primary, + 0, + (uint32_t) monitor->width_in_millimeters, + (uint32_t) monitor->height_in_millimeters, + "xcb-randr-monitor"); + if (display) { + display->bitDepth = bitDepth; + } + return !!display; +} + +static bool xcbRandrHandleMonitors(XcbRandrData* data, xcb_screen_t* screen) { + xcb_randr_get_monitors_cookie_t monitorsCookie = data->ffxcb_randr_get_monitors(data->connection, screen->root, true); + FF_AUTO_FREE xcb_randr_get_monitors_reply_t* monitorsReply = data->ffxcb_randr_get_monitors_reply(data->connection, monitorsCookie, NULL); + if (monitorsReply == NULL) { + return false; + } + + // Init screen resources. They are used to iterate over all modes. xcbRandrHandleMode checks for " == NULL", to fail as late as possible. + xcb_randr_get_screen_resources_current_cookie_t screenResourcesCookie = data->ffxcb_randr_get_screen_resources_current(data->connection, screen->root); + FF_AUTO_FREE struct xcb_randr_get_screen_resources_current_reply_t* screenResources = data->ffxcb_randr_get_screen_resources_current_reply(data->connection, screenResourcesCookie, NULL); + + uint32_t dpi = 0; + FF_AUTO_FREE const char* resourceManager = xcbGetProperty(data, screen->root, "RESOURCE_MANAGER"); + if (resourceManager) { + FF_STRBUF_AUTO_DESTROY dpiStr = ffStrbufCreate(); + if (ffParsePropLines(resourceManager, "Xft.dpi:", &dpiStr)) { + dpi = (uint32_t) ffStrbufToUInt(&dpiStr, 96); + } + } + uint8_t bitDepth = (uint8_t) (screen->root_depth / 3); + + xcb_randr_monitor_info_iterator_t monitorInfoIterator = data->ffxcb_randr_get_monitors_monitors_iterator(monitorsReply); + + bool foundMonitor = false; + + while (monitorInfoIterator.rem > 0) { + if (xcbRandrHandleMonitor(data, monitorInfoIterator.data, screenResources, bitDepth, dpi)) { + foundMonitor = true; + } + data->ffxcb_randr_monitor_info_next(&monitorInfoIterator); + } + + return foundMonitor; +} + +static void xcbRandrHandleScreen(XcbRandrData* data, xcb_screen_t* screen) { + // With all the initialisation done, start the detection + if (xcbRandrHandleMonitors(data, screen)) { + return; + } + + // If detetction failed, fallback to screen = monitor, like in the libxcb.so implementation + ffdsAppendDisplay( + data->result, + (uint32_t) screen->width_in_pixels, + (uint32_t) screen->height_in_pixels, + 0, + 0, + 0, + 0, + 0, + 0, + NULL, + FF_DISPLAY_TYPE_UNKNOWN, + false, + (uint64_t) screen->root, + (uint32_t) screen->width_in_millimeters, + (uint32_t) screen->height_in_millimeters, + "xcb-randr-screen"); +} + +const char* ffdsConnectXcbRandr(FFDisplayServerResult* result) { + FF_LIBRARY_LOAD_MESSAGE(xcbRandr, "libxcb-randr" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_connect) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_connection_has_error) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_get_setup) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_setup_roots_iterator) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_screen_next) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_disconnect) + + XcbRandrData data; + + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_intern_atom) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_intern_atom_reply) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_reply) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_value) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_value_length) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_name) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_name_length) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_reply) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_setup) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_setup_vendor) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_setup_vendor_length) + + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current_reply) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current_modes_iterator) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_mode_info_next) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors_reply) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors_monitors_iterator) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_next) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_outputs_length) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_outputs) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_output_next) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_info) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_info_reply) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_reply) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_data) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_data_length) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_crtc_info) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_crtc_info_reply) + + data.connection = ffxcb_connect(NULL, NULL); + if (ffxcb_connection_has_error(data.connection) > 0) { + ffxcb_disconnect(data.connection); + return "xcb_connect() failed"; + } + + data.result = result; + + xcb_screen_iterator_t iterator = ffxcb_setup_roots_iterator(ffxcb_get_setup(data.connection)); + + if (iterator.rem > 0) { + xcbDetectWMfromEWMH(&data, iterator.data->root, result); + xcbFetchServerVendor(&data, result); + } + + while (iterator.rem > 0) { + xcbRandrHandleScreen(&data, iterator.data); + ffxcb_screen_next(&iterator); + } + + ffxcb_disconnect(data.connection); + + // If wayland hasn't set this, connection failed for it. So we are running only a X Server, not XWayland. + if (result->wmProtocolName.length == 0) { + ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_X11); + } + + return NULL; +} + +#else + +const char* ffdsConnectXcbRandr(FFDisplayServerResult* result) { + // Do nothing. There are other implementations coming + FF_UNUSED(result) + return "Fastfetch was compiled without libxcb-randr support"; +} + +#endif -- cgit v1.2.3