206 lines
6.2 KiB
Text
206 lines
6.2 KiB
Text
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 = 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); }
|
|
|
|
/**
|
|
* 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 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;
|
|
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 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
// 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++) {
|
|
write_brightness(brightness_now + step*i);
|
|
thread::sleep_ms(SMOOTH_DURATION/SMOOTH_SAMPLES);
|
|
}
|
|
write_brightness(brightness_adjusted);
|
|
|};
|
|
|
|
thread::sleep_ms(SLEEP_DELAY);
|
|
|
|
// 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 (%.5f%%)", brightness_target, brightness_adjusted, user_offset, luminance_median, luminance_percent*100);
|
|
}
|
|
}
|