rearrange as modules
This commit is contained in:
parent
a6ee8faf31
commit
8bab54b524
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -653,7 +653,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sway-flash-indicator"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"directories",
|
||||
"futures-util",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sway-flash-indicator"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
|
11
src/colour.rs
Normal file
11
src/colour.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use lab::Lab;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn parse_hex(hex: &str) -> Res<Lab> {
|
||||
let hex = hex.strip_prefix('#').unwrap_or(hex);
|
||||
let r = u8::from_str_radix(&hex[..2], 16).map_err(|_| Error::HexParse)?;
|
||||
let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| Error::HexParse)?;
|
||||
let b = u8::from_str_radix(&hex[4..], 16).map_err(|_| Error::HexParse)?;
|
||||
Ok(Lab::from_rgb(&[r, g, b]))
|
||||
}
|
68
src/config.rs
Normal file
68
src/config.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use lab::Lab;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Config {
|
||||
pub frames_delay: u32,
|
||||
pub frames_anim: u32,
|
||||
pub refresh_rate: u64,
|
||||
#[serde(serialize_with = "lab_ser", deserialize_with = "lab_de")]
|
||||
pub flash_colour: Lab,
|
||||
}
|
||||
|
||||
pub fn parse_config() -> Res<()> {
|
||||
let dirs = directories::ProjectDirs::from("com", "alexjanka", "sway-flash-indicator")
|
||||
.ok_or(Error::NoMatchingConfig)?;
|
||||
|
||||
let config_dir = dirs.config_dir();
|
||||
if let Ok(false) = config_dir.try_exists() {
|
||||
log::info!("config dir doesn't exist, creating");
|
||||
std::fs::create_dir_all(config_dir)?;
|
||||
}
|
||||
|
||||
let config_path = config_dir.join("config.toml");
|
||||
let config = if config_path.try_exists().is_ok_and(|v| v) {
|
||||
let v = toml::from_str(&std::fs::read_to_string(&config_path)?)?;
|
||||
log::info!("read config from {config_path:?}");
|
||||
v
|
||||
} else {
|
||||
let c = Config::default();
|
||||
let stringified = toml::to_string(&c)?;
|
||||
std::fs::write(&config_path, stringified)?;
|
||||
log::info!("wrote default to {config_path:?}");
|
||||
c
|
||||
};
|
||||
|
||||
crate::CONFIG.set(config)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
frames_delay: 10,
|
||||
frames_anim: 20,
|
||||
refresh_rate: 60,
|
||||
flash_colour: Lab {
|
||||
l: 53.2,
|
||||
a: 80.1,
|
||||
b: 67.2,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lab_ser<S: serde::Serializer>(colour: &Lab, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let [r, g, b] = colour.to_rgb();
|
||||
format!("#{r:02x}{g:02x}{b:02x}").serialize(serializer)
|
||||
}
|
||||
|
||||
fn lab_de<'a, D: serde::Deserializer<'a>>(deserializer: D) -> Result<Lab, D::Error> {
|
||||
use serde::de::Error;
|
||||
|
||||
let hex = String::deserialize(deserializer)?;
|
||||
crate::colour::parse_hex(&hex).map_err(|_| D::Error::custom("couldn't parse hex"))
|
||||
}
|
25
src/error.rs
Normal file
25
src/error.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("no matching config")]
|
||||
NoMatchingConfig,
|
||||
#[error("couldnt parse hex")]
|
||||
HexParse,
|
||||
#[error(transparent)]
|
||||
Swayipc(#[from] swayipc_async::Error),
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("couldn't set cell")]
|
||||
SetCell,
|
||||
#[error(transparent)]
|
||||
TomlDe(#[from] toml::de::Error),
|
||||
#[error(transparent)]
|
||||
TomlSer(#[from] toml::ser::Error),
|
||||
}
|
||||
|
||||
impl<T> From<tokio::sync::SetError<T>> for Error {
|
||||
fn from(_: tokio::sync::SetError<T>) -> Self {
|
||||
Self::SetCell
|
||||
}
|
||||
}
|
||||
|
||||
pub type Res<T> = Result<T, Error>;
|
48
src/flash.rs
Normal file
48
src/flash.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub async fn interpolate_task() -> Res<()> {
|
||||
let mut connection = swayipc_async::Connection::new().await?;
|
||||
|
||||
let to = crate::DEFAULT_BORDER.get().ok_or(Error::NoMatchingConfig)?;
|
||||
let config = CONFIG.get().ok_or(Error::NoMatchingConfig)?;
|
||||
let per_frame = 1.0 / config.frames_anim as f32;
|
||||
let (d_l, d_a, d_b) = (
|
||||
(to.l - config.flash_colour.l) * per_frame,
|
||||
(to.a - config.flash_colour.a) * per_frame,
|
||||
(to.b - config.flash_colour.b) * per_frame,
|
||||
);
|
||||
|
||||
let mut c = config.flash_colour;
|
||||
connection.run_command(set_command(&c)?).await?;
|
||||
|
||||
let dur_per_frame = std::time::Duration::from_secs_f64(1.0 / config.refresh_rate as f64);
|
||||
|
||||
tokio::time::sleep(dur_per_frame * config.frames_delay).await;
|
||||
for _ in 0..config.frames_anim {
|
||||
c.l += d_l;
|
||||
c.a += d_a;
|
||||
c.b += d_b;
|
||||
|
||||
connection.run_command(set_command(&c)?).await?;
|
||||
|
||||
tokio::time::sleep(dur_per_frame).await;
|
||||
}
|
||||
connection
|
||||
.run_command(
|
||||
crate::DEFAULT_COLOURS
|
||||
.get()
|
||||
.ok_or(Error::NoMatchingConfig)?,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_command(colour: &lab::Lab) -> Res<String> {
|
||||
let [r, g, b] = colour.to_rgb();
|
||||
Ok(format!(
|
||||
"{} #{r:02x}{g:02x}{b:02x}",
|
||||
crate::DEFAULT_COLOURS_NO_INDICATOR
|
||||
.get()
|
||||
.ok_or(Error::NoMatchingConfig)?
|
||||
))
|
||||
}
|
157
src/main.rs
157
src/main.rs
|
@ -1,70 +1,20 @@
|
|||
use config::Config;
|
||||
use futures_util::StreamExt;
|
||||
use lab::Lab;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
enum Error {
|
||||
#[error("no matching config")]
|
||||
NoMatchingConfig,
|
||||
#[error("couldnt parse hex")]
|
||||
HexParse,
|
||||
#[error(transparent)]
|
||||
Swayipc(#[from] swayipc_async::Error),
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("couldn't set cell")]
|
||||
SetCell,
|
||||
#[error(transparent)]
|
||||
TomlDe(#[from] toml::de::Error),
|
||||
#[error(transparent)]
|
||||
TomlSer(#[from] toml::ser::Error),
|
||||
mod colour;
|
||||
mod config;
|
||||
mod error;
|
||||
mod flash;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::error::*;
|
||||
pub use crate::CONFIG;
|
||||
}
|
||||
|
||||
impl<T> From<tokio::sync::SetError<T>> for Error {
|
||||
fn from(_: tokio::sync::SetError<T>) -> Self {
|
||||
Self::SetCell
|
||||
}
|
||||
}
|
||||
use prelude::*;
|
||||
|
||||
type Res<T> = Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
struct Config {
|
||||
frames_delay: u32,
|
||||
frames_anim: u32,
|
||||
refresh_rate: u64,
|
||||
#[serde(serialize_with = "lab_ser", deserialize_with = "lab_de")]
|
||||
flash_colour: Lab,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
frames_delay: 10,
|
||||
frames_anim: 20,
|
||||
refresh_rate: 60,
|
||||
flash_colour: Lab {
|
||||
l: 53.2,
|
||||
a: 80.1,
|
||||
b: 67.2,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lab_ser<S: serde::Serializer>(colour: &Lab, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let [r, g, b] = colour.to_rgb();
|
||||
format!("#{r:02x}{g:02x}{b:02x}").serialize(serializer)
|
||||
}
|
||||
|
||||
fn lab_de<'a, D: serde::Deserializer<'a>>(deserializer: D) -> Result<Lab, D::Error> {
|
||||
use serde::de::Error;
|
||||
|
||||
let hex = String::deserialize(deserializer)?;
|
||||
parse_hex(&hex).map_err(|_| D::Error::custom("couldn't parse hex"))
|
||||
}
|
||||
|
||||
static CONFIG: tokio::sync::OnceCell<Config> = tokio::sync::OnceCell::const_new();
|
||||
pub static CONFIG: tokio::sync::OnceCell<Config> = tokio::sync::OnceCell::const_new();
|
||||
|
||||
static DEFAULT_COLOURS: tokio::sync::OnceCell<String> = tokio::sync::OnceCell::const_new();
|
||||
static DEFAULT_COLOURS_NO_INDICATOR: tokio::sync::OnceCell<String> =
|
||||
|
@ -78,7 +28,7 @@ async fn main() -> Res<()> {
|
|||
}
|
||||
pretty_env_logger::init();
|
||||
|
||||
parse_config()?;
|
||||
config::parse_config()?;
|
||||
|
||||
let mut event_connection = swayipc_async::Connection::new().await?;
|
||||
|
||||
|
@ -99,7 +49,7 @@ async fn main() -> Res<()> {
|
|||
fut.abort();
|
||||
}
|
||||
fut = Some(tokio::spawn(async {
|
||||
if let Err(e) = interpolate_task().await {
|
||||
if let Err(e) = flash::interpolate_task().await {
|
||||
log::warn!("error {e:?}");
|
||||
}
|
||||
}));
|
||||
|
@ -113,34 +63,6 @@ async fn main() -> Res<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_config() -> Res<()> {
|
||||
let dirs = directories::ProjectDirs::from("com", "alexjanka", "sway-flash-indicator")
|
||||
.ok_or(Error::NoMatchingConfig)?;
|
||||
|
||||
let config_dir = dirs.config_dir();
|
||||
if let Ok(false) = config_dir.try_exists() {
|
||||
log::info!("config dir doesn't exist, creating");
|
||||
std::fs::create_dir_all(config_dir)?;
|
||||
}
|
||||
|
||||
let config_path = config_dir.join("config.toml");
|
||||
let config = if config_path.try_exists().is_ok_and(|v| v) {
|
||||
let v = toml::from_str(&std::fs::read_to_string(&config_path)?)?;
|
||||
log::info!("read config from {config_path:?}");
|
||||
v
|
||||
} else {
|
||||
let c = Config::default();
|
||||
let stringified = toml::to_string(&c)?;
|
||||
std::fs::write(&config_path, stringified)?;
|
||||
log::info!("wrote default to {config_path:?}");
|
||||
c
|
||||
};
|
||||
|
||||
CONFIG.set(config)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_sway_config(connection: &mut swayipc_async::Connection) -> Res<()> {
|
||||
let config = connection.get_config().await?;
|
||||
let default_colours = config
|
||||
|
@ -154,7 +76,7 @@ async fn get_sway_config(connection: &mut swayipc_async::Connection) -> Res<()>
|
|||
.split_whitespace()
|
||||
.nth(2)
|
||||
.ok_or(Error::NoMatchingConfig)?;
|
||||
DEFAULT_BORDER.set(parse_hex(default_border)?)?;
|
||||
DEFAULT_BORDER.set(colour::parse_hex(default_border)?)?;
|
||||
|
||||
DEFAULT_COLOURS.set(default_colours.to_string())?;
|
||||
let mut split = default_colours.split_whitespace().collect::<Vec<_>>();
|
||||
|
@ -163,54 +85,3 @@ async fn get_sway_config(connection: &mut swayipc_async::Connection) -> Res<()>
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_hex(hex: &str) -> Res<Lab> {
|
||||
let hex = hex.strip_prefix('#').unwrap_or(hex);
|
||||
let r = u8::from_str_radix(&hex[..2], 16).map_err(|_| Error::HexParse)?;
|
||||
let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| Error::HexParse)?;
|
||||
let b = u8::from_str_radix(&hex[4..], 16).map_err(|_| Error::HexParse)?;
|
||||
Ok(Lab::from_rgb(&[r, g, b]))
|
||||
}
|
||||
|
||||
async fn interpolate_task() -> Res<()> {
|
||||
let mut connection = swayipc_async::Connection::new().await?;
|
||||
|
||||
let to = DEFAULT_BORDER.get().ok_or(Error::NoMatchingConfig)?;
|
||||
let config = CONFIG.get().ok_or(Error::NoMatchingConfig)?;
|
||||
let per_frame = 1.0 / config.frames_anim as f32;
|
||||
let (d_l, d_a, d_b) = (
|
||||
(to.l - config.flash_colour.l) * per_frame,
|
||||
(to.a - config.flash_colour.a) * per_frame,
|
||||
(to.b - config.flash_colour.b) * per_frame,
|
||||
);
|
||||
|
||||
let mut c = config.flash_colour;
|
||||
connection.run_command(set_command(&c)?).await?;
|
||||
|
||||
let dur_per_frame = std::time::Duration::from_secs_f64(1.0 / config.refresh_rate as f64);
|
||||
|
||||
tokio::time::sleep(dur_per_frame * config.frames_delay).await;
|
||||
for _ in 0..config.frames_anim {
|
||||
c.l += d_l;
|
||||
c.a += d_a;
|
||||
c.b += d_b;
|
||||
|
||||
connection.run_command(set_command(&c)?).await?;
|
||||
|
||||
tokio::time::sleep(dur_per_frame).await;
|
||||
}
|
||||
connection
|
||||
.run_command(DEFAULT_COLOURS.get().ok_or(Error::NoMatchingConfig)?)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_command(colour: &Lab) -> Res<String> {
|
||||
let [r, g, b] = colour.to_rgb();
|
||||
Ok(format!(
|
||||
"{} #{r:02x}{g:02x}{b:02x}",
|
||||
DEFAULT_COLOURS_NO_INDICATOR
|
||||
.get()
|
||||
.ok_or(Error::NoMatchingConfig)?
|
||||
))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue