added power, pixelbuds and gamemode support
This commit is contained in:
@@ -86,6 +86,9 @@ fn handle_request(module_name: &str, args: &[&str], state: &SharedState, config:
|
|||||||
"gpu" => crate::modules::gpu::GpuModule.run(config, state, args),
|
"gpu" => crate::modules::gpu::GpuModule.run(config, state, args),
|
||||||
"sys" => crate::modules::sys::SysModule.run(config, state, args),
|
"sys" => crate::modules::sys::SysModule.run(config, state, args),
|
||||||
"bt" | "bluetooth" => crate::modules::bt::BtModule.run(config, state, args),
|
"bt" | "bluetooth" => crate::modules::bt::BtModule.run(config, state, args),
|
||||||
|
"buds" => crate::modules::buds::BudsModule.run(config, state, args),
|
||||||
|
"power" => crate::modules::power::PowerModule.run(config, state, args),
|
||||||
|
"game" => crate::modules::game::GameModule.run(config, state, args),
|
||||||
_ => {
|
_ => {
|
||||||
warn!("Received request for unknown module: '{}'", module_name);
|
warn!("Received request for unknown module: '{}'", module_name);
|
||||||
Err(anyhow::anyhow!("Unknown module: {}", module_name))
|
Err(anyhow::anyhow!("Unknown module: {}", module_name))
|
||||||
|
|||||||
18
src/main.rs
18
src/main.rs
@@ -63,6 +63,15 @@ enum Commands {
|
|||||||
#[arg(default_value = "show")]
|
#[arg(default_value = "show")]
|
||||||
action: String,
|
action: String,
|
||||||
},
|
},
|
||||||
|
/// Pixel Buds Pro ANC and Battery
|
||||||
|
Buds {
|
||||||
|
#[arg(default_value = "show")]
|
||||||
|
action: String,
|
||||||
|
},
|
||||||
|
/// System power and battery status
|
||||||
|
Power,
|
||||||
|
/// Hyprland gamemode status
|
||||||
|
Game,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -114,6 +123,15 @@ fn main() {
|
|||||||
Commands::Bt { action } => {
|
Commands::Bt { action } => {
|
||||||
handle_ipc_response(ipc::request_data("bt", &[action.clone()]));
|
handle_ipc_response(ipc::request_data("bt", &[action.clone()]));
|
||||||
}
|
}
|
||||||
|
Commands::Buds { action } => {
|
||||||
|
handle_ipc_response(ipc::request_data("buds", &[action.clone()]));
|
||||||
|
}
|
||||||
|
Commands::Power => {
|
||||||
|
handle_ipc_response(ipc::request_data("power", &[]));
|
||||||
|
}
|
||||||
|
Commands::Game => {
|
||||||
|
handle_ipc_response(ipc::request_data("game", &[]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
123
src/modules/buds.rs
Normal file
123
src/modules/buds.rs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
use crate::config::Config;
|
||||||
|
use crate::modules::WaybarModule;
|
||||||
|
use crate::output::WaybarOutput;
|
||||||
|
use crate::state::SharedState;
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
pub struct BudsModule;
|
||||||
|
|
||||||
|
const MAC_ADDRESS: &str = "B4:23:A2:09:D3:53";
|
||||||
|
|
||||||
|
impl WaybarModule for BudsModule {
|
||||||
|
fn run(&self, _config: &Config, _state: &SharedState, args: &[&str]) -> Result<WaybarOutput> {
|
||||||
|
let action = args.first().unwrap_or(&"show");
|
||||||
|
|
||||||
|
match *action {
|
||||||
|
"cycle_anc" => {
|
||||||
|
let output = Command::new("pbpctrl").args(["get", "anc"]).output()?;
|
||||||
|
let current_mode = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||||
|
|
||||||
|
let next_mode = match current_mode.as_str() {
|
||||||
|
"active" => "aware",
|
||||||
|
"aware" => "off",
|
||||||
|
_ => "active", // default or off goes to active
|
||||||
|
};
|
||||||
|
|
||||||
|
Command::new("pbpctrl").args(["set", "anc", next_mode]).status()?;
|
||||||
|
return Ok(WaybarOutput {
|
||||||
|
text: String::new(),
|
||||||
|
tooltip: None,
|
||||||
|
class: None,
|
||||||
|
percentage: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
"connect" => {
|
||||||
|
Command::new("bluetoothctl").args(["connect", MAC_ADDRESS]).status()?;
|
||||||
|
return Ok(WaybarOutput {
|
||||||
|
text: String::new(),
|
||||||
|
tooltip: None,
|
||||||
|
class: None,
|
||||||
|
percentage: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
"disconnect" => {
|
||||||
|
Command::new("bluetoothctl").args(["disconnect", MAC_ADDRESS]).status()?;
|
||||||
|
return Ok(WaybarOutput {
|
||||||
|
text: String::new(),
|
||||||
|
tooltip: None,
|
||||||
|
class: None,
|
||||||
|
percentage: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
"show" | _ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if connected
|
||||||
|
let bt_info = Command::new("bluetoothctl").args(["info", MAC_ADDRESS]).output()?;
|
||||||
|
let bt_str = String::from_utf8_lossy(&bt_info.stdout);
|
||||||
|
|
||||||
|
if !bt_str.contains("Connected: yes") {
|
||||||
|
return Ok(WaybarOutput {
|
||||||
|
text: "<span size='large'></span>".to_string(),
|
||||||
|
tooltip: Some("Pixel Buds Pro 2 not connected".to_string()),
|
||||||
|
class: Some("disconnected".to_string()),
|
||||||
|
percentage: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get battery output
|
||||||
|
let bat_cmd = Command::new("pbpctrl").args(["show", "battery"]).output();
|
||||||
|
if bat_cmd.is_err() || !bat_cmd.as_ref().unwrap().status.success() {
|
||||||
|
return Ok(WaybarOutput {
|
||||||
|
text: "<span size='large'></span>".to_string(),
|
||||||
|
tooltip: Some("Pixel Buds Pro 2 connected (No Data)".to_string()),
|
||||||
|
class: Some("disconnected".to_string()),
|
||||||
|
percentage: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let bat_result = bat_cmd.unwrap();
|
||||||
|
let bat_output = String::from_utf8_lossy(&bat_result.stdout);
|
||||||
|
let mut left_bud = "unknown";
|
||||||
|
let mut right_bud = "unknown";
|
||||||
|
|
||||||
|
for line in bat_output.lines() {
|
||||||
|
if line.contains("left bud:") {
|
||||||
|
left_bud = line.split_whitespace().nth(2).unwrap_or("unknown");
|
||||||
|
} else if line.contains("right bud:") {
|
||||||
|
right_bud = line.split_whitespace().nth(2).unwrap_or("unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if left_bud == "unknown" && right_bud == "unknown" {
|
||||||
|
return Ok(WaybarOutput {
|
||||||
|
text: "{}".to_string(),
|
||||||
|
tooltip: None,
|
||||||
|
class: None,
|
||||||
|
percentage: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let left_display = if left_bud == "unknown" { "L: ---".to_string() } else { format!("L: {}", left_bud) };
|
||||||
|
let right_display = if right_bud == "unknown" { "R: ---".to_string() } else { format!("R: {}", right_bud) };
|
||||||
|
|
||||||
|
// Get ANC info
|
||||||
|
let anc_cmd = Command::new("pbpctrl").args(["get", "anc"]).output()?;
|
||||||
|
let current_mode = String::from_utf8_lossy(&anc_cmd.stdout).trim().to_string();
|
||||||
|
|
||||||
|
let (anc_icon, class) = match current_mode.as_str() {
|
||||||
|
"active" => ("ANC", "anc-active"),
|
||||||
|
"aware" => ("Aware", "anc-aware"),
|
||||||
|
"off" => ("Off", "anc-off"),
|
||||||
|
_ => ("?", "anc-unknown"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(WaybarOutput {
|
||||||
|
text: format!("{} | {} | {}", left_display, right_display, anc_icon),
|
||||||
|
tooltip: Some("Pixel Buds Pro 2".to_string()),
|
||||||
|
class: Some(class.to_string()),
|
||||||
|
percentage: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/modules/game.rs
Normal file
45
src/modules/game.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
use crate::config::Config;
|
||||||
|
use crate::modules::WaybarModule;
|
||||||
|
use crate::output::WaybarOutput;
|
||||||
|
use crate::state::SharedState;
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
pub struct GameModule;
|
||||||
|
|
||||||
|
impl WaybarModule for GameModule {
|
||||||
|
fn run(&self, _config: &Config, _state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
|
||||||
|
let output = Command::new("hyprctl")
|
||||||
|
.args(["getoption", "animations:enabled", "-j"])
|
||||||
|
.output();
|
||||||
|
|
||||||
|
let mut is_gamemode = false; // default to deactivated
|
||||||
|
|
||||||
|
if let Ok(out) = output {
|
||||||
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
|
|
||||||
|
// The JSON from hyprctl looks like {"int": 0, "float": 0.0, ...}
|
||||||
|
// If int is 0, animations are disabled (Gamemode active)
|
||||||
|
// If int is 1, animations are enabled (Gamemode deactivated)
|
||||||
|
if stdout.contains("\"int\": 0") {
|
||||||
|
is_gamemode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_gamemode {
|
||||||
|
Ok(WaybarOutput {
|
||||||
|
text: "<span size='large'></span>".to_string(),
|
||||||
|
tooltip: Some("Gamemode activated".to_string()),
|
||||||
|
class: Some("active".to_string()),
|
||||||
|
percentage: None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(WaybarOutput {
|
||||||
|
text: "<span size='large'></span>".to_string(),
|
||||||
|
tooltip: Some("Gamemode deactivated".to_string()),
|
||||||
|
class: None,
|
||||||
|
percentage: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ pub mod audio;
|
|||||||
pub mod gpu;
|
pub mod gpu;
|
||||||
pub mod sys;
|
pub mod sys;
|
||||||
pub mod bt;
|
pub mod bt;
|
||||||
|
pub mod buds;
|
||||||
|
pub mod power;
|
||||||
|
pub mod game;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::output::WaybarOutput;
|
use crate::output::WaybarOutput;
|
||||||
|
|||||||
100
src/modules/power.rs
Normal file
100
src/modules/power.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
use crate::config::Config;
|
||||||
|
use crate::modules::WaybarModule;
|
||||||
|
use crate::output::WaybarOutput;
|
||||||
|
use crate::state::SharedState;
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
pub struct PowerModule;
|
||||||
|
|
||||||
|
impl WaybarModule for PowerModule {
|
||||||
|
fn run(&self, _config: &Config, _state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
|
||||||
|
let critical_threshold = 15;
|
||||||
|
let warning_threshold = 50;
|
||||||
|
|
||||||
|
// Find the first battery
|
||||||
|
let mut battery_path = None;
|
||||||
|
if let Ok(entries) = fs::read_dir("/sys/class/power_supply") {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let name = entry.file_name().to_string_lossy().to_string();
|
||||||
|
if name.starts_with("BAT") {
|
||||||
|
battery_path = Some(entry.path());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check AC status as fallback or TLP proxy
|
||||||
|
let mut ac_online = false;
|
||||||
|
if let Ok(entries) = fs::read_dir("/sys/class/power_supply") {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let name = entry.file_name().to_string_lossy().to_string();
|
||||||
|
if name.starts_with("AC") || name.starts_with("ADP") {
|
||||||
|
let online_path = entry.path().join("online");
|
||||||
|
if let Ok(online_str) = fs::read_to_string(online_path) {
|
||||||
|
if online_str.trim() == "1" {
|
||||||
|
ac_online = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(bat_path) = battery_path else {
|
||||||
|
if ac_online {
|
||||||
|
return Ok(WaybarOutput {
|
||||||
|
text: "".to_string(),
|
||||||
|
tooltip: Some("AC Power (No Battery)".to_string()),
|
||||||
|
class: Some("ac".to_string()),
|
||||||
|
percentage: None,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Ok(WaybarOutput {
|
||||||
|
text: "".to_string(),
|
||||||
|
tooltip: Some("Error: Battery not found".to_string()),
|
||||||
|
class: Some("unknown".to_string()),
|
||||||
|
percentage: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read battery capacity and status
|
||||||
|
let capacity_str = fs::read_to_string(bat_path.join("capacity")).unwrap_or_else(|_| "0".to_string());
|
||||||
|
let percentage: u8 = capacity_str.trim().parse().unwrap_or(0);
|
||||||
|
let status_str = fs::read_to_string(bat_path.join("status")).unwrap_or_else(|_| "Unknown".to_string());
|
||||||
|
let state = status_str.trim().to_lowercase();
|
||||||
|
|
||||||
|
let (icon, class, tooltip) = if state == "charging" || ac_online {
|
||||||
|
(
|
||||||
|
"",
|
||||||
|
"charging",
|
||||||
|
format!("TLP: AC | Charging at {}%", percentage),
|
||||||
|
)
|
||||||
|
} else if state == "discharging" {
|
||||||
|
let t = format!("TLP: Battery | Discharging at {}%", percentage);
|
||||||
|
if percentage <= critical_threshold {
|
||||||
|
("", "critical", t)
|
||||||
|
} else if percentage <= warning_threshold {
|
||||||
|
("", "warning", t)
|
||||||
|
} else if percentage <= 85 {
|
||||||
|
("", "bat", t)
|
||||||
|
} else {
|
||||||
|
("", "bat", t)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
"",
|
||||||
|
"charging",
|
||||||
|
format!("TLP: AC | Fully Charged at {}%", percentage),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(WaybarOutput {
|
||||||
|
text: format!("{}% {}", percentage, icon),
|
||||||
|
tooltip: Some(tooltip),
|
||||||
|
class: Some(class.to_string()),
|
||||||
|
percentage: Some(percentage),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user