From bac2f081fabac7dba8c5b6af14d3681455023852 Mon Sep 17 00:00:00 2001 From: Rob Chiocchio <1686473+RobChiocchio@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:32:48 +0200 Subject: [PATCH] Multiple display support prototype --- src/main.rs | 116 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 103 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3b71918..b51e3d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ use simplelog::{ use std::env; use std::fs::{File, OpenOptions}; use std::io::prelude::*; +use std::process::Command; use syslog::Facility; mod config; @@ -24,6 +25,70 @@ mod discrete_value; mod kalman; mod switch_monitor; +trait BrightnessDevice { + fn set(&self, value: u32) -> Result<(), ErrorCode>; + fn max(&self) -> u32; +} + +pub struct SysfsBacklight { + path: String, + max: u32, +} + +impl SysfsBacklight { + pub fn new(path: String, max: u32) -> Self { + Self { path, max } + } +} + +impl BrightnessDevice for SysfsBacklight { + fn set(&self, value: u32) -> Result<(), ErrorCode> { + write_u32_to_file(&self.path, value) + .map_err(|_| ErrorCode::CannotSetBacklight) + } + + fn max(&self) -> u32 { + self.max + } +} + +pub struct DdcUtilDevice { + bus: u32, + max: u32, +} + +impl DdcUtilDevice { + pub fn new(bus: u32) -> Self { + Self { bus, max: 100 } + } +} + +impl BrightnessDevice for DdcUtilDevice { + fn set(&self, value: u32) -> Result<(), ErrorCode> { + let output = Command::new("ddcutil") + .args([ + "setvcp", + "10", + &value.to_string(), + "--bus", + &self.bus.to_string(), + "--noverify", + ]) + .output() + .map_err(|_| ErrorCode::CannotSetBacklight)?; + + if !output.status.success() { + return Err(ErrorCode::CannotSetBacklight); + } + + Ok(()) + } + + fn max(&self) -> u32 { + self.max + } +} + #[derive(Debug)] struct LightConvertor { points: Vec, @@ -64,6 +129,10 @@ impl LightConvertor { } } } + + fn max_light(&self) -> f32 { + self.points.last().map(|p| p.light as f32).unwrap_or(1.0) + } } fn read_file_to_string(filename: &str) -> std::io::Result { @@ -102,7 +171,7 @@ fn write_u32_to_file(filename: &str, value: u32) -> std::io::Result<()> { fn main_loop( config: &Config, light_convertor: &LightConvertor, - max_brightness: u32, + devices: Vec>, mut switch_monitor: switch_monitor::SwitchMonitor, illuminance_filename: &str, ) -> Result<(), ErrorCode> { @@ -111,9 +180,11 @@ fn main_loop( config.kalman_r(), config.kalman_covariance(), ); + //let max_brightness = light_convertor.max_light(); + let max_brightness = 255.0; let mut stepped_brightness = discrete_value::DiscreteValue::new( config.min_backlight(), - max_brightness, + max_brightness as u32, config.light_steps(), config.step_barrier(), ); @@ -130,29 +201,33 @@ fn main_loop( "BRIGHTNESS CHANGED: ambient light raw={} kalman={:.1} -> level={:.2} -> hardware brightness={}", illuminance, illuminance_k, brightness, new ); - set_brightness(config, new); + let normalized = new as f32 / max_brightness; + set_brightness(&devices, normalized); } } _ => error!("Cannot read illuminance"), } - if try_process_switch(&mut switch_monitor, config, max_brightness) { + if try_process_switch(&mut switch_monitor, &devices) { stepped_brightness.update(config.light_steps() as f32); } + std::thread::sleep(std::time::Duration::from_millis(300)); } } -fn set_brightness(config: &Config, value: u32) { - if let Err(e) = write_u32_to_file(config.backlight_filename(), value) { - error!("Cannot set brightness: {}", e); +fn set_brightness(devices: &[Box], normalized: f32) { + for device in devices { + let value = (normalized * device.max() as f32) as u32; + if let Err(e) = device.set(value) { + error!("Cannot set brightness: {:?}", e); + } } } fn try_process_switch( switch_monitor: &mut switch_monitor::SwitchMonitor, - config: &Config, - max_brightness: u32, + devices: &[Box], ) -> bool { - let mut timeout = config.check_period_in_seconds(); + let mut timeout = 1; //config.check_period_in_seconds(); loop { match switch_monitor.wait_state_update(timeout) { (switch_monitor::State::Off, changed) => { @@ -171,7 +246,9 @@ fn try_process_switch( if changed { info!("maximum by event"); } - set_brightness(config, max_brightness); + for device in devices { + let _ = device.set(device.max()); + } timeout = 3600; } } @@ -189,6 +266,7 @@ pub struct LightPoint { light: u32, } +#[derive(Debug)] pub enum ErrorCode { InvalidArgs, ConfigReadError, @@ -296,9 +374,21 @@ fn run() -> Result<(), ErrorCode> { let light_points = config.light_points()?; let light_convertor = LightConvertor::new(light_points); - let max_brightness = read_file_to_u32(config.max_backlight_filename()) + + let mut devices: Vec> = Vec::new(); + + let sysfs_max = read_file_to_u32(config.max_backlight_filename()) .ok_or(ErrorCode::ReadMaxBrightnessError)?; + devices.push(Box::new(SysfsBacklight::new( + config.backlight_filename().to_string(), + sysfs_max, + ))); + + // Example DDC monitors (hardcoded for now) + devices.push(Box::new(DdcUtilDevice::new(16))); + devices.push(Box::new(DdcUtilDevice::new(18))); + let illuminance_filename = match glob::glob(config.illuminance_filename()) { Err(e) => { error!("Cannot glob({}): {}", config.illuminance_filename(), e); @@ -344,7 +434,7 @@ fn run() -> Result<(), ErrorCode> { main_loop( &config, &light_convertor, - max_brightness, + devices, switch_monitor, &illuminance_filename, )