ohmu
·
2026-10-15
1#include "displayserver_linux.h"
2#include "common/io.h"
3#include "common/edidHelper.h"
4#include "common/strutil.h"
5#include "common/mallocHelper.h"
6
7#ifdef __linux__
8 #include <dirent.h>
9
10static const char* drmParseSysfs(FFDisplayServerResult* result) {
11 const char* drmDirPath = "/sys/class/drm/";
12
13 FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath);
14 if (dirp == nullptr) {
15 return "opendir(drmDirPath) failed";
16 }
17
18 FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64);
19 ffStrbufAppendS(&drmDir, drmDirPath);
20
21 uint32_t drmDirLength = drmDir.length;
22
23 struct dirent* entry;
24 while ((entry = readdir(dirp)) != nullptr) {
25 if (entry->d_name[0] == '.') {
26 continue;
27 }
28
29 ffStrbufAppendS(&drmDir, entry->d_name);
30 uint32_t drmDirWithDnameLength = drmDir.length;
31
32 char buf;
33 ffStrbufAppendS(&drmDir, "/enabled");
34 if (ffReadFileData(drmDir.chars, sizeof(buf), &buf) <= 0 || buf != 'e') {
35 /* read failed or enabled != "enabled" */
36 ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength);
37 ffStrbufAppendS(&drmDir, "/status");
38 buf = 'd';
39 ffReadFileData(drmDir.chars, sizeof(buf), &buf);
40 if (buf != 'c') {
41 /* read failed or status != "connected" */
42 ffStrbufSubstrBefore(&drmDir, drmDirLength);
43 continue;
44 }
45 }
46
47 uint32_t width = 0, height = 0, physicalWidth = 0, physicalHeight = 0, preferredWidth = 0, preferredHeight = 0;
48 double preferredRefreshRate = 0;
49 FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();
50
51 ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength);
52 ffStrbufAppendS(&drmDir, "/edid");
53
54 const char* plainName = entry->d_name;
55 if (ffStrStartsWith(plainName, "card")) {
56 const char* tmp = strchr(plainName + strlen("card"), '-');
57 if (tmp) {
58 plainName = tmp + 1;
59 }
60 }
61
62 ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength);
63 ffStrbufAppendS(&drmDir, "/modes");
64
65 char modes[32];
66 if (ffReadFileData(drmDir.chars, ARRAY_SIZE(modes), modes) >= 3) {
67 // This is actually preferred resolution
68 sscanf(modes, "%ux%u", &width, &height);
69 ffStrbufAppendS(&name, plainName);
70 }
71
72 uint8_t edidData[1024];
73 ssize_t edidLength = ffReadFileData(drmDir.chars, ARRAY_SIZE(edidData), edidData);
74 if (edidLength > 0 && ffEdidIsValid(edidData, (uint32_t) edidLength)) {
75 ffEdidGetName(edidData, &name);
76 ffEdidGetPreferredResolutionAndRefreshRate(edidData, &preferredWidth, &preferredHeight, &preferredRefreshRate);
77 ffEdidGetPhysicalSize(edidData, &physicalWidth, &physicalHeight);
78 } else {
79 edidLength = 0;
80 }
81
82 FFDisplayResult* item = ffdsAppendDisplay(
83 result,
84 width,
85 height,
86 0,
87 0,
88 preferredWidth,
89 preferredHeight,
90 preferredRefreshRate,
91 0,
92 &name,
93 ffdsGetDisplayType(plainName),
94 false,
95 0,
96 physicalWidth,
97 physicalHeight,
98 "sysfs-drm");
99 if (item && edidLength) {
100 item->hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
101 ffEdidGetManufactureDate(edidData, &item->manufactureYear, &item->manufactureWeek);
102 ffEdidGetSerial(edidData, &item->serial);
103 }
104
105 ffStrbufSubstrBefore(&drmDir, drmDirLength);
106 }
107
108 return nullptr;
109}
110#endif
111
112#if FF_HAVE_DRM || __has_include(<drm/drm.h>)
113
114 #include <fcntl.h>
115 #include <sys/ioctl.h>
116 #if FF_HAVE_DRM
117 #include <drm.h>
118 #include <drm_mode.h>
119 #else
120 #define FF_HAVE_DRM 1
121 #include <drm/drm.h>
122 #include <drm/drm_mode.h>
123 #endif
124
125// https://gitlab.freedesktop.org/mesa/drm/-/blob/main/xf86drmMode.c#L1785
126// It's not supported on Ubuntu 20.04
127static inline const char* drmType2Name(uint32_t connector_type) {
128 /* Keep the strings in sync with the kernel's drm_connector_enum_list in
129 * drm_connector.c. */
130 switch (connector_type) {
131 case DRM_MODE_CONNECTOR_Unknown:
132 return "Unknown";
133 case DRM_MODE_CONNECTOR_VGA:
134 return "VGA";
135 case DRM_MODE_CONNECTOR_DVII:
136 return "DVI-I";
137 case DRM_MODE_CONNECTOR_DVID:
138 return "DVI-D";
139 case DRM_MODE_CONNECTOR_DVIA:
140 return "DVI-A";
141 case DRM_MODE_CONNECTOR_Composite:
142 return "Composite";
143 case DRM_MODE_CONNECTOR_SVIDEO:
144 return "SVIDEO";
145 case DRM_MODE_CONNECTOR_LVDS:
146 return "LVDS";
147 case DRM_MODE_CONNECTOR_Component:
148 return "Component";
149 case DRM_MODE_CONNECTOR_9PinDIN:
150 return "DIN";
151 case DRM_MODE_CONNECTOR_DisplayPort:
152 return "DP";
153 case DRM_MODE_CONNECTOR_HDMIA:
154 return "HDMI-A";
155 case DRM_MODE_CONNECTOR_HDMIB:
156 return "HDMI-B";
157 case DRM_MODE_CONNECTOR_TV:
158 return "TV";
159 case DRM_MODE_CONNECTOR_eDP:
160 return "eDP";
161 case DRM_MODE_CONNECTOR_VIRTUAL:
162 return "Virtual";
163 case DRM_MODE_CONNECTOR_DSI:
164 return "DSI";
165 case DRM_MODE_CONNECTOR_DPI:
166 return "DPI";
167 case DRM_MODE_CONNECTOR_WRITEBACK:
168 return "Writeback";
169 case 19 /*DRM_MODE_CONNECTOR_SPI*/:
170 return "SPI";
171 case 20 /*DRM_MODE_CONNECTOR_USB*/:
172 return "USB";
173 default:
174 return "Unsupported";
175 }
176}
177
178[[maybe_unused]] static const char* drmGetEdidByConnId(uint32_t connId, uint8_t* edidData, ssize_t* edidLength) {
179 const char* drmDirPath = "/sys/class/drm/";
180
181 FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath);
182 if (dirp == nullptr) {
183 return "opendir(drmDirPath) failed";
184 }
185
186 FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64);
187 ffStrbufAppendS(&drmDir, drmDirPath);
188
189 uint32_t drmDirLength = drmDir.length;
190
191 struct dirent* entry;
192 while ((entry = readdir(dirp)) != nullptr) {
193 if (entry->d_name[0] == '.') {
194 continue;
195 }
196
197 ffStrbufAppendS(&drmDir, entry->d_name);
198 uint32_t drmDirWithDnameLength = drmDir.length;
199
200 char connectorId[16] = {};
201
202 ffStrbufAppendS(&drmDir, "/connector_id");
203 ffReadFileData(drmDir.chars, ARRAY_SIZE(connectorId), connectorId);
204 if (strtoul(connectorId, nullptr, 10) != connId) {
205 ffStrbufSubstrBefore(&drmDir, drmDirLength);
206 continue;
207 }
208
209 ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength);
210 ffStrbufAppendS(&drmDir, "/edid");
211 *edidLength = ffReadFileData(drmDir.chars, (uint32_t) *edidLength, edidData);
212 return nullptr;
213 }
214
215 return "Failed to match connector ID";
216}
217
218static const char* drmConnectLibdrm(FFDisplayServerResult* result) {
219 FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/dev/dri/");
220 if (dirp == nullptr) {
221 return "opendir(/dev/dri/) failed";
222 }
223 int drifd = dirfd(dirp);
224
225 int nSuccess = 0;
226
227 struct dirent* entry;
228 while ((entry = readdir(dirp)) != nullptr) {
229 if (entry->d_name[0] == '.' || !ffStrStartsWith(entry->d_name, "card")) {
230 continue;
231 }
232
233 #if __linux__
234 char powerStatusBuf[512];
235 snprintf(powerStatusBuf, ARRAY_SIZE(powerStatusBuf), "/sys/class/drm/%s/device/power/runtime_status", entry->d_name);
236
237 char buffer[8] = "";
238 if (ffReadFileData(powerStatusBuf, strlen("suspend"), buffer) > 0 && ffStrStartsWith(buffer, "suspend")) {
239 continue;
240 }
241 #endif
242
243 FF_AUTO_CLOSE_FD int primaryFd = openat(drifd, entry->d_name, O_RDWR | O_CLOEXEC);
244 if (primaryFd < 0) {
245 continue;
246 }
247
248 struct drm_mode_card_res res_ = {};
249 if (ioctl(primaryFd, DRM_IOCTL_MODE_GETRESOURCES, &res_) < 0 || res_.count_connectors <= 0) {
250 continue;
251 }
252
253 FF_AUTO_FREE uint32_t* connectors = malloc(res_.count_connectors * sizeof(*connectors));
254 struct drm_mode_card_res res = {
255 .count_connectors = res_.count_connectors,
256 .connector_id_ptr = (uintptr_t) connectors,
257 };
258
259 if (ioctl(primaryFd, DRM_IOCTL_MODE_GETRESOURCES, &res) < 0) {
260 continue;
261 }
262
263 ++nSuccess;
264
265 for (uint32_t iConn = 0; iConn < res.count_connectors; ++iConn) {
266 struct drm_mode_get_connector conn = {
267 .connector_id = connectors[iConn]
268 };
269 if (ioctl(primaryFd, DRM_IOCTL_MODE_GETCONNECTOR, &conn) < 0) {
270 continue;
271 }
272
273 if (conn.connection == 2 /* connector_status_disconnected */) {
274 continue;
275 }
276
277 uint32_t width = 0, height = 0, refreshRate = 0;
278 uint8_t bitDepth = 0;
279
280 if (conn.encoder_id > 0) {
281 struct drm_mode_get_encoder enc = {
282 .encoder_id = conn.encoder_id
283 };
284 if (ioctl(primaryFd, DRM_IOCTL_MODE_GETENCODER, &enc) >= 0 && enc.crtc_id > 0) {
285 struct drm_mode_crtc crtc = {
286 .crtc_id = enc.crtc_id,
287 };
288 if (ioctl(primaryFd, DRM_IOCTL_MODE_GETCRTC, &crtc) >= 0) {
289 width = crtc.mode.hdisplay;
290 height = crtc.mode.vdisplay;
291 refreshRate = crtc.mode.vrefresh;
292
293 if (crtc.fb_id > 0) {
294 struct drm_mode_fb_cmd fb = {
295 .fb_id = crtc.fb_id,
296 };
297 if (ioctl(primaryFd, DRM_IOCTL_MODE_GETFB, &fb) >= 0) {
298 bitDepth = (uint8_t) (fb.depth / 3);
299 }
300 }
301 }
302 }
303 }
304
305 uint32_t preferredWidth = 0, preferredHeight = 0, preferredRefreshRate = 0;
306 if (conn.count_modes > 0) {
307 FF_AUTO_FREE struct drm_mode_modeinfo* modes = malloc(conn.count_modes * sizeof(*modes));
308 struct drm_mode_get_connector connModes = {
309 .connector_id = connectors[iConn],
310 .modes_ptr = (uintptr_t) modes,
311 .count_modes = conn.count_modes
312 };
313 if (ioctl(primaryFd, DRM_IOCTL_MODE_GETCONNECTOR, &connModes) >= 0) {
314 for (uint32_t iMode = 0; iMode < connModes.count_modes; ++iMode) {
315 if (modes[iMode].type & DRM_MODE_TYPE_PREFERRED) {
316 preferredWidth = modes[iMode].hdisplay;
317 preferredHeight = modes[iMode].vdisplay;
318 preferredRefreshRate = modes[iMode].vrefresh;
319 break;
320 }
321 }
322 }
323 }
324
325 if (width == 0 || height == 0) {
326 width = preferredWidth;
327 height = preferredHeight;
328 refreshRate = preferredRefreshRate;
329 }
330
331 FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();
332 uint16_t myear = 0, mweak = 0;
333 FF_STRBUF_AUTO_DESTROY serial = ffStrbufCreate();
334 FFDisplayHdrStatus hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;
335
336 uint32_t* props = malloc(conn.count_props * sizeof(uint32_t));
337 uint64_t* propValues = malloc(conn.count_props * sizeof(uint64_t));
338 if (props && propValues && conn.count_props > 0) {
339 struct drm_mode_get_connector connProps = {
340 .connector_id = connectors[iConn],
341 .props_ptr = (uintptr_t) props,
342 .prop_values_ptr = (uintptr_t) propValues,
343 .count_props = conn.count_props,
344 };
345 if (ioctl(primaryFd, DRM_IOCTL_MODE_GETCONNECTOR, &connProps) >= 0) {
346 for (uint32_t iProp = 0; iProp < connProps.count_props; ++iProp) {
347 struct drm_mode_get_property prop = {
348 .prop_id = props[iProp],
349 };
350 if (ioctl(primaryFd, DRM_IOCTL_MODE_GETPROPERTY, &prop) < 0) {
351 continue;
352 }
353
354 uint32_t type = prop.flags & (DRM_MODE_PROP_LEGACY_TYPE | DRM_MODE_PROP_EXTENDED_TYPE);
355 if (type == DRM_MODE_PROP_BLOB && ffStrEquals(prop.name, "EDID")) {
356 struct drm_mode_get_blob blob_ = {
357 .blob_id = (uint32_t) propValues[iProp]
358 };
359 if (ioctl(primaryFd, DRM_IOCTL_MODE_GETPROPBLOB, &blob_) >= 0 && blob_.length > 0) {
360 FF_AUTO_FREE void* blobData = malloc(blob_.length);
361 struct drm_mode_get_blob blob = {
362 .blob_id = blob_.blob_id,
363 .length = blob_.length,
364 .data = (uintptr_t) blobData
365 };
366 if (ioctl(primaryFd, DRM_IOCTL_MODE_GETPROPBLOB, &blob) >= 0 && ffEdidIsValid(blobData, blob.length)) {
367 ffEdidGetName(blobData, &name);
368 hdrStatus = ffEdidGetHdrCompatible(blobData, blob.length) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
369 ffEdidGetManufactureDate(blobData, &myear, &mweak);
370 ffEdidGetSerial(blobData, &serial);
371 }
372 }
373 break;
374 }
375 }
376 }
377 }
378
379 #if __linux__
380 if (name.length == 0) {
381 uint8_t edidData[512];
382 ssize_t edidLength = 0;
383 drmGetEdidByConnId(conn.connector_id, edidData, &edidLength);
384 if (edidLength > 0 && ffEdidIsValid(edidData, (uint32_t) edidLength)) {
385 ffEdidGetName(edidData, &name);
386 hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
387 ffEdidGetManufactureDate(edidData, &myear, &mweak);
388 ffEdidGetSerial(edidData, &serial);
389 }
390 }
391 #endif
392
393 if (name.length == 0) {
394 const char* connectorTypeName = drmType2Name(conn.connector_type);
395 if (connectorTypeName == nullptr) {
396 connectorTypeName = "Unknown";
397 }
398 ffStrbufSetF(&name, "%s-%d", connectorTypeName, iConn + 1);
399 }
400
401 FFDisplayResult* item = ffdsAppendDisplay(result,
402 width,
403 height,
404 refreshRate,
405 0,
406 preferredWidth,
407 preferredHeight,
408 preferredRefreshRate,
409 0,
410 &name,
411 conn.connector_type == DRM_MODE_CONNECTOR_eDP || conn.connector_type == DRM_MODE_CONNECTOR_LVDS
412 ? FF_DISPLAY_TYPE_BUILTIN
413 : conn.connector_type == DRM_MODE_CONNECTOR_HDMIA || conn.connector_type == DRM_MODE_CONNECTOR_HDMIB || conn.connector_type == DRM_MODE_CONNECTOR_DisplayPort
414 ? FF_DISPLAY_TYPE_EXTERNAL
415 : FF_DISPLAY_TYPE_UNKNOWN,
416 false,
417 conn.connector_id,
418 conn.mm_width,
419 conn.mm_height,
420 "libdrm");
421
422 if (item) {
423 item->hdrStatus = hdrStatus;
424 ffStrbufInitMove(&item->serial, &serial);
425 item->manufactureYear = myear;
426 item->manufactureWeek = mweak;
427 item->bitDepth = bitDepth;
428 }
429 }
430 }
431
432 return nSuccess > 0 ? nullptr : "No connectors found using libdrm";
433}
434
435#endif
436
437const char* ffdsConnectDrm([[maybe_unused]] FFDisplayServerResult* result) {
438#if FF_HAVE_DRM
439 if (instance.config.general.dsForceDrm != FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY) {
440 if (drmConnectLibdrm(result) == nullptr) {
441 return nullptr;
442 }
443 }
444#endif
445
446#ifdef __linux__
447 return drmParseSysfs(result);
448#endif
449
450 return "fastfetch was compiled without drm support";
451}