From d819d63339a6e0bb97b3eb6e79087e372e631631 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 21 Oct 2024 12:27:51 +0200 Subject: [PATCH 01/24] adjusted max brightness --- ambientd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ambientd.c b/ambientd.c index 7bd413e..1745eba 100644 --- a/ambientd.c +++ b/ambientd.c @@ -21,7 +21,7 @@ char STR_BUFFER[128] = {0}; FILE *luminance_fp = NULL; // max luminance that the display can output -int max_luminance = 550; +int max_luminance = 700; // minimum display brightness in percent float min_brightness_percent = 0.05; From bee1f35542a8ab6af632fcd27bac95e2bd2083f1 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 22 Oct 2024 15:59:40 +0200 Subject: [PATCH 02/24] more TODOs --- TODO | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index 2c6d014..0964e05 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,6 @@ [ ] Add a config file [ ] Use a better filter than a simple median, maybe a kalman filter -[ ] Add a way to customise the brightness curve +[x] Add a way to customise the brightness curve Percieved brightness is not linear, on low light you need a slightly higher brightness and on high light you can get away with a lower brightness than the max, the goal is to find the right curve for the screen @@ -8,3 +8,6 @@ [ ] Add signal handlers [ ] SIGUSR1 to reset the user offset [ ] Handle kill by closing all file descriptors + [ ] Handle suspend and resume, usually suspending means that the screen is + turned off so do not apply user offset +[ ] Account for DPMS and do not apply offset in that case From 550fa7f18b021fef0437b8821b87c4b37c40cf66 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 22 Oct 2024 16:00:06 +0200 Subject: [PATCH 03/24] don't apply offset when screen is off --- ambientd.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ambientd.c b/ambientd.c index 1745eba..7de75be 100644 --- a/ambientd.c +++ b/ambientd.c @@ -165,9 +165,14 @@ int main (void) actual_brightness = brightness_adjusted; } + // if the actual brightness is zero, it means that the screen is turned + // off for some reason, usually DPMS. As such it should not count for user + // offset. Look at the TODO file. + if (actual_brightness != 0) { off = actual_brightness - brightness_target; - if (brightness_target != max_brightness || off < 0){ - user_offset = off; + if (brightness_target != max_brightness || off < 0){ + user_offset = off; + } } // TODO: option to turn off logging From 73dc9bc0def73d7808cbbf080dd813a744661ae0 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 22 Oct 2024 19:05:38 +0200 Subject: [PATCH 04/24] implemented write_int() --- ambientd.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/ambientd.c b/ambientd.c index 7de75be..f5aa3ed 100644 --- a/ambientd.c +++ b/ambientd.c @@ -41,9 +41,11 @@ int get_luminance_raw (void) // read a string from a file and convert it to int, should only be used for // positive numbers since negative numbers mean an error -int read_int(FILE *fp) +static int read_int(FILE *fp) { - freopen(NULL, "r", fp); + if (freopen(NULL, "r", fp) == NULL) { + return -1; + } const char *p = fgets(STR_BUFFER, sizeof(STR_BUFFER), fp); if (p == NULL) { return -1; @@ -55,10 +57,23 @@ int read_int(FILE *fp) return r; } -float bezier(float t) +// write an intger value to a file as a string +static int write_int(FILE* fp, int value) +{ + if (freopen(NULL, "w", fp) == NULL) { + return -1; + } + int x = snprintf(STR_BUFFER, sizeof(STR_BUFFER), "%d", value); + if (x <= 0) { + return -1; + } + return fputs(STR_BUFFER, fp); +} + +static inline float bezier(float t) { const float B = 0.66; - const float D = 0.28; + const float D = 0.28; return 3*B*t+(3*D-6*B)*t*t+(1+3*B-3*D)*t*t*t; } @@ -144,15 +159,10 @@ int main (void) } brightness_adjusted = MIN(MAX(brightness_target + user_offset, min_brightness), max_brightness); - // TODO: put this in write_int() function - x = snprintf(STR_BUFFER, sizeof(STR_BUFFER), "%d", brightness_adjusted); - if (x <= 0) { - fprintf(stderr, "Error in snprintf(): %s\n", strerror(errno)); - } - if (fputs(STR_BUFFER, brightness_fp) == EOF) { + x = write_int(brightness_fp, brightness_adjusted); + if (x < 0) { fprintf(stderr, "error writing brightness: %s\n", strerror(errno)); } - rewind(brightness_fp); sleep(3); From 127c7a48175f9ea004263824a665e2e0f3157c67 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 22 Oct 2024 19:14:51 +0200 Subject: [PATCH 05/24] ignore zed editor folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b330eb6..058ec61 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ ambientd !sv/** .ccls-cache +.zed From 496b112a97208cb298ad5d6b98bd2dc4b8cb0c03 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 22 Oct 2024 19:15:25 +0200 Subject: [PATCH 06/24] removed python implementation --- ambientd.py | 73 ----------------------------------------------------- 1 file changed, 73 deletions(-) delete mode 100755 ambientd.py diff --git a/ambientd.py b/ambientd.py deleted file mode 100755 index c8bfb5e..0000000 --- a/ambientd.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/python - -# Simple daemon to change brightness based on luminance -# TODO: add a config -# TODO: add debug option -# TODO: add dry-run -# TODO automatically find the right brightness path and do not assume intel_backlight - -import time - -# Open the luminance sensor device file -try: - luminance_file = open('/sys/bus/iio/devices/iio:device0/in_illuminance_raw', 'r') -except OSError as err: - print(err) - exit(1) - -# max luminance of the screen - 100 (maybe) -max_luminance = 400 - -# get luminance value, on fail return the max -def get_luminance(): - try: - l = int(luminance_file.read()) - luminance_file.seek(0) - except: - l = max_luminance - return l - -# Get the max brightness -max_brightness = 100 -with open('/sys/class/backlight/intel_backlight/max_brightness', 'r') as f: - max_brightness = int(f.read()) -min_brightness = int(max_brightness * 0.05) - -# loop delay in seconds -delay = 3 - -lum = [get_luminance()]*4 -user_offset = 0 -brightness_target = min_brightness -brightness_old = min_brightness -while True: - lum.append(lum.pop(0)) - lum[0] = get_luminance() - - luminance = sum(lum)/len(lum) - luminance_percent = min(luminance/max_luminance, 1.0) - brightness_old = brightness_target - brightness_target = max(int(luminance_percent * max_brightness), min_brightness) - brightness_delta = abs((brightness_target - brightness_old)/brightness_old) - - # if the brightness changes by more than 10% reduce the user offset, scaling - # with the delta, this accounts for large changes in brightness no longer - # corresponding to the adjustments that the user made to the brightness - if brightness_delta > 0.1: - user_offset = int(user_offset * max(min(1-(brightness_delta*1.2), 1), 0)) - - brightness_adjusted = min(max(brightness_target + user_offset, min_brightness), max_brightness) - b = open('/sys/class/backlight/intel_backlight/brightness', 'w') - b.write(str(brightness_adjusted)) - b.close() - - time.sleep(delay) - - # if the value of the brightness changed then update the user offset - a = open('/sys/class/backlight/intel_backlight/actual_brightness') - x = int(a.read()) - brightness_target - a.close() - if brightness_target != max_brightness or x < 0: - user_offset = x - - print("brightness target: ", brightness_target, "user offset: ", user_offset, "brightness adjusted: ", brightness_adjusted) From 382e58b40c070017a6ce17df0ad0185d6171306b Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 22 Oct 2024 22:51:50 +0200 Subject: [PATCH 07/24] implementation in C3 --- ambientd.c3 | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 ambientd.c3 diff --git a/ambientd.c3 b/ambientd.c3 new file mode 100644 index 0000000..6aff2e6 --- /dev/null +++ b/ambientd.c3 @@ -0,0 +1,198 @@ +import std::io; +import std::io::file; +import std::thread; + +// import some functions from the C standard library +extern fn long strtol(char *buffer, char **endptr, int base); + +const int MEDIAN_SAMPLES = 4; + +const String LUMINANCE_DEV = "/sys/bus/iio/devices/iio:device0/in_illuminance_raw"; +const String MAX_BRIGHTNESS_DEV = "/sys/class/backlight/intel_backlight/max_brightness"; +const String BRIGHTNESS_DEV = "/sys/class/backlight/intel_backlight/brightness"; +const String ACTUAL_BRIGHTNESS_DEV = "/sys/class/backlight/intel_backlight/actual_brightness"; + +char[128] str_buffer; + +// max luminance that the display can output +const int MAX_LUMINANCE = 700; + +// minimum display brightness in percent +const float MIN_BRIGHTNESS_PERCENT = 0.05; + +macro abs(x) +{ + if (x < 0) { + return -x; + } else { + return x; + } +} + +// l <= x <= h +macro clamp(x, l, h) +{ + return max(min(x, h), l); +} + +// read a string to the buffer up until the end of the buffer or the first '\n' +/** + * @require fp != null +**/ +fn void! io::File.gets(&self, char[] buffer) +{ + usz i = 0; + char !c; + for (; i < buffer.len-1; i++) { + c = self.read_byte(); + if (catch excuse = c) { + if (excuse == io::IoError.EOF) { + break; + } else { + return excuse?; + } + } + buffer[i] = c; + } + buffer[i] = '\0'; +} + +// read a string from a file and convert it to int, should only be used for +// positive numbers since negative numbers mean an error +/** + * @ensure return > 0, return < 1000000 +**/ +fn int! read_int(String path) +{ + static io::File! fp; + fp = io::file::open((String)path, "r")!; + defer (void)fp.close(); + + fp.gets(str_buffer[..])!; + int r = (int)strtol(&str_buffer, null, 10); + return r; +} + +// write an intger value to a file as a string +fn void! write_int(String path, int value) +{ + static io::File! fp; + fp = io::file::open(path, "w")!; + defer (void)fp.close(); + + char[] b = io::bprintf(str_buffer[..], "%d\n", value)!; + fp.write(b)!; +} + +// returns the luminance value in percentage between 0 and 1 +macro get_luminance_raw() +{ + return read_int(LUMINANCE_DEV); +} + +fn float bezier(float t) +{ + const float B = 0.66; + const float D = 0.28; + return 3*B*t+(3*D-6*B)*t*t+(1+3*B-3*D)*t*t*t; +} + +// TODO: add some command line options like: +// -v: verbose/logging +// -c file; specify a config file +fn int main() +{ + // open the luminance device and try reading from it + if (catch excuse = get_luminance_raw()) { + io::eprintfn("Error on initial opening of %s: %s", LUMINANCE_DEV, excuse); + thread::exit(1); + } + + // get the max brightness + int! _x = read_int(MAX_BRIGHTNESS_DEV); + if (catch excuse = _x) { + io::eprintfn("Error reading max brightness on %s: %s", MAX_BRIGHTNESS_DEV, excuse); + thread::exit(1); + } + int max_brightness = _x!!; + int min_brightness = (int)(max_brightness * MIN_BRIGHTNESS_PERCENT); + + // try writing to brightness device + _x = read_int(BRIGHTNESS_DEV); + if (catch excuse = _x) { + io::eprintfn("Error reading from %s: %s", BRIGHTNESS_DEV, excuse); + thread::exit(1); + } + if (catch excuse = write_int(BRIGHTNESS_DEV, _x)) { + io::eprintfn("Error writing to brightness device %s: %s", BRIGHTNESS_DEV, excuse); + thread::exit(1); + } + + // try reading from the actual brightness device + if (catch excuse = read_int(ACTUAL_BRIGHTNESS_DEV)) { + io::eprintfn("Error reading from %s: %s", ACTUAL_BRIGHTNESS_DEV, excuse); + thread::exit(1); + } + + _x = get_luminance_raw()!!; + int[<*>] luminance_samples = {_x!!, _x!!, _x!!, _x!!}; + int idx; + int user_offset; + int brightness_target = min_brightness; + int brightness_old = min_brightness; + int brightness_adjusted = min_brightness; + int brightness_delta; + int luminance_median; + float luminance_percent; + while (1) { + _x = get_luminance_raw(); + if (catch excuse = _x) { + io::eprintfn("Error reading luminance: %s", excuse); + } else { + idx = (idx + 1) % MEDIAN_SAMPLES; + } + + luminance_median = luminance_samples.sum() / MEDIAN_SAMPLES; + luminance_percent = bezier(clamp(luminance_median/MAX_LUMINANCE, 0.0f, 1.0f)); + + brightness_old = brightness_target; + brightness_target = max((int)(luminance_percent * max_brightness), min_brightness); + brightness_delta = (int)abs((float)(brightness_target - brightness_old)/brightness_old); + + // if the brightness changes by more than 10% reduce the user offset, scaling + // with the delta, this accounts for large changes in brightness no longer + // corresponding to the adjustments that the user made to the brightness + if (brightness_delta > 0.1) { + user_offset = (int)(user_offset * clamp(1-(int)(brightness_delta*1.2), 0, 1)); + } + + brightness_adjusted = clamp(brightness_target + user_offset, min_brightness, max_brightness); + if (catch excuse = write_int(BRIGHTNESS_DEV, brightness_adjusted)) { + io::eprintfn("Error writing brightness: %s", excuse); + } + + thread::sleep_ms(3000); + + // read actual brightness to get the user offset + int! actual_brightness; + int off; + actual_brightness = read_int(ACTUAL_BRIGHTNESS_DEV); + if (catch excuse = actual_brightness) { + io::eprintfn("Error reading actual brightness: %s", excuse); + actual_brightness = brightness_adjusted; + } + + // if the actual brightness is zero, it means that the screen is turned + // off for some reason, usually DPMS. As such it should not count for user + // offset. Look at the TODO file. + if (actual_brightness!! != 0) { + off = actual_brightness!! - brightness_target; + if (brightness_target != max_brightness || off < 0){ + user_offset = off; + } + } + + // TODO: option to turn off logging + io::printfn("brightness target: %d; user offset: %d; brightness adjusted: %d", brightness_target, user_offset, brightness_adjusted); + } +} From 16226f81a74ae1fec732537185ae6037c3336c77 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 22 Oct 2024 23:17:40 +0200 Subject: [PATCH 08/24] fix samples not getting updated --- ambientd.c3 | 1 + 1 file changed, 1 insertion(+) diff --git a/ambientd.c3 b/ambientd.c3 index 6aff2e6..ec2ff57 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -149,6 +149,7 @@ fn int main() if (catch excuse = _x) { io::eprintfn("Error reading luminance: %s", excuse); } else { + luminance_samples[idx] = _x; idx = (idx + 1) % MEDIAN_SAMPLES; } From 09cb13b7e92f7e716b3781e642be0feb5898a041 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 23 Oct 2024 00:00:04 +0200 Subject: [PATCH 09/24] better formatting --- ambientd.c3 | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/ambientd.c3 b/ambientd.c3 index ec2ff57..c9bd470 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -20,23 +20,11 @@ const int MAX_LUMINANCE = 700; // minimum display brightness in percent const float MIN_BRIGHTNESS_PERCENT = 0.05; -macro abs(x) -{ - if (x < 0) { - return -x; - } else { - return x; - } -} +macro abs(x) { return x < 0 ? -x : x; } +macro clamp(x, l, h) { return max(min(x, h), l); } -// l <= x <= h -macro clamp(x, l, h) -{ - return max(min(x, h), l); -} - -// read a string to the buffer up until the end of the buffer or the first '\n' /** + * read a string to the buffer up until the end of the buffer or the first '\n' * @require fp != null **/ fn void! io::File.gets(&self, char[] buffer) @@ -57,9 +45,9 @@ fn void! io::File.gets(&self, char[] buffer) buffer[i] = '\0'; } -// read a string from a file and convert it to int, should only be used for -// positive numbers since negative numbers mean an error /** + * read a string from a file and convert it to int, should only be used for + * positive numbers since negative numbers mean an error * @ensure return > 0, return < 1000000 **/ fn int! read_int(String path) @@ -85,10 +73,7 @@ fn void! write_int(String path, int value) } // returns the luminance value in percentage between 0 and 1 -macro get_luminance_raw() -{ - return read_int(LUMINANCE_DEV); -} +macro get_luminance_raw() { return read_int(LUMINANCE_DEV); } fn float bezier(float t) { From fe980a8bd5747bd81c1622aa6f40838922f6bcb8 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 23 Oct 2024 00:00:18 +0200 Subject: [PATCH 10/24] compile with make --- Makefile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 74fa8f7..7d474d9 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,7 @@ -CC = gcc -CFLAGS = -Wall -Wextra -pedantic -std=c99 -O2 PREFIX = /usr/local -ambientd: ambientd.c +ambientd: ambientd.c3 + c3c compile -O1 -g0 --optsize=small --single-module=yes --fp-math=fast --x86cpu=native ambientd.c3 install: cp ambientd ${PREFIX}/bin/ From b513e25329c0bfbdb395e4d7114f0fa720d6525f Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 24 Oct 2024 23:33:17 +0200 Subject: [PATCH 11/24] disabled single module option --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7d474d9..3251055 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ PREFIX = /usr/local ambientd: ambientd.c3 - c3c compile -O1 -g0 --optsize=small --single-module=yes --fp-math=fast --x86cpu=native ambientd.c3 + c3c compile -O1 -g0 --optsize=small --fp-math=fast --x86cpu=native ambientd.c3 install: cp ambientd ${PREFIX}/bin/ From a712429466bd05d3eb018c6006f236c59eec4d71 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 24 Oct 2024 23:36:25 +0200 Subject: [PATCH 12/24] correct median samples --- ambientd.c3 | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/ambientd.c3 b/ambientd.c3 index c9bd470..8d24f80 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -5,8 +5,6 @@ import std::thread; // import some functions from the C standard library extern fn long strtol(char *buffer, char **endptr, int base); -const int MEDIAN_SAMPLES = 4; - const String LUMINANCE_DEV = "/sys/bus/iio/devices/iio:device0/in_illuminance_raw"; const String MAX_BRIGHTNESS_DEV = "/sys/class/backlight/intel_backlight/max_brightness"; const String BRIGHTNESS_DEV = "/sys/class/backlight/intel_backlight/brightness"; @@ -119,9 +117,9 @@ fn int main() thread::exit(1); } - _x = get_luminance_raw()!!; - int[<*>] luminance_samples = {_x!!, _x!!, _x!!, _x!!}; - int idx; + int x = get_luminance_raw() ?? MAX_LUMINANCE; + int[<4>] luminance_samples = x; + usz idx; int user_offset; int brightness_target = min_brightness; int brightness_old = min_brightness; @@ -130,15 +128,15 @@ fn int main() int luminance_median; float luminance_percent; while (1) { - _x = get_luminance_raw(); - if (catch excuse = _x) { - io::eprintfn("Error reading luminance: %s", excuse); + int! tmp = get_luminance_raw(); + if (catch excuse = tmp) { + io::eprintfn("Error reading luminance: %s", excuse); } else { - luminance_samples[idx] = _x; - idx = (idx + 1) % MEDIAN_SAMPLES; + luminance_samples[idx] = tmp; + idx = (idx + 1) % luminance_samples.len; } - luminance_median = luminance_samples.sum() / MEDIAN_SAMPLES; + luminance_median = (float)luminance_samples.sum() / luminance_samples.len; luminance_percent = bezier(clamp(luminance_median/MAX_LUMINANCE, 0.0f, 1.0f)); brightness_old = brightness_target; From 191292298cb18a5b466f1d97e107a1593c955c23 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 24 Oct 2024 23:37:52 +0200 Subject: [PATCH 13/24] use a constant for sleep delay --- ambientd.c3 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ambientd.c3 b/ambientd.c3 index 8d24f80..df3c564 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -18,6 +18,8 @@ const int MAX_LUMINANCE = 700; // minimum display brightness in percent const float MIN_BRIGHTNESS_PERCENT = 0.05; +const int SLEEP_DELAY = 3000; + macro abs(x) { return x < 0 ? -x : x; } macro clamp(x, l, h) { return max(min(x, h), l); } @@ -155,7 +157,7 @@ fn int main() io::eprintfn("Error writing brightness: %s", excuse); } - thread::sleep_ms(3000); + thread::sleep_ms(SLEEP_DELAY); // read actual brightness to get the user offset int! actual_brightness; From f6db903cb7853a958d74706625f5bc725d724734 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 24 Oct 2024 23:39:04 +0200 Subject: [PATCH 14/24] fix fp division really being int --- ambientd.c3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ambientd.c3 b/ambientd.c3 index df3c564..ea9346d 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -127,7 +127,7 @@ fn int main() int brightness_old = min_brightness; int brightness_adjusted = min_brightness; int brightness_delta; - int luminance_median; + float luminance_median; float luminance_percent; while (1) { int! tmp = get_luminance_raw(); From d62d0e86b868ee28ffe0d1b1f102a4ceefdf7684 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 24 Oct 2024 23:39:50 +0200 Subject: [PATCH 15/24] better optional handling (maybe) --- ambientd.c3 | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/ambientd.c3 b/ambientd.c3 index ea9346d..005f017 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -94,29 +94,32 @@ fn int main() } // get the max brightness - int! _x = read_int(MAX_BRIGHTNESS_DEV); - if (catch excuse = _x) { + int max_brightness; + int min_brightness; + int! opt = read_int(MAX_BRIGHTNESS_DEV); + if (try unwrapped = opt) { + max_brightness = unwrapped; + } if (catch excuse = opt) { io::eprintfn("Error reading max brightness on %s: %s", MAX_BRIGHTNESS_DEV, excuse); - thread::exit(1); + return 1; } - int max_brightness = _x!!; - int min_brightness = (int)(max_brightness * MIN_BRIGHTNESS_PERCENT); + min_brightness = (int)(max_brightness * MIN_BRIGHTNESS_PERCENT); // try writing to brightness device - _x = read_int(BRIGHTNESS_DEV); - if (catch excuse = _x) { + int! br = read_int(BRIGHTNESS_DEV); + if (catch excuse = br) { io::eprintfn("Error reading from %s: %s", BRIGHTNESS_DEV, excuse); - thread::exit(1); + return 1; } - if (catch excuse = write_int(BRIGHTNESS_DEV, _x)) { - io::eprintfn("Error writing to brightness device %s: %s", BRIGHTNESS_DEV, excuse); - thread::exit(1); + if (catch excuse = write_int(BRIGHTNESS_DEV, br)) { + io::eprintfn("Error writing to brightness device %s: %s", BRIGHTNESS_DEV, excuse); + return 1; } // try reading from the actual brightness device if (catch excuse = read_int(ACTUAL_BRIGHTNESS_DEV)) { io::eprintfn("Error reading from %s: %s", ACTUAL_BRIGHTNESS_DEV, excuse); - thread::exit(1); + return 1; } int x = get_luminance_raw() ?? MAX_LUMINANCE; @@ -160,19 +163,19 @@ fn int main() thread::sleep_ms(SLEEP_DELAY); // read actual brightness to get the user offset - int! actual_brightness; + int actual_brightness; int off; - actual_brightness = read_int(ACTUAL_BRIGHTNESS_DEV); - if (catch excuse = actual_brightness) { + tmp = read_int(ACTUAL_BRIGHTNESS_DEV); + actual_brightness = tmp ?? brightness_adjusted; + if (catch excuse = tmp) { io::eprintfn("Error reading actual brightness: %s", excuse); - actual_brightness = brightness_adjusted; } // if the actual brightness is zero, it means that the screen is turned // off for some reason, usually DPMS. As such it should not count for user // offset. Look at the TODO file. - if (actual_brightness!! != 0) { - off = actual_brightness!! - brightness_target; + if (actual_brightness != 0) { + off = actual_brightness - brightness_target; if (brightness_target != max_brightness || off < 0){ user_offset = off; } From 04d32ab8a521e2487bb1f4a4ce6c314aecc5f76f Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 24 Oct 2024 23:40:02 +0200 Subject: [PATCH 16/24] better logging --- ambientd.c3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ambientd.c3 b/ambientd.c3 index 005f017..26a04f6 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -182,6 +182,6 @@ fn int main() } // TODO: option to turn off logging - io::printfn("brightness target: %d; user offset: %d; brightness adjusted: %d", brightness_target, user_offset, brightness_adjusted); + io::printfn("brightness {target=%d, adjusted=%d}, offset=%d, luminance=%d (%.5f%%)", brightness_target, brightness_adjusted, user_offset, luminance_median, luminance_percent); } } From b0a6551b066bd2beb5b580f32b4235ae2c51cc87 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 24 Oct 2024 23:41:52 +0200 Subject: [PATCH 17/24] don't track c implementation in c3 branch --- ambientd.c | 193 ----------------------------------------------------- 1 file changed, 193 deletions(-) delete mode 100644 ambientd.c diff --git a/ambientd.c b/ambientd.c deleted file mode 100644 index f5aa3ed..0000000 --- a/ambientd.c +++ /dev/null @@ -1,193 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#define MAX(x, y) ((x) > (y) ? (x) : (y)) -#define MIN(x, y) ((x) < (y) ? (x) : (y)) -#define ABS(x) ((x) < 0 ? -(x) : (x)) -#define MEDIAN_SAMPLES 4 - -const char *LUMINANCE_DEV = "/sys/bus/iio/devices/iio:device0/in_illuminance_raw"; -const char *MAX_BRIGHTNESS_DEV = "/sys/class/backlight/intel_backlight/max_brightness"; -const char *BRIGHTNESS_DEV = "/sys/class/backlight/intel_backlight/brightness"; -const char *ACTUAL_BRIGHTNESS_DEV = "/sys/class/backlight/intel_backlight/actual_brightness"; - -char STR_BUFFER[128] = {0}; - -// file pointer to the luminance device -FILE *luminance_fp = NULL; - -// max luminance that the display can output -int max_luminance = 700; - -// minimum display brightness in percent -float min_brightness_percent = 0.05; - -// returns the luminance value in percentage between 0 and 1 -int get_luminance_raw (void) -{ - freopen(NULL, "r", luminance_fp); - const char *x = fgets(STR_BUFFER, sizeof(STR_BUFFER), luminance_fp); - if (x == NULL) { - return -1; - } - int luminance = strtol(STR_BUFFER, NULL, 10); - - return luminance; -} - -// read a string from a file and convert it to int, should only be used for -// positive numbers since negative numbers mean an error -static int read_int(FILE *fp) -{ - if (freopen(NULL, "r", fp) == NULL) { - return -1; - } - const char *p = fgets(STR_BUFFER, sizeof(STR_BUFFER), fp); - if (p == NULL) { - return -1; - } - long r = strtol(STR_BUFFER, NULL, 10); - if (r == LONG_MIN || r == LONG_MAX) { - return -1; - } - return r; -} - -// write an intger value to a file as a string -static int write_int(FILE* fp, int value) -{ - if (freopen(NULL, "w", fp) == NULL) { - return -1; - } - int x = snprintf(STR_BUFFER, sizeof(STR_BUFFER), "%d", value); - if (x <= 0) { - return -1; - } - return fputs(STR_BUFFER, fp); -} - -static inline float bezier(float t) -{ - const float B = 0.66; - const float D = 0.28; - return 3*B*t+(3*D-6*B)*t*t+(1+3*B-3*D)*t*t*t; -} - -// TODO: add some command line options like: -// -v: verbose/logging -// -c file; specify a config file -int main (void) -{ - // open the luminance device - luminance_fp = fopen(LUMINANCE_DEV, "r"); - if (luminance_fp == NULL) { - fprintf(stderr, "Could not open luminance device '%s': %s\n", - LUMINANCE_DEV, strerror(errno)); - return 1; - } - - // get max and min brightness - FILE *fp = fopen(MAX_BRIGHTNESS_DEV, "r"); - if (fp == NULL) { - fprintf(stderr, "Could not open max_brightness file '%s': %s\n", - MAX_BRIGHTNESS_DEV, strerror(errno)); - return 1; - } - int max_brightness = read_int(fp); - if (max_brightness < 0) { - fprintf(stderr, "Could not read max brightness value: %s\n", strerror(errno)); - return 1; - } - int min_brightness = max_brightness * min_brightness_percent; - fclose(fp); - - - // open brightness device - FILE *brightness_fp = fopen(BRIGHTNESS_DEV, "w"); - if (brightness_fp == NULL) { - fprintf(stderr, "Could not open brightness device '%s': %s\n", - BRIGHTNESS_DEV, strerror(errno)); - return 1; - } - - // open actual brightness device - FILE *actual_brightness_fp = fopen(ACTUAL_BRIGHTNESS_DEV, "r"); - if (brightness_fp == NULL) { - fprintf(stderr, "Could not open actual brightness device '%s': %s\n", - ACTUAL_BRIGHTNESS_DEV, strerror(errno)); - return 1; - } - - int x = get_luminance_raw(); - if (x < 0) { - fprintf(stderr, "Could not read luminance value: %s\n", strerror(errno)); - } - int luminance_samples[MEDIAN_SAMPLES] = {x, x, x, x}; - int idx = 0; - int user_offset = 0; - int brightness_target = min_brightness; - int brightness_old = min_brightness; - int brightness_adjusted = min_brightness; - int brightness_delta = 0; - while (1) { - static float luminance; - static float luminance_percent; - - x = get_luminance_raw(); - if (x >= 0) luminance_samples[idx] = x; - idx = (idx + 1) % MEDIAN_SAMPLES; - - luminance = 0; - for (int i = 0; i < MEDIAN_SAMPLES; i++) { - luminance += luminance_samples[i] / MEDIAN_SAMPLES; - } - luminance_percent = bezier(MIN(luminance/max_luminance, 1.0)); - - brightness_old = brightness_target; - brightness_target = MAX((int)(luminance_percent * max_brightness), min_brightness); - brightness_delta = ABS((float)(brightness_target - brightness_old)/brightness_old); - - // if the brightness changes by more than 10% reduce the user offset, scaling - // with the delta, this accounts for large changes in brightness no longer - // corresponding to the adjustments that the user made to the brightness - if (brightness_delta > 0.1) { - user_offset = user_offset * MAX(MIN(1-(brightness_delta*1.2), 1), 0); - } - - brightness_adjusted = MIN(MAX(brightness_target + user_offset, min_brightness), max_brightness); - x = write_int(brightness_fp, brightness_adjusted); - if (x < 0) { - fprintf(stderr, "error writing brightness: %s\n", strerror(errno)); - } - - sleep(3); - - // read actual brightness to get the user offset - int actual_brightness = 0; - int off = 0; - actual_brightness = read_int(actual_brightness_fp); - if (actual_brightness < 0) { - fprintf(stderr, "error reading actual brightness: %s\n", strerror(errno)); - actual_brightness = brightness_adjusted; - } - - // if the actual brightness is zero, it means that the screen is turned - // off for some reason, usually DPMS. As such it should not count for user - // offset. Look at the TODO file. - if (actual_brightness != 0) { - off = actual_brightness - brightness_target; - if (brightness_target != max_brightness || off < 0){ - user_offset = off; - } - } - - // TODO: option to turn off logging - printf("brightness target: %d; user offset: %d; brightness adjusted: %d\n", brightness_target, user_offset, brightness_adjusted); - } - - return 0; -} From 3a3879f6cdd86b9f5de17b1ef12a1c875beab419 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 24 Oct 2024 23:44:18 +0200 Subject: [PATCH 18/24] update todo --- TODO | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index 0964e05..5bd205a 100644 --- a/TODO +++ b/TODO @@ -7,7 +7,8 @@ [ ] Automatically detect backlight device, or at least make it configurable [ ] Add signal handlers [ ] SIGUSR1 to reset the user offset - [ ] Handle kill by closing all file descriptors + [x] Handle kill by closing all file descriptors + (every read is done by only keeping the file open for as long as it is needed) [ ] Handle suspend and resume, usually suspending means that the screen is turned off so do not apply user offset [ ] Account for DPMS and do not apply offset in that case From 9ba6ed6136a9671ed91a0a78a3bd09e620c4d4f6 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 29 Oct 2024 13:22:11 +0100 Subject: [PATCH 19/24] smooth brightness changes --- ambientd.c3 | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/ambientd.c3 b/ambientd.c3 index 26a04f6..f923461 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -18,7 +18,9 @@ const int MAX_LUMINANCE = 700; // minimum display brightness in percent const float MIN_BRIGHTNESS_PERCENT = 0.05; -const int SLEEP_DELAY = 3000; +const int SLEEP_DELAY = 2500; +const int SMOOTH_DURATION = 500; +const int SMOOTH_SAMPLES = 20; macro abs(x) { return x < 0 ? -x : x; } macro clamp(x, l, h) { return max(min(x, h), l); } @@ -154,11 +156,25 @@ fn int main() if (brightness_delta > 0.1) { user_offset = (int)(user_offset * clamp(1-(int)(brightness_delta*1.2), 0, 1)); } - brightness_adjusted = clamp(brightness_target + user_offset, min_brightness, max_brightness); - if (catch excuse = write_int(BRIGHTNESS_DEV, brightness_adjusted)) { - io::eprintfn("Error writing brightness: %s", excuse); - } + + // animate the transition to the brightness target + {| + int brightness_now = read_int(ACTUAL_BRIGHTNESS_DEV) ?? brightness_adjusted; + if (brightness_now == brightness_adjusted) { return; } + int step = (brightness_adjusted-brightness_now)/(SMOOTH_SAMPLES-1); + for (int i; i < SMOOTH_SAMPLES-1; i++) { + anyfault e = @catch(write_int(BRIGHTNESS_DEV, brightness_now + step*i)); + if (e) { + io::eprintfn("Error writing brightness: %s", e); + } + thread::sleep_ms(SMOOTH_DURATION/SMOOTH_SAMPLES); + } + anyfault e = @catch(write_int(BRIGHTNESS_DEV, brightness_adjusted)); + if (e) { + io::eprintfn("Error writing brightness: %s", e); + } + |}; thread::sleep_ms(SLEEP_DELAY); @@ -182,6 +198,6 @@ fn int main() } // TODO: option to turn off logging - io::printfn("brightness {target=%d, adjusted=%d}, offset=%d, luminance=%d (%.5f%%)", brightness_target, brightness_adjusted, user_offset, luminance_median, luminance_percent); + io::printfn("brightness {target=%d, adjusted=%d}, offset=%d, luminance=%d (%.5f%%)", brightness_target, brightness_adjusted, user_offset, luminance_median, luminance_percent*100); } } From 4bb263c96156c4c937917976901208de359e6282 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 29 Oct 2024 13:32:02 +0100 Subject: [PATCH 20/24] macro to write brightness --- ambientd.c3 | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/ambientd.c3 b/ambientd.c3 index f923461..a8fabad 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -74,9 +74,18 @@ fn void! write_int(String path, int value) fp.write(b)!; } -// returns the luminance value in percentage between 0 and 1 +// returns the raw luminance value macro get_luminance_raw() { return read_int(LUMINANCE_DEV); } +// write the brightness value, on fail log on stderr +macro write_brightness(int b) +{ + anyfault e = @catch(write_int(BRIGHTNESS_DEV, b)); + if (e) { + io::eprintfn("Error writing brightness: %s", e); + } +} + fn float bezier(float t) { const float B = 0.66; @@ -164,16 +173,10 @@ fn int main() if (brightness_now == brightness_adjusted) { return; } int step = (brightness_adjusted-brightness_now)/(SMOOTH_SAMPLES-1); for (int i; i < SMOOTH_SAMPLES-1; i++) { - anyfault e = @catch(write_int(BRIGHTNESS_DEV, brightness_now + step*i)); - if (e) { - io::eprintfn("Error writing brightness: %s", e); - } + write_brightness(brightness_now + step*i); thread::sleep_ms(SMOOTH_DURATION/SMOOTH_SAMPLES); } - anyfault e = @catch(write_int(BRIGHTNESS_DEV, brightness_adjusted)); - if (e) { - io::eprintfn("Error writing brightness: %s", e); - } + write_brightness(brightness_adjusted); |}; thread::sleep_ms(SLEEP_DELAY); From ae854e8de110885e698aec9f0da92690be14070e Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 29 Oct 2024 13:32:48 +0100 Subject: [PATCH 21/24] log less decimals for luminance percent --- ambientd.c3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ambientd.c3 b/ambientd.c3 index a8fabad..30253d5 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -201,6 +201,6 @@ fn int main() } // TODO: option to turn off logging - io::printfn("brightness {target=%d, adjusted=%d}, offset=%d, luminance=%d (%.5f%%)", brightness_target, brightness_adjusted, user_offset, luminance_median, luminance_percent*100); + io::printfn("brightness {target=%d, adjusted=%d}, offset=%d, luminance=%d (%.1f%%)", brightness_target, brightness_adjusted, user_offset, luminance_median, luminance_percent*100); } } From 3efc3b19748c971ce02af2e60941b61984bd6eea Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 9 Nov 2024 12:15:42 +0100 Subject: [PATCH 22/24] smoother transition --- ambientd.c3 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ambientd.c3 b/ambientd.c3 index 30253d5..4c7c0d4 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -171,8 +171,8 @@ fn int main() {| int brightness_now = read_int(ACTUAL_BRIGHTNESS_DEV) ?? brightness_adjusted; if (brightness_now == brightness_adjusted) { return; } - int step = (brightness_adjusted-brightness_now)/(SMOOTH_SAMPLES-1); - for (int i; i < SMOOTH_SAMPLES-1; i++) { + int step = (brightness_adjusted-brightness_now)/(SMOOTH_SAMPLES); + for (int i; i < SMOOTH_SAMPLES; i++) { write_brightness(brightness_now + step*i); thread::sleep_ms(SMOOTH_DURATION/SMOOTH_SAMPLES); } From 36c4aff4570d14a7a6d21170e6f40c2d52f9cf2a Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 5 Aug 2026 15:34:38 +0200 Subject: [PATCH 23/24] rewritten, much simpler with config file now --- TODO | 14 -- ambientd.c3 | 388 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 225 insertions(+), 177 deletions(-) delete mode 100644 TODO diff --git a/TODO b/TODO deleted file mode 100644 index 5bd205a..0000000 --- a/TODO +++ /dev/null @@ -1,14 +0,0 @@ -[ ] Add a config file -[ ] Use a better filter than a simple median, maybe a kalman filter -[x] Add a way to customise the brightness curve - Percieved brightness is not linear, on low light you need a slightly higher - brightness and on high light you can get away with a lower brightness - than the max, the goal is to find the right curve for the screen -[ ] Automatically detect backlight device, or at least make it configurable -[ ] Add signal handlers - [ ] SIGUSR1 to reset the user offset - [x] Handle kill by closing all file descriptors - (every read is done by only keeping the file open for as long as it is needed) - [ ] Handle suspend and resume, usually suspending means that the screen is - turned off so do not apply user offset -[ ] Account for DPMS and do not apply offset in that case diff --git a/ambientd.c3 b/ambientd.c3 index 4c7c0d4..675d911 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -1,206 +1,268 @@ +module ambientd; + import std::io; -import std::io::file; +import std::io::path; +import std::os::env; +import std::os::posix; +import std::collections::list; +import std::collections::object; +import std::encoding::ini; import std::thread; +import std::time; +import std::math; +import libc; -// import some functions from the C standard library -extern fn long strtol(char *buffer, char **endptr, int base); -const String LUMINANCE_DEV = "/sys/bus/iio/devices/iio:device0/in_illuminance_raw"; -const String MAX_BRIGHTNESS_DEV = "/sys/class/backlight/intel_backlight/max_brightness"; -const String BRIGHTNESS_DEV = "/sys/class/backlight/intel_backlight/brightness"; -const String ACTUAL_BRIGHTNESS_DEV = "/sys/class/backlight/intel_backlight/actual_brightness"; +alias StrList = List{String}; -char[128] str_buffer; -// max luminance that the display can output -const int MAX_LUMINANCE = 700; +const String DEF_LIGHT_SENSOR_PATH = "/sys/bus/iio/devices/iio:device0/in_illuminance_raw"; +const String DEF_BACKLIGHT_PATH = "/sys/class/backlight/intel_backlight"; +const float DEF_SCREEN_NIT = 400.0; +const float DEF_SENSOR_K = 0.98; +const int DEF_UPDATE_INTERVAL_MS = 200; -// minimum display brightness in percent -const float MIN_BRIGHTNESS_PERCENT = 0.05; +const String[*] CONFIG_PATHS = { + "$XDG_CONFIG_HOME/ambientd/ambientd.ini", + "$HOME/.config/ambientd/ambientd.ini", + "/etc/ambientd/ambientd.ini", +}; -const int SLEEP_DELAY = 2500; -const int SMOOTH_DURATION = 500; -const int SMOOTH_SAMPLES = 20; -macro abs(x) { return x < 0 ? -x : x; } -macro clamp(x, l, h) { return max(min(x, h), l); } +String light_sensor_path = DEF_LIGHT_SENSOR_PATH; +String backlight_path = DEF_BACKLIGHT_PATH; +float backlight_max_luminance = DEF_SCREEN_NIT; +float light_sensor_k = DEF_SENSOR_K; +int update_interval_ms = DEF_UPDATE_INTERVAL_MS; -/** - * read a string to the buffer up until the end of the buffer or the first '\n' - * @require fp != null -**/ -fn void! io::File.gets(&self, char[] buffer) -{ - usz i = 0; - char !c; - for (; i < buffer.len-1; i++) { - c = self.read_byte(); - if (catch excuse = c) { - if (excuse == io::IoError.EOF) { - break; - } else { - return excuse?; - } - } - buffer[i] = c; - } - buffer[i] = '\0'; + +struct LuminanceFilter { + float x; // estimate + float p; // error + float r; // mesurement noise + float q; // process noise } -/** - * read a string from a file and convert it to int, should only be used for - * positive numbers since negative numbers mean an error - * @ensure return > 0, return < 1000000 -**/ -fn int! read_int(String path) -{ - static io::File! fp; - fp = io::file::open((String)path, "r")!; - defer (void)fp.close(); - fp.gets(str_buffer[..])!; - int r = (int)strtol(&str_buffer, null, 10); - return r; +fn float LuminanceFilter.update(&self, int reading, float dt) +{ + float x_1 = self.x; + float p_1 = self.p + self.q * dt; + + float k = p_1 / (p_1 + self.r); + + self.x = x_1 + k * ((float)reading - x_1); + self.p = (1.0 - k) * p_1; + + return self.x; } -// write an intger value to a file as a string -fn void! write_int(String path, int value) -{ - static io::File! fp; - fp = io::file::open(path, "w")!; - defer (void)fp.close(); - char[] b = io::bprintf(str_buffer[..], "%d\n", value)!; - fp.write(b)!; +fn void LuminanceFilter.init(&self, int reading) +{ + self.p = 0.0; + self.x = (float)reading; } -// returns the raw luminance value -macro get_luminance_raw() { return read_int(LUMINANCE_DEV); } -// write the brightness value, on fail log on stderr -macro write_brightness(int b) +// TODO: this should be configurable +LuminanceFilter filter = {.r = 5.0, .q = 0.1}; + + +fn String? String.to_expanded_path(&str, Allocator allocator, String[] extra_env = {}) { - anyfault e = @catch(write_int(BRIGHTNESS_DEV, b)); - if (e) { - io::eprintfn("Error writing brightness: %s", e); + @pool() { + StrList l; + l.tinit(); + Path str_path = str.to_tpath()!; + + l.push(str_path.basename()); + Path? p = str_path.parent(); + while (true) { + Path? x; + if (try p) { + String n = p.basename(); + l.push("/"); + + // env variable + if (n.starts_with("$")) { + String? e = env::tget_var(n[1..]); + if (catch e) { + // var is not in regular environment, check extra_env + bool in_extra = false; + foreach_r (j: extra_env) { + if (j.starts_with(n[1..])) { + l.push(j.strip_suffix(n[1..])[1..]); + in_extra = true; + break; + } + } + if (!in_extra) { + return NOT_FOUND~; + } + } else { + l.push(e); + } + } else { + l.push(n); + } + + x = p.parent(); + } else { + break; + } + p = x; + } + l.reverse(); + String path; + path = string::tjoin(l.array_view(), path); + + return (String)path::new(allocator, path); + }; +} + + +fn void update_config() +{ + bool config_found = false; + foreach (c: CONFIG_PATHS) { + String? p = c.to_expanded_path(tmem); + if (catch e = p) { + io::eprintfn("Error expanding path %s: %s", c, e); + continue; + } + if (!file::exists(p)) { + io::printfn("%s not present", p); + continue; + } + + // Read file + File f = file::open(p, "r")!!; + Object*? o = ini::load_temp(&f).get(ini::GLOBAL); + if (catch e = o) { + io::eprintfn("Error reading config file %s: %s", p, e); + continue; + } + + // Read backlight device folder + String? backlight = o.get_string("backlight"); + if (catch e = backlight) { + io::eprintfn("Error reading backlight path from config: %s", e); + continue; + } + if (!file::is_dir(backlight)) { + io::eprintfn("Error setting the backlight path: path should be a directory"); + continue; + } + backlight_path = backlight; + + // Read light sensor device path + String? sensor = o.get_string("sensor"); + if (catch e = sensor) { + io::eprintfn("Error reading luminance sensor path from config: %s", e); + continue; + } + light_sensor_path = sensor; + + // Optional configurations + + // Read the max screen brightness + if (try x = o.get_string("max_brightness").to_float()) { + backlight_max_luminance = (float)x; + } + + // Read the light sensor factor + if (try x = o.get_string("sensor_factor").to_float()) { + light_sensor_k = (float)x; + } + + // Read the light sensor factor + if (try x = o.get_string("update_interval").to_int()) { + update_interval_ms = x; + } + + config_found = true; + break; + } + + if (!config_found) { + io::eprintfn("Keping previous configuration"); } } -fn float bezier(float t) + +macro float @bezier(float t) { const float B = 0.66; const float D = 0.28; return 3*B*t+(3*D-6*B)*t*t+(1+3*B-3*D)*t*t*t; } -// TODO: add some command line options like: -// -v: verbose/logging -// -c file; specify a config file -fn int main() + +// https://docs.kernel.org/admin-guide/abi-stable-files.html#abi-file-stable-sysfs-class-backlight +macro int @read_int_from_file(String path) => ((String)file::load_temp(path)[..^2]).to_int() ?? 0; +macro void @write_int_to_file(String path, int i) => (void)file::save(path, string::tformat("%d\n", i)); +macro int @get_luminance() => @read_int_from_file(light_sensor_path); +macro int @get_max_backlight() => @read_int_from_file(backlight_path.tconcat("/max_brightness")); +macro int @get_backlight() => @read_int_from_file(backlight_path.tconcat("/brightness")); +macro void @set_backlight(int v) => @write_int_to_file(backlight_path.tconcat("/brightness"), v); +macro bool @is_screen_on() => @read_int_from_file(backlight_path.tconcat("/bl_power")) == 0; + + +fn void sigusr1_handler(CInt sig, void* act, void* oldact) { - // open the luminance device and try reading from it - if (catch excuse = get_luminance_raw()) { - io::eprintfn("Error on initial opening of %s: %s", LUMINANCE_DEV, excuse); - thread::exit(1); - } + (void)sig; + (void)act; + (void)oldact; + update_config(); +} - // get the max brightness - int max_brightness; - int min_brightness; - int! opt = read_int(MAX_BRIGHTNESS_DEV); - if (try unwrapped = opt) { - max_brightness = unwrapped; - } if (catch excuse = opt) { - io::eprintfn("Error reading max brightness on %s: %s", MAX_BRIGHTNESS_DEV, excuse); - return 1; - } - min_brightness = (int)(max_brightness * MIN_BRIGHTNESS_PERCENT); - // try writing to brightness device - int! br = read_int(BRIGHTNESS_DEV); - if (catch excuse = br) { - io::eprintfn("Error reading from %s: %s", BRIGHTNESS_DEV, excuse); - return 1; - } - if (catch excuse = write_int(BRIGHTNESS_DEV, br)) { - io::eprintfn("Error writing to brightness device %s: %s", BRIGHTNESS_DEV, excuse); - return 1; - } +// TODO: add command line arguments, like -h, -v and -d +// TODO: daemonization +// TODO: override config file with arguments +fn void main(String[] args) +{ + update_config(); - // try reading from the actual brightness device - if (catch excuse = read_int(ACTUAL_BRIGHTNESS_DEV)) { - io::eprintfn("Error reading from %s: %s", ACTUAL_BRIGHTNESS_DEV, excuse); - return 1; - } + posix::install_signal_handler(libc::SIGUSR1, &sigusr1_handler); - int x = get_luminance_raw() ?? MAX_LUMINANCE; - int[<4>] luminance_samples = x; - usz idx; - int user_offset; - int brightness_target = min_brightness; - int brightness_old = min_brightness; - int brightness_adjusted = min_brightness; - int brightness_delta; - float luminance_median; - float luminance_percent; - while (1) { - int! tmp = get_luminance_raw(); - if (catch excuse = tmp) { - io::eprintfn("Error reading luminance: %s", excuse); - } else { - luminance_samples[idx] = tmp; - idx = (idx + 1) % luminance_samples.len; + filter.init(@get_luminance()); + float luminance_old = filter.update(@get_luminance(), 0.0); + int brightness_set = @get_backlight(); + int user_offset = 0; + int backlight_max = @get_max_backlight(); + Time start = time::now(); + + while (true) { + if (!@is_screen_on()) { + thread::sleep_ms(5000); + continue; } - luminance_median = (float)luminance_samples.sum() / luminance_samples.len; - luminance_percent = bezier(clamp(luminance_median/MAX_LUMINANCE, 0.0f, 1.0f)); + Time now = time::now(); + Time dt = (Time)(now - start); - brightness_old = brightness_target; - brightness_target = max((int)(luminance_percent * max_brightness), min_brightness); - brightness_delta = (int)abs((float)(brightness_target - brightness_old)/brightness_old); + // Check user offset + user_offset = @get_backlight() - brightness_set; - // if the brightness changes by more than 10% reduce the user offset, scaling - // with the delta, this accounts for large changes in brightness no longer - // corresponding to the adjustments that the user made to the brightness - if (brightness_delta > 0.1) { - user_offset = (int)(user_offset * clamp(1-(int)(brightness_delta*1.2), 0, 1)); - } - brightness_adjusted = clamp(brightness_target + user_offset, min_brightness, max_brightness); + // Filtered luminance, depends heavily on the type of sensor, for the CF-SV7 + // seems to be in lux + float luminance = filter.update(@get_luminance(), (float)dt.to_seconds()) * light_sensor_k; + // Percent of the luminance received compared to what the panel can output + float p = min((luminance/backlight_max_luminance), 1.0f); - // animate the transition to the brightness target - {| - int brightness_now = read_int(ACTUAL_BRIGHTNESS_DEV) ?? brightness_adjusted; - if (brightness_now == brightness_adjusted) { return; } - int step = (brightness_adjusted-brightness_now)/(SMOOTH_SAMPLES); - for (int i; i < SMOOTH_SAMPLES; i++) { - write_brightness(brightness_now + step*i); - thread::sleep_ms(SMOOTH_DURATION/SMOOTH_SAMPLES); - } - write_brightness(brightness_adjusted); - |}; + // int new_brightness = (int)(@bezier(p)*backlight_max); + // The panel should output slightly more light than the ambient, moreover the + // relationship between a percent increase in the backlight power and the + // perceived luminance is non-linear, I think the inverse square law is correct + int new_brightness = (int)(math::pow(p*1.1f, 0.5f)*backlight_max); + brightness_set = new_brightness; + @set_backlight(brightness_set + user_offset); - thread::sleep_ms(SLEEP_DELAY); + // io::printfn("%f, %d, %d", luminance, brightness_set, user_offset); - // read actual brightness to get the user offset - int actual_brightness; - int off; - tmp = read_int(ACTUAL_BRIGHTNESS_DEV); - actual_brightness = tmp ?? brightness_adjusted; - if (catch excuse = tmp) { - io::eprintfn("Error reading actual brightness: %s", excuse); - } - - // if the actual brightness is zero, it means that the screen is turned - // off for some reason, usually DPMS. As such it should not count for user - // offset. Look at the TODO file. - if (actual_brightness != 0) { - off = actual_brightness - brightness_target; - if (brightness_target != max_brightness || off < 0){ - user_offset = off; - } - } - - // TODO: option to turn off logging - io::printfn("brightness {target=%d, adjusted=%d}, offset=%d, luminance=%d (%.1f%%)", brightness_target, brightness_adjusted, user_offset, luminance_median, luminance_percent*100); + start = now; + thread::sleep_ms(update_interval_ms); } } From 56af20f4fe2072b4cd1781d9dd57d1ff5d3134de Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 10 Aug 2026 15:30:07 +0200 Subject: [PATCH 24/24] verbose and retry on file read --- ambientd.c3 | 61 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/ambientd.c3 b/ambientd.c3 index 675d911..6cfd808 100644 --- a/ambientd.c3 +++ b/ambientd.c3 @@ -200,13 +200,24 @@ macro float @bezier(float t) // https://docs.kernel.org/admin-guide/abi-stable-files.html#abi-file-stable-sysfs-class-backlight -macro int @read_int_from_file(String path) => ((String)file::load_temp(path)[..^2]).to_int() ?? 0; +macro int? @read_int_from_file(String path) => ((String)file::load_temp(path)[..^2]).to_int(); macro void @write_int_to_file(String path, int i) => (void)file::save(path, string::tformat("%d\n", i)); -macro int @get_luminance() => @read_int_from_file(light_sensor_path); -macro int @get_max_backlight() => @read_int_from_file(backlight_path.tconcat("/max_brightness")); -macro int @get_backlight() => @read_int_from_file(backlight_path.tconcat("/brightness")); +macro int? @get_luminance() => @read_int_from_file(light_sensor_path); +macro int? @get_max_backlight() => @read_int_from_file(backlight_path.tconcat("/max_brightness")); +macro int? @get_backlight() => @read_int_from_file(backlight_path.tconcat("/brightness")); macro void @set_backlight(int v) => @write_int_to_file(backlight_path.tconcat("/brightness"), v); -macro bool @is_screen_on() => @read_int_from_file(backlight_path.tconcat("/bl_power")) == 0; +macro bool? @is_screen_on() => @read_int_from_file(backlight_path.tconcat("/bl_power")) == 0; + +macro @retry_every(#f, int ms = 100) +{ + const sz TRIES = 10; + for (sz i = 0; i < TRIES; i++) { + if (try x = #f) return x; + thread::sleep_ms(ms); + } + io::eprintfn("ERROR: tried to execute '%s' but it failed too many times (%d)", $stringify(#f), TRIES); + std::os::exit(1); +} fn void sigusr1_handler(CInt sig, void* act, void* oldact) @@ -223,32 +234,50 @@ fn void sigusr1_handler(CInt sig, void* act, void* oldact) // TODO: override config file with arguments fn void main(String[] args) { + bool verbose = false; + foreach (a : args[1..]) { + switch (a) { + case "-v": + verbose = true; + break; + case "-h": + io::printfn("ambientd [-hv]"); + std::os::exit(0); + default: + io::eprintfn("Unrecognized argument: '%s'", a); + std::os::exit(1); + } + } + update_config(); posix::install_signal_handler(libc::SIGUSR1, &sigusr1_handler); - filter.init(@get_luminance()); - float luminance_old = filter.update(@get_luminance(), 0.0); - int brightness_set = @get_backlight(); + int _l = @get_luminance() ?? 0; + filter.init(_l); + float luminance_old = filter.update(_l, 0.0); + int brightness_set = @retry_every(@get_backlight()); int user_offset = 0; - int backlight_max = @get_max_backlight(); + int backlight_max = @get_max_backlight() ?? 0; Time start = time::now(); while (true) { - if (!@is_screen_on()) { + if (!@is_screen_on() ?? false) { thread::sleep_ms(5000); continue; } + + // Check user offset + user_offset = @retry_every(@get_backlight(), update_interval_ms/2) - brightness_set; + int _lx = @retry_every(@get_luminance(), update_interval_ms/2); + Time now = time::now(); Time dt = (Time)(now - start); - // Check user offset - user_offset = @get_backlight() - brightness_set; - // Filtered luminance, depends heavily on the type of sensor, for the CF-SV7 // seems to be in lux - float luminance = filter.update(@get_luminance(), (float)dt.to_seconds()) * light_sensor_k; + float luminance = filter.update(_lx, (float)dt.to_seconds()) * light_sensor_k; // Percent of the luminance received compared to what the panel can output float p = min((luminance/backlight_max_luminance), 1.0f); @@ -258,9 +287,9 @@ fn void main(String[] args) // perceived luminance is non-linear, I think the inverse square law is correct int new_brightness = (int)(math::pow(p*1.1f, 0.5f)*backlight_max); brightness_set = new_brightness; - @set_backlight(brightness_set + user_offset); + @set_backlight(max(brightness_set + user_offset, 0)); - // io::printfn("%f, %d, %d", luminance, brightness_set, user_offset); + if (verbose) io::printfn("%f, %d, %d", luminance, brightness_set, user_offset); start = now; thread::sleep_ms(update_interval_ms);