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 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; const int SLEEP_DELAY = 3000; macro abs(x) { return x < 0 ? -x : x; } 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); } 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; int 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; } 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; 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(SLEEP_DELAY); // 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); } }