ambientd/ambientd.c3

199 lines
5.6 KiB
Text
Raw Normal View History

2024-10-22 22:51:50 +02:00
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);
}
}