Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

General brightness improvements: new brightnessctl backend, set by percentage, and pick device #60

Merged
merged 10 commits into from
Dec 9, 2023
14 changes: 8 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
3 changes: 3 additions & 0 deletions src/argtypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum ArgTypes {
SourceVolumeMuteToggle = 7,
BrightnessRaise = 8,
BrightnessLower = 9,
BrightnessSet = 12,
NumLock = 10,
ScrollLock = 11,
}
Expand All @@ -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",
Expand All @@ -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,
Expand Down
39 changes: 39 additions & 0 deletions src/brightness_backend/blight.rs
Original file line number Diff line number Diff line change
@@ -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<String>) -> anyhow::Result<Self> {
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)?)
}
}
156 changes: 156 additions & 0 deletions src/brightness_backend/brightnessctl.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
current: Option<u32>,
max: Option<u32>,
}

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<String>) -> anyhow::Result<Self> {
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<CliArg<'arg>>>(&self, arg: A) -> anyhow::Result<T>
where
<T as FromStr>::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<String>) -> anyhow::Result<Self> {
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)
}
}
37 changes: 37 additions & 0 deletions src/brightness_backend/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use self::{blight::Blight, brightnessctl::BrightnessCtl};

mod blight;

mod brightnessctl;

pub type BrightnessBackendResult = anyhow::Result<Box<dyn BrightnessBackend>>;

pub trait BrightnessBackendConstructor: BrightnessBackend + Sized + 'static {
fn try_new(device_name: Option<String>) -> anyhow::Result<Self>;

fn try_new_boxed(device_name: Option<String>) -> 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<String>) -> 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)
})
}
3 changes: 3 additions & 0 deletions src/client/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
9 changes: 1 addition & 8 deletions src/global_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,7 @@ pub(crate) fn handle_application_args(
let value = child.value().str().unwrap_or("");
match (value, value.parse::<i8>()) {
// 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, _) => {
Expand Down
21 changes: 17 additions & 4 deletions src/server/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/server/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading