ohmu
·
2026-10-15
1#include "displayserver_linux.h"
2
3#ifdef FF_HAVE_XRANDR
4
5 #include "common/library.h"
6 #include "common/properties.h"
7 #include "common/edidHelper.h"
8 #include "common/strutil.h"
9
10 #include <X11/extensions/Xrandr.h>
11 #include <X11/Xlib.h>
12
13typedef struct XrandrData {
14 FF_LIBRARY_SYMBOL(XInternAtom)
15 FF_LIBRARY_SYMBOL(XGetAtomName)
16 FF_LIBRARY_SYMBOL(XGetWindowProperty)
17 FF_LIBRARY_SYMBOL(XServerVendor)
18 FF_LIBRARY_SYMBOL(XFree)
19 FF_LIBRARY_SYMBOL(XRRGetMonitors)
20 FF_LIBRARY_SYMBOL(XRRGetScreenResourcesCurrent)
21 FF_LIBRARY_SYMBOL(XRRGetOutputInfo)
22 FF_LIBRARY_SYMBOL(XRRGetOutputProperty)
23 FF_LIBRARY_SYMBOL(XRRGetCrtcInfo)
24 FF_LIBRARY_SYMBOL(XRRFreeCrtcInfo)
25 FF_LIBRARY_SYMBOL(XRRFreeOutputInfo)
26 FF_LIBRARY_SYMBOL(XRRFreeScreenResources)
27 FF_LIBRARY_SYMBOL(XRRFreeMonitors)
28
29 // Init once
30 Display* display;
31 FFDisplayServerResult* result;
32} XrandrData;
33
34static unsigned char* x11GetProperty(XrandrData* data, Display* display, Window window, const char* request) {
35 Atom requestAtom = data->ffXInternAtom(display, request, False);
36 if (requestAtom == None) {
37 return nullptr;
38 }
39
40 Atom actualType;
41 unsigned long unused;
42 unsigned char* result = nullptr;
43
44 if (data->ffXGetWindowProperty(display, window, requestAtom, 0, 64, False, AnyPropertyType, &actualType, (int*) &unused, &unused, &unused, &result) != Success) {
45 return nullptr;
46 }
47
48 return result;
49}
50
51static uint8_t* xrandrGetProperty(XrandrData* data, RROutput output, const char* name, uint32_t* bufSize) {
52 unsigned long size = 0;
53 uint8_t* result = nullptr;
54 Atom atomEdid = data->ffXInternAtom(data->display, name, true);
55 if (atomEdid != None) {
56 int actual_format = 0;
57 unsigned long bytes_after = 0;
58 Atom actual_type = None;
59 if (data->ffXRRGetOutputProperty(data->display, output, atomEdid, 0, 100, false, false, AnyPropertyType, &actual_type, &actual_format, &size, &bytes_after, &result) == Success) {
60 if (size == 0) {
61 data->ffXFree(result);
62 } else {
63 if (bufSize) {
64 *bufSize = (uint32_t) size;
65 }
66 return result;
67 }
68 }
69 }
70
71 return nullptr;
72}
73
74static void x11DetectWMFromEWMH(XrandrData* data, FFDisplayServerResult* result) {
75 if (result->wmProcessName.length > 0 || ffStrbufEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND)) {
76 return;
77 }
78
79 Window* wmWindow = (Window*) x11GetProperty(data, data->display, DefaultRootWindow(data->display), "_NET_SUPPORTING_WM_CHECK");
80 if (wmWindow == nullptr) {
81 return;
82 }
83
84 char* wmName = (char*) x11GetProperty(data, data->display, *wmWindow, "WM_NAME");
85 if (!ffStrSet(wmName)) {
86 wmName = (char*) x11GetProperty(data, data->display, *wmWindow, "_NET_WM_NAME");
87 }
88
89 if (ffStrSet(wmName)) {
90 ffStrbufSetS(&result->wmProcessName, wmName);
91 }
92
93 data->ffXFree(wmName);
94 data->ffXFree(wmWindow);
95}
96
97static void x11FetchServerVendor(XrandrData* data, FFDisplayServerResult* result) {
98 const char* serverVendor = data->ffXServerVendor(data->display);
99 if (serverVendor && !ffStrEquals(serverVendor, "The X.Org Foundation")) {
100 ffStrbufSetS(&result->wmProtocolName, serverVendor);
101 }
102}
103
104static bool xrandrHandleCrtc(XrandrData* data, XRROutputInfo* output, FFstrbuf* name, bool primary, FFDisplayType displayType, uint8_t* edidData, uint32_t edidLength, XRRScreenResources* screenResources, uint8_t bitDepth, uint32_t dpi, bool randrEmulation) {
105 // We do the check here, because we want the best fallback display if this call failed
106 if (screenResources == nullptr) {
107 return false;
108 }
109
110 XRRCrtcInfo* crtcInfo = data->ffXRRGetCrtcInfo(data->display, screenResources, output->crtc);
111 if (crtcInfo == nullptr) {
112 return false;
113 }
114
115 uint32_t rotation;
116 switch (crtcInfo->rotation) {
117 case RR_Rotate_90:
118 rotation = 90;
119 break;
120 case RR_Rotate_180:
121 rotation = 180;
122 break;
123 case RR_Rotate_270:
124 rotation = 270;
125 break;
126 default:
127 rotation = 0;
128 break;
129 }
130
131 XRRModeInfo* currentMode = nullptr;
132 for (int i = 0; i < screenResources->nmode; i++) {
133 if (screenResources->modes[i].id == crtcInfo->mode) {
134 currentMode = &screenResources->modes[i];
135 break;
136 }
137 }
138
139 XRRModeInfo* preferredMode = output->npreferred > 0 ? &screenResources->modes[0] : nullptr;
140
141 FFDisplayResult* item = ffdsAppendDisplay(
142 data->result,
143 (uint32_t) (currentMode ? currentMode->width : crtcInfo->width),
144 (uint32_t) (currentMode ? currentMode->height : crtcInfo->height),
145 currentMode ? (double) currentMode->dotClock / (double) ((uint32_t) currentMode->hTotal * currentMode->vTotal) : 0,
146 dpi,
147 preferredMode ? (uint32_t) preferredMode->width : 0,
148 preferredMode ? (uint32_t) preferredMode->height : 0,
149 preferredMode ? (double) preferredMode->dotClock / (double) ((uint32_t) preferredMode->hTotal * preferredMode->vTotal) : 0,
150 rotation,
151 name,
152 displayType,
153 primary,
154 0,
155 (uint32_t) output->mm_width,
156 (uint32_t) output->mm_height,
157 randrEmulation
158 ? (currentMode ? "xlib-randr-emu-mode" : "xlib-randr-emu-crtc")
159 : (currentMode ? "xlib-randr-mode" : "xlib-randr-crtc"));
160
161 if (item) {
162 if (edidLength) {
163 item->hdrStatus = ffEdidGetHdrCompatible(edidData, edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
164 ffEdidGetManufactureDate(edidData, &item->manufactureYear, &item->manufactureWeek);
165 ffEdidGetSerial(edidData, &item->serial);
166 }
167 item->bitDepth = bitDepth;
168 if ((rotation == 90 || rotation == 180) && !randrEmulation) {
169 // In XWayland mode, width / height has been swapped out of box
170 uint32_t tmp = item->width;
171 item->width = item->height;
172 item->height = tmp;
173 }
174 }
175
176 data->ffXRRFreeCrtcInfo(crtcInfo);
177 return !!item;
178}
179
180static bool xrandrHandleOutput(XrandrData* data, RROutput output, FFstrbuf* name, bool primary, FFDisplayType displayType, XRRScreenResources* screenResources, uint8_t bitDepth, uint32_t dpi) {
181 XRROutputInfo* outputInfo = data->ffXRRGetOutputInfo(data->display, screenResources, output);
182 if (outputInfo == nullptr) {
183 return false;
184 }
185
186 uint32_t edidLength = 0;
187 uint8_t* edidData = xrandrGetProperty(data, output, RR_PROPERTY_RANDR_EDID, &edidLength);
188
189 if (edidLength >= 128) {
190 ffStrbufClear(name);
191 ffEdidGetName(edidData, name);
192 } else {
193 edidLength = 0;
194 }
195
196 uint8_t* randrEmulation = xrandrGetProperty(data, output, "RANDR Emulation", nullptr);
197
198 bool res = xrandrHandleCrtc(data, outputInfo, name, primary, displayType, edidData, edidLength, screenResources, bitDepth, dpi, randrEmulation ? !!randrEmulation[0] : false);
199
200 if (edidData) {
201 data->ffXFree(edidData);
202 }
203 if (randrEmulation) {
204 data->ffXFree(randrEmulation);
205 }
206 data->ffXRRFreeOutputInfo(outputInfo);
207
208 return res;
209}
210
211static bool xrandrHandleMonitor(XrandrData* data, XRRMonitorInfo* monitorInfo, XRRScreenResources* screenResources, uint8_t bitDepth, uint32_t dpi) {
212 bool foundOutput = false;
213 char* xname = data->ffXGetAtomName(data->display, monitorInfo->name);
214 FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateS(xname);
215 data->ffXFree(xname);
216 FFDisplayType displayType = ffdsGetDisplayType(name.chars);
217 for (int i = 0; i < monitorInfo->noutput; i++) {
218 if (xrandrHandleOutput(data, monitorInfo->outputs[i], &name, monitorInfo->primary, displayType, screenResources, bitDepth, dpi)) {
219 foundOutput = true;
220 }
221 }
222
223 if (foundOutput) {
224 return true;
225 }
226
227 FFDisplayResult* display = ffdsAppendDisplay(
228 data->result,
229 (uint32_t) monitorInfo->width,
230 (uint32_t) monitorInfo->height,
231 0,
232 dpi,
233 0,
234 0,
235 0,
236 0,
237 &name,
238 displayType,
239 !!monitorInfo->primary,
240 0,
241 (uint32_t) monitorInfo->mwidth,
242 (uint32_t) monitorInfo->mheight,
243 "xlib-randr-monitor");
244 if (display) {
245 display->bitDepth = bitDepth;
246 }
247 return !!display;
248}
249
250static bool xrandrHandleMonitors(XrandrData* data, Screen* screen) {
251 int numberOfMonitors;
252 XRRMonitorInfo* monitorInfos = data->ffXRRGetMonitors(data->display, RootWindowOfScreen(screen), True, &numberOfMonitors);
253 if (monitorInfos == nullptr) {
254 return false;
255 }
256
257 XRRScreenResources* screenResources = data->ffXRRGetScreenResourcesCurrent(data->display, RootWindowOfScreen(screen));
258
259 uint32_t dpi = 1;
260 char* resourceManager = (char*) x11GetProperty(data, data->display, screen->root, "RESOURCE_MANAGER");
261 if (resourceManager) {
262 FF_STRBUF_AUTO_DESTROY dpiStr = ffStrbufCreate();
263 if (ffParsePropLines(resourceManager, "Xft.dpi:", &dpiStr)) {
264 dpi = (uint32_t) ffStrbufToUInt(&dpiStr, 96);
265 }
266 data->ffXFree(resourceManager);
267 }
268 uint8_t bitDepth = (uint8_t) (screen->root_depth / 3);
269
270 bool foundAMonitor = false;
271
272 for (int i = 0; i < numberOfMonitors; i++) {
273 if (xrandrHandleMonitor(data, &monitorInfos[i], screenResources, bitDepth, dpi)) {
274 foundAMonitor = true;
275 }
276 }
277
278 data->ffXRRFreeMonitors(monitorInfos);
279 data->ffXRRFreeScreenResources(screenResources);
280
281 return foundAMonitor;
282}
283
284static void xrandrHandleScreen(XrandrData* data, Screen* screen) {
285 if (xrandrHandleMonitors(data, screen)) {
286 return;
287 }
288
289 // Fallback to screen
290 ffdsAppendDisplay(
291 data->result,
292 (uint32_t) WidthOfScreen(screen),
293 (uint32_t) HeightOfScreen(screen),
294 0,
295 0,
296 0,
297 0,
298 0,
299 0,
300 nullptr,
301 FF_DISPLAY_TYPE_UNKNOWN,
302 false,
303 RootWindowOfScreen(screen),
304 (uint32_t) WidthMMOfScreen(screen),
305 (uint32_t) HeightMMOfScreen(screen),
306 "xlib-randr-screen");
307}
308
309const char* ffdsConnectXrandr(FFDisplayServerResult* result) {
310 FF_LIBRARY_LOAD_MESSAGE(xrandr, "libXrandr" FF_LIBRARY_EXTENSION, 3)
311
312 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xrandr, XOpenDisplay)
313 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xrandr, XCloseDisplay)
314
315 XrandrData data;
316
317 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XInternAtom);
318 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XGetAtomName);
319 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XGetWindowProperty);
320 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XServerVendor);
321 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XFree);
322 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetMonitors);
323 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetScreenResourcesCurrent);
324 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetOutputInfo);
325 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetOutputProperty);
326 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetCrtcInfo);
327 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeCrtcInfo);
328 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeOutputInfo);
329 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeScreenResources);
330 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeMonitors);
331
332 data.display = ffXOpenDisplay(nullptr);
333 if (data.display == nullptr) {
334 return "XOpenDisplay() failed";
335 }
336
337 if (ScreenCount(data.display) > 0) {
338 x11DetectWMFromEWMH(&data, result);
339 x11FetchServerVendor(&data, result);
340 }
341
342 data.result = result;
343
344 for (int i = 0; i < ScreenCount(data.display); i++) {
345 xrandrHandleScreen(&data, ScreenOfDisplay(data.display, i));
346 }
347
348 ffXCloseDisplay(data.display);
349
350 // If wayland hasn't set this, connection failed for it. So we are running only a X Server, not XWayland.
351 if (result->wmProtocolName.length == 0) {
352 ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_X11);
353 }
354
355 return nullptr;
356}
357
358#else
359
360const char* ffdsConnectXrandr(FFDisplayServerResult* result) {
361 // Do nothing here. There are more x11 implementations to come.
362 FF_UNUSED(result);
363 return "Fastfetch was compiled without libXrandr support";
364}
365
366#endif // FF_HAVE_XRANDR