165 lines
4.6 KiB
Rust
165 lines
4.6 KiB
Rust
use anyhow::Result;
|
|
use clap::{Parser, Subcommand};
|
|
use enboxer::macros::print_macros;
|
|
use enboxer::profile::{self, Profile};
|
|
use enboxer::session;
|
|
use std::path::PathBuf;
|
|
use tracing_subscriber::EnvFilter;
|
|
|
|
#[derive(Parser)]
|
|
#[command(
|
|
name = "enboxer",
|
|
version,
|
|
about = "Mapped-key routing for WoW on Hyprland"
|
|
)]
|
|
struct Cli {
|
|
#[command(subcommand)]
|
|
cmd: Cmd,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum Cmd {
|
|
/// Load a profile and route keys while a managed game is focused
|
|
Run {
|
|
#[arg(short, long)]
|
|
config: Option<PathBuf>,
|
|
},
|
|
/// Fire a mapped hotkey (daemon must be running)
|
|
Press {
|
|
hotkey: String,
|
|
#[arg(long)]
|
|
sock: Option<PathBuf>,
|
|
},
|
|
Status {
|
|
#[arg(long)]
|
|
sock: Option<PathBuf>,
|
|
},
|
|
/// Print the in-game macros / binds to create
|
|
Macros {
|
|
#[arg(short, long)]
|
|
config: Option<PathBuf>,
|
|
},
|
|
/// Check Hyprland + grim + profile
|
|
Doctor {
|
|
#[arg(short, long)]
|
|
config: Option<PathBuf>,
|
|
},
|
|
/// Called by Hyprland binds; not for humans
|
|
Ipc {
|
|
#[arg(long)]
|
|
sock: PathBuf,
|
|
verb: String,
|
|
#[arg(trailing_var_arg = true)]
|
|
rest: Vec<String>,
|
|
},
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(EnvFilter::from_default_env().add_directive("enboxer=info".parse()?))
|
|
.init();
|
|
let cli = Cli::parse();
|
|
match cli.cmd {
|
|
Cmd::Run { config } => {
|
|
let path = config.unwrap_or_else(profile::default_config_path);
|
|
let profile = Profile::load(&path)?;
|
|
tracing::info!("profile {} ({} slots)", profile.name, profile.slots);
|
|
session::run(profile, session::default_sock()).await
|
|
}
|
|
Cmd::Press { hotkey, sock } => {
|
|
let sock = sock.unwrap_or_else(session::default_sock);
|
|
println!(
|
|
"{}",
|
|
session::ipc_send(&sock, &format!("hotkey {hotkey}")).await?
|
|
);
|
|
Ok(())
|
|
}
|
|
Cmd::Status { sock } => {
|
|
let sock = sock.unwrap_or_else(session::default_sock);
|
|
println!("{}", session::ipc_send(&sock, "status").await?);
|
|
Ok(())
|
|
}
|
|
Cmd::Macros { config } => {
|
|
let path = config.unwrap_or_else(profile::default_config_path);
|
|
let profile = Profile::load(&path)?;
|
|
print!("{}", print_macros(&profile));
|
|
Ok(())
|
|
}
|
|
Cmd::Doctor { config } => doctor(config).await,
|
|
Cmd::Ipc { sock, verb, rest } => {
|
|
let arg = rest.join(" ");
|
|
let line = if arg.is_empty() {
|
|
verb
|
|
} else {
|
|
format!("{verb} {arg}")
|
|
};
|
|
println!("{}", session::ipc_send(&sock, &line).await?);
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn doctor(config: Option<PathBuf>) -> Result<()> {
|
|
let mut ok = true;
|
|
match std::env::var("HYPRLAND_INSTANCE_SIGNATURE") {
|
|
Ok(s) => println!("hyprland session: {s}"),
|
|
Err(_) => {
|
|
println!("hyprland session: MISSING (not in Hyprland?)");
|
|
ok = false;
|
|
}
|
|
}
|
|
match enboxer::hypr::eval_lua("return 'ok'").await {
|
|
Ok(s) => println!("hyprctl eval: {s}"),
|
|
Err(e) => {
|
|
println!("hyprctl eval: FAIL {e}");
|
|
ok = false;
|
|
}
|
|
}
|
|
match tokio::process::Command::new("grim")
|
|
.arg("-h")
|
|
.output()
|
|
.await
|
|
{
|
|
Ok(_) => println!("grim: present"),
|
|
Err(_) => println!("grim: MISSING (Video FX capture needs grim)"),
|
|
}
|
|
if let Some(path) = config.or_else(|| {
|
|
let p = profile::default_config_path();
|
|
p.exists().then_some(p)
|
|
}) {
|
|
match Profile::load(&path) {
|
|
Ok(p) => {
|
|
println!(
|
|
"profile: {} maps={} passthrough={:?} vfx={}",
|
|
path.display(),
|
|
p.maps.len(),
|
|
p.passthrough,
|
|
p.video_fx.len()
|
|
);
|
|
}
|
|
Err(e) => {
|
|
println!("profile {}: FAIL {e}", path.display());
|
|
ok = false;
|
|
}
|
|
}
|
|
} else {
|
|
println!(
|
|
"profile: none at {}",
|
|
profile::default_config_path().display()
|
|
);
|
|
}
|
|
if let Ok(clients) = enboxer::hypr::clients().await {
|
|
println!("windows: {}", clients.len());
|
|
for c in clients.iter().take(12) {
|
|
println!(" {} {} {:?}", c.address, c.class, c.title);
|
|
}
|
|
}
|
|
if ok {
|
|
println!("doctor: ok");
|
|
Ok(())
|
|
} else {
|
|
anyhow::bail!("doctor found problems")
|
|
}
|
|
}
|