diff --git a/Cargo.lock b/Cargo.lock index d0df1be..490977a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "async-broadcast" @@ -1492,6 +1492,7 @@ dependencies = [ name = "swayosd" version = "0.1.0" dependencies = [ + "anyhow", "async-std", "blight", "cascade", @@ -1506,6 +1507,7 @@ dependencies = [ "pulsectl-rs", "shrinkwraprs", "substring", + "thiserror", "zbus", ] @@ -1566,18 +1568,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.43" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.43" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 05009d8..75284f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ shrinkwraprs = "0.3.0" cascade = "1.0.1" pulse = { version = "2.26.0", package = "libpulse-binding" } pulsectl-rs = "0.3.2" -blight = "0.7.0" substring = "1.4.5" lazy_static = "1.4.0" zbus = "3.14.1" @@ -35,3 +34,6 @@ libc = "0.2.147" evdev-rs = "0.6.1" async-std = "1.12.0" nix = "0.26.2" +blight = "0.7.0" +anyhow = "1.0.75" +thiserror = "1.0.49" diff --git a/src/argtypes.rs b/src/argtypes.rs index b2d9610..950f619 100644 --- a/src/argtypes.rs +++ b/src/argtypes.rs @@ -18,6 +18,7 @@ pub enum ArgTypes { SourceVolumeMuteToggle = 7, BrightnessRaise = 8, BrightnessLower = 9, + BrightnessSet = 12, NumLock = 10, ScrollLock = 11, } @@ -36,6 +37,7 @@ impl fmt::Display for ArgTypes { ArgTypes::SourceVolumeMuteToggle => "SOURCE-VOLUME-MUTE-TOGGLE", ArgTypes::BrightnessRaise => "BRIGHTNESS-RAISE", ArgTypes::BrightnessLower => "BRIGHTNESS-LOWER", + ArgTypes::BrightnessSet => "BRIGHTNESS-SET", ArgTypes::NumLock => "NUM-LOCK", ArgTypes::ScrollLock => "SCROLL-LOCK", ArgTypes::DeviceName => "DEVICE-NAME", @@ -59,6 +61,7 @@ impl str::FromStr for ArgTypes { "SOURCE-VOLUME-MUTE-TOGGLE" => ArgTypes::SourceVolumeMuteToggle, "BRIGHTNESS-RAISE" => ArgTypes::BrightnessRaise, "BRIGHTNESS-LOWER" => ArgTypes::BrightnessLower, + "BRIGHTNESS-SET" => ArgTypes::BrightnessSet, "MAX-VOLUME" => ArgTypes::MaxVolume, "NUM-LOCK" => ArgTypes::NumLock, "SCROLL-LOCK" => ArgTypes::ScrollLock, diff --git a/src/brightness_backend/blight.rs b/src/brightness_backend/blight.rs new file mode 100644 index 0000000..c1cc14f --- /dev/null +++ b/src/brightness_backend/blight.rs @@ -0,0 +1,39 @@ +use blight::{Device, Direction}; + +use super::{BrightnessBackend, BrightnessBackendConstructor}; + +pub(super) struct Blight { + device: Device, +} + +impl BrightnessBackendConstructor for Blight { + fn try_new(device_name: Option) -> anyhow::Result { + Ok(Self { + device: Device::new(device_name.map(Into::into))?, + }) + } +} + +impl BrightnessBackend for Blight { + fn get_current(&mut self) -> u32 { + self.device.current() + } + + fn get_max(&mut self) -> u32 { + self.device.max() + } + + fn lower(&mut self, by: u32) -> anyhow::Result<()> { + let val = self.device.calculate_change(by, Direction::Dec); + Ok(self.device.write_value(val)?) + } + + fn raise(&mut self, by: u32) -> anyhow::Result<()> { + let val = self.device.calculate_change(by, Direction::Inc); + Ok(self.device.write_value(val)?) + } + + fn set(&mut self, val: u32) -> anyhow::Result<()> { + Ok(self.device.write_value(val)?) + } +} diff --git a/src/brightness_backend/brightnessctl.rs b/src/brightness_backend/brightnessctl.rs new file mode 100644 index 0000000..ccb0e11 --- /dev/null +++ b/src/brightness_backend/brightnessctl.rs @@ -0,0 +1,156 @@ +use super::{BrightnessBackend, BrightnessBackendConstructor}; + +const EXPECT_STR: &str = "VirtualDevice didn't test the command during initialization"; + +use anyhow::bail; +use std::{error::Error, process::Command, str::FromStr}; +use thiserror::Error; + +enum CliArg<'arg> { + Simple(&'arg str), + KeyValue { key: &'arg str, value: &'arg str }, +} + +impl<'arg> From<&'arg str> for CliArg<'arg> { + fn from(value: &'arg str) -> Self { + CliArg::Simple(value) + } +} + +impl<'arg> From<(&'arg str, &'arg str)> for CliArg<'arg> { + fn from((key, value): (&'arg str, &'arg str)) -> Self { + CliArg::KeyValue { key, value } + } +} + +#[derive(Default)] +struct VirtualDevice { + name: Option, + current: Option, + max: Option, +} + +pub(super) struct BrightnessCtl { + device: VirtualDevice, +} + +#[derive(Error, Debug)] +#[error("Requested device '{device_name}' does not exist ")] +pub struct DeviceDoesntExistError { + device_name: String, +} + +impl VirtualDevice { + fn try_new(device_name: Option) -> anyhow::Result { + let s = Self { + name: device_name.clone(), + ..Default::default() + }; + + // Check if the command is available to us before running it in other occasions + let exit_code = s.command(CliArg::Simple("info")).output()?.status; + + if exit_code.success() { + Ok(s) + } else { + bail!(DeviceDoesntExistError { + device_name: device_name.unwrap() + }) + } + } + + fn command(&self, arg: CliArg) -> Command { + let mut cmd = Command::new("brightnessctl"); + + if let Some(name) = &self.name { + cmd.arg("--device").arg(name); + } + + match arg { + CliArg::Simple(arg) => cmd.arg(arg), + CliArg::KeyValue { key, value } => cmd.arg(key).arg(value), + }; + + cmd + } + + fn run<'arg, T: FromStr, A: Into>>(&self, arg: A) -> anyhow::Result + where + ::Err: Error + Send + Sync + 'static, + { + let cmd_output = self.command(arg.into()).output()?.stdout; + + let cmd_output = String::from_utf8_lossy(&cmd_output); + + Ok(cmd_output.trim().parse()?) + } + + fn get_current(&mut self) -> u32 { + match self.current { + Some(val) => val, + None => { + let val = self.run("get").expect(EXPECT_STR); + self.current = Some(val); + val + } + } + } + + fn get_max(&mut self) -> u32 { + match self.max { + Some(val) => val, + None => { + let val = self.run("max").expect(EXPECT_STR); + self.max = Some(val); + val + } + } + } + + fn set_percent(&mut self, mut val: u32) -> anyhow::Result<()> { + val = val.clamp(0, 100); + self.current = self.max.map(|max| val * max / 100); + let _: String = self.run(("set", &*format!("{val}%")))?; + Ok(()) + } +} + +impl BrightnessBackendConstructor for BrightnessCtl { + fn try_new(device_name: Option) -> anyhow::Result { + Ok(Self { + device: VirtualDevice::try_new(device_name)?, + }) + } +} + +impl BrightnessBackend for BrightnessCtl { + fn get_current(&mut self) -> u32 { + self.device.get_current() + } + + fn get_max(&mut self) -> u32 { + self.device.get_max() + } + + fn lower(&mut self, by: u32) -> anyhow::Result<()> { + let curr = self.get_current(); + let max = self.get_max(); + + let curr = curr * 100 / max; + + self.device.set_percent(curr.saturating_sub(by)) + } + + fn raise(&mut self, by: u32) -> anyhow::Result<()> { + let curr = self.get_current(); + let max = self.get_max(); + + let curr = curr * 100 / max; + + self.device.set_percent(curr + by) + } + + fn set(&mut self, val: u32) -> anyhow::Result<()> { + self.device.set_percent(val) + } +} diff --git a/src/brightness_backend/mod.rs b/src/brightness_backend/mod.rs new file mode 100644 index 0000000..60b3c38 --- /dev/null +++ b/src/brightness_backend/mod.rs @@ -0,0 +1,37 @@ +use self::{blight::Blight, brightnessctl::BrightnessCtl}; + +mod blight; + +mod brightnessctl; + +pub type BrightnessBackendResult = anyhow::Result>; + +pub trait BrightnessBackendConstructor: BrightnessBackend + Sized + 'static { + fn try_new(device_name: Option) -> anyhow::Result; + + fn try_new_boxed(device_name: Option) -> BrightnessBackendResult { + let backend = Self::try_new(device_name); + match backend { + Ok(backend) => Ok(Box::new(backend)), + Err(e) => Err(e), + } + } +} + +pub trait BrightnessBackend { + fn get_current(&mut self) -> u32; + fn get_max(&mut self) -> u32; + + fn lower(&mut self, by: u32) -> anyhow::Result<()>; + fn raise(&mut self, by: u32) -> anyhow::Result<()>; + fn set(&mut self, val: u32) -> anyhow::Result<()>; +} + +#[allow(dead_code)] +pub fn get_preferred_backend(device_name: Option) -> BrightnessBackendResult { + println!("Trying BrightnessCtl Backend..."); + BrightnessCtl::try_new_boxed(device_name.clone()).or_else(|_| { + println!("...Command failed! Falling back to Blight"); + Blight::try_new_boxed(device_name) + }) +} diff --git a/src/client/main.rs b/src/client/main.rs index 2be0133..fee2279 100644 --- a/src/client/main.rs +++ b/src/client/main.rs @@ -5,6 +5,9 @@ mod config; #[path = "../global_utils.rs"] mod global_utils; +#[path = "../brightness_backend/mod.rs"] +mod brightness_backend; + use config::APPLICATION_NAME; use global_utils::{handle_application_args, HandleLocalStatus}; use gtk::glib::{OptionArg, OptionFlags}; diff --git a/src/global_utils.rs b/src/global_utils.rs index f0281b7..9fbd6ee 100644 --- a/src/global_utils.rs +++ b/src/global_utils.rs @@ -70,14 +70,7 @@ pub(crate) fn handle_application_args( let value = child.value().str().unwrap_or(""); match (value, value.parse::()) { // Parse custom step values - (_, Ok(num)) => ( - if num.is_positive() { - ArgTypes::BrightnessRaise - } else { - ArgTypes::BrightnessLower - }, - Some(num.abs().to_string()), - ), + (_, Ok(num)) => (ArgTypes::BrightnessSet, Some(num.abs().to_string())), ("raise", _) => (ArgTypes::BrightnessRaise, None), ("lower", _) => (ArgTypes::BrightnessLower, None), (e, _) => { diff --git a/src/server/application.rs b/src/server/application.rs index 741da39..e54ac81 100644 --- a/src/server/application.rs +++ b/src/server/application.rs @@ -243,16 +243,29 @@ impl SwayOSDApplication { } // TODO: Brightness (ArgTypes::BrightnessRaise, step) => { - if let Ok(Some(device)) = change_brightness(BrightnessChangeType::Raise, step) { + if let Ok(mut brightness_backend) = + change_brightness(BrightnessChangeType::Raise, step) + { for window in osd_app.windows.borrow().to_owned() { - window.changed_brightness(&device); + window.changed_brightness(brightness_backend.as_mut()); } } } (ArgTypes::BrightnessLower, step) => { - if let Ok(Some(device)) = change_brightness(BrightnessChangeType::Lower, step) { + if let Ok(mut brightness_backend) = + change_brightness(BrightnessChangeType::Lower, step) + { + for window in osd_app.windows.borrow().to_owned() { + window.changed_brightness(brightness_backend.as_mut()); + } + } + } + (ArgTypes::BrightnessSet, value) => { + if let Ok(mut brightness_backend) = + change_brightness(BrightnessChangeType::Set, value) + { for window in osd_app.windows.borrow().to_owned() { - window.changed_brightness(&device); + window.changed_brightness(brightness_backend.as_mut()); } } } diff --git a/src/server/main.rs b/src/server/main.rs index b656e86..11192a2 100644 --- a/src/server/main.rs +++ b/src/server/main.rs @@ -9,6 +9,9 @@ mod config; #[path = "../global_utils.rs"] mod global_utils; +#[path = "../brightness_backend/mod.rs"] +mod brightness_backend; + #[macro_use] extern crate shrinkwraprs; diff --git a/src/server/osd_window.rs b/src/server/osd_window.rs index 915349d..e37f253 100644 --- a/src/server/osd_window.rs +++ b/src/server/osd_window.rs @@ -9,8 +9,10 @@ use gtk::{ }; use pulsectl::controllers::types::DeviceInfo; -use crate::utils::{get_max_volume, get_top_margin, volume_to_f64, KeysLocks, VolumeDeviceType}; -use blight::Device; +use crate::{ + brightness_backend::BrightnessBackend, + utils::{get_max_volume, get_top_margin, volume_to_f64, KeysLocks, VolumeDeviceType}, +}; const ICON_SIZE: i32 = 32; @@ -105,14 +107,14 @@ impl SwayosdWindow { self.run_timeout(); } - pub fn changed_brightness(&self, device: &Device) { + pub fn changed_brightness(&self, brightness_backend: &mut dyn BrightnessBackend) { self.clear_osd(); let icon_name = "display-brightness-symbolic"; let icon = self.build_icon_widget(icon_name); - let brightness = device.current() as f64; - let max = device.max() as f64; + let brightness = brightness_backend.get_current() as f64; + let max = brightness_backend.get_max() as f64; let progress = self.build_progress_widget(brightness / max); self.container.add(&icon); diff --git a/src/server/utils.rs b/src/server/utils.rs index f093dda..8b87b56 100644 --- a/src/server/utils.rs +++ b/src/server/utils.rs @@ -8,16 +8,18 @@ use std::{ sync::Mutex, }; -use blight::{change_bl, err::BlibError, Change, Device, Direction}; use pulse::volume::Volume; use pulsectl::controllers::{types::DeviceInfo, DeviceControl, SinkController, SourceController}; +use crate::brightness_backend; + static PRIV_MAX_VOLUME_DEFAULT: u8 = 100_u8; + lazy_static! { static ref MAX_VOLUME_DEFAULT: Mutex = Mutex::new(PRIV_MAX_VOLUME_DEFAULT); static ref MAX_VOLUME: Mutex = Mutex::new(PRIV_MAX_VOLUME_DEFAULT); pub static ref DEVICE_NAME_DEFAULT: &'static str = "default"; - static ref DEVICE_NAME: Mutex = Mutex::new(DEVICE_NAME_DEFAULT.to_string()); + static ref DEVICE_NAME: Mutex> = Mutex::new(None); pub static ref TOP_MARGIN_DEFAULT: f32 = 0.85_f32; static ref TOP_MARGIN: Mutex = Mutex::new(*TOP_MARGIN_DEFAULT); } @@ -60,18 +62,18 @@ pub fn set_top_margin(margin: f32) { *margin_mut = margin; } -pub fn get_device_name() -> String { +pub fn get_device_name() -> Option { (*DEVICE_NAME.lock().unwrap()).clone() } pub fn set_device_name(name: String) { let mut global_name = DEVICE_NAME.lock().unwrap(); - *global_name = name; + *global_name = Some(name); } pub fn reset_device_name() { let mut global_name = DEVICE_NAME.lock().unwrap(); - *global_name = DEVICE_NAME_DEFAULT.to_string(); + *global_name = None; } pub fn get_key_lock_state(key: KeysLocks, led: Option) -> bool { @@ -140,6 +142,7 @@ pub enum VolumeDeviceType { pub enum BrightnessChangeType { Raise, Lower, + Set, } pub fn change_device_volume( @@ -151,7 +154,7 @@ pub fn change_device_volume( VolumeDeviceType::Sink(controller) => { let server_info = controller.get_server_info(); let global_name = get_device_name(); - let device_name: String = if global_name == "default" { + let device_name: String = if global_name.is_none() { match server_info { Ok(info) => info.default_sink_name.unwrap_or("".to_string()), Err(e) => { @@ -160,8 +163,8 @@ pub fn change_device_volume( } } } else { - set_device_name("default".to_string()); - global_name + set_device_name(DEVICE_NAME_DEFAULT.to_string()); + get_device_name().unwrap() }; match controller.get_device_by_name(&device_name) { Ok(device) => (device, device_name.clone()), @@ -174,7 +177,7 @@ pub fn change_device_volume( VolumeDeviceType::Source(controller) => { let server_info = controller.get_server_info(); let global_name = get_device_name(); - let device_name: String = if global_name == "default" { + let device_name: String = if global_name.is_none() { match server_info { Ok(info) => info.default_source_name.unwrap_or("".to_string()), Err(e) => { @@ -183,8 +186,8 @@ pub fn change_device_volume( } } } else { - set_device_name("default".to_string()); - global_name + set_device_name(DEVICE_NAME_DEFAULT.to_string()); + get_device_name().unwrap() }; match controller.get_device_by_name(&device_name) { Ok(device) => (device, device_name.clone()), @@ -199,7 +202,7 @@ pub fn change_device_volume( const VOLUME_CHANGE_DELTA: u8 = 5; let volume_delta = step .clone() - .unwrap_or(String::new()) + .unwrap_or_default() .parse::() .unwrap_or(VOLUME_CHANGE_DELTA) as f64 * 0.01; @@ -250,7 +253,7 @@ pub fn change_device_volume( else if over_max_volume { let delta_to_max = max_volume - volume_percent; let volume_delta = step - .unwrap_or(String::new()) + .unwrap_or_default() .parse::() .unwrap_or(delta_to_max) as f64 * 0.01; @@ -324,32 +327,23 @@ pub fn change_device_volume( pub fn change_brightness( change_type: BrightnessChangeType, step: Option, -) -> Result, BlibError> { +) -> brightness_backend::BrightnessBackendResult { const BRIGHTNESS_CHANGE_DELTA: u8 = 5; - let brightness_delta = step - .unwrap_or(String::new()) - .parse::() - .unwrap_or(BRIGHTNESS_CHANGE_DELTA) as u32; - let direction = match change_type { - BrightnessChangeType::Raise => Direction::Inc, + let value = step.unwrap_or_default().parse::(); + + let mut backend = brightness_backend::get_preferred_backend(get_device_name())?; + + match change_type { + BrightnessChangeType::Raise => { + backend.raise(value.unwrap_or(BRIGHTNESS_CHANGE_DELTA) as u32)? + } BrightnessChangeType::Lower => { - let device = Device::new(None)?; - let change = device.calculate_change(brightness_delta, Direction::Dec) as f64; - let max = device.max() as f64; - // Limits the lowest brightness to 5% - if change / max < (brightness_delta as f64) * 0.01 { - return Ok(Some(device)); - } - Direction::Dec + backend.lower(value.unwrap_or(BRIGHTNESS_CHANGE_DELTA) as u32)? } + BrightnessChangeType::Set => backend.set(value.unwrap() as u32)?, }; - match change_bl(brightness_delta, Change::Regular, direction, None) { - Err(e) => { - eprintln!("Brightness Error: {}", e); - Err(e) - } - _ => Ok(Some(Device::new(None)?)), - } + + Ok(backend) } pub fn volume_to_f64(volume: &Volume) -> f64 {