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