Compare commits
4 Commits
071d973fea
..
v0.4.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 2050c345f1 | |||
| 75601305e2 | |||
| 52cc041c42 | |||
| cb8b641447 |
Generated
+1
-1
@@ -572,7 +572,7 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fluxo-rs"
|
name = "fluxo-rs"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bluer",
|
"bluer",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fluxo-rs"
|
name = "fluxo-rs"
|
||||||
version = "0.4.1"
|
version = "0.4.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -29,40 +29,49 @@ backlight = 13
|
|||||||
dnd = 14
|
dnd = 14
|
||||||
|
|
||||||
[network]
|
[network]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
# tokens: {interface}, {ip}, {rx}, {tx}
|
# tokens: {interface}, {ip}, {rx}, {tx}
|
||||||
format = "{interface} ({ip}): {rx:^4.1} MB/s {tx:^4.1} MB/s"
|
format = "{interface} ({ip}): {rx:^4.1} MB/s {tx:^4.1} MB/s"
|
||||||
|
|
||||||
[cpu]
|
[cpu]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
# tokens: {usage}, {temp}, {model}
|
# tokens: {usage}, {temp}, {model}
|
||||||
format = "CPU: {usage:^4.1}% {temp:^4.1}C"
|
format = "CPU: {usage:^4.1}% {temp:^4.1}C"
|
||||||
|
|
||||||
[memory]
|
[memory]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
# tokens: {used}, {total}
|
# tokens: {used}, {total}
|
||||||
format = "MEM: {used:^4.1}/{total:^4.1}GB"
|
format = "MEM: {used:^4.1}/{total:^4.1}GB"
|
||||||
|
|
||||||
[gpu]
|
[gpu]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
# tokens: {usage}, {vram_used}, {vram_total}, {temp}
|
# tokens: {usage}, {vram_used}, {vram_total}, {temp}
|
||||||
format_amd = "AMD: {usage:>3.0}% {vram_used:>4.1}/{vram_total:>4.1}GB {temp:>4.1}C"
|
format_amd = "AMD: {usage:>3.0}% {vram_used:>4.1}/{vram_total:>4.1}GB {temp:>4.1}C"
|
||||||
format_intel = "iGPU: {usage:>3.0}%"
|
format_intel = "iGPU: {usage:>3.0}%"
|
||||||
format_nvidia = "NV: {usage:>3.0}% {vram_used:>4.1}/{vram_total:>4.1}GB {temp:>4.1}C"
|
format_nvidia = "NV: {usage:>3.0}% {vram_used:>4.1}/{vram_total:>4.1}GB {temp:>4.1}C"
|
||||||
|
|
||||||
[sys]
|
[sys]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
# tokens: {uptime}, {load1}, {load5}, {load15}, {procs}
|
# tokens: {uptime}, {load1}, {load5}, {load15}, {procs}
|
||||||
format = "UP: {uptime} LOAD: {load1:^3.1} "
|
format = "UP: {uptime} LOAD: {load1:^3.1} "
|
||||||
|
|
||||||
[disk]
|
[disk]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
# tokens: {mount}, {used}, {total}
|
# tokens: {mount}, {used}, {total}
|
||||||
format = "{mount} {used:^3.0}/{total:^3.0}G"
|
format = "{mount} {used:^3.0}/{total:^3.0}G"
|
||||||
|
|
||||||
[pool]
|
[pool]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
# tokens: {used}, {total}
|
# tokens: {used}, {total}
|
||||||
format = "{used:>4.0}G / {total:>4.0}G"
|
format = "{used:>4.0}G / {total:>4.0}G"
|
||||||
|
|
||||||
[power]
|
[power]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
# tokens: {percentage}, {icon}
|
# tokens: {percentage}, {icon}
|
||||||
format = "{percentage:>3}% {icon}"
|
format = "{percentage:>3}% {icon}"
|
||||||
|
|
||||||
[audio]
|
[audio]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
# tokens: {name}, {volume}, {icon}
|
# tokens: {name}, {volume}, {icon}
|
||||||
format_sink_unmuted = "{name} {volume:>3}% {icon}"
|
format_sink_unmuted = "{name} {volume:>3}% {icon}"
|
||||||
format_sink_muted = "{name} {icon}"
|
format_sink_muted = "{name} {icon}"
|
||||||
@@ -70,6 +79,7 @@ format_source_unmuted = "{name} {volume:>3}% {icon}"
|
|||||||
format_source_muted = "{name} {icon}"
|
format_source_muted = "{name} {icon}"
|
||||||
|
|
||||||
[bt]
|
[bt]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
# tokens: {alias}, {mac}, {left}, {right}, {anc}
|
# tokens: {alias}, {mac}, {left}, {right}, {anc}
|
||||||
format_plugin = "{alias} [{left}|{right}] {anc} "
|
format_plugin = "{alias} [{left}|{right}] {anc} "
|
||||||
format_connected = " {alias}"
|
format_connected = " {alias}"
|
||||||
@@ -77,21 +87,30 @@ format_disconnected = " Disconnected"
|
|||||||
format_disabled = " Off"
|
format_disabled = " Off"
|
||||||
|
|
||||||
[game]
|
[game]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
format_active = "<span size='large'></span>"
|
format_active = "<span size='large'></span>"
|
||||||
format_inactive = "<span size='large'></span>"
|
format_inactive = "<span size='large'></span>"
|
||||||
|
|
||||||
[mpris]
|
[mpris]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
|
# max_length = 30 # truncate text beyond this character length (adds '...')
|
||||||
|
# scroll = true # enable marquee scroll animation (requires max_length)
|
||||||
|
# scroll_speed = 500 # ms between scroll steps (only while playing)
|
||||||
|
# scroll_separator = " /// " # separator shown between loops when scrolling
|
||||||
# tokens: {artist}, {title}, {album}, {status_icon}
|
# tokens: {artist}, {title}, {album}, {status_icon}
|
||||||
format = "{status_icon} {artist} - {title}"
|
format = "{status_icon} {artist} - {title}"
|
||||||
|
|
||||||
[backlight]
|
[backlight]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
# tokens: {percentage}, {icon}
|
# tokens: {percentage}, {icon}
|
||||||
format = "{percentage:>3}% {icon}"
|
format = "{percentage:>3}% {icon}"
|
||||||
|
|
||||||
[keyboard]
|
[keyboard]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
# tokens: {layout}
|
# tokens: {layout}
|
||||||
format = "{layout}"
|
format = "{layout}"
|
||||||
|
|
||||||
[dnd]
|
[dnd]
|
||||||
|
# enabled = false # set to false to disable this module at runtime
|
||||||
format_dnd = "<span size='large'></span>"
|
format_dnd = "<span size='large'></span>"
|
||||||
format_normal = "<span size='large'></span>"
|
format_normal = "<span size='large'></span>"
|
||||||
|
|||||||
+107
@@ -91,12 +91,15 @@ pub struct SignalsConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct NetworkConfig {
|
pub struct NetworkConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format: String,
|
pub format: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NetworkConfig {
|
impl Default for NetworkConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format: "{interface} ({ip}): {rx:>5.2} MB/s {tx:>5.2} MB/s".to_string(),
|
format: "{interface} ({ip}): {rx:>5.2} MB/s {tx:>5.2} MB/s".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,12 +107,15 @@ impl Default for NetworkConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct CpuConfig {
|
pub struct CpuConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format: String,
|
pub format: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CpuConfig {
|
impl Default for CpuConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format: "CPU: {usage:>4.1}% {temp:>4.1}C".to_string(),
|
format: "CPU: {usage:>4.1}% {temp:>4.1}C".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,12 +123,15 @@ impl Default for CpuConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct MemoryConfig {
|
pub struct MemoryConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format: String,
|
pub format: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MemoryConfig {
|
impl Default for MemoryConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format: "{used:>5.2}/{total:>5.2}GB".to_string(),
|
format: "{used:>5.2}/{total:>5.2}GB".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,6 +139,8 @@ impl Default for MemoryConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct GpuConfig {
|
pub struct GpuConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format_amd: String,
|
pub format_amd: String,
|
||||||
pub format_intel: String,
|
pub format_intel: String,
|
||||||
pub format_nvidia: String,
|
pub format_nvidia: String,
|
||||||
@@ -138,6 +149,7 @@ pub struct GpuConfig {
|
|||||||
impl Default for GpuConfig {
|
impl Default for GpuConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format_amd: "AMD: {usage:>3.0}% {vram_used:>4.1}/{vram_total:>4.1}GB {temp:>4.1}C"
|
format_amd: "AMD: {usage:>3.0}% {vram_used:>4.1}/{vram_total:>4.1}GB {temp:>4.1}C"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
format_intel: "iGPU: {usage:>3.0}%".to_string(),
|
format_intel: "iGPU: {usage:>3.0}%".to_string(),
|
||||||
@@ -149,12 +161,15 @@ impl Default for GpuConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct SysConfig {
|
pub struct SysConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format: String,
|
pub format: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SysConfig {
|
impl Default for SysConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format: "UP: {uptime} | LOAD: {load1:>4.2} {load5:>4.2} {load15:>4.2}".to_string(),
|
format: "UP: {uptime} | LOAD: {load1:>4.2} {load5:>4.2} {load15:>4.2}".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,12 +177,15 @@ impl Default for SysConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct DiskConfig {
|
pub struct DiskConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format: String,
|
pub format: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DiskConfig {
|
impl Default for DiskConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format: "{mount} {used:>5.1}/{total:>5.1}G".to_string(),
|
format: "{mount} {used:>5.1}/{total:>5.1}G".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,12 +193,15 @@ impl Default for DiskConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct PoolConfig {
|
pub struct PoolConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format: String,
|
pub format: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PoolConfig {
|
impl Default for PoolConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format: "{used:>4.0}G / {total:>4.0}G".to_string(),
|
format: "{used:>4.0}G / {total:>4.0}G".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,12 +209,15 @@ impl Default for PoolConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct PowerConfig {
|
pub struct PowerConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format: String,
|
pub format: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PowerConfig {
|
impl Default for PowerConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format: "{percentage:>3}% {icon}".to_string(),
|
format: "{percentage:>3}% {icon}".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,6 +225,8 @@ impl Default for PowerConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct AudioConfig {
|
pub struct AudioConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format_sink_unmuted: String,
|
pub format_sink_unmuted: String,
|
||||||
pub format_sink_muted: String,
|
pub format_sink_muted: String,
|
||||||
pub format_source_unmuted: String,
|
pub format_source_unmuted: String,
|
||||||
@@ -210,6 +236,7 @@ pub struct AudioConfig {
|
|||||||
impl Default for AudioConfig {
|
impl Default for AudioConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format_sink_unmuted: "{name} {volume:>3}% {icon}".to_string(),
|
format_sink_unmuted: "{name} {volume:>3}% {icon}".to_string(),
|
||||||
format_sink_muted: "{name} {icon}".to_string(),
|
format_sink_muted: "{name} {icon}".to_string(),
|
||||||
format_source_unmuted: "{name} {volume:>3}% {icon}".to_string(),
|
format_source_unmuted: "{name} {volume:>3}% {icon}".to_string(),
|
||||||
@@ -220,6 +247,8 @@ impl Default for AudioConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct BtConfig {
|
pub struct BtConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format_connected: String,
|
pub format_connected: String,
|
||||||
pub format_plugin: String,
|
pub format_plugin: String,
|
||||||
pub format_disconnected: String,
|
pub format_disconnected: String,
|
||||||
@@ -229,6 +258,7 @@ pub struct BtConfig {
|
|||||||
impl Default for BtConfig {
|
impl Default for BtConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format_connected: "{alias} ".to_string(),
|
format_connected: "{alias} ".to_string(),
|
||||||
format_plugin: "{alias} [{left}|{right}] {anc} ".to_string(),
|
format_plugin: "{alias} [{left}|{right}] {anc} ".to_string(),
|
||||||
format_disconnected: "".to_string(),
|
format_disconnected: "".to_string(),
|
||||||
@@ -239,6 +269,8 @@ impl Default for BtConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct GameConfig {
|
pub struct GameConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format_active: String,
|
pub format_active: String,
|
||||||
pub format_inactive: String,
|
pub format_inactive: String,
|
||||||
}
|
}
|
||||||
@@ -246,6 +278,7 @@ pub struct GameConfig {
|
|||||||
impl Default for GameConfig {
|
impl Default for GameConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format_active: "<span size='large'></span>".to_string(),
|
format_active: "<span size='large'></span>".to_string(),
|
||||||
format_inactive: "<span size='large'></span>".to_string(),
|
format_inactive: "<span size='large'></span>".to_string(),
|
||||||
}
|
}
|
||||||
@@ -254,25 +287,51 @@ impl Default for GameConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct MprisConfig {
|
pub struct MprisConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format: String,
|
pub format: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub max_length: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub scroll: bool,
|
||||||
|
#[serde(default = "default_scroll_speed")]
|
||||||
|
pub scroll_speed: u64,
|
||||||
|
#[serde(default = "default_scroll_separator")]
|
||||||
|
pub scroll_separator: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_scroll_speed() -> u64 {
|
||||||
|
500
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_scroll_separator() -> String {
|
||||||
|
" /// ".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MprisConfig {
|
impl Default for MprisConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format: "{status_icon} {artist} - {title}".to_string(),
|
format: "{status_icon} {artist} - {title}".to_string(),
|
||||||
|
max_length: None,
|
||||||
|
scroll: false,
|
||||||
|
scroll_speed: 500,
|
||||||
|
scroll_separator: " /// ".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct BacklightConfig {
|
pub struct BacklightConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format: String,
|
pub format: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BacklightConfig {
|
impl Default for BacklightConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format: "{percentage:>3}% {icon}".to_string(),
|
format: "{percentage:>3}% {icon}".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,12 +339,15 @@ impl Default for BacklightConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct KeyboardConfig {
|
pub struct KeyboardConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format: String,
|
pub format: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for KeyboardConfig {
|
impl Default for KeyboardConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format: "{layout}".to_string(),
|
format: "{layout}".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,13 +355,20 @@ impl Default for KeyboardConfig {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct DndConfig {
|
pub struct DndConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
pub format_dnd: String,
|
pub format_dnd: String,
|
||||||
pub format_normal: String,
|
pub format_normal: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for DndConfig {
|
impl Default for DndConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
enabled: true,
|
||||||
format_dnd: "<span size='large'></span>".to_string(),
|
format_dnd: "<span size='large'></span>".to_string(),
|
||||||
format_normal: "<span size='large'></span>".to_string(),
|
format_normal: "<span size='large'></span>".to_string(),
|
||||||
}
|
}
|
||||||
@@ -325,6 +394,44 @@ fn validate_format(label: &str, format_str: &str, known_tokens: &[&str]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
/// Check if a module is enabled in the configuration.
|
||||||
|
/// Returns false if the module is explicitly disabled; true if enabled or unknown.
|
||||||
|
pub fn is_module_enabled(&self, module_name: &str) -> bool {
|
||||||
|
match module_name {
|
||||||
|
#[cfg(feature = "mod-network")]
|
||||||
|
"net" | "network" => self.network.enabled,
|
||||||
|
#[cfg(feature = "mod-hardware")]
|
||||||
|
"cpu" => self.cpu.enabled,
|
||||||
|
#[cfg(feature = "mod-hardware")]
|
||||||
|
"mem" | "memory" => self.memory.enabled,
|
||||||
|
#[cfg(feature = "mod-hardware")]
|
||||||
|
"gpu" => self.gpu.enabled,
|
||||||
|
#[cfg(feature = "mod-hardware")]
|
||||||
|
"sys" => self.sys.enabled,
|
||||||
|
#[cfg(feature = "mod-hardware")]
|
||||||
|
"disk" => self.disk.enabled,
|
||||||
|
#[cfg(feature = "mod-hardware")]
|
||||||
|
"pool" | "btrfs" => self.pool.enabled,
|
||||||
|
#[cfg(feature = "mod-hardware")]
|
||||||
|
"power" => self.power.enabled,
|
||||||
|
#[cfg(feature = "mod-hardware")]
|
||||||
|
"game" => self.game.enabled,
|
||||||
|
#[cfg(feature = "mod-audio")]
|
||||||
|
"vol" | "audio" | "mic" => self.audio.enabled,
|
||||||
|
#[cfg(feature = "mod-bt")]
|
||||||
|
"bt" | "bluetooth" => self.bt.enabled,
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
"mpris" => self.mpris.enabled,
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
"backlight" => self.backlight.enabled,
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
"kbd" | "keyboard" => self.keyboard.enabled,
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
"dnd" => self.dnd.enabled,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn validate(&self) {
|
pub fn validate(&self) {
|
||||||
#[cfg(feature = "mod-network")]
|
#[cfg(feature = "mod-network")]
|
||||||
validate_format(
|
validate_format(
|
||||||
|
|||||||
+72
-37
@@ -78,6 +78,10 @@ pub async fn run_daemon(config_path: Option<PathBuf>) -> Result<()> {
|
|||||||
let (keyboard_tx, keyboard_rx) = watch::channel(Default::default());
|
let (keyboard_tx, keyboard_rx) = watch::channel(Default::default());
|
||||||
#[cfg(feature = "mod-dbus")]
|
#[cfg(feature = "mod-dbus")]
|
||||||
let (dnd_tx, dnd_rx) = watch::channel(Default::default());
|
let (dnd_tx, dnd_rx) = watch::channel(Default::default());
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
let mpris_scroll = Arc::new(RwLock::new(crate::state::MprisScrollState::default()));
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
let (mpris_scroll_tick_tx, mpris_scroll_tick_rx) = watch::channel(0u64);
|
||||||
let health = Arc::new(RwLock::new(HashMap::new()));
|
let health = Arc::new(RwLock::new(HashMap::new()));
|
||||||
#[cfg(feature = "mod-bt")]
|
#[cfg(feature = "mod-bt")]
|
||||||
let (bt_force_tx, mut bt_force_rx) = mpsc::channel(1);
|
let (bt_force_tx, mut bt_force_rx) = mpsc::channel(1);
|
||||||
@@ -109,6 +113,10 @@ pub async fn run_daemon(config_path: Option<PathBuf>) -> Result<()> {
|
|||||||
keyboard: keyboard_rx,
|
keyboard: keyboard_rx,
|
||||||
#[cfg(feature = "mod-dbus")]
|
#[cfg(feature = "mod-dbus")]
|
||||||
dnd: dnd_rx,
|
dnd: dnd_rx,
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
mpris_scroll: Arc::clone(&mpris_scroll),
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
mpris_scroll_tick: mpris_scroll_tick_rx,
|
||||||
health: Arc::clone(&health),
|
health: Arc::clone(&health),
|
||||||
#[cfg(feature = "mod-bt")]
|
#[cfg(feature = "mod-bt")]
|
||||||
bt_force_poll: bt_force_tx,
|
bt_force_poll: bt_force_tx,
|
||||||
@@ -183,7 +191,7 @@ pub async fn run_daemon(config_path: Option<PathBuf>) -> Result<()> {
|
|||||||
|
|
||||||
// 1. Network Task
|
// 1. Network Task
|
||||||
#[cfg(feature = "mod-network")]
|
#[cfg(feature = "mod-network")]
|
||||||
{
|
if config.read().await.network.enabled {
|
||||||
let token = cancel_token.clone();
|
let token = cancel_token.clone();
|
||||||
let net_health = Arc::clone(&health);
|
let net_health = Arc::clone(&health);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
@@ -206,48 +214,58 @@ pub async fn run_daemon(config_path: Option<PathBuf>) -> Result<()> {
|
|||||||
// 2. Fast Hardware Task (CPU, Mem, Load)
|
// 2. Fast Hardware Task (CPU, Mem, Load)
|
||||||
#[cfg(feature = "mod-hardware")]
|
#[cfg(feature = "mod-hardware")]
|
||||||
{
|
{
|
||||||
let token = cancel_token.clone();
|
let cfg = config.read().await;
|
||||||
let hw_health = Arc::clone(&health);
|
let fast_enabled = cfg.cpu.enabled || cfg.memory.enabled || cfg.sys.enabled;
|
||||||
tokio::spawn(async move {
|
drop(cfg);
|
||||||
info!("Starting Fast Hardware polling task");
|
if fast_enabled {
|
||||||
let mut daemon = HardwareDaemon::new();
|
let token = cancel_token.clone();
|
||||||
loop {
|
let hw_health = Arc::clone(&health);
|
||||||
if !crate::health::is_poll_in_backoff("cpu", &hw_health).await {
|
tokio::spawn(async move {
|
||||||
daemon.poll_fast(&cpu_tx, &mem_tx, &sys_tx).await;
|
info!("Starting Fast Hardware polling task");
|
||||||
|
let mut daemon = HardwareDaemon::new();
|
||||||
|
loop {
|
||||||
|
if !crate::health::is_poll_in_backoff("cpu", &hw_health).await {
|
||||||
|
daemon.poll_fast(&cpu_tx, &mem_tx, &sys_tx).await;
|
||||||
|
}
|
||||||
|
tokio::select! {
|
||||||
|
_ = token.cancelled() => break,
|
||||||
|
_ = sleep(Duration::from_secs(1)) => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tokio::select! {
|
info!("Fast Hardware task shut down.");
|
||||||
_ = token.cancelled() => break,
|
});
|
||||||
_ = sleep(Duration::from_secs(1)) => {}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
info!("Fast Hardware task shut down.");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Slow Hardware Task (GPU, Disks)
|
// 3. Slow Hardware Task (GPU, Disks)
|
||||||
#[cfg(feature = "mod-hardware")]
|
#[cfg(feature = "mod-hardware")]
|
||||||
{
|
{
|
||||||
let token = cancel_token.clone();
|
let cfg = config.read().await;
|
||||||
let slow_health = Arc::clone(&health);
|
let slow_enabled = cfg.gpu.enabled || cfg.disk.enabled;
|
||||||
tokio::spawn(async move {
|
drop(cfg);
|
||||||
info!("Starting Slow Hardware polling task");
|
if slow_enabled {
|
||||||
let mut daemon = HardwareDaemon::new();
|
let token = cancel_token.clone();
|
||||||
loop {
|
let slow_health = Arc::clone(&health);
|
||||||
if !crate::health::is_poll_in_backoff("gpu", &slow_health).await {
|
tokio::spawn(async move {
|
||||||
daemon.poll_slow(&gpu_tx, &disks_tx).await;
|
info!("Starting Slow Hardware polling task");
|
||||||
|
let mut daemon = HardwareDaemon::new();
|
||||||
|
loop {
|
||||||
|
if !crate::health::is_poll_in_backoff("gpu", &slow_health).await {
|
||||||
|
daemon.poll_slow(&gpu_tx, &disks_tx).await;
|
||||||
|
}
|
||||||
|
tokio::select! {
|
||||||
|
_ = token.cancelled() => break,
|
||||||
|
_ = sleep(Duration::from_secs(5)) => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tokio::select! {
|
info!("Slow Hardware task shut down.");
|
||||||
_ = token.cancelled() => break,
|
});
|
||||||
_ = sleep(Duration::from_secs(5)) => {}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
info!("Slow Hardware task shut down.");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Bluetooth Task
|
// 4. Bluetooth Task
|
||||||
#[cfg(feature = "mod-bt")]
|
#[cfg(feature = "mod-bt")]
|
||||||
{
|
if config.read().await.bt.enabled {
|
||||||
let token = cancel_token.clone();
|
let token = cancel_token.clone();
|
||||||
let bt_health = Arc::clone(&health);
|
let bt_health = Arc::clone(&health);
|
||||||
let poll_config = Arc::clone(&config);
|
let poll_config = Arc::clone(&config);
|
||||||
@@ -272,37 +290,51 @@ pub async fn run_daemon(config_path: Option<PathBuf>) -> Result<()> {
|
|||||||
|
|
||||||
// 5. Audio Thread (Event driven)
|
// 5. Audio Thread (Event driven)
|
||||||
#[cfg(feature = "mod-audio")]
|
#[cfg(feature = "mod-audio")]
|
||||||
{
|
if config.read().await.audio.enabled {
|
||||||
let audio_daemon = AudioDaemon::new();
|
let audio_daemon = AudioDaemon::new();
|
||||||
audio_daemon.start(&audio_tx, audio_cmd_rx);
|
audio_daemon.start(&audio_tx, audio_cmd_rx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5.1 Backlight Thread (Event driven)
|
// 5.1 Backlight Thread (Event driven)
|
||||||
#[cfg(feature = "mod-dbus")]
|
#[cfg(feature = "mod-dbus")]
|
||||||
{
|
if config.read().await.backlight.enabled {
|
||||||
let backlight_daemon = BacklightDaemon::new();
|
let backlight_daemon = BacklightDaemon::new();
|
||||||
backlight_daemon.start(backlight_tx);
|
backlight_daemon.start(backlight_tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5.2 Keyboard Thread (Event driven)
|
// 5.2 Keyboard Thread (Event driven)
|
||||||
#[cfg(feature = "mod-dbus")]
|
#[cfg(feature = "mod-dbus")]
|
||||||
{
|
if config.read().await.keyboard.enabled {
|
||||||
let keyboard_daemon = KeyboardDaemon::new();
|
let keyboard_daemon = KeyboardDaemon::new();
|
||||||
keyboard_daemon.start(keyboard_tx);
|
keyboard_daemon.start(keyboard_tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5.3 DND Thread (Event driven)
|
// 5.3 DND Thread (Event driven)
|
||||||
#[cfg(feature = "mod-dbus")]
|
#[cfg(feature = "mod-dbus")]
|
||||||
{
|
if config.read().await.dnd.enabled {
|
||||||
let dnd_daemon = DndDaemon::new();
|
let dnd_daemon = DndDaemon::new();
|
||||||
dnd_daemon.start(dnd_tx);
|
dnd_daemon.start(dnd_tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5.4 MPRIS Thread
|
// 5.4 MPRIS Thread
|
||||||
#[cfg(feature = "mod-dbus")]
|
#[cfg(feature = "mod-dbus")]
|
||||||
{
|
if config.read().await.mpris.enabled {
|
||||||
let mpris_daemon = MprisDaemon::new();
|
let mpris_daemon = MprisDaemon::new();
|
||||||
mpris_daemon.start(mpris_tx);
|
mpris_daemon.start(mpris_tx);
|
||||||
|
|
||||||
|
// Scroll ticker for MPRIS marquee animation
|
||||||
|
let scroll_config = Arc::clone(&config);
|
||||||
|
let scroll_rx = receivers.mpris.clone();
|
||||||
|
let scroll_state = Arc::clone(&mpris_scroll);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
crate::modules::mpris::mpris_scroll_ticker(
|
||||||
|
scroll_config,
|
||||||
|
scroll_rx,
|
||||||
|
scroll_state,
|
||||||
|
mpris_scroll_tick_tx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Waybar Signaler Task
|
// 6. Waybar Signaler Task
|
||||||
@@ -416,6 +448,9 @@ async fn handle_request(
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(output) => serde_json::to_string(&output).unwrap_or_else(|_| "{}".to_string()),
|
Ok(output) => serde_json::to_string(&output).unwrap_or_else(|_| "{}".to_string()),
|
||||||
|
Err(crate::error::FluxoError::Disabled(_)) => {
|
||||||
|
"{\"text\":\"\",\"tooltip\":\"Module disabled\",\"class\":\"disabled\"}".to_string()
|
||||||
|
}
|
||||||
Err(e) => crate::health::error_response(module_name, &e, cached_output),
|
Err(e) => crate::health::error_response(module_name, &e, cached_output),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ pub enum FluxoError {
|
|||||||
#[error("Serialization error: {0}")]
|
#[error("Serialization error: {0}")]
|
||||||
Serialization(#[from] serde_json::Error),
|
Serialization(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("Module disabled: {0}")]
|
||||||
|
Disabled(String),
|
||||||
|
|
||||||
#[error("Other error: {0}")]
|
#[error("Other error: {0}")]
|
||||||
Other(#[from] anyhow::Error),
|
Other(#[from] anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ pub async fn update_health(
|
|||||||
health.backoff_until = None;
|
health.backoff_until = None;
|
||||||
health.last_successful_output = Some(output.clone());
|
health.last_successful_output = Some(output.clone());
|
||||||
}
|
}
|
||||||
|
Err(crate::error::FluxoError::Disabled(_)) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
health.consecutive_failures += 1;
|
health.consecutive_failures += 1;
|
||||||
health.last_failure = Some(Instant::now());
|
health.last_failure = Some(Instant::now());
|
||||||
|
|||||||
+48
-48
@@ -29,19 +29,19 @@ impl WaybarModule for DndModule {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Try toggling SwayNC
|
// Try toggling SwayNC
|
||||||
if let Ok(proxy) = SwayncControlProxy::new(&connection).await {
|
if let Ok(proxy) = SwayncControlProxy::new(&connection).await
|
||||||
if let Ok(is_dnd) = proxy.dnd().await {
|
&& let Ok(is_dnd) = proxy.dnd().await
|
||||||
let _ = proxy.set_dnd(!is_dnd).await;
|
{
|
||||||
return Ok(WaybarOutput::default());
|
let _ = proxy.set_dnd(!is_dnd).await;
|
||||||
}
|
return Ok(WaybarOutput::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try toggling Dunst
|
// Try toggling Dunst
|
||||||
if let Ok(proxy) = DunstControlProxy::new(&connection).await {
|
if let Ok(proxy) = DunstControlProxy::new(&connection).await
|
||||||
if let Ok(is_paused) = proxy.paused().await {
|
&& let Ok(is_paused) = proxy.paused().await
|
||||||
let _ = proxy.set_paused(!is_paused).await;
|
{
|
||||||
return Ok(WaybarOutput::default());
|
let _ = proxy.set_paused(!is_paused).await;
|
||||||
}
|
return Ok(WaybarOutput::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(crate::error::FluxoError::Module {
|
return Err(crate::error::FluxoError::Module {
|
||||||
@@ -118,52 +118,52 @@ impl DndDaemon {
|
|||||||
info!("Connected to D-Bus for DND monitoring");
|
info!("Connected to D-Bus for DND monitoring");
|
||||||
|
|
||||||
// Try SwayNC
|
// Try SwayNC
|
||||||
if let Ok(proxy) = SwayncControlProxy::new(&connection).await {
|
if let Ok(proxy) = SwayncControlProxy::new(&connection).await
|
||||||
if let Ok(is_dnd) = proxy.dnd().await {
|
&& let Ok(is_dnd) = proxy.dnd().await
|
||||||
debug!("Found SwayNC, using it for DND state.");
|
{
|
||||||
let _ = tx.send(DndState { is_dnd });
|
debug!("Found SwayNC, using it for DND state.");
|
||||||
|
let _ = tx.send(DndState { is_dnd });
|
||||||
|
|
||||||
if let Ok(props_proxy) = PropertiesProxy::builder(&connection)
|
if let Ok(props_proxy) = PropertiesProxy::builder(&connection)
|
||||||
.destination("org.erikreider.swaync.control")?
|
.destination("org.erikreider.swaync.control")?
|
||||||
.path("/org/erikreider/swaync/control")?
|
.path("/org/erikreider/swaync/control")?
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
let mut stream = props_proxy.receive_properties_changed().await?;
|
let mut stream = props_proxy.receive_properties_changed().await?;
|
||||||
while let Some(signal) = stream.next().await {
|
while let Some(signal) = stream.next().await {
|
||||||
let args = signal.args()?;
|
let args = signal.args()?;
|
||||||
if args.interface_name == "org.erikreider.swaync.control"
|
if args.interface_name == "org.erikreider.swaync.control"
|
||||||
&& let Some(val) = args.changed_properties.get("dnd")
|
&& let Some(val) = args.changed_properties.get("dnd")
|
||||||
&& let Ok(is_dnd) = bool::try_from(val)
|
&& let Ok(is_dnd) = bool::try_from(val)
|
||||||
{
|
{
|
||||||
let _ = tx.send(DndState { is_dnd });
|
let _ = tx.send(DndState { is_dnd });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try Dunst
|
// Try Dunst
|
||||||
if let Ok(proxy) = DunstControlProxy::new(&connection).await {
|
if let Ok(proxy) = DunstControlProxy::new(&connection).await
|
||||||
if let Ok(is_dnd) = proxy.paused().await {
|
&& let Ok(is_dnd) = proxy.paused().await
|
||||||
debug!("Found Dunst, using it for DND state.");
|
{
|
||||||
let _ = tx.send(DndState { is_dnd });
|
debug!("Found Dunst, using it for DND state.");
|
||||||
|
let _ = tx.send(DndState { is_dnd });
|
||||||
|
|
||||||
if let Ok(props_proxy) = PropertiesProxy::builder(&connection)
|
if let Ok(props_proxy) = PropertiesProxy::builder(&connection)
|
||||||
.destination("org.freedesktop.Notifications")?
|
.destination("org.freedesktop.Notifications")?
|
||||||
.path("/org/freedesktop/Notifications")?
|
.path("/org/freedesktop/Notifications")?
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
let mut stream = props_proxy.receive_properties_changed().await?;
|
let mut stream = props_proxy.receive_properties_changed().await?;
|
||||||
while let Some(signal) = stream.next().await {
|
while let Some(signal) = stream.next().await {
|
||||||
let args = signal.args()?;
|
let args = signal.args()?;
|
||||||
if args.interface_name == "org.dunstproject.cmd0"
|
if args.interface_name == "org.dunstproject.cmd0"
|
||||||
&& let Some(val) = args.changed_properties.get("paused")
|
&& let Some(val) = args.changed_properties.get("paused")
|
||||||
&& let Ok(is_dnd) = bool::try_from(val)
|
&& let Ok(is_dnd) = bool::try_from(val)
|
||||||
{
|
{
|
||||||
let _ = tx.send(DndState { is_dnd });
|
let _ = tx.send(DndState { is_dnd });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+120
-24
@@ -2,12 +2,66 @@ use crate::config::Config;
|
|||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::modules::WaybarModule;
|
use crate::modules::WaybarModule;
|
||||||
use crate::output::WaybarOutput;
|
use crate::output::WaybarOutput;
|
||||||
use crate::state::{AppReceivers, MprisState};
|
use crate::state::{AppReceivers, MprisScrollState, MprisState};
|
||||||
use crate::utils::{TokenValue, format_template};
|
use crate::utils::{TokenValue, format_template};
|
||||||
use tokio::sync::watch;
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::{RwLock, watch};
|
||||||
|
use tokio::time::Duration;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
use zbus::{Connection, proxy};
|
use zbus::{Connection, proxy};
|
||||||
|
|
||||||
|
fn format_mpris_text(format: &str, mpris: &MprisState) -> (String, &'static str) {
|
||||||
|
let status_icon = if mpris.is_playing {
|
||||||
|
""
|
||||||
|
} else if mpris.is_paused {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
let class = if mpris.is_playing {
|
||||||
|
"playing"
|
||||||
|
} else if mpris.is_paused {
|
||||||
|
"paused"
|
||||||
|
} else {
|
||||||
|
"stopped"
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = format_template(
|
||||||
|
format,
|
||||||
|
&[
|
||||||
|
("artist", TokenValue::String(mpris.artist.clone())),
|
||||||
|
("title", TokenValue::String(mpris.title.clone())),
|
||||||
|
("album", TokenValue::String(mpris.album.clone())),
|
||||||
|
("status_icon", TokenValue::String(status_icon.to_string())),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
(text, class)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_scroll_window(full_text: &str, max_len: usize, offset: usize, separator: &str) -> String {
|
||||||
|
let char_count = full_text.chars().count();
|
||||||
|
let total_len = char_count + separator.chars().count();
|
||||||
|
let offset = offset % total_len;
|
||||||
|
full_text
|
||||||
|
.chars()
|
||||||
|
.chain(separator.chars())
|
||||||
|
.cycle()
|
||||||
|
.skip(offset)
|
||||||
|
.take(max_len)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate_with_ellipsis(text: &str, max_len: usize) -> String {
|
||||||
|
let char_count = text.chars().count();
|
||||||
|
if char_count <= max_len {
|
||||||
|
return text.to_string();
|
||||||
|
}
|
||||||
|
let truncated: String = text.chars().take(max_len.saturating_sub(3)).collect();
|
||||||
|
format!("{}...", truncated)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MprisModule;
|
pub struct MprisModule;
|
||||||
|
|
||||||
impl WaybarModule for MprisModule {
|
impl WaybarModule for MprisModule {
|
||||||
@@ -28,32 +82,26 @@ impl WaybarModule for MprisModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let status_icon = if mpris.is_playing {
|
let (full_text, class) = format_mpris_text(&config.mpris.format, &mpris);
|
||||||
""
|
|
||||||
} else if mpris.is_paused {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
let class = if mpris.is_playing {
|
let text = if config.mpris.scroll {
|
||||||
"playing"
|
if let Some(max_len) = config.mpris.max_length {
|
||||||
} else if mpris.is_paused {
|
let scroll = state.mpris_scroll.read().await;
|
||||||
"paused"
|
apply_scroll_window(
|
||||||
|
&full_text,
|
||||||
|
max_len,
|
||||||
|
scroll.offset,
|
||||||
|
&config.mpris.scroll_separator,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
full_text.clone()
|
||||||
|
}
|
||||||
|
} else if let Some(max_len) = config.mpris.max_length {
|
||||||
|
truncate_with_ellipsis(&full_text, max_len)
|
||||||
} else {
|
} else {
|
||||||
"stopped"
|
full_text.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let text = format_template(
|
|
||||||
&config.mpris.format,
|
|
||||||
&[
|
|
||||||
("artist", TokenValue::String(mpris.artist.clone())),
|
|
||||||
("title", TokenValue::String(mpris.title.clone())),
|
|
||||||
("album", TokenValue::String(mpris.album.clone())),
|
|
||||||
("status_icon", TokenValue::String(status_icon.to_string())),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(WaybarOutput {
|
Ok(WaybarOutput {
|
||||||
text,
|
text,
|
||||||
tooltip: Some(format!("{} - {}", mpris.artist, mpris.title)),
|
tooltip: Some(format!("{} - {}", mpris.artist, mpris.title)),
|
||||||
@@ -63,6 +111,54 @@ impl WaybarModule for MprisModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn mpris_scroll_ticker(
|
||||||
|
config: Arc<RwLock<Config>>,
|
||||||
|
mut mpris_rx: watch::Receiver<MprisState>,
|
||||||
|
scroll_state: Arc<RwLock<MprisScrollState>>,
|
||||||
|
tick_tx: watch::Sender<u64>,
|
||||||
|
) {
|
||||||
|
let mut generation: u64 = 0;
|
||||||
|
let mut last_track_key = String::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mpris = mpris_rx.borrow_and_update().clone();
|
||||||
|
let cfg = config.read().await;
|
||||||
|
let scroll_enabled = cfg.mpris.scroll;
|
||||||
|
let has_max_length = cfg.mpris.max_length.is_some();
|
||||||
|
let scroll_speed = cfg.mpris.scroll_speed;
|
||||||
|
let format_str = cfg.mpris.format.clone();
|
||||||
|
drop(cfg);
|
||||||
|
|
||||||
|
let (full_text, _) = format_mpris_text(&format_str, &mpris);
|
||||||
|
let track_key = format!("{}|{}|{}", mpris.artist, mpris.title, mpris.album);
|
||||||
|
|
||||||
|
if track_key != last_track_key {
|
||||||
|
let mut state = scroll_state.write().await;
|
||||||
|
state.offset = 0;
|
||||||
|
state.full_text = full_text.clone();
|
||||||
|
last_track_key = track_key;
|
||||||
|
generation += 1;
|
||||||
|
let _ = tick_tx.send(generation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if scroll_enabled && has_max_length && mpris.is_playing {
|
||||||
|
tokio::time::sleep(Duration::from_millis(scroll_speed)).await;
|
||||||
|
let mut state = scroll_state.write().await;
|
||||||
|
state.offset += 1;
|
||||||
|
state.full_text = full_text;
|
||||||
|
drop(state);
|
||||||
|
generation += 1;
|
||||||
|
let _ = tick_tx.send(generation);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not scrolling — wait for next state change
|
||||||
|
if mpris_rx.changed().await.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MprisDaemon;
|
pub struct MprisDaemon;
|
||||||
|
|
||||||
#[proxy(
|
#[proxy(
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ pub async fn dispatch(
|
|||||||
#[allow(unused)] state: &AppReceivers,
|
#[allow(unused)] state: &AppReceivers,
|
||||||
#[allow(unused)] args: &[&str],
|
#[allow(unused)] args: &[&str],
|
||||||
) -> FluxoResult<WaybarOutput> {
|
) -> FluxoResult<WaybarOutput> {
|
||||||
|
if !config.is_module_enabled(module_name) {
|
||||||
|
return Err(FluxoError::Disabled(module_name.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
match module_name {
|
match module_name {
|
||||||
#[cfg(feature = "mod-network")]
|
#[cfg(feature = "mod-network")]
|
||||||
"net" | "network" => {
|
"net" | "network" => {
|
||||||
|
|||||||
@@ -175,6 +175,13 @@ impl WaybarSignaler {
|
|||||||
#[cfg(feature = "mod-dbus")]
|
#[cfg(feature = "mod-dbus")]
|
||||||
let mpris_changed = receivers.mpris.changed();
|
let mpris_changed = receivers.mpris.changed();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "mod-dbus"))]
|
||||||
|
let mpris_scroll_tick_changed = std::future::pending::<
|
||||||
|
std::result::Result<(), tokio::sync::watch::error::RecvError>,
|
||||||
|
>();
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
let mpris_scroll_tick_changed = receivers.mpris_scroll_tick.changed();
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
res = net_changed, if signals.network.is_some() => {
|
res = net_changed, if signals.network.is_some() => {
|
||||||
if res.is_ok() { check_and_signal!("net", signals.network); }
|
if res.is_ok() { check_and_signal!("net", signals.network); }
|
||||||
@@ -215,6 +222,10 @@ impl WaybarSignaler {
|
|||||||
res = mpris_changed, if signals.mpris.is_some() => {
|
res = mpris_changed, if signals.mpris.is_some() => {
|
||||||
if res.is_ok() { check_and_signal!("mpris", signals.mpris); }
|
if res.is_ok() { check_and_signal!("mpris", signals.mpris); }
|
||||||
}
|
}
|
||||||
|
res = mpris_scroll_tick_changed, if signals.mpris.is_some() => {
|
||||||
|
if res.is_ok()
|
||||||
|
&& let Some(sig) = signals.mpris { self.send_signal(sig); }
|
||||||
|
}
|
||||||
_ = sleep(Duration::from_secs(5)) => {
|
_ = sleep(Duration::from_secs(5)) => {
|
||||||
// loop and refresh config
|
// loop and refresh config
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ pub struct AppReceivers {
|
|||||||
pub keyboard: watch::Receiver<KeyboardState>,
|
pub keyboard: watch::Receiver<KeyboardState>,
|
||||||
#[cfg(feature = "mod-dbus")]
|
#[cfg(feature = "mod-dbus")]
|
||||||
pub dnd: watch::Receiver<DndState>,
|
pub dnd: watch::Receiver<DndState>,
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
pub mpris_scroll: Arc<RwLock<MprisScrollState>>,
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
pub mpris_scroll_tick: watch::Receiver<u64>,
|
||||||
pub health: Arc<RwLock<HashMap<String, ModuleHealth>>>,
|
pub health: Arc<RwLock<HashMap<String, ModuleHealth>>>,
|
||||||
#[cfg(feature = "mod-bt")]
|
#[cfg(feature = "mod-bt")]
|
||||||
pub bt_force_poll: mpsc::Sender<()>,
|
pub bt_force_poll: mpsc::Sender<()>,
|
||||||
@@ -169,6 +173,12 @@ pub struct BacklightState {
|
|||||||
pub percentage: u8,
|
pub percentage: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct MprisScrollState {
|
||||||
|
pub offset: usize,
|
||||||
|
pub full_text: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct MprisState {
|
pub struct MprisState {
|
||||||
pub is_playing: bool,
|
pub is_playing: bool,
|
||||||
@@ -295,6 +305,13 @@ pub fn mock_state(state: AppState) -> MockState {
|
|||||||
keyboard: keyboard_rx,
|
keyboard: keyboard_rx,
|
||||||
#[cfg(feature = "mod-dbus")]
|
#[cfg(feature = "mod-dbus")]
|
||||||
dnd: dnd_rx,
|
dnd: dnd_rx,
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
mpris_scroll: Arc::new(RwLock::new(MprisScrollState::default())),
|
||||||
|
#[cfg(feature = "mod-dbus")]
|
||||||
|
mpris_scroll_tick: {
|
||||||
|
let (_, rx) = watch::channel(0u64);
|
||||||
|
rx
|
||||||
|
},
|
||||||
health: Arc::new(RwLock::new(state.health)),
|
health: Arc::new(RwLock::new(state.health)),
|
||||||
#[cfg(feature = "mod-bt")]
|
#[cfg(feature = "mod-bt")]
|
||||||
bt_force_poll: bt_force_tx,
|
bt_force_poll: bt_force_tx,
|
||||||
|
|||||||
Reference in New Issue
Block a user