diff options
| author | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
|---|---|---|
| committer | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
| commit | 76424950e373d3b04ac3dd13019151bfba3e8423 (patch) | |
| tree | 4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/displayserver/linux/drm.c | |
Add the files
Diffstat (limited to 'src/detection/displayserver/linux/drm.c')
| -rw-r--r-- | src/detection/displayserver/linux/drm.c | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/src/detection/displayserver/linux/drm.c b/src/detection/displayserver/linux/drm.c new file mode 100644 index 0000000..43c6afc --- /dev/null +++ b/src/detection/displayserver/linux/drm.c @@ -0,0 +1,437 @@ +#include "displayserver_linux.h" +#include "common/io.h" +#include "common/edidHelper.h" +#include "common/strutil.h" + +#ifdef __linux__ + #include <dirent.h> + +static const char* drmParseSysfs(FFDisplayServerResult* result) { + const char* drmDirPath = "/sys/class/drm/"; + + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath); + if (dirp == NULL) { + return "opendir(drmDirPath) failed"; + } + + FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64); + ffStrbufAppendS(&drmDir, drmDirPath); + + uint32_t drmDirLength = drmDir.length; + + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + + ffStrbufAppendS(&drmDir, entry->d_name); + uint32_t drmDirWithDnameLength = drmDir.length; + + char buf; + ffStrbufAppendS(&drmDir, "/enabled"); + if (ffReadFileData(drmDir.chars, sizeof(buf), &buf) <= 0 || buf != 'e') { + /* read failed or enabled != "enabled" */ + ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength); + ffStrbufAppendS(&drmDir, "/status"); + buf = 'd'; + ffReadFileData(drmDir.chars, sizeof(buf), &buf); + if (buf != 'c') { + /* read failed or status != "connected" */ + ffStrbufSubstrBefore(&drmDir, drmDirLength); + continue; + } + } + + unsigned width = 0, height = 0, physicalWidth = 0, physicalHeight = 0; + double refreshRate = 0; + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); + + ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength); + ffStrbufAppendS(&drmDir, "/edid"); + + const char* plainName = entry->d_name; + if (ffStrStartsWith(plainName, "card")) { + const char* tmp = strchr(plainName + strlen("card"), '-'); + if (tmp) { + plainName = tmp + 1; + } + } + + uint8_t edidData[512]; + ssize_t edidLength = ffReadFileData(drmDir.chars, ARRAY_SIZE(edidData), edidData); + if (edidLength <= 0 || edidLength % 128 != 0) { + edidLength = 0; + ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength); + ffStrbufAppendS(&drmDir, "/modes"); + + char modes[32]; + if (ffReadFileData(drmDir.chars, ARRAY_SIZE(modes), modes) >= 3) { + sscanf(modes, "%ux%u", &width, &height); + ffStrbufAppendS(&name, plainName); + } + } else { + ffEdidGetName(edidData, &name); + ffEdidGetPreferredResolutionAndRefreshRate(edidData, &width, &height, &refreshRate); + ffEdidGetPhysicalSize(edidData, &physicalWidth, &physicalHeight); + } + + FFDisplayResult* item = ffdsAppendDisplay( + result, + width, + height, + refreshRate, + 0, + 0, + 0, + 0, + 0, + &name, + ffdsGetDisplayType(plainName), + false, + 0, + physicalWidth, + physicalHeight, + "sysfs-drm"); + if (item && edidLength) { + 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); + } + + ffStrbufSubstrBefore(&drmDir, drmDirLength); + } + + return NULL; +} +#endif + +#ifdef FF_HAVE_DRM + + #include "common/library.h" + + #include <xf86drm.h> + #include <xf86drmMode.h> + #include <fcntl.h> + +// https://gitlab.freedesktop.org/mesa/drm/-/blob/main/xf86drmMode.c#L1785 +// It's not supported on Ubuntu 20.04 +static inline const char* drmType2Name(uint32_t connector_type) { + /* Keep the strings in sync with the kernel's drm_connector_enum_list in + * drm_connector.c. */ + switch (connector_type) { + case DRM_MODE_CONNECTOR_Unknown: + return "Unknown"; + case DRM_MODE_CONNECTOR_VGA: + return "VGA"; + case DRM_MODE_CONNECTOR_DVII: + return "DVI-I"; + case DRM_MODE_CONNECTOR_DVID: + return "DVI-D"; + case DRM_MODE_CONNECTOR_DVIA: + return "DVI-A"; + case DRM_MODE_CONNECTOR_Composite: + return "Composite"; + case DRM_MODE_CONNECTOR_SVIDEO: + return "SVIDEO"; + case DRM_MODE_CONNECTOR_LVDS: + return "LVDS"; + case DRM_MODE_CONNECTOR_Component: + return "Component"; + case DRM_MODE_CONNECTOR_9PinDIN: + return "DIN"; + case DRM_MODE_CONNECTOR_DisplayPort: + return "DP"; + case DRM_MODE_CONNECTOR_HDMIA: + return "HDMI-A"; + case DRM_MODE_CONNECTOR_HDMIB: + return "HDMI-B"; + case DRM_MODE_CONNECTOR_TV: + return "TV"; + case DRM_MODE_CONNECTOR_eDP: + return "eDP"; + case DRM_MODE_CONNECTOR_VIRTUAL: + return "Virtual"; + case DRM_MODE_CONNECTOR_DSI: + return "DSI"; + case DRM_MODE_CONNECTOR_DPI: + return "DPI"; + case DRM_MODE_CONNECTOR_WRITEBACK: + return "Writeback"; + case 19 /*DRM_MODE_CONNECTOR_SPI*/: + return "SPI"; + case 20 /*DRM_MODE_CONNECTOR_USB*/: + return "USB"; + default: + return "Unsupported"; + } +} + +FF_A_UNUSED static const char* drmGetEdidByConnId(uint32_t connId, uint8_t* edidData, ssize_t* edidLength) { + const char* drmDirPath = "/sys/class/drm/"; + + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath); + if (dirp == NULL) { + return "opendir(drmDirPath) failed"; + } + + FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64); + ffStrbufAppendS(&drmDir, drmDirPath); + + uint32_t drmDirLength = drmDir.length; + + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + + ffStrbufAppendS(&drmDir, entry->d_name); + uint32_t drmDirWithDnameLength = drmDir.length; + + char connectorId[16] = {}; + + ffStrbufAppendS(&drmDir, "/connector_id"); + ffReadFileData(drmDir.chars, ARRAY_SIZE(connectorId), connectorId); + if (strtoul(connectorId, NULL, 10) != connId) { + ffStrbufSubstrBefore(&drmDir, drmDirLength); + continue; + } + + ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength); + ffStrbufAppendS(&drmDir, "/edid"); + *edidLength = ffReadFileData(drmDir.chars, (uint32_t) *edidLength, edidData); + return NULL; + } + + return "Failed to match connector ID"; +} + +static const char* drmConnectLibdrm(FFDisplayServerResult* result) { + FF_LIBRARY_LOAD_MESSAGE(libdrm, "libdrm" FF_LIBRARY_EXTENSION, 2) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmGetDevices) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetResources) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetConnectorCurrent) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetCrtc) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetEncoder) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetFB) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetProperty) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetPropertyBlob) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeResources) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeCrtc) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeConnector) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeEncoder) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeFB) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeProperty) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreePropertyBlob) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmFreeDevices) + + drmDevice* devices[64]; + int nDevices = ffdrmGetDevices(devices, ARRAY_SIZE(devices)); + if (nDevices <= 0) { + return "drmGetDevices() failed"; + } + + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); + + for (int iDev = 0; iDev < nDevices; ++iDev) { + drmDevice* dev = devices[iDev]; + + if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY))) { + continue; + } + + const char* path = dev->nodes[DRM_NODE_PRIMARY]; + + #if __linux__ + ffStrbufSetF(&name, "/sys/class/drm/%s/device/power/runtime_status", strrchr(path, '/') + 1); + + char buffer[8] = ""; + if (ffReadFileData(name.chars, strlen("suspend"), buffer) > 0 && ffStrStartsWith(buffer, "suspend")) { + continue; + } + #endif + + FF_AUTO_CLOSE_FD int primaryFd = open(path, O_RDWR | O_CLOEXEC); + if (primaryFd < 0) { + continue; + } + + drmModeRes* res = ffdrmModeGetResources(primaryFd); + if (!res) { + continue; + } + + for (int iConn = 0; iConn < res->count_connectors; ++iConn) { + drmModeConnector* conn = ffdrmModeGetConnectorCurrent(primaryFd, res->connectors[iConn]); + if (!conn) { + continue; + } + + if (conn->connection != DRM_MODE_DISCONNECTED) { + drmModeEncoder* encoder = ffdrmModeGetEncoder(primaryFd, conn->encoder_id); + uint32_t width = 0, height = 0, refreshRate = 0; + uint8_t bitDepth = 0; + + if (encoder) { + drmModeCrtc* crtc = ffdrmModeGetCrtc(primaryFd, encoder->crtc_id); + if (crtc) { + width = crtc->mode.hdisplay; + height = crtc->mode.vdisplay; + refreshRate = crtc->mode.vrefresh; + if (refreshRate == 0) { + // There are weird cases that we can't get the refresh rate from the CRTC but from the modes + for (int iMode = 0; iMode < conn->count_modes; ++iMode) { + drmModeModeInfo* mode = &conn->modes[iMode]; + if (mode->clock == crtc->mode.clock && mode->htotal == crtc->mode.htotal) { + refreshRate = mode->vrefresh; + break; + } + } + } + + drmModeFBPtr fb = ffdrmModeGetFB(primaryFd, crtc->buffer_id); + if (fb) { + bitDepth = (uint8_t) (fb->depth / 3); + ffdrmModeFreeFB(fb); + } + + ffdrmModeFreeCrtc(crtc); + } + + ffdrmModeFreeEncoder(encoder); + } + + uint32_t preferredWidth = 0, preferredHeight = 0, preferredRefreshRate = 0; + + for (int iMode = 0; iMode < conn->count_modes; ++iMode) { + drmModeModeInfo* mode = &conn->modes[iMode]; + + if (mode->type & DRM_MODE_TYPE_PREFERRED) { + preferredWidth = mode->hdisplay; + preferredHeight = mode->vdisplay; + preferredRefreshRate = mode->vrefresh; + break; + } + } + + // NVIDIA DRM driver seems incomplete and conn->encoder_id == 0 + // Assume preferred resolution is used as what we do in drmParseSys + if (width == 0 || height == 0) { + width = preferredWidth; + height = preferredHeight; + refreshRate = preferredRefreshRate; + } + + ffStrbufClear(&name); + uint16_t myear = 0, mweak = 0; + uint32_t serial = 0; + FFDisplayHdrStatus hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; + + for (int iProp = 0; iProp < conn->count_props; ++iProp) { + drmModePropertyRes* prop = ffdrmModeGetProperty(primaryFd, conn->props[iProp]); + if (!prop) { + continue; + } + + uint32_t type = prop->flags & (DRM_MODE_PROP_LEGACY_TYPE | DRM_MODE_PROP_EXTENDED_TYPE); + if (type == DRM_MODE_PROP_BLOB && ffStrEquals(prop->name, "EDID")) { + drmModePropertyBlobPtr blob = NULL; + + if (prop->count_blobs > 0 && prop->blob_ids != NULL) { + blob = ffdrmModeGetPropertyBlob(primaryFd, prop->blob_ids[0]); + } else { + blob = ffdrmModeGetPropertyBlob(primaryFd, (uint32_t) conn->prop_values[iProp]); + } + + if (blob) { + if (blob->length >= 128) { + ffEdidGetName(blob->data, &name); + hdrStatus = ffEdidGetHdrCompatible(blob->data, blob->length) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED; + ffEdidGetSerialAndManufactureDate(blob->data, &serial, &myear, &mweak); + } + ffdrmModeFreePropertyBlob(blob); + } + break; + } + ffdrmModeFreeProperty(prop); + } + + #if __linux__ + if (name.length == 0) { + uint8_t edidData[512]; + ssize_t edidLength = 0; + drmGetEdidByConnId(conn->connector_id, edidData, &edidLength); + if (edidLength > 0 && edidLength % 128 == 0) { + ffEdidGetName(edidData, &name); + hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED; + ffEdidGetSerialAndManufactureDate(edidData, &serial, &myear, &mweak); + } + } + #endif + + if (name.length == 0) { + const char* connectorTypeName = drmType2Name(conn->connector_type); + if (connectorTypeName == NULL) { + connectorTypeName = "Unknown"; + } + ffStrbufSetF(&name, "%s-%d", connectorTypeName, iConn + 1); + } + + FFDisplayResult* item = ffdsAppendDisplay(result, + width, + height, + refreshRate, + 0, + preferredWidth, + preferredHeight, + preferredRefreshRate, + 0, + &name, + conn->connector_type == DRM_MODE_CONNECTOR_eDP || conn->connector_type == DRM_MODE_CONNECTOR_LVDS + ? FF_DISPLAY_TYPE_BUILTIN + : conn->connector_type == DRM_MODE_CONNECTOR_HDMIA || conn->connector_type == DRM_MODE_CONNECTOR_HDMIB || conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort + ? FF_DISPLAY_TYPE_EXTERNAL + : FF_DISPLAY_TYPE_UNKNOWN, + false, + conn->connector_id, + conn->mmWidth, + conn->mmHeight, + "libdrm"); + + if (item) { + item->hdrStatus = hdrStatus; + item->serial = serial; + item->manufactureYear = myear; + item->manufactureWeek = mweak; + item->bitDepth = bitDepth; + } + } + + ffdrmModeFreeConnector(conn); + } + + ffdrmModeFreeResources(res); + } + + ffdrmFreeDevices(devices, nDevices); + + return NULL; +} + +#endif + +const char* ffdsConnectDrm(FF_A_UNUSED FFDisplayServerResult* result) { +#ifdef FF_HAVE_DRM + if (instance.config.general.dsForceDrm != FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY) { + if (drmConnectLibdrm(result) == NULL) { + return NULL; + } + } +#endif + +#ifdef __linux__ + return drmParseSysfs(result); +#endif + + return "fastfetch was compiled without drm support"; +} |