From fc9388db9ec14541d40608dc6ed41a536416e2bc Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 22 Jun 2022 10:52:14 -0700 Subject: [PATCH] battery: Working display backlight control --- .../src/backlight.rs | 51 ++++++++------ .../cosmic-applet-power-battery/src/main.rs | 66 ++++++++++++++++--- 2 files changed, 88 insertions(+), 29 deletions(-) diff --git a/applets/cosmic-applet-power-battery/src/backlight.rs b/applets/cosmic-applet-power-battery/src/backlight.rs index d86ca41d..5af42467 100644 --- a/applets/cosmic-applet-power-battery/src/backlight.rs +++ b/applets/cosmic-applet-power-battery/src/backlight.rs @@ -4,72 +4,85 @@ use std::{ fs::{self, File}, io::{self, Read}, - path::PathBuf, - str::FromStr, + os::unix::ffi::OsStrExt, + path::Path, + str::{self, FromStr}, }; +const BACKLIGHT_SYSDIR: &str = "/sys/class/backlight"; + #[zbus::dbus_proxy( default_service = "org.freedesktop.login1", - interface = "org.freedesktop.login1.Session" + interface = "org.freedesktop.login1.Session", + default_path = "/org/freedesktop/login1/session/auto" )] trait LogindSession { fn set_brightness(&self, subsystem: &str, name: &str, brightness: u32) -> zbus::Result<()>; } -struct Backlight(PathBuf); +#[derive(Clone)] +pub struct Backlight(String); impl Backlight { - fn brightness(&self) -> Option { + pub fn brightness(&self) -> Option { self.prop("brightness") } - fn max_brightness(&self) -> Option { + // XXX cache value. Async? + pub fn max_brightness(&self) -> Option { self.prop("max_brightness") } - async fn set_brightness( + pub async fn set_brightness( &self, session: &LogindSessionProxy<'_>, value: u32, ) -> zbus::Result<()> { - session.set_brightness("backlight", "", value).await // XXX + session.set_brightness("backlight", &self.0, value).await } fn prop(&self, name: &str) -> Option { - let mut file = File::open(&self.0.join(name)).ok()?; + let path = Path::new(BACKLIGHT_SYSDIR).join(&self.0).join(name); + let mut file = File::open(path).ok()?; let mut s = String::new(); file.read_to_string(&mut s).ok()?; - s.parse().ok() + s.trim().parse().ok() } } // Choose backlight with most "precision". This is what `light` does. -fn backlight() -> io::Result> { +pub fn backlight() -> io::Result> { let mut best_backlight = None; let mut best_max_brightness = 0; - for i in fs::read_dir("/sys/class/backlight")? { - let backlight = Backlight(i?.path()); - if let Some(max_brightness) = backlight.max_brightness() { - if max_brightness > best_max_brightness { - best_backlight = Some(backlight); - best_max_brightness = max_brightness; + for i in fs::read_dir(BACKLIGHT_SYSDIR)? { + if let Ok(filename) = str::from_utf8(i?.file_name().as_bytes()) { + let backlight = Backlight(filename.to_string()); + if let Some(max_brightness) = backlight.max_brightness() { + if max_brightness > best_max_brightness { + best_backlight = Some(backlight); + best_max_brightness = max_brightness; + } } } } Ok(best_backlight) } +/* // TODO: Cache device, max_brightness, etc. async fn set_display_brightness(brightness: f64) -> io::Result<()> { if let Some(backlight) = backlight()? { if let Some(max_brightness) = backlight.max_brightness() { let value = brightness.clamp(0., 1.) * (max_brightness as f64); let value = value.round() as u32; - // XXX TODO - backlight.set_brightness(todo!(), value).await; + let connection = zbus::Connection::system().await?; + if let Ok(session) = LogindSessionProxy::builder(&connection).build().await { + backlight.set_brightness(&session, value).await; + } } } Ok(()) } +*/ // TODO: keyboard backlight diff --git a/applets/cosmic-applet-power-battery/src/main.rs b/applets/cosmic-applet-power-battery/src/main.rs index 58aa4d3e..0001a2b4 100644 --- a/applets/cosmic-applet-power-battery/src/main.rs +++ b/applets/cosmic-applet-power-battery/src/main.rs @@ -4,6 +4,7 @@ use relm4::{ComponentParts, ComponentSender, RelmApp, SimpleComponent, WidgetPlu use std::{process::Command, time::Duration}; mod backlight; +use backlight::{backlight, Backlight, LogindSessionProxy}; mod upower; use upower::UPowerProxy; mod upower_device; @@ -27,7 +28,7 @@ fn format_duration(duration: Duration) -> String { if secs > 60 { let min = secs / 60; if min > 60 { - format!("{}:{}", min / 60, min % 60) + format!("{}:{:02}", min / 60, min % 60) } else { format!("{}m", min) } @@ -44,12 +45,15 @@ struct AppModel { display_brightness: f64, keyboard_brightness: f64, device: Option>, + session: Option>, + backlight: Option, } enum AppMsg { SetDisplayBrightness(f64), SetKeyboardBrightness(f64), SetDevice(DeviceProxy<'static>), + SetSession(LogindSessionProxy<'static>), UpdateProperties, } @@ -134,7 +138,7 @@ impl SimpleComponent for AppModel { }, gtk4::Scale { set_hexpand: true, - set_adjustment: >k4::Adjustment::new(0., 0., 100., 1., 1., 0.), + set_adjustment: >k4::Adjustment::new(0., 0., 1., 1., 1., 0.), #[watch] set_value: model.display_brightness, connect_change_value[sender] => move |_, _, value| { @@ -144,7 +148,7 @@ impl SimpleComponent for AppModel { }, gtk4::Label { #[watch] - set_label: &format!("{:.0}%", model.display_brightness), + set_label: &format!("{:.0}%", model.display_brightness * 100.), }, }, gtk4::Box { @@ -154,7 +158,7 @@ impl SimpleComponent for AppModel { }, gtk4::Scale { set_hexpand: true, - set_adjustment: >k4::Adjustment::new(0., 0., 100., 1., 1., 0.), + set_adjustment: >k4::Adjustment::new(0., 0., 1., 1., 1., 0.), #[watch] set_value: model.keyboard_brightness, connect_change_value[sender] => move |_, _, value| { @@ -164,7 +168,7 @@ impl SimpleComponent for AppModel { }, gtk4::Label { #[watch] - set_label: &format!("{:.0}%", model.keyboard_brightness), + set_label: &format!("{:.0}%", model.keyboard_brightness * 100.), }, }, @@ -190,20 +194,44 @@ impl SimpleComponent for AppModel { root: &Self::Root, sender: &ComponentSender, ) -> ComponentParts { - let model = AppModel { + let mut model = AppModel { icon_name: "battery-symbolic".to_string(), ..Default::default() }; let widgets = view_output!(); - let sender = sender.clone(); - glib::MainContext::default().spawn(async move { + match backlight() { + Ok(Some(backlight)) => { + if let (Some(brightness), Some(max_brightness)) = + (backlight.brightness(), backlight.max_brightness()) + { + model.display_brightness = brightness as f64 / max_brightness as f64; + } + model.backlight = Some(backlight); + } + Ok(None) => {} + Err(err) => eprintln!("Error finding backlight: {}", err), + }; + + glib::MainContext::default().spawn(glib::clone!(@strong sender => async move { match display_device().await { Ok(device) => sender.input(AppMsg::SetDevice(device)), Err(err) => eprintln!("Failed to open UPower display device: {}", err), } - }); + })); + + glib::MainContext::default().spawn(glib::clone!(@strong sender => async move { + // XXX avoid multiple connections? + let proxy = async { + let connection = zbus::Connection::system().await?; + LogindSessionProxy::builder(&connection).build().await + }.await; + match proxy { + Ok(session) => sender.input(AppMsg::SetSession(session)), + Err(err) => eprintln!("Failed to open logind session: {}", err), + } + })); ComponentParts { model, widgets } } @@ -212,7 +240,21 @@ impl SimpleComponent for AppModel { match msg { AppMsg::SetDisplayBrightness(value) => { self.display_brightness = value; - // XXX set brightness + // XXX clone + if let Some(backlight) = self.backlight.clone() { + if let Some(session) = self.session.clone() { + if let Some(max_brightness) = backlight.max_brightness() { + let value = value.clamp(0., 1.) * (max_brightness as f64); + let value = value.round() as u32; + // XXX limit queueing? + glib::MainContext::default().spawn(async move { + if let Err(err) = backlight.set_brightness(&session, value).await { + eprintln!("Failed to set backlight: {}", err); + } + }); + } + } + } } AppMsg::SetKeyboardBrightness(value) => { self.keyboard_brightness = value; @@ -235,6 +277,10 @@ impl SimpleComponent for AppModel { } }); } + AppMsg::SetSession(session) => { + self.session = Some(session); + // TODO + } AppMsg::UpdateProperties => { if let Some(device) = self.device.as_ref() { if let Ok(Some(percentage)) = device.cached_percentage() {