mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-28 02:31:25 +01:00
Brightness (Linux): adds a native impl
This commit is contained in:
@@ -11,6 +11,7 @@ typedef struct FFBrightnessResult {
|
||||
|
||||
enum {
|
||||
FF_DDC_EDID_ADDR = 0x50u,
|
||||
FF_DDC_EDID_WRITE_ADDR = FF_DDC_EDID_ADDR << 1,
|
||||
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,
|
||||
@@ -19,8 +20,55 @@ enum {
|
||||
FF_DDC_CI_SET_VCP = 0x03u,
|
||||
FF_DDC_CI_COMMAND_PACKET = 0x80u,
|
||||
FF_DDC_CI_LUMINANCE_OPCODE = 0x10u,
|
||||
FF_DDC_CI_VCP_RESPONSE_LENGTH = 8u,
|
||||
FF_DDC_CI_VCP_RESPONSE_PACKET_SIZE = 11u,
|
||||
FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE = 12u,
|
||||
FF_DDC_CI_RESPONSE_CHECKSUM_ADDR = 0x50u,
|
||||
};
|
||||
#define FF_BRIGHTNESS_DDCCI_SLEEP_SKIP ((uint32_t) -1)
|
||||
#define FF_DDC_CI_MAKE_HEADER(len) (FF_DDC_CI_COMMAND_PACKET | ((len) & 0x7F))
|
||||
|
||||
static inline uint8_t ffDdcCiChecksum(uint8_t address, const uint8_t* data, size_t length) {
|
||||
uint8_t checksum = address;
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
checksum ^= data[i];
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
static inline void ffDdcCiBuildGetVcpRequest(uint8_t request[5]) {
|
||||
request[0] = FF_DDC_CI_VCP_COMMAND;
|
||||
request[1] = FF_DDC_CI_MAKE_HEADER(2);
|
||||
request[2] = FF_DDC_CI_GET_VCP;
|
||||
request[3] = FF_DDC_CI_LUMINANCE_OPCODE;
|
||||
request[4] = ffDdcCiChecksum(FF_DDC_CI_WRITE_ADDR, request, 4);
|
||||
}
|
||||
|
||||
static inline bool ffDdcCiValidateGetVcpResponse(const uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE]) {
|
||||
if (response[0] != FF_DDC_CI_WRITE_ADDR
|
||||
|| response[1] != FF_DDC_CI_MAKE_HEADER(FF_DDC_CI_VCP_RESPONSE_LENGTH)
|
||||
|| response[2] != 0x02u
|
||||
|| response[3] != 0x00u
|
||||
|| response[4] != FF_DDC_CI_LUMINANCE_OPCODE
|
||||
|| response[5] != 0x00u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ffDdcCiChecksum(FF_DDC_CI_RESPONSE_CHECKSUM_ADDR, response, FF_DDC_CI_VCP_RESPONSE_PACKET_SIZE - 1u) == response[FF_DDC_CI_VCP_RESPONSE_PACKET_SIZE - 1u];
|
||||
}
|
||||
|
||||
#define FF_I2C_DEVICE_CACHE_MAX 32
|
||||
|
||||
typedef struct FFBrightnessDdcCache {
|
||||
int devices[FF_I2C_DEVICE_CACHE_MAX];
|
||||
size_t count;
|
||||
} FFBrightnessDdcCache;
|
||||
|
||||
static inline FFstrbuf ffBrightnessDdcCachePath(void) {
|
||||
FFstrbuf path = ffStrbufCreateCopy(&instance.state.platform.cacheDir);
|
||||
ffStrbufEnsureEndsWithC(&path, '/');
|
||||
ffStrbufAppendS(&path, "fastfetch/brightness/i2c_cache.txt");
|
||||
return path;
|
||||
}
|
||||
|
||||
const char* ffDetectBrightness(FFBrightnessOptions* options, FFlist* result); // list of FFBrightnessResult
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
#include "common/kmod.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/time.h"
|
||||
#include "common/edidHelper.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <unistd.h>
|
||||
@@ -12,65 +16,261 @@
|
||||
#if __has_include(<dev/iicbus/iic.h>)
|
||||
#include <dev/iicbus/iic.h>
|
||||
|
||||
const char* detectWithDdcci([[maybe_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;
|
||||
// Try to load cache from file
|
||||
static bool loadCacheFromFile(FFBrightnessDdcCache* cache) {
|
||||
cache->count = 0;
|
||||
FF_STRBUF_AUTO_DESTROY cacheDir = ffBrightnessDdcCachePath();
|
||||
FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();
|
||||
bool valid = false;
|
||||
FF_DEBUG("Brightness DDC/CI: loading I2C cache from %s", cacheDir.chars);
|
||||
if (ffReadFileBuffer(cacheDir.chars, &cacheContent)) {
|
||||
// Parse cache: first number is count, rest are device numbers
|
||||
char* ptr = cacheContent.chars;
|
||||
char* end = ptr + cacheContent.length;
|
||||
|
||||
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";
|
||||
// Read count
|
||||
char* next;
|
||||
long count = strtol(ptr, &next, 10);
|
||||
if (next != ptr && count > 0 && count <= FF_I2C_DEVICE_CACHE_MAX) {
|
||||
ptr = next;
|
||||
|
||||
// Read device numbers and reject truncated or trailing data.
|
||||
size_t parsedCount = 0;
|
||||
for (; parsedCount < (size_t) count && ptr < end; ++parsedCount) {
|
||||
long dev = strtol(ptr, &next, 10);
|
||||
if (next == ptr || dev < 0 || dev >= 100) {
|
||||
break;
|
||||
}
|
||||
cache->devices[parsedCount] = (int) dev;
|
||||
ptr = next;
|
||||
}
|
||||
|
||||
while (ptr < end && isspace((unsigned char) *ptr)) {
|
||||
++ptr;
|
||||
}
|
||||
|
||||
if (parsedCount == (size_t) count && ptr == end) {
|
||||
cache->count = parsedCount;
|
||||
valid = true;
|
||||
for (size_t i = 0; i < cache->count; ++i) {
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "/dev/iic%d", cache->devices[i]);
|
||||
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
int error = errno;
|
||||
FF_DEBUG("Brightness DDC/CI: cached bus %d is unavailable (errno=%d: %s)", cache->devices[i], error, strerror(error));
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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];
|
||||
if (!valid) {
|
||||
FF_DEBUG("Brightness DDC/CI: I2C cache is missing or invalid");
|
||||
cache->count = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
FF_DEBUG("Brightness DDC/CI: loaded %zu bus(es) from cache", cache->count);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Save cache to file
|
||||
static void saveCacheToFile(const FFBrightnessDdcCache* cache) {
|
||||
if (cache->count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY cacheDir = ffBrightnessDdcCachePath();
|
||||
FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateF("%zu", cache->count);
|
||||
for (size_t i = 0; i < cache->count; i++) {
|
||||
ffStrbufAppendF(&cacheContent, " %d", cache->devices[i]);
|
||||
}
|
||||
|
||||
if (ffWriteFileBuffer(cacheDir.chars, &cacheContent)) {
|
||||
FF_DEBUG("Brightness DDC/CI: saved %zu bus(es) to %s", cache->count, cacheDir.chars);
|
||||
} else {
|
||||
FF_DEBUG("Brightness DDC/CI: failed to save cache to %s", cacheDir.chars);
|
||||
}
|
||||
}
|
||||
|
||||
static bool readDdcCiResponse(int fd, FFBrightnessOptions* options, uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE]) {
|
||||
uint8_t request[5];
|
||||
ffDdcCiBuildGetVcpRequest(request);
|
||||
|
||||
struct iic_msg msg = {
|
||||
.slave = FF_DDC_CI_WRITE_ADDR,
|
||||
.flags = IIC_M_WR,
|
||||
.len = sizeof(request),
|
||||
.buf = request
|
||||
};
|
||||
|
||||
struct iic_rdwr_data data = {
|
||||
.msgs = &msg,
|
||||
.nmsgs = 1
|
||||
};
|
||||
|
||||
int ioctlResult = ioctl(fd, I2CRDWR, &data);
|
||||
if (ioctlResult < 0) {
|
||||
int error = errno;
|
||||
FF_DEBUG("Brightness DDC/CI: write request failed (fd=%d, result=%d, errno=%d: %s)", fd, ioctlResult, error, strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
msg.slave = FF_DDC_CI_WRITE_ADDR;
|
||||
msg.flags = IIC_M_RD;
|
||||
msg.len = FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE;
|
||||
msg.buf = response;
|
||||
ioctlResult = ioctl(fd, I2CRDWR, &data);
|
||||
if (ioctlResult < 0) {
|
||||
int error = errno;
|
||||
FF_DEBUG("Brightness DDC/CI: read response failed (fd=%d, result=%d, errno=%d: %s)", fd, ioctlResult, error, strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9];
|
||||
uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7];
|
||||
if (!ffDdcCiValidateGetVcpResponse(response)) {
|
||||
FF_DEBUG("Brightness DDC/CI: invalid response (addr=0x%02x, header=0x%02x, type=0x%02x, opcode=0x%02x, checksum=0x%02x)",
|
||||
(unsigned) response[0], (unsigned) response[1], (unsigned) response[2], (unsigned) response[4], (unsigned) response[10]);
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
FF_DEBUG("Brightness DDC/CI: valid response (fd=%d, current=%u, max=%u)", fd,
|
||||
((unsigned) response[8] << 8u) | (unsigned) response[9],
|
||||
((unsigned) response[6] << 8u) | (unsigned) response[7]);
|
||||
return true;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
static bool scanI2cDevices(FFBrightnessOptions* options, FFBrightnessDdcCache* cache) {
|
||||
FF_DEBUG("Brightness DDC/CI: scanning FreeBSD I2C buses");
|
||||
cache->count = 0;
|
||||
|
||||
for (int i = 0; i < 100 && cache->count < FF_I2C_DEVICE_CACHE_MAX; i++) {
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "/dev/iic%d", i);
|
||||
|
||||
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FF_DEBUG("Brightness DDC/CI: probing %s", path);
|
||||
uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE];
|
||||
if (!readDdcCiResponse(fd, options, response)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cache->devices[cache->count++] = i;
|
||||
FF_DEBUG("Brightness DDC/CI: found compatible display on %s", path);
|
||||
}
|
||||
|
||||
FF_DEBUG("Brightness DDC/CI: scan finished with %zu compatible bus(es)", cache->count);
|
||||
return cache->count > 0;
|
||||
}
|
||||
|
||||
static bool readEdid(int fd, uint8_t* edidData) {
|
||||
uint8_t offset = 0;
|
||||
|
||||
struct iic_msg msgs[2];
|
||||
msgs[0].slave = FF_DDC_EDID_WRITE_ADDR;
|
||||
msgs[0].flags = IIC_M_WR;
|
||||
msgs[0].len = 1;
|
||||
msgs[0].buf = &offset;
|
||||
|
||||
msgs[1].slave = FF_DDC_EDID_WRITE_ADDR;
|
||||
msgs[1].flags = IIC_M_RD;
|
||||
msgs[1].len = 128;
|
||||
msgs[1].buf = edidData;
|
||||
|
||||
struct iic_rdwr_data data = {
|
||||
.msgs = msgs,
|
||||
.nmsgs = 2
|
||||
};
|
||||
|
||||
return ioctl(fd, I2CRDWR, &data) >= 0;
|
||||
}
|
||||
|
||||
static bool detectCachedDevices(FFBrightnessOptions* options, FFlist* result, FFBrightnessDdcCache* cache) {
|
||||
const size_t cachedCount = cache->count;
|
||||
const uint32_t resultLength = result->length;
|
||||
size_t validCount = 0;
|
||||
|
||||
FF_DEBUG("Brightness DDC/CI: validating %zu cached bus(es)", cachedCount);
|
||||
for (size_t i = 0; i < cachedCount; ++i) {
|
||||
const int device = cache->devices[i];
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "/dev/iic%d", device);
|
||||
|
||||
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
int error = errno;
|
||||
FF_DEBUG("Brightness DDC/CI: failed to open cached bus %d (errno=%d: %s)", device, error, strerror(error));
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE];
|
||||
if (!readDdcCiResponse(fd, options, response)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t current = ((uint32_t) response[8] << 8u) + (uint32_t) response[9];
|
||||
uint32_t max = ((uint32_t) response[6] << 8u) + (uint32_t) response[7];
|
||||
if (max == 0 || current > max) {
|
||||
FF_DEBUG("Brightness DDC/CI: ignoring bus %d with invalid brightness range %u/%u", device, current, max);
|
||||
continue;
|
||||
}
|
||||
|
||||
cache->devices[validCount++] = device;
|
||||
|
||||
// Try to read EDID for monitor name
|
||||
uint8_t edidData[128];
|
||||
FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(32);
|
||||
if (readEdid(fd, edidData)) {
|
||||
ffEdidGetName(edidData, &name);
|
||||
}
|
||||
if (name.length == 0) {
|
||||
ffStrbufSetF(&name, "I2C-%d", device);
|
||||
}
|
||||
|
||||
FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
|
||||
brightness->max = max;
|
||||
brightness->min = 0;
|
||||
brightness->current = current;
|
||||
brightness->builtin = false;
|
||||
ffStrbufInitMove(&brightness->name, &name);
|
||||
FF_DEBUG("Brightness DDC/CI: added display '%s' from bus %d (%u/%u)", brightness->name.chars, device, current, max);
|
||||
}
|
||||
|
||||
cache->count = validCount;
|
||||
FF_DEBUG("Brightness DDC/CI: cache validation kept %zu bus(es), result count is %u", validCount, result->length);
|
||||
return result->length > resultLength;
|
||||
}
|
||||
|
||||
const char* detectWithDdcci(FFBrightnessOptions* options, FFlist* result) {
|
||||
FFBrightnessDdcCache cache = {0};
|
||||
FF_DEBUG("Brightness DDC/CI: detection started (result count=%u)", result->length);
|
||||
if (!loadCacheFromFile(&cache)) {
|
||||
if (!scanI2cDevices(options, &cache)) {
|
||||
FF_DEBUG("Brightness DDC/CI: no compatible I2C device found during initial scan");
|
||||
return "No I2C devices found";
|
||||
}
|
||||
}
|
||||
|
||||
if (!detectCachedDevices(options, result, &cache)) {
|
||||
FF_DEBUG("Brightness DDC/CI: cached detection found no usable display; rescanning");
|
||||
if (!scanI2cDevices(options, &cache) || !detectCachedDevices(options, result, &cache)) {
|
||||
FF_DEBUG("Brightness DDC/CI: rescan found no compatible display");
|
||||
return "No DDC/CI compatible displays found";
|
||||
}
|
||||
}
|
||||
|
||||
saveCacheToFile(&cache);
|
||||
FF_DEBUG("Brightness DDC/CI: detection finished with %u result(s)", result->length);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
@@ -138,10 +338,18 @@ const char* detectWithBacklight([[maybe_unused]] FFBrightnessOptions* options, [
|
||||
#endif
|
||||
|
||||
const char* ffDetectBrightness([[maybe_unused]] FFBrightnessOptions* options, FFlist* result) {
|
||||
FF_DEBUG("Brightness: detection started (result count=%u)", result->length);
|
||||
detectWithBacklight(options, result);
|
||||
FF_DEBUG("Brightness: backlight detection finished (result count=%u)", result->length);
|
||||
|
||||
if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP && result->length == 0) {
|
||||
FF_DEBUG("Brightness: trying DDC/CI because no backlight result was found");
|
||||
detectWithDdcci(options, result);
|
||||
} else if (options->ddcciSleep == FF_BRIGHTNESS_DDCCI_SLEEP_SKIP) {
|
||||
FF_DEBUG("Brightness: skipping DDC/CI because it is disabled");
|
||||
} else {
|
||||
FF_DEBUG("Brightness: skipping DDC/CI because backlight detection already found %u result(s)", result->length);
|
||||
}
|
||||
FF_DEBUG("Brightness: detection finished (result count=%u)", result->length);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,297 @@
|
||||
#include "common/io.h"
|
||||
#include "common/edidHelper.h"
|
||||
#include "common/strutil.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/time.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-dev.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
// Try to load cache from file
|
||||
static bool loadCacheFromFile(FFBrightnessDdcCache* cache) {
|
||||
cache->count = 0;
|
||||
FF_STRBUF_AUTO_DESTROY cacheDir = ffBrightnessDdcCachePath();
|
||||
FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();
|
||||
bool valid = false;
|
||||
FF_DEBUG("Brightness DDC/CI: loading I2C cache from %s", cacheDir.chars);
|
||||
if (ffReadFileBuffer(cacheDir.chars, &cacheContent)) {
|
||||
// Parse cache: first number is count, rest are device numbers
|
||||
char* ptr = cacheContent.chars;
|
||||
char* end = ptr + cacheContent.length;
|
||||
|
||||
// Read count
|
||||
char* next;
|
||||
long count = strtol(ptr, &next, 10);
|
||||
if (next != ptr && count > 0 && count <= FF_I2C_DEVICE_CACHE_MAX) {
|
||||
ptr = next;
|
||||
|
||||
// Read device numbers and reject truncated or trailing data.
|
||||
size_t parsedCount = 0;
|
||||
for (; parsedCount < (size_t) count && ptr < end; ++parsedCount) {
|
||||
long dev = strtol(ptr, &next, 10);
|
||||
if (next == ptr || dev < 0 || dev >= 100) {
|
||||
break;
|
||||
}
|
||||
cache->devices[parsedCount] = (int) dev;
|
||||
ptr = next;
|
||||
}
|
||||
|
||||
while (ptr < end && isspace((unsigned char) *ptr)) {
|
||||
++ptr;
|
||||
}
|
||||
|
||||
if (parsedCount == (size_t) count && ptr == end) {
|
||||
cache->count = parsedCount;
|
||||
valid = true;
|
||||
for (size_t i = 0; i < cache->count; ++i) {
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "/dev/i2c-%d", cache->devices[i]);
|
||||
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
int error = errno;
|
||||
FF_DEBUG("Brightness DDC/CI: cached bus %d is unavailable (errno=%d: %s)", cache->devices[i], error, strerror(error));
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
FF_DEBUG("Brightness DDC/CI: I2C cache is missing or invalid");
|
||||
cache->count = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
FF_DEBUG("Brightness DDC/CI: loaded %zu bus(es) from cache", cache->count);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Save cache to file
|
||||
static void saveCacheToFile(const FFBrightnessDdcCache* cache) {
|
||||
if (cache->count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY cacheDir = ffBrightnessDdcCachePath();
|
||||
FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateF("%zu", cache->count);
|
||||
for (size_t i = 0; i < cache->count; i++) {
|
||||
ffStrbufAppendF(&cacheContent, " %d", cache->devices[i]);
|
||||
}
|
||||
|
||||
if (ffWriteFileBuffer(cacheDir.chars, &cacheContent)) {
|
||||
FF_DEBUG("Brightness DDC/CI: saved %zu bus(es) to %s", cache->count, cacheDir.chars);
|
||||
} else {
|
||||
FF_DEBUG("Brightness DDC/CI: failed to save cache to %s", cacheDir.chars);
|
||||
}
|
||||
}
|
||||
|
||||
static bool readDdcCiResponse(int fd, FFBrightnessOptions* options, uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE]) {
|
||||
uint8_t request[5];
|
||||
ffDdcCiBuildGetVcpRequest(request);
|
||||
|
||||
// i2c_msg.addr is a 7-bit address; the 8-bit write/read addresses are
|
||||
// used only for the DDC/CI checksum.
|
||||
struct i2c_msg msg = {
|
||||
.addr = FF_DDC_CI_ADDR,
|
||||
.flags = 0,
|
||||
.len = sizeof(request),
|
||||
.buf = request
|
||||
};
|
||||
|
||||
struct i2c_rdwr_ioctl_data data = {
|
||||
.msgs = &msg,
|
||||
.nmsgs = 1
|
||||
};
|
||||
|
||||
int ioctlResult = ioctl(fd, I2C_RDWR, &data);
|
||||
if (ioctlResult != 1) {
|
||||
int error = errno;
|
||||
FF_DEBUG("Brightness DDC/CI: write request failed (fd=%d, result=%d, errno=%d: %s)", fd, ioctlResult, error, strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
ffTimeSleep(options->ddcciSleep);
|
||||
|
||||
msg.flags = I2C_M_RD;
|
||||
msg.len = FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE;
|
||||
msg.buf = response;
|
||||
ioctlResult = ioctl(fd, I2C_RDWR, &data);
|
||||
if (ioctlResult != 1) {
|
||||
int error = errno;
|
||||
FF_DEBUG("Brightness DDC/CI: read response failed (fd=%d, result=%d, errno=%d: %s)", fd, ioctlResult, error, strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ffDdcCiValidateGetVcpResponse(response)) {
|
||||
FF_DEBUG("Brightness DDC/CI: invalid response (addr=0x%02x, header=0x%02x, type=0x%02x, opcode=0x%02x, checksum=0x%02x)",
|
||||
(unsigned) response[0], (unsigned) response[1], (unsigned) response[2], (unsigned) response[4], (unsigned) response[10]);
|
||||
return false;
|
||||
}
|
||||
|
||||
FF_DEBUG("Brightness DDC/CI: valid response (fd=%d, current=%u, max=%u)", fd,
|
||||
((unsigned) response[8] << 8u) | (unsigned) response[9],
|
||||
((unsigned) response[6] << 8u) | (unsigned) response[7]);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool scanI2cDevices(FFBrightnessOptions* options, FFBrightnessDdcCache* cache) {
|
||||
FF_DEBUG("Brightness DDC/CI: scanning Linux I2C buses");
|
||||
cache->count = 0;
|
||||
|
||||
for (int i = 0; i < 100 && cache->count < FF_I2C_DEVICE_CACHE_MAX; i++) {
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "/dev/i2c-%d", i);
|
||||
|
||||
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FF_DEBUG("Brightness DDC/CI: probing %s", path);
|
||||
uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE];
|
||||
if (!readDdcCiResponse(fd, options, response)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cache->devices[cache->count++] = i;
|
||||
FF_DEBUG("Brightness DDC/CI: found compatible display on %s", path);
|
||||
}
|
||||
|
||||
FF_DEBUG("Brightness DDC/CI: scan finished with %zu compatible bus(es)", cache->count);
|
||||
return cache->count > 0;
|
||||
}
|
||||
|
||||
static bool readEdid(int fd, uint8_t* edidData) {
|
||||
uint8_t offset = 0;
|
||||
|
||||
struct i2c_msg msgs[2];
|
||||
msgs[0].addr = FF_DDC_EDID_ADDR;
|
||||
msgs[0].flags = 0;
|
||||
msgs[0].len = 1;
|
||||
msgs[0].buf = &offset;
|
||||
|
||||
msgs[1].addr = FF_DDC_EDID_ADDR;
|
||||
msgs[1].flags = I2C_M_RD;
|
||||
msgs[1].len = 128;
|
||||
msgs[1].buf = edidData;
|
||||
|
||||
struct i2c_rdwr_ioctl_data data;
|
||||
data.msgs = msgs;
|
||||
data.nmsgs = 2;
|
||||
|
||||
return ioctl(fd, I2C_RDWR, &data) == 2;
|
||||
}
|
||||
|
||||
static bool detectCachedDevices(FFBrightnessOptions* options, FFlist* result, FFBrightnessDdcCache* cache) {
|
||||
const size_t cachedCount = cache->count;
|
||||
const uint32_t resultLength = result->length;
|
||||
size_t validCount = 0;
|
||||
|
||||
FF_DEBUG("Brightness DDC/CI: validating %zu cached bus(es)", cachedCount);
|
||||
for (size_t i = 0; i < cachedCount; ++i) {
|
||||
const int device = cache->devices[i];
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "/dev/i2c-%d", device);
|
||||
|
||||
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
int error = errno;
|
||||
FF_DEBUG("Brightness DDC/CI: failed to open cached bus %d (errno=%d: %s)", device, error, strerror(error));
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE];
|
||||
if (!readDdcCiResponse(fd, options, response)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t current = ((uint32_t) response[8] << 8u) + (uint32_t) response[9];
|
||||
uint32_t max = ((uint32_t) response[6] << 8u) + (uint32_t) response[7];
|
||||
if (max == 0 || current > max) {
|
||||
FF_DEBUG("Brightness DDC/CI: ignoring bus %d with invalid brightness range %u/%u", device, current, max);
|
||||
continue;
|
||||
}
|
||||
|
||||
cache->devices[validCount++] = device;
|
||||
|
||||
// Try to read EDID for monitor name
|
||||
uint8_t edidData[128];
|
||||
FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(32);
|
||||
if (readEdid(fd, edidData)) {
|
||||
ffEdidGetName(edidData, &name);
|
||||
}
|
||||
if (name.length == 0) {
|
||||
ffStrbufSetF(&name, "I2C-%d", device);
|
||||
}
|
||||
|
||||
FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
|
||||
brightness->max = max;
|
||||
brightness->min = 0;
|
||||
brightness->current = current;
|
||||
brightness->builtin = false;
|
||||
ffStrbufInitMove(&brightness->name, &name);
|
||||
FF_DEBUG("Brightness DDC/CI: added display '%s' from bus %d (%u/%u)", brightness->name.chars, device, current, max);
|
||||
}
|
||||
|
||||
cache->count = validCount;
|
||||
FF_DEBUG("Brightness DDC/CI: cache validation kept %zu bus(es), result count is %u", validCount, result->length);
|
||||
return result->length > resultLength;
|
||||
}
|
||||
|
||||
static const char* detectWithDdcciNative(FFBrightnessOptions* options, FFlist* result, uint32_t expectedResultLength) {
|
||||
#ifdef FF_HAVE_DDCUTIL
|
||||
const uint32_t resultLength = result->length;
|
||||
#else
|
||||
(void) expectedResultLength;
|
||||
#endif
|
||||
FFBrightnessDdcCache cache = {0};
|
||||
FF_DEBUG("Brightness DDC/CI: native detection started (result count=%u, expected=%u)", result->length, expectedResultLength);
|
||||
if (!loadCacheFromFile(&cache)) {
|
||||
if (!scanI2cDevices(options, &cache)) {
|
||||
FF_DEBUG("Brightness DDC/CI: no compatible I2C device found during initial scan");
|
||||
return "No I2C devices found";
|
||||
}
|
||||
}
|
||||
|
||||
if (!detectCachedDevices(options, result, &cache)) {
|
||||
FF_DEBUG("Brightness DDC/CI: cached detection found no usable display; rescanning");
|
||||
// A cached bus can remain present after its monitor is unplugged or
|
||||
// its number is reused. Rebuild the cache when no valid reply arrives.
|
||||
if (!scanI2cDevices(options, &cache) || !detectCachedDevices(options, result, &cache)) {
|
||||
FF_DEBUG("Brightness DDC/CI: rescan found no compatible display");
|
||||
return "No DDC/CI compatible displays found";
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef FF_HAVE_DDCUTIL
|
||||
if (result->length < expectedResultLength) {
|
||||
FF_DEBUG("Brightness DDC/CI: native result is incomplete (%u/%u); rolling back for ddcutil fallback", result->length, expectedResultLength);
|
||||
FFBrightnessResult discarded;
|
||||
while (result->length > resultLength && FF_LIST_POP(*result, &discarded)) {
|
||||
ffStrbufDestroy(&discarded.name);
|
||||
}
|
||||
return "Native DDC/CI found only a subset of displays";
|
||||
}
|
||||
#endif
|
||||
|
||||
saveCacheToFile(&cache);
|
||||
FF_DEBUG("Brightness DDC/CI: native detection finished with %u result(s)", result->length);
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const char* detectWithBacklight(FFlist* result) {
|
||||
// https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-class-backlight
|
||||
@@ -75,8 +363,9 @@ static const char* detectWithBacklight(FFlist* result) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#include "detection/displayserver/displayserver.h"
|
||||
|
||||
#ifdef FF_HAVE_DDCUTIL
|
||||
#include "detection/displayserver/displayserver.h"
|
||||
#include "common/library.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
@@ -162,16 +451,35 @@ static const char* detectWithDdcci([[maybe_unused]] FFBrightnessOptions* options
|
||||
#endif
|
||||
|
||||
const char* ffDetectBrightness([[maybe_unused]] FFBrightnessOptions* options, FFlist* result) {
|
||||
FF_DEBUG("Brightness: detection started (result count=%u)", result->length);
|
||||
detectWithBacklight(result);
|
||||
FF_DEBUG("Brightness: backlight detection finished (result count=%u)", result->length);
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
FF_DEBUG("Brightness: trying native DDC/CI (result count=%u, display count=%u)", result->length, displayServer->displays.length);
|
||||
#if defined(__linux__)
|
||||
// Try native I2C first
|
||||
const char* nativeError = detectWithDdcciNative(options, result, displayServer->displays.length);
|
||||
if (nativeError != nullptr) {
|
||||
#ifdef FF_HAVE_DDCUTIL
|
||||
FF_DEBUG("Native I2C DDC/CI failed: %s, falling back to ddcutil", nativeError);
|
||||
detectWithDdcci(options, result);
|
||||
#else
|
||||
FF_DEBUG("Native I2C DDC/CI failed: %s; ddcutil fallback is unavailable", nativeError);
|
||||
#endif
|
||||
}
|
||||
#elif defined(FF_HAVE_DDCUTIL)
|
||||
detectWithDdcci(options, result);
|
||||
#endif
|
||||
} else {
|
||||
FF_DEBUG("Brightness: skipping DDC/CI because result count=%u meets display count=%u", result->length, displayServer->displays.length);
|
||||
}
|
||||
} else {
|
||||
FF_DEBUG("Brightness: skipping DDC/CI because it is disabled");
|
||||
}
|
||||
|
||||
FF_DEBUG("Brightness: detection finished (result count=%u)", result->length);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user