From d0ce6aabb6e90e4f70efeadb94f93b8a0d6a3a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sun, 9 Jul 2023 17:22:04 +0800 Subject: [PATCH] Brightness (Linux): detect brightness of external monitors with DDC/CI --- .github/workflows/ci.yml | 4 +- CHANGELOG.md | 1 + CMakeLists.txt | 5 + README.md | 1 + completions/bash | 2 + doc/json_schema.json | 4 + src/common/init.c | 5 + src/common/jsonconfig.c | 2 + src/data/config_user.txt | 2 + src/data/help.txt | 1 + src/detection/brightness/brightness_linux.c | 128 ++++++++++++++++++-- src/fastfetch.c | 2 + src/fastfetch.h | 1 + 13 files changed, 146 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 904dff2a8..c352081cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v3 - name: install required packages - run: sudo apt-get update && sudo apt-get install -y musl-dev musl-tools linux-headers-generic libpci-dev libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev rpm librpm-dev libzstd-dev libegl-dev libglx-dev libosmesa6-dev ocl-icd-opencl-dev libnm-dev libpulse-dev + run: sudo apt-get update && sudo apt-get install -y musl-dev musl-tools linux-headers-generic libpci-dev libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev rpm librpm-dev libzstd-dev libegl-dev libglx-dev libosmesa6-dev ocl-icd-opencl-dev libnm-dev libpulse-dev libddcutil-dev - name: configure project env: @@ -57,7 +57,7 @@ jobs: uses: actions/checkout@v3 - name: install required packages - run: sudo apt-get update && sudo apt-get install -y libpci-dev libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev rpm librpm-dev libzstd-dev libegl-dev libglx-dev libosmesa6-dev ocl-icd-opencl-dev libnm-dev libpulse-dev + run: sudo apt-get update && sudo apt-get install -y libpci-dev libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libxfconf-0-dev libsqlite3-dev rpm librpm-dev libzstd-dev libegl-dev libglx-dev libosmesa6-dev ocl-icd-opencl-dev libnm-dev libpulse-dev libddcutil-dev - name: Initialize CodeQL uses: github/codeql-action/init@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c622cc65..3270a4c3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Features: * Add LM (Login Manager) module. Currently requires systemd installed (thus Linux only) * Add `--wmi-timeout` option (Windows) * Add `--logo-type small` to search for small logos +* Support detecting brightness of external displays with DDC/CI (guard behind `--allow-slow-operations`) (Brightness) # 1.12.2 diff --git a/CMakeLists.txt b/CMakeLists.txt index dc95b0fcc..dd8b7415f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,7 @@ cmake_dependent_option(ENABLE_OPENCL "Enable opencl" ON "LINUX OR BSD OR WIN32" cmake_dependent_option(ENABLE_LIBNM "Enable libnm" ON "LINUX" OFF) cmake_dependent_option(ENABLE_FREETYPE "Enable freetype" ON "ANDROID" OFF) cmake_dependent_option(ENABLE_PULSE "Enable pulse" ON "LINUX OR BSD" OFF) +cmake_dependent_option(ENABLE_DDCUTIL "Enable ddcutil" ON "LINUX" OFF) cmake_dependent_option(ENABLE_THREADS "Enable multithreading" ON "Threads_FOUND" OFF) option(BUILD_TESTS "Build tests" OFF) # Also create test executables @@ -715,6 +716,10 @@ ff_lib_enable(PULSE "libpulse" "Pulse" ) +ff_lib_enable(DDCUTIL + "ddcutil" + "Ddcutil" +) if(ENABLE_THREADS) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_THREADS) diff --git a/README.md b/README.md index 11af03dae..71d14b750 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ The following libraries are used if present at runtime: * [`librpm`](http://rpm.org/): Slower fallback for rpm package count. Needed on openSUSE. * [`libnm`](https://networkmanager.dev/docs/libnm/latest/): Used for Wifi detection. * [`libpulse`](https://freedesktop.org/software/pulseaudio/doxygen/): Used for Sound detection. +* [`libddcutil`](https://github.com/rockowitz/ddcutil): Used for brightness detection of external displays ### macOS diff --git a/completions/bash b/completions/bash index c340753cd..a8db1c600 100644 --- a/completions/bash +++ b/completions/bash @@ -383,6 +383,8 @@ __fastfetch_completion() "--lib-osmesa" "--lib-opencl" "--lib-pulse" + "--lib-ddcutil" + "--lib-nm" "--battery-dir" ) diff --git a/doc/json_schema.json b/doc/json_schema.json index b12848e4f..ce264db47 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -406,6 +406,10 @@ "nm": { "title": "NetworkManager. Used for Wifi detection (Linux)", "type": "string" + }, + "ddcutil": { + "title": "Used for brightness detection of external displays (Linux)", + "type": "string" } }, "additionalProperties": false diff --git a/src/common/init.c b/src/common/init.c index d684fae1a..c17188bd4 100644 --- a/src/common/init.c +++ b/src/common/init.c @@ -139,6 +139,7 @@ static void defaultConfig(void) ffStrbufInit(&instance.config.libfreetype); ffStrbufInit(&instance.config.libPulse); ffStrbufInit(&instance.config.libnm); + ffStrbufInit(&instance.config.libDdcutil); instance.config.percentType = 1; } @@ -344,6 +345,7 @@ static void destroyConfig(void) ffStrbufDestroy(&instance.config.libfreetype); ffStrbufDestroy(&instance.config.libPulse); ffStrbufDestroy(&instance.config.libnm); + ffStrbufDestroy(&instance.config.libDdcutil); } static void destroyState(void) @@ -437,6 +439,9 @@ void ffListFeatures(void) #ifdef FF_HAVE_LIBNM "libnm\n" #endif + #ifdef FF_HAVE_DDCUTIL + "libddcutil\n" + #endif "" , stdout); } diff --git a/src/common/jsonconfig.c b/src/common/jsonconfig.c index af56b6c57..f67766a7c 100644 --- a/src/common/jsonconfig.c +++ b/src/common/jsonconfig.c @@ -463,6 +463,8 @@ const char* ffParseLibraryJsonConfig(void) ffStrbufSetS(&config->libPulse, yyjson_get_str(val)); else if (ffStrEqualsIgnCase(key, "nm")) ffStrbufSetS(&config->libnm, yyjson_get_str(val)); + else if (ffStrEqualsIgnCase(key, "ddcutil")) + ffStrbufSetS(&config->libDdcutil, yyjson_get_str(val)); else return "Unknown library property"; } diff --git a/src/data/config_user.txt b/src/data/config_user.txt index 29823779e..b424a4585 100644 --- a/src/data/config_user.txt +++ b/src/data/config_user.txt @@ -458,3 +458,5 @@ #--lib-opencl /usr/lib/libOpenCL.so #--lib-freetype /data/data/com.termux/files/usr/lib #--lib-pulse /usr/lib/libpulse.so +#--lib-ddcutil /usr/lib/libddcutil.so +#--lib-nm /usr/lib/libnm.so diff --git a/src/data/help.txt b/src/data/help.txt index 137a09597..0f60e7b7a 100644 --- a/src/data/help.txt +++ b/src/data/help.txt @@ -99,6 +99,7 @@ Library options: Set the path of a library to load --lib-opencl --lib-pulse --lib-freetype + --lib-ddcutil Module specific options: --title-fqdn : Set if the title should use fully qualified domain name. Default is false diff --git a/src/detection/brightness/brightness_linux.c b/src/detection/brightness/brightness_linux.c index fc4afd556..41a47aac8 100644 --- a/src/detection/brightness/brightness_linux.c +++ b/src/detection/brightness/brightness_linux.c @@ -6,7 +6,7 @@ #include #include -const char* ffDetectBrightness(FF_MAYBE_UNUSED FFlist* result) +static const char* detectWithBacklight(FFlist* result) { //https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-class-backlight const char* backlightDirPath = "/sys/class/backlight/"; @@ -38,21 +38,21 @@ const char* ffDetectBrightness(FF_MAYBE_UNUSED FFlist* result) ffStrbufAppendS(&backlightDir, "/max_brightness"); if(ffReadFileBuffer(backlightDir.chars, &buffer)) { - FFBrightnessResult* display = (FFBrightnessResult*) ffListAdd(result); + FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result); ffStrbufSubstrBeforeLastC(&backlightDir, '/'); ffStrbufAppendS(&backlightDir, "/device"); - ffStrbufInitA(&display->name, PATH_MAX + 1); - if(realpath(backlightDir.chars, display->name.chars)) + ffStrbufInitA(&brightness->name, PATH_MAX + 1); + if(realpath(backlightDir.chars, brightness->name.chars)) { - ffStrbufRecalculateLength(&display->name); - ffStrbufSubstrAfterLastC(&display->name, '/'); - if(ffStrbufStartsWithS(&display->name, "card") && isdigit(display->name.chars[4])) - ffStrbufSubstrAfterFirstC(&display->name, '-'); + ffStrbufRecalculateLength(&brightness->name); + ffStrbufSubstrAfterLastC(&brightness->name, '/'); + if(ffStrbufStartsWithS(&brightness->name, "card") && isdigit(brightness->name.chars[4])) + ffStrbufSubstrAfterFirstC(&brightness->name, '-'); } else - ffStrbufInitS(&display->name, entry->d_name); + ffStrbufInitS(&brightness->name, entry->d_name); double maxBrightness = ffStrbufToDouble(&buffer); - display->value = (float) (actualBrightness * 100 / maxBrightness); + brightness->value = (float) (actualBrightness * 100 / maxBrightness); } } ffStrbufSubstrBefore(&backlightDir, backlightDirLength); @@ -62,3 +62,111 @@ const char* ffDetectBrightness(FF_MAYBE_UNUSED FFlist* result) return NULL; } + +#ifdef FF_HAVE_DDCUTIL +#include "detection/displayserver/displayserver.h" +#include "common/library.h" +#include "util/mallocHelper.h" + +#include + +static bool findDrmByEdid(const uint8_t srcEdidData[128], FFstrbuf* result) +{ + const char* drmDirPath = "/sys/class/drm/"; + + DIR* dirp = opendir(drmDirPath); + if(dirp == NULL) + return false; + + FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64); + ffStrbufAppendS(&drmDir, drmDirPath); + + uint32_t drmDirLength = drmDir.length; + + struct dirent* entry; + while((entry = readdir(dirp)) != NULL) + { + if(ffStrEquals(entry->d_name, ".") || ffStrEquals(entry->d_name, "..")) + continue; + + ffStrbufAppendS(&drmDir, entry->d_name); + ffStrbufAppendS(&drmDir, "/edid"); + + uint8_t edidData[128]; + if(ffReadFileData(drmDir.chars, sizeof(edidData), edidData) != sizeof(edidData)) + { + ffStrbufSubstrBefore(&drmDir, drmDirLength); + continue; + } + if (memcmp(srcEdidData, edidData, sizeof(edidData)) == 0) + { + ffStrbufAppendS(result, entry->d_name); + closedir(dirp); + return true; + } + } + return false; +} + +static const char* detectWithDdcci(FFlist* result) +{ + FF_LIBRARY_LOAD(libddcutil, &instance.config.libDdcutil, "dlopen ddcutil failed", "libddcutil" FF_LIBRARY_EXTENSION, 4); + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_get_display_info_list2) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_open_display2) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_get_any_vcp_value_using_explicit_type) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_free_any_vcp_value) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_close_display) + + FF_AUTO_FREE DDCA_Display_Info_List* infoList = NULL; + if (__builtin_expect(ffddca_get_display_info_list2(false, &infoList) < 0, 0)) + return "ddca_get_display_info_list2(false, &infoList) failed"; + + if (infoList->ct == 0) + return "No DDC/CI compatible displays found"; + + for (int index = 0; index < infoList->ct; ++index) + { + const DDCA_Display_Info* display = &infoList->info[index]; + + DDCA_Display_Handle handle; + if (ffddca_open_display2(display->dref, false, &handle) >= 0) + { + DDCA_Any_Vcp_Value* vcpValue = NULL; + if (ffddca_get_any_vcp_value_using_explicit_type(handle, 0x10 /*brightness*/, DDCA_NON_TABLE_VCP_VALUE, &vcpValue) >= 0) + { + assert(vcpValue->value_type == DDCA_NON_TABLE_VCP_VALUE); + int current = VALREC_CUR_VAL(vcpValue), max = VALREC_MAX_VAL(vcpValue); + ffddca_free_any_vcp_value(vcpValue); + + FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result); + brightness->value = (float) current * 100.f / (float) max; + ffStrbufInit(&brightness->name); + if (findDrmByEdid(display->edid_bytes, &brightness->name)) + { + if (ffStrbufStartsWithS(&brightness->name, "card")) + ffStrbufSubstrAfterFirstC(&brightness->name, '-'); + } + } + ffddca_close_display(handle); + } + } + + return NULL; +} +#endif + +const char* ffDetectBrightness(FFlist* result) +{ + detectWithBacklight(result); + + #ifdef FF_HAVE_DDCUTIL + if (instance.config.allowSlowOperations) + { + const FFDisplayServerResult* displayServer = ffConnectDisplayServer(); + if (result->length < displayServer->displays.length) + detectWithDdcci(result); + } + #endif + + return NULL; +} diff --git a/src/fastfetch.c b/src/fastfetch.c index 23143c318..c6850f7d4 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -1028,6 +1028,8 @@ static void parseOption(FFdata* data, const char* key, const char* value) ffOptionParseString(key, value, &instance.config.libPulse); else if(ffStrEqualsIgnCase(subkey, "-nm")) ffOptionParseString(key, value, &instance.config.libnm); + else if(ffStrEqualsIgnCase(subkey, "-ddcutil")) + ffOptionParseString(key, value, &instance.config.libDdcutil); else goto error; } diff --git a/src/fastfetch.h b/src/fastfetch.h index 7db8ec8d1..f59506877 100644 --- a/src/fastfetch.h +++ b/src/fastfetch.h @@ -132,6 +132,7 @@ typedef struct FFconfig FFstrbuf libfreetype; FFstrbuf libPulse; FFstrbuf libnm; + FFstrbuf libDdcutil; uint32_t percentType;