use crate::Error;
use itertools::Either;
use std::{fs, io, iter::once, path::PathBuf};
pub(crate) const BACKLIGHT_DIR: &str = "/sys/class/backlight";
pub(crate) const USER_DBUS_NAME: &str = "org.freedesktop.login1";
pub(crate) const SESSION_OBJECT_PATH: &str = "/org/freedesktop/login1/session/auto";
pub(crate) const SESSION_INTERFACE: &str = "org.freedesktop.login1.Session";
pub(crate) const SET_BRIGHTNESS_METHOD: &str = "SetBrightness";
#[derive(Debug)]
pub(crate) struct BlockingDeviceImpl {
device: String,
}
impl crate::blocking::Brightness for BlockingDeviceImpl {
fn device_name(&self) -> Result<String, Error> {
Ok(self.device.clone())
}
fn friendly_device_name(&self) -> Result<String, Error> {
Ok(self.device.clone())
}
fn get(&self) -> Result<u32, Error> {
let max = read_value(&self.device, Value::Max)?;
let actual = read_value(&self.device, Value::Actual)?;
let percentage = if max == 0 {
0
} else {
(actual * 100 / max) as u32
};
Ok(percentage)
}
fn set(&self, percentage: u32) -> Result<(), Error> {
let percentage = percentage.min(100);
let max = read_value(&self.device, Value::Max)?;
let desired_value = (u64::from(percentage) * u64::from(max) / 100) as u32;
let desired = ("backlight", &self.device, desired_value);
let bus = zbus::blocking::Connection::system().map_err(|e| Error::SettingBrightness {
device: self.device.clone(),
source: e.into(),
})?;
let response = bus.call_method(
Some(USER_DBUS_NAME),
SESSION_OBJECT_PATH,
Some(SESSION_INTERFACE),
SET_BRIGHTNESS_METHOD,
&desired,
);
match response {
Ok(_) => Ok(()),
Err(zbus::Error::MethodError(..)) => {
set_value(&self.device, desired_value)?;
Ok(())
}
Err(e) => Err(Error::SettingBrightness {
device: self.device.clone(),
source: e.into(),
}),
}
}
}
pub(crate) fn brightness_devices() -> impl Iterator<Item = Result<BlockingDeviceImpl, SysError>> {
match fs::read_dir(BACKLIGHT_DIR) {
Ok(devices) => Either::Left(
devices
.map(|device| {
let device = device.map_err(SysError::ReadingBacklightDir)?;
let path = device.path();
let keep = path.join(Value::Actual.as_str()).exists()
&& path.join(Value::Max.as_str()).exists();
Ok(device
.file_name()
.into_string()
.ok()
.map(|device| BlockingDeviceImpl { device })
.filter(|_| keep))
})
.filter_map(Result::transpose),
),
Err(e) => Either::Right(once(Err(SysError::ReadingBacklightDir(e)))),
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum Value {
Actual,
Max,
}
impl Value {
pub(crate) fn as_str(&self) -> &str {
match self {
Value::Actual => "actual_brightness",
Value::Max => "max_brightness",
}
}
}
#[derive(Debug, Error)]
pub(crate) enum SysError {
#[error("Failed to read {} directory", BACKLIGHT_DIR)]
ReadingBacklightDir(#[source] io::Error),
#[error("Failed to read backlight device info {}", .path.display())]
ReadingBacklightDevice {
device: String,
path: PathBuf,
source: io::Error,
},
#[error("Failed to parse backlight info in {}: {reason}", .path.display())]
ParsingBacklightInfo {
device: String,
path: PathBuf,
reason: String,
},
#[error("Failed to write brightness to {}", .path.display())]
WritingBrightness {
device: String,
path: PathBuf,
source: io::Error,
},
}
impl From<SysError> for Error {
fn from(e: SysError) -> Self {
match &e {
SysError::ReadingBacklightDir(_) => Error::ListingDevices(e.into()),
SysError::ReadingBacklightDevice { device, .. }
| SysError::ParsingBacklightInfo { device, .. } => Error::GettingDeviceInfo {
device: device.clone(),
source: e.into(),
},
SysError::WritingBrightness { device, .. } => Error::SettingBrightness {
device: device.clone(),
source: e.into(),
},
}
}
}
pub(crate) fn read_value(device: &str, name: Value) -> Result<u32, SysError> {
let path = [BACKLIGHT_DIR, device, name.as_str()]
.iter()
.collect::<PathBuf>();
fs::read_to_string(&path)
.map_err(|source| SysError::ReadingBacklightDevice {
device: device.into(),
path: path.clone(),
source,
})?
.trim()
.parse::<u32>()
.map_err(|e| SysError::ParsingBacklightInfo {
device: device.into(),
path,
reason: e.to_string(),
})
}
pub(crate) fn set_value(device: &str, value: u32) -> Result<(), SysError> {
let path = [BACKLIGHT_DIR, device, "brightness"]
.iter()
.collect::<PathBuf>();
fs::write(&path, value.to_string()).map_err(|source| SysError::WritingBrightness {
device: device.into(),
path: path.clone(),
source,
})?;
Ok(())
}