i3status_rs/blocks/
hueshift.rsuse super::prelude::*;
use crate::subprocess::{spawn_process, spawn_shell};
use crate::util::has_command;
use futures::future::pending;
#[derive(Deserialize, Debug, SmartDefault)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
pub format: FormatConfig,
#[default(5.into())]
pub interval: Seconds,
#[default(10_000)]
pub max_temp: u16,
#[default(1_000)]
pub min_temp: u16,
#[default(6_500)]
pub current_temp: u16,
pub hue_shifter: Option<HueShifter>,
#[default(100)]
pub step: u16,
#[default(6_500)]
pub click_temp: u16,
}
pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
let mut actions = api.get_actions()?;
api.set_default_actions(&[
(MouseButton::Left, None, "set_click_temp"),
(MouseButton::Right, None, "reset"),
(MouseButton::WheelUp, None, "temperature_up"),
(MouseButton::WheelDown, None, "temperature_down"),
])?;
let format = config.format.with_default(" $temperature ")?;
let step = config.step.min(500);
let max_temp = config.max_temp.min(10_000);
let min_temp = config.min_temp.clamp(1_000, max_temp);
let hue_shifter = match config.hue_shifter {
Some(driver) => driver,
None => {
if has_command("wl-gammarelay-rs").await? {
HueShifter::WlGammarelayRs
} else if has_command("wl-gammarelay").await? {
HueShifter::WlGammarelay
} else if has_command("redshift").await? {
HueShifter::Redshift
} else if has_command("sct").await? {
HueShifter::Sct
} else if has_command("gammastep").await? {
HueShifter::Gammastep
} else if has_command("wlsunset").await? {
HueShifter::Wlsunset
} else {
return Err(Error::new("Could not detect driver program"));
}
}
};
let mut driver: Box<dyn HueShiftDriver> = match hue_shifter {
HueShifter::Redshift => Box::new(Redshift::new(config.interval)),
HueShifter::Sct => Box::new(Sct::new(config.interval)),
HueShifter::Gammastep => Box::new(Gammastep::new(config.interval)),
HueShifter::Wlsunset => Box::new(Wlsunset::new(config.interval)),
HueShifter::WlGammarelay => Box::new(WlGammarelayRs::new("wl-gammarelay").await?),
HueShifter::WlGammarelayRs => Box::new(WlGammarelayRs::new("wl-gammarelay-rs").await?),
};
let mut current_temp = driver.get().await?.unwrap_or(config.current_temp);
loop {
let mut widget = Widget::new().with_format(format.clone());
widget.set_values(map!("temperature" => Value::number(current_temp)));
api.set_widget(widget)?;
select! {
update = driver.receive_update() => {
current_temp = update?;
}
_ = api.wait_for_update_request() => {
if let Some(val) = driver.get().await? {
current_temp = val;
}
}
Some(action) = actions.recv() => match action.as_ref() {
"set_click_temp" => {
current_temp = config.click_temp;
driver.update(current_temp).await?;
}
"reset" => {
if max_temp > 6500 {
current_temp = 6500;
driver.reset().await?;
} else {
current_temp = max_temp;
driver.update(current_temp).await?;
}
}
"temperature_up" => {
current_temp = (current_temp + step).min(max_temp);
driver.update(current_temp).await?;
}
"temperature_down" => {
current_temp = current_temp.saturating_sub(step).max(min_temp);
driver.update(current_temp).await?;
}
_ => (),
}
}
}
}
#[derive(Deserialize, Debug, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum HueShifter {
Redshift,
Sct,
Gammastep,
Wlsunset,
WlGammarelay,
WlGammarelayRs,
}
#[async_trait]
trait HueShiftDriver {
async fn get(&mut self) -> Result<Option<u16>>;
async fn update(&mut self, temp: u16) -> Result<()>;
async fn reset(&mut self) -> Result<()>;
async fn receive_update(&mut self) -> Result<u16>;
}
struct Redshift {
interval: Seconds,
}
impl Redshift {
fn new(interval: Seconds) -> Self {
Self { interval }
}
}
#[async_trait]
impl HueShiftDriver for Redshift {
async fn get(&mut self) -> Result<Option<u16>> {
Ok(None)
}
async fn update(&mut self, temp: u16) -> Result<()> {
spawn_process("redshift", &["-O", &temp.to_string(), "-P"])
.error("Failed to set new color temperature using redshift.")
}
async fn reset(&mut self) -> Result<()> {
spawn_process("redshift", &["-x"])
.error("Failed to set new color temperature using redshift.")
}
async fn receive_update(&mut self) -> Result<u16> {
sleep(self.interval.0).await;
pending().await
}
}
struct Sct {
interval: Seconds,
}
impl Sct {
fn new(interval: Seconds) -> Self {
Self { interval }
}
}
#[async_trait]
impl HueShiftDriver for Sct {
async fn get(&mut self) -> Result<Option<u16>> {
Ok(None)
}
async fn update(&mut self, temp: u16) -> Result<()> {
spawn_shell(&format!("sct {temp} >/dev/null 2>&1"))
.error("Failed to set new color temperature using sct.")
}
async fn reset(&mut self) -> Result<()> {
spawn_process("sct", &[]).error("Failed to set new color temperature using sct.")
}
async fn receive_update(&mut self) -> Result<u16> {
sleep(self.interval.0).await;
pending().await
}
}
struct Gammastep {
interval: Seconds,
}
impl Gammastep {
fn new(interval: Seconds) -> Self {
Self { interval }
}
}
#[async_trait]
impl HueShiftDriver for Gammastep {
async fn get(&mut self) -> Result<Option<u16>> {
Ok(None)
}
async fn update(&mut self, temp: u16) -> Result<()> {
spawn_shell(&format!("pkill gammastep; gammastep -O {temp} -P &",))
.error("Failed to set new color temperature using gammastep.")
}
async fn reset(&mut self) -> Result<()> {
spawn_process("gammastep", &["-x"])
.error("Failed to set new color temperature using gammastep.")
}
async fn receive_update(&mut self) -> Result<u16> {
sleep(self.interval.0).await;
pending().await
}
}
struct Wlsunset {
interval: Seconds,
}
impl Wlsunset {
fn new(interval: Seconds) -> Self {
Self { interval }
}
}
#[async_trait]
impl HueShiftDriver for Wlsunset {
async fn get(&mut self) -> Result<Option<u16>> {
Ok(None)
}
async fn update(&mut self, temp: u16) -> Result<()> {
spawn_shell(&format!(
"pkill wlsunset; wlsunset -T {} -t {} &",
temp + 1,
temp
))
.error("Failed to set new color temperature using wlsunset.")
}
async fn reset(&mut self) -> Result<()> {
spawn_process("pkill", &["wlsunset"])
.error("Failed to set new color temperature using wlsunset.")
}
async fn receive_update(&mut self) -> Result<u16> {
sleep(self.interval.0).await;
pending().await
}
}
struct WlGammarelayRs {
proxy: WlGammarelayRsBusProxy<'static>,
updates: zbus::proxy::PropertyStream<'static, u16>,
}
impl WlGammarelayRs {
async fn new(cmd: &str) -> Result<Self> {
spawn_process(cmd, &[]).error("Failed to start wl-gammarelay daemon")?;
sleep(Duration::from_millis(100)).await;
let conn = crate::util::new_dbus_connection().await?;
let proxy = WlGammarelayRsBusProxy::new(&conn)
.await
.error("Failed to create wl-gammarelay-rs DBus proxy")?;
let updates = proxy.receive_temperature_changed().await;
Ok(Self { proxy, updates })
}
}
#[async_trait]
impl HueShiftDriver for WlGammarelayRs {
async fn get(&mut self) -> Result<Option<u16>> {
let value = self
.proxy
.temperature()
.await
.error("Failed to get temperature")?;
Ok(Some(value))
}
async fn update(&mut self, temp: u16) -> Result<()> {
self.proxy
.set_temperature(temp)
.await
.error("Failed to set temperature")
}
async fn reset(&mut self) -> Result<()> {
self.update(6500).await
}
async fn receive_update(&mut self) -> Result<u16> {
let update = self.updates.next().await.error("No next update")?;
update.get().await.error("Failed to get temperature")
}
}
#[zbus::proxy(
interface = "rs.wl.gammarelay",
default_service = "rs.wl-gammarelay",
default_path = "/"
)]
trait WlGammarelayRsBus {
#[zbus(property)]
fn brightness(&self) -> zbus::Result<f64>;
#[zbus(property)]
fn set_brightness(&self, value: f64) -> zbus::Result<()>;
#[zbus(property)]
fn temperature(&self) -> zbus::Result<u16>;
#[zbus(property)]
fn set_temperature(&self, value: u16) -> zbus::Result<()>;
}