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/brightness | |
Add the files
Diffstat (limited to 'src/detection/brightness')
| -rw-r--r-- | src/detection/brightness/brightness.h | 26 | ||||
| -rw-r--r-- | src/detection/brightness/brightness_apple.c | 244 | ||||
| -rw-r--r-- | src/detection/brightness/brightness_bsd.c | 147 | ||||
| -rw-r--r-- | src/detection/brightness/brightness_haiku.cpp | 46 | ||||
| -rw-r--r-- | src/detection/brightness/brightness_linux.c | 176 | ||||
| -rw-r--r-- | src/detection/brightness/brightness_nbsd.c | 26 | ||||
| -rw-r--r-- | src/detection/brightness/brightness_nosupport.c | 5 | ||||
| -rw-r--r-- | src/detection/brightness/brightness_obsd.c | 44 | ||||
| -rw-r--r-- | src/detection/brightness/brightness_windows.c | 223 |
9 files changed, 937 insertions, 0 deletions
diff --git a/src/detection/brightness/brightness.h b/src/detection/brightness/brightness.h new file mode 100644 index 0000000..abb9478 --- /dev/null +++ b/src/detection/brightness/brightness.h @@ -0,0 +1,26 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/brightness/option.h" + +typedef struct FFBrightnessResult { + FFstrbuf name; + double min, max, current; + bool builtin; +} FFBrightnessResult; + +enum { + FF_DDC_EDID_ADDR = 0x50u, + FF_DDC_CI_ADDR = 0x37u, + FF_DDC_CI_WRITE_ADDR = FF_DDC_CI_ADDR << 1, + FF_DDC_CI_READ_ADDR = FF_DDC_CI_WRITE_ADDR | 1, + FF_DDC_CI_VCP_COMMAND = 0x51u, + FF_DDC_CI_GET_VCP = 0x01u, + FF_DDC_CI_SET_VCP = 0x03u, + FF_DDC_CI_COMMAND_PACKET = 0x80u, + FF_DDC_CI_LUMINANCE_OPCODE = 0x10u, +}; +#define FF_BRIGHTNESS_DDCCI_SLEEP_SKIP ((uint32_t) -1) +#define FF_DDC_CI_MAKE_HEADER(len) (FF_DDC_CI_COMMAND_PACKET | ((len) & 0x7F)) + +const char* ffDetectBrightness(FFBrightnessOptions* options, FFlist* result); // list of FFBrightnessResult diff --git a/src/detection/brightness/brightness_apple.c b/src/detection/brightness/brightness_apple.c new file mode 100644 index 0000000..5dd12b2 --- /dev/null +++ b/src/detection/brightness/brightness_apple.c @@ -0,0 +1,244 @@ +#include "brightness.h" +#include "detection/displayserver/displayserver.h" +#include "common/apple/cf_helpers.h" +#include "common/edidHelper.h" +#include "common/time.h" + +#include <CoreGraphics/CoreGraphics.h> + +// DDC/CI +#ifdef __aarch64__ +typedef CFTypeRef IOAVServiceRef; +extern IOAVServiceRef IOAVServiceCreate(CFAllocatorRef allocator) FF_A_WEAK_IMPORT; +extern IOAVServiceRef IOAVServiceCreateWithService(CFAllocatorRef allocator, io_service_t service) FF_A_WEAK_IMPORT; +extern IOReturn IOAVServiceCopyEDID(IOAVServiceRef service, CFDataRef* x2) FF_A_WEAK_IMPORT; +extern IOReturn IOAVServiceReadI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t offset, void* outputBuffer, uint32_t outputBufferSize) FF_A_WEAK_IMPORT; +extern IOReturn IOAVServiceWriteI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t dataAddress, void* inputBuffer, uint32_t inputBufferSize) FF_A_WEAK_IMPORT; +#else + // DDC/CI (Intel) + #include <IOKit/IOKitLib.h> + #include <IOKit/graphics/IOGraphicsLib.h> + #include <IOKit/i2c/IOI2CInterface.h> +extern void CGSServiceForDisplayNumber(CGDirectDisplayID display, io_service_t* service) FF_A_WEAK_IMPORT; +#endif + +// ACPI +extern int DisplayServicesGetBrightness(CGDirectDisplayID display, float* brightness) FF_A_WEAK_IMPORT; + +// Works for internal display +static const char* detectWithDisplayServices(const FFDisplayServerResult* displayServer, FFlist* result) { + if (DisplayServicesGetBrightness == NULL) { + return "DisplayServices function DisplayServicesGetBrightness is not available"; + } + + FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) { + if (display->type == FF_DISPLAY_TYPE_BUILTIN || display->type == FF_DISPLAY_TYPE_UNKNOWN) { + float value; + if (DisplayServicesGetBrightness((CGDirectDisplayID) display->id, &value) == kCGErrorSuccess) { + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + brightness->current = value; + brightness->max = 1; + brightness->min = 0; + ffStrbufInitCopy(&brightness->name, &display->name); + brightness->builtin = true; + } + } + } + + return NULL; +} + +#ifdef __aarch64__ +// https://github.com/waydabber/m1ddc +// Works for Apple Silicon and USB-C adapter connection ( but not HTMI ) +static const char* detectWithDdcci(FF_A_UNUSED const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result) { + if (!IOAVServiceCreate || !IOAVServiceReadI2C || !IOAVServiceWriteI2C) { + return "IOAVService is not available"; + } + + FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL; + if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("DCPAVServiceProxy"), &iterator) != kIOReturnSuccess) { + return "IOServiceGetMatchingServices() failed"; + } + + io_registry_entry_t registryEntry; + while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) { + FF_CFTYPE_AUTO_RELEASE IOAVServiceRef service = NULL; + { + FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryAv = registryEntry; + + FF_CFTYPE_AUTO_RELEASE CFBooleanRef IOAVServiceUserInterfaceSupported = IORegistryEntryCreateCFProperty(entryAv, CFSTR("IOAVServiceUserInterfaceSupported"), kCFAllocatorDefault, kNilOptions); + if (IOAVServiceUserInterfaceSupported && !CFBooleanGetValue(IOAVServiceUserInterfaceSupported)) { + // IOAVServiceCreateWithService won't work + continue; + } + + FF_CFTYPE_AUTO_RELEASE CFStringRef location = IORegistryEntryCreateCFProperty(entryAv, CFSTR("Location"), kCFAllocatorDefault, kNilOptions); + if (location && CFStringCompare(location, CFSTR("Embedded"), 0) == 0) { + // Builtin display should be handled by DisplayServices + continue; + } + + service = IOAVServiceCreateWithService(kCFAllocatorDefault, (io_service_t) registryEntry); + if (!service) { + continue; + } + } + + { + uint8_t i2cIn[4] = { FF_DDC_CI_MAKE_HEADER(2), FF_DDC_CI_GET_VCP, FF_DDC_CI_LUMINANCE_OPCODE }; + i2cIn[3] = FF_DDC_CI_WRITE_ADDR ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2]; + + for (uint32_t i = 0; i < 2; ++i) { + IOAVServiceWriteI2C(service, FF_DDC_CI_ADDR, FF_DDC_CI_VCP_COMMAND, i2cIn, ARRAY_SIZE(i2cIn)); + ffTimeSleep(options->ddcciSleep); + } + } + + uint8_t i2cOut[12] = {}; + if (IOAVServiceReadI2C(service, FF_DDC_CI_ADDR, FF_DDC_CI_VCP_COMMAND, i2cOut, ARRAY_SIZE(i2cOut)) == KERN_SUCCESS) { + if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) { + continue; + } + + uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9]; + uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7]; + + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + brightness->max = max; + brightness->min = 0; + brightness->current = current; + ffStrbufInit(&brightness->name); + brightness->builtin = false; + + uint8_t edid[128] = {}; + if (IOAVServiceReadI2C(service, 0x50, 0x00, edid, ARRAY_SIZE(edid)) == KERN_SUCCESS) { + ffEdidGetName(edid, &brightness->name); + } + } + } + + return NULL; +} +#else +static IOOptionBits getSupportedTransactionType(void) { + FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL; + + if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceNameMatching("IOFramebufferI2CInterface"), &iterator) != KERN_SUCCESS) { + return kIOI2CNoTransactionType; + } + + io_registry_entry_t registryEntry; + while ((registryEntry = IOIteratorNext(iterator)) != MACH_PORT_NULL) { + FF_IOOBJECT_AUTO_RELEASE io_service_t io_service = registryEntry; + FF_CFTYPE_AUTO_RELEASE CFNumberRef IOI2CTransactionTypes = IORegistryEntryCreateCFProperty(io_service, CFSTR(kIOI2CTransactionTypesKey), kCFAllocatorDefault, kNilOptions); + + if (IOI2CTransactionTypes) { + int64_t types = 0; + ffCfNumGetInt64(IOI2CTransactionTypes, &types); + + if (types) { + if ((1 << kIOI2CDDCciReplyTransactionType) & (uint64_t) types) { + return kIOI2CDDCciReplyTransactionType; + } + if ((1 << kIOI2CSimpleTransactionType) & (uint64_t) types) { + return kIOI2CSimpleTransactionType; + } + } + } + break; + } + + return kIOI2CNoTransactionType; +} + +static const char* detectWithDdcci(const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result) { + if (!CGSServiceForDisplayNumber) { + return "CGSServiceForDisplayNumber is not available"; + } + IOOptionBits transactionType = getSupportedTransactionType(); + if (transactionType == kIOI2CNoTransactionType) { + return "No supported IOI2C transaction type found"; + } + + FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) { + if (display->type == FF_DISPLAY_TYPE_EXTERNAL) { + FF_IOOBJECT_AUTO_RELEASE io_service_t framebuffer = IO_OBJECT_NULL; + CGSServiceForDisplayNumber((CGDirectDisplayID) display->id, &framebuffer); + if (framebuffer == IO_OBJECT_NULL) { + continue; + } + + IOItemCount count; + if (IOFBGetI2CInterfaceCount(framebuffer, &count) != KERN_SUCCESS || count == 0) { + continue; + } + + for (IOItemCount bus = 0; bus < count; ++bus) { + FF_IOOBJECT_AUTO_RELEASE io_service_t interface = IO_OBJECT_NULL; + if (IOFBCopyI2CInterfaceForBus(framebuffer, bus, &interface) != KERN_SUCCESS) { + continue; + } + + uint8_t i2cOut[12] = {}; + IOI2CConnectRef connect = NULL; + if (IOI2CInterfaceOpen(interface, kNilOptions, &connect) != KERN_SUCCESS) { + continue; + } + + uint8_t i2cIn[] = { FF_DDC_CI_VCP_COMMAND, FF_DDC_CI_MAKE_HEADER(2), FF_DDC_CI_GET_VCP, FF_DDC_CI_LUMINANCE_OPCODE, 0 }; + i2cIn[4] = FF_DDC_CI_WRITE_ADDR ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2] ^ i2cIn[3]; + + IOI2CRequest request = { + .commFlags = kNilOptions, + .sendAddress = FF_DDC_CI_WRITE_ADDR, + .sendTransactionType = kIOI2CSimpleTransactionType, + .sendBuffer = (vm_address_t) i2cIn, + .sendBytes = ARRAY_SIZE(i2cIn), + .minReplyDelay = options->ddcciSleep * 1000ULL, + .replyAddress = FF_DDC_CI_READ_ADDR, + .replySubAddress = FF_DDC_CI_VCP_COMMAND, + .replyTransactionType = transactionType, + .replyBytes = ARRAY_SIZE(i2cOut), + .replyBuffer = (vm_address_t) i2cOut, + }; + IOReturn ret = IOI2CSendRequest(connect, kNilOptions, &request); + IOI2CInterfaceClose(connect, kNilOptions); + + if (ret != KERN_SUCCESS || request.result != kIOReturnSuccess || request.replyBytes < 10) { + continue; + } + if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) { + continue; + } + + uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9]; + uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7]; + + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + brightness->max = max; + brightness->min = 0; + brightness->current = current; + ffStrbufInitCopy(&brightness->name, &display->name); + brightness->builtin = false; + + break; + } + } + } + + return NULL; +} +#endif + +const char* ffDetectBrightness(FFBrightnessOptions* options, FFlist* result) { + const FFDisplayServerResult* displayServer = ffConnectDisplayServer(); + + detectWithDisplayServices(displayServer, result); + + if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP && displayServer->displays.length > result->length) { + detectWithDdcci(displayServer, options, result); + } + + return NULL; +} diff --git a/src/detection/brightness/brightness_bsd.c b/src/detection/brightness/brightness_bsd.c new file mode 100644 index 0000000..5ff411c --- /dev/null +++ b/src/detection/brightness/brightness_bsd.c @@ -0,0 +1,147 @@ +#include "brightness.h" +#include "common/arrutil.h" +#include "common/io.h" +#include "common/kmod.h" +#include "common/debug.h" +#include "common/time.h" + +#include <sys/ioctl.h> +#include <sys/fcntl.h> +#include <unistd.h> + +#if __has_include(<dev/iicbus/iic.h>) + #include <dev/iicbus/iic.h> + +const char* detectWithDdcci(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + // FIXME: doesn't work for me + for (char i = '0'; i <= '9'; ++i) { + char path[] = "/dev/iic0"; + path[ARRAY_SIZE(path) - 2] = i; + + FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC); + if (fd < 0) { + int err = errno; + if (err == ENOENT) { + FF_DEBUG("No more I2C devices"); + break; + } else if (i == '0' && !ffKmodLoaded("iicbus")) { + FF_DEBUG("iicbus module is not loaded"); + return "No I2C devices found and iicbus module is not loaded"; + } + FF_DEBUG("open(/dev/iic%c) failed: %s", i, strerror(errno)); + continue; + } + + uint8_t i2cIn[] = { FF_DDC_CI_VCP_COMMAND, FF_DDC_CI_MAKE_HEADER(2), FF_DDC_CI_GET_VCP, FF_DDC_CI_LUMINANCE_OPCODE, 0 }; + i2cIn[4] = FF_DDC_CI_WRITE_ADDR ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2] ^ i2cIn[3]; + + int ret = ioctl(fd, I2CRDWR, &(struct iic_rdwr_data){ .msgs = &(struct iic_msg){ .slave = FF_DDC_CI_WRITE_ADDR, .flags = IIC_M_WR, .len = ARRAY_SIZE(i2cIn), .buf = i2cIn }, .nmsgs = 1 }); + if (ret < 0) { + FF_DEBUG("First ioctl(/dev/iic%c, I2CRDWR) failed: %s", i, strerror(errno)); + continue; + } + + ffTimeSleep(options->ddcciSleep); + + uint8_t i2cOut[12] = {}; + ret = ioctl(fd, I2CRDWR, &(struct iic_rdwr_data){ .msgs = &(struct iic_msg){ .slave = FF_DDC_CI_READ_ADDR, // LSB will be overridden by kernel to set read bit + .flags = IIC_M_RD, + .len = ARRAY_SIZE(i2cOut), + .buf = i2cOut }, + .nmsgs = 1 }); + if (ret < 0) { + FF_DEBUG("Second ioctl(/dev/iic%c, I2CRDWR) failed: %s", i, strerror(errno)); + continue; + } + if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) { + FF_DEBUG("i2c out validation failed: i2cOut[2] = 0x%02x, i2cOut[3] = 0x%02x", i2cOut[2], i2cOut[3]); + continue; + } + + uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9]; + uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7]; + + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + brightness->max = max; + brightness->min = 0; + brightness->current = current; + ffStrbufInitS(&brightness->name, path + strlen("/dev/")); + brightness->builtin = false; + } + + return NULL; +} + +#else + +const char* detectWithDdcci(FF_A_UNUSED FFBrightnessOptions* options, FF_A_UNUSED FFlist* result) { + FF_DEBUG("DDC/CI support is not available on this system"); + return "DDC/CI is supported only on FreeBSD"; +} + +#endif + +#if __has_include(<sys/backlight.h>) + #include <sys/backlight.h> + +const char* detectWithBacklight(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + // https://man.freebsd.org/cgi/man.cgi?query=backlight&sektion=9 + char path[] = "/dev/backlight/backlight0"; + + for (char i = '0'; i <= '9'; ++i) { + path[ARRAY_SIZE(path) - 2] = i; + + FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + int err = errno; + if (err == ENOENT) { + FF_DEBUG("No more backlight devices"); + break; + } else if (i == '0' && !ffKmodLoaded("backlight")) { + FF_DEBUG("backlight module is not loaded"); + return "No backlight devices found and backlight module is not loaded"; + } + FF_DEBUG("open(/dev/backlight/backlight%c) failed: %s", i, strerror(errno)); + continue; + } + + struct backlight_props status; + if (ioctl(fd, BACKLIGHTGETSTATUS, &status) < 0) { + continue; + } + + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + ffStrbufInit(&brightness->name); + + brightness->max = BACKLIGHTMAXLEVELS; + brightness->min = 0; + brightness->current = status.brightness; + brightness->builtin = true; + + struct backlight_info info; + if (ioctl(fd, BACKLIGHTGETINFO, &info) == 0) { + ffStrbufAppendS(&brightness->name, info.name); + } else { + ffStrbufAppendS(&brightness->name, path + strlen("/dev/backlight/")); + } + } + return NULL; +} + +#else + +const char* detectWithBacklight(FF_A_UNUSED FFBrightnessOptions* options, FF_A_UNUSED FFlist* result) { + FF_DEBUG("Backlight support is not available on this system"); + return "Backlight is supported only on FreeBSD 13 and newer"; +} + +#endif + +const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + detectWithBacklight(options, result); + + if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP && result->length == 0) { + detectWithDdcci(options, result); + } + return NULL; +} diff --git a/src/detection/brightness/brightness_haiku.cpp b/src/detection/brightness/brightness_haiku.cpp new file mode 100644 index 0000000..86fbe6d --- /dev/null +++ b/src/detection/brightness/brightness_haiku.cpp @@ -0,0 +1,46 @@ +extern "C" { +#include "brightness.h" +#include "common/strutil.h" +} + +#include <Application.h> +#include <Screen.h> + +const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + // We need a valid be_app to query the app_server here. + BApplication app("application/x-vnd.fastfetch-cli-fastfetch"); + BScreen screen{}; // default screen is the main one + + do { + if (!screen.IsValid()) { + continue; + } + + float value = 1.0f; + status_t status = screen.GetBrightness(&value); + if (status != B_OK) { + continue; + } + + monitor_info monitor; + // WARNING: This is experimental new Haiku API + status_t err = screen.GetMonitorInfo(&monitor); + + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + + if (err == B_OK) { + ffStrbufInitF(&brightness->name, "%s %s (%d)", monitor.vendor, monitor.name, screen.ID().id); + ffStrbufTrimRightSpace(&brightness->name); + } else { + ffStrbufInitF(&brightness->name, "Screen %d", screen.ID().id); + } + + brightness->max = 1.0f; + brightness->min = 0.0f; + brightness->current = value; + brightness->builtin = true; + + } while (screen.SetToNext() == B_OK); + + return NULL; +} diff --git a/src/detection/brightness/brightness_linux.c b/src/detection/brightness/brightness_linux.c new file mode 100644 index 0000000..e7125f8 --- /dev/null +++ b/src/detection/brightness/brightness_linux.c @@ -0,0 +1,176 @@ +#include "brightness.h" +#include "common/io.h" +#include "common/edidHelper.h" +#include "common/strutil.h" + +#include <dirent.h> +#include <limits.h> + +static const char* detectWithBacklight(FFlist* result) { + // https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-class-backlight + const char* backlightDirPath = "/sys/class/backlight/"; + + DIR* dirp = opendir(backlightDirPath); + if (dirp == NULL) { + return "Failed to open `/sys/class/backlight/`"; + } + + FF_STRBUF_AUTO_DESTROY backlightDir = ffStrbufCreateA(64); + ffStrbufAppendS(&backlightDir, backlightDirPath); + + uint32_t backlightDirLength = backlightDir.length; + + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + + ffStrbufAppendS(&backlightDir, entry->d_name); + ffStrbufAppendS(&backlightDir, "/brightness"); + if (ffReadFileBuffer(backlightDir.chars, &buffer)) { + double actualBrightness = ffStrbufToDouble(&buffer, 0); + ffStrbufSubstrBefore(&backlightDir, backlightDirLength); + ffStrbufAppendS(&backlightDir, entry->d_name); + ffStrbufAppendS(&backlightDir, "/max_brightness"); + if (ffReadFileBuffer(backlightDir.chars, &buffer)) { + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + ffStrbufSubstrBeforeLastC(&backlightDir, '/'); + ffStrbufAppendS(&backlightDir, "/device"); + ffStrbufInitA(&brightness->name, PATH_MAX); + if (realpath(backlightDir.chars, brightness->name.chars)) { + ffStrbufRecalculateLength(&brightness->name); + // if we managed to get edid, use it + ffStrbufAppendS(&brightness->name, "/edid"); + uint8_t edidData[128]; + if (ffReadFileData(brightness->name.chars, ARRAY_SIZE(edidData), edidData) == ARRAY_SIZE(edidData)) { + ffStrbufClear(&brightness->name); + ffEdidGetName(edidData, &brightness->name); + } else { + ffStrbufSubstrBeforeLastC(&brightness->name, '/'); // remove "/edid" + ffStrbufSubstrAfterLastC(&brightness->name, '/'); // try getting DRM connector name + if (ffCharIsDigit(brightness->name.chars[0])) { + // PCI address or some unknown path, give up + ffStrbufSetS(&brightness->name, entry->d_name); + } else { + if (ffStrbufStartsWithS(&brightness->name, "card") && ffCharIsDigit(brightness->name.chars[4])) { + ffStrbufSubstrAfterFirstC(&brightness->name, '-'); + } + } + } + } else { + ffStrbufInitS(&brightness->name, entry->d_name); + } + brightness->max = ffStrbufToDouble(&buffer, 0); + brightness->min = 0; + brightness->current = actualBrightness; + brightness->builtin = true; + } + } + ffStrbufSubstrBefore(&backlightDir, backlightDirLength); + } + + closedir(dirp); + + return NULL; +} + +#ifdef FF_HAVE_DDCUTIL + #include "detection/displayserver/displayserver.h" + #include "common/library.h" + #include "common/mallocHelper.h" + + #include <ddcutil_macros.h> + #include <ddcutil_c_api.h> + + // Try to be compatible with ddcutil 2.0 + #if DDCUTIL_VMAJOR >= 2 +double ddca_set_default_sleep_multiplier(double multiplier); // ddcutil 1.4 + #else +DDCA_Status ddca_init(const char* libopts, int syslog_level, int opts); + #endif + +static const char* detectWithDdcci(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + FF_LIBRARY_LOAD_MESSAGE(libddcutil, "libddcutil" FF_LIBRARY_EXTENSION, 5); + 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) + + #ifndef FF_DISABLE_DLOPEN + FF_LIBRARY_LOAD_SYMBOL_LAZY(libddcutil, ddca_init) + if (ffddca_init) { + FF_SUPPRESS_IO(); + // Ref: https://github.com/rockowitz/ddcutil/issues/344 + if (ffddca_init(NULL, -1 /*DDCA_SYSLOG_NOT_SET*/, 1 /*DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE*/) < 0) { + return "ddca_init() failed"; + } + } else { + FF_LIBRARY_LOAD_SYMBOL_LAZY(libddcutil, ddca_set_default_sleep_multiplier); + if (ffddca_set_default_sleep_multiplier) { + ffddca_set_default_sleep_multiplier(options->ddcciSleep / 40.0); + } + + libddcutil = NULL; // Don't dlclose libddcutil. See https://github.com/rockowitz/ddcutil/issues/330 + } + #else + #if DDCUTIL_VMAJOR >= 2 + if (ddca_init(NULL, -1 /*DDCA_SYSLOG_NOT_SET*/, 1 /*DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE*/) < 0) { + return "ddca_init() failed"; + } + #else + ddca_set_default_sleep_multiplier(options->ddcciSleep / 40.0); + #endif + #endif + + FF_AUTO_FREE DDCA_Display_Info_List* infoList = NULL; + if (ffddca_get_display_info_list2(false, &infoList) < 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 = FF_LIST_ADD(FFBrightnessResult, *result); + brightness->max = max; + brightness->min = 0; + brightness->current = current; + ffStrbufInitS(&brightness->name, display->model_name); + } + ffddca_close_display(handle); + } + } + + return NULL; +} +#endif + +const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + detectWithBacklight(result); + +#ifdef FF_HAVE_DDCUTIL + if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP) { + const FFDisplayServerResult* displayServer = ffConnectDisplayServer(); + if (result->length < displayServer->displays.length) { + detectWithDdcci(options, result); + } + } +#endif + + return NULL; +} diff --git a/src/detection/brightness/brightness_nbsd.c b/src/detection/brightness/brightness_nbsd.c new file mode 100644 index 0000000..afd6656 --- /dev/null +++ b/src/detection/brightness/brightness_nbsd.c @@ -0,0 +1,26 @@ +#include "brightness.h" + +#include "common/sysctl.h" + +const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + // https://man.netbsd.org/NetBSD-10.1/acpiout.4#DESCRIPTION + char key[] = "hw.acpi.acpiout0.brightness"; + char* pn = key + strlen("hw.acpi.acpiout"); + + for (uint32_t i = 0; i <= 9; ++i) { + *pn = (char) ('0' + i); + int value = ffSysctlGetInt(key, -1); + if (value == -1) { + continue; + } + + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + ffStrbufInitF(&brightness->name, "acpiout%d", i); + + brightness->max = 100; + brightness->min = 0; + brightness->current = value; + brightness->builtin = true; + } + return NULL; +} diff --git a/src/detection/brightness/brightness_nosupport.c b/src/detection/brightness/brightness_nosupport.c new file mode 100644 index 0000000..6e9284e --- /dev/null +++ b/src/detection/brightness/brightness_nosupport.c @@ -0,0 +1,5 @@ +#include "brightness.h" + +const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FF_A_UNUSED FFlist* result) { + return "Not supported on this platform"; +} diff --git a/src/detection/brightness/brightness_obsd.c b/src/detection/brightness/brightness_obsd.c new file mode 100644 index 0000000..102a8c2 --- /dev/null +++ b/src/detection/brightness/brightness_obsd.c @@ -0,0 +1,44 @@ +#include "brightness.h" +#include "common/io.h" + +#include <dev/wscons/wsconsio.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <fcntl.h> + +const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + char path[] = "/dev/ttyCX"; + for (char i = '0'; i <= '9'; ++i) { + path[strlen("/dev/ttyC")] = i; + + FF_AUTO_CLOSE_FD int devfd = open(path, O_RDONLY | O_CLOEXEC); + + if (devfd < 0) { + if (errno == EACCES && i == '0') { + return "Permission denied when opening tty device"; + } + if (errno == ENOENT) { + break; + } + continue; + } + + struct wsdisplay_param param = { + .param = WSDISPLAYIO_PARAM_BRIGHTNESS, + }; + + if (ioctl(devfd, WSDISPLAYIO_GETPARAM, ¶m) < 0) { + continue; + } + + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + ffStrbufInitF(&brightness->name, "ttyC%c", i); + + brightness->max = param.max; + brightness->min = param.min; + brightness->current = param.curval; + brightness->builtin = true; + } + + return NULL; +} diff --git a/src/detection/brightness/brightness_windows.c b/src/detection/brightness/brightness_windows.c new file mode 100644 index 0000000..70b7f01 --- /dev/null +++ b/src/detection/brightness/brightness_windows.c @@ -0,0 +1,223 @@ +#include "brightness.h" +#include "detection/displayserver/displayserver.h" +#include "common/debug.h" +#include "common/library.h" +#include "common/mallocHelper.h" +#include "common/windows/wmi.h" +#include "common/windows/unicode.h" + +#include <inttypes.h> +#include <winternl.h> +#include <windows.h> + +NTSYSAPI NTSTATUS WINAPI GetPhysicalMonitors( + _In_ UNICODE_STRING* pstrDeviceName, + _In_ DWORD dwPhysicalMonitorArraySize, + _Out_ DWORD* pdwNumPhysicalMonitorHandlesInArray, + _Out_ HANDLE* phPhysicalMonitorArray); + +typedef enum _MC_VCP_CODE_TYPE { + MC_MOMENTARY, + MC_SET_PARAMETER +} MC_VCP_CODE_TYPE, + *LPMC_VCP_CODE_TYPE; + +NTSYSAPI NTSTATUS WINAPI DDCCIGetVCPFeature( + _In_ HANDLE hMonitor, + _In_ DWORD dwVCPCode, + _Out_opt_ LPMC_VCP_CODE_TYPE pvct, + _Out_ DWORD* pdwCurrentValue, + _Out_opt_ DWORD* pdwMaximumValue); + +NTSYSAPI NTSTATUS WINAPI DestroyPhysicalMonitorInternal( + _In_ HANDLE hMonitor); + +NTSTATUS WINAPI GetPhysicalMonitorDescription( + _In_ HANDLE hMonitor, + _In_ DWORD dwPhysicalMonitorDescriptionSizeInChars, + _Out_ LPWSTR szPhysicalMonitorDescription); + +static const char* detectWithWmi(FFlist* result) { + FF_DEBUG("WMI: start detection"); + + // https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/km/wmicore.mof#L21200 + const GUID WmiMonitorBrightnessGuid = { + 0xd43412ac, 0x67f9, 0x4fbb, { 0xa0, 0x81, 0x17, 0x52, 0xa2, 0xc3, 0x3e, 0x84 } + }; + + FF_AUTO_CLOSE_WMI_BLOCK HANDLE hBlock = NULL; + + ULONG status = WmiOpenBlock(&WmiMonitorBrightnessGuid, WMIGUID_QUERY, &hBlock); + if (status != 0) { + FF_DEBUG("WMI: WmiOpenBlock failed: %s", ffDebugWin32Error(status)); + return "WmiOpenBlock() failed"; + } + + ULONG bufferSize = 0; + status = WmiQueryAllDataW(hBlock, &bufferSize, NULL); + if (status != ERROR_SUCCESS && status != ERROR_INSUFFICIENT_BUFFER) { + FF_DEBUG("WMI: first WmiQueryAllDataW failed, bufferSize=%lu: %s", bufferSize, ffDebugWin32Error(status)); + return "WmiQueryAllDataW() failed"; + } + + FF_DEBUG("WMI: initial query bufferSize=%lu", bufferSize); + + if (bufferSize == 0) { + FF_DEBUG("WMI: WmiQueryAllDataW returned empty buffer"); + return "WmiQueryAllDataW() returned no data"; + } + + FF_AUTO_FREE PWNODE_ALL_DATA pAllData = (PWNODE_ALL_DATA) malloc(bufferSize); + + status = WmiQueryAllDataW(hBlock, &bufferSize, pAllData); + if (status != ERROR_SUCCESS) { + FF_DEBUG("WMI: second WmiQueryAllDataW failed, bufferSize=%lu: %s", bufferSize, ffDebugWin32Error(status)); + return "WmiQueryAllDataW() failed"; + } + + if (bufferSize < sizeof(WNODE_ALL_DATA)) { + FF_DEBUG("WMI: insufficient buffer for WNODE_ALL_DATA, bufferSize=%lu", bufferSize); + return "WmiQueryAllDataW() returned insufficient data for WNODE_ALL_DATA"; + } + + FF_DEBUG("WMI: instanceCount=%lu, flags=0x%lX", pAllData->InstanceCount, pAllData->WnodeHeader.Flags); + + PULONG pNameOffsets = (PULONG) ((PUCHAR) pAllData + pAllData->OffsetInstanceNameOffsets); + + for (ULONG i = 0; i < pAllData->InstanceCount; i++) { + ULONG dataOffset = 0; + ULONG dataLength = 0; + + if (pAllData->WnodeHeader.Flags & WNODE_FLAG_FIXED_INSTANCE_SIZE) { + dataLength = pAllData->FixedInstanceSize; + dataOffset = pAllData->DataBlockOffset + i * dataLength; + } else { + dataOffset = pAllData->OffsetInstanceDataAndLength[i].OffsetInstanceData; + dataLength = pAllData->OffsetInstanceDataAndLength[i].LengthInstanceData; + } + + if (dataLength == 0 || dataOffset >= bufferSize || dataLength > bufferSize - dataOffset) { + FF_DEBUG("WMI: skip invalid instance %lu (dataOffset=%lu, dataLength=%lu, bufferSize=%lu)", i, dataOffset, dataLength, bufferSize); + continue; + } + + USHORT nameCharsCount = *(PUSHORT) ((PUCHAR) pAllData + pNameOffsets[i]) / sizeof(WCHAR); + PCWSTR pNameChars = (PCWSTR) ((PUCHAR) pAllData + (pNameOffsets[i] + sizeof(USHORT))); + + PUCHAR pDataBlock = (PUCHAR) pAllData + dataOffset; + UCHAR currentBrightness = pDataBlock[0]; + + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + brightness->max = 100; + brightness->min = 0; + brightness->current = currentBrightness; + brightness->builtin = true; + ffStrbufInitNWS(&brightness->name, nameCharsCount, pNameChars); + ffStrbufSubstrAfterFirstC(&brightness->name, '\\'); + ffStrbufSubstrBeforeFirstC(&brightness->name, '\\'); + + FF_DEBUG("WMI: detected builtin display '%s', current=%u", brightness->name.chars, (unsigned) currentBrightness); + } + + FF_DEBUG("WMI: finished detection, total results=%u", result->length); + + return NULL; +} + +static const char* detectWithDdcci(const FFDisplayServerResult* displayServer, FFlist* result) { + FF_DEBUG("DDC/CI: start detection, displayCount=%u", displayServer->displays.length); + + void* gdi32 = ffLibraryGetModule(L"gdi32.dll"); + if (!gdi32) { + FF_DEBUG("DDC/CI: failed to load gdi32.dll"); + return "ffLibraryGetModule(gdi32.dll) failed"; + } + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, GetPhysicalMonitors) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, DDCCIGetVCPFeature) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, DestroyPhysicalMonitorInternal) + + FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) { + if (display->type == FF_DISPLAY_TYPE_BUILTIN) { + FF_DEBUG("DDC/CI: skip builtin display id=%" PRIu64, display->id); + continue; + } + + MONITORINFOEXW mi; + mi.cbSize = sizeof(mi); + if (!GetMonitorInfoW((HMONITOR) (uintptr_t) display->id, (LPMONITORINFO) &mi)) { + FF_DEBUG("DDC/CI: GetMonitorInfoW failed for display id=%" PRIu64 ": %s", display->id, ffDebugWin32Error(GetLastError())); + continue; + } + + UNICODE_STRING deviceName = { + .Length = (USHORT) (wcslen(mi.szDevice) * sizeof(wchar_t)), + .MaximumLength = 0, + .Buffer = mi.szDevice, + }; + HANDLE physicalMonitor; + DWORD monitorCount = 0; + NTSTATUS monitorStatus = ffGetPhysicalMonitors(&deviceName, 1, &monitorCount, &physicalMonitor); + if (NT_SUCCESS(monitorStatus) && monitorCount >= 1) { + DWORD curr = 0, max = 0; + if (NT_SUCCESS(ffDDCCIGetVCPFeature(physicalMonitor, FF_DDC_CI_LUMINANCE_OPCODE, NULL, &curr, &max))) { + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + if (display->name.length > 0) { + ffStrbufInitCopy(&brightness->name, &display->name); + } else { + FF_LIBRARY_LOAD_SYMBOL_LAZY(gdi32, GetPhysicalMonitorDescription) + if (ffGetPhysicalMonitorDescription) { + wchar_t description[128 /*MUST be PHYSICAL_MONITOR_DESCRIPTION_SIZE*/]; + if (NT_SUCCESS(ffGetPhysicalMonitorDescription(physicalMonitor, ARRAY_SIZE(description), description))) { + ffStrbufInitWS(&brightness->name, description); + } + } + if (brightness->name.length == 0) { + ffStrbufSetNWS(&brightness->name, deviceName.Length / 2, deviceName.Buffer); + } + } + brightness->max = max; + brightness->min = 0; + brightness->current = curr; + brightness->builtin = false; + + FF_DEBUG("DDC/CI: detected external display '%s', current=%u, max=%u", brightness->name.chars, (unsigned) curr, (unsigned) max); + } else { + FF_DEBUG("DDC/CI: DDCCIGetVCPFeature failed for monitor '%ls': %s", deviceName.Buffer, ffDebugWin32Error(GetLastError())); + } + + ffDestroyPhysicalMonitorInternal(physicalMonitor); + } else { + FF_DEBUG("DDC/CI: GetPhysicalMonitors failed for '%ls', status=0x%08X, monitorCount=%lu, error=%s", deviceName.Buffer, (unsigned) monitorStatus, monitorCount, ffDebugWin32Error(GetLastError())); + } + } + + FF_DEBUG("DDC/CI: finished detection, total results=%u", result->length); + return NULL; +} + +static bool hasBuiltinDisplay(const FFDisplayServerResult* displayServer) { + FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) { + if (display->type == FF_DISPLAY_TYPE_BUILTIN || display->type == FF_DISPLAY_TYPE_UNKNOWN) { + return true; + } + } + return false; +} + +const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + const FFDisplayServerResult* displayServer = ffConnectDisplayServer(); + FF_DEBUG("start, displayCount=%u", displayServer->displays.length); + + if (hasBuiltinDisplay(displayServer)) { + FF_DEBUG("builtin display detected, trying WMI"); + detectWithWmi(result); + } + + if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP && result->length < displayServer->displays.length) { + FF_DEBUG("resultCount=%u < displayCount=%u, trying DDC/CI", result->length, displayServer->displays.length); + detectWithDdcci(displayServer, result); + } + + FF_DEBUG("finished, resultCount=%u", result->length); + return NULL; +} |