use std::process::Command;
use anyhow::Context;
use serde::Deserialize;
use tracing::debug;
use super::WindowTracker;
pub struct HyprlandTracker;
impl Default for HyprlandTracker {
fn default() -> Self {
Self
}
}
impl HyprlandTracker {
pub fn new() -> Self {
Self
}
}
#[derive(Debug, Deserialize)]
struct HyprctlActiveWindow {
address: String,
#[serde(default)]
class: String,
}
impl WindowTracker for HyprlandTracker {
fn get_focused_window(&self) -> anyhow::Result<String> {
let output = Command::new("hyprctl")
.args(["activewindow", "-j"])
.output()
.context("failed to run hyprctl — is Hyprland running?")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("hyprctl activewindow failed: {stderr}");
}
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: HyprctlActiveWindow =
serde_json::from_str(&stdout).context("failed to parse hyprctl activewindow JSON")?;
debug!("hyprland focused window: {}", parsed.address);
Ok(parsed.address)
}
fn get_focused_window_class(&self) -> Option<String> {
let output = Command::new("hyprctl")
.args(["activewindow", "-j"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: HyprctlActiveWindow = serde_json::from_str(&stdout).ok()?;
if parsed.class.is_empty() {
None
} else {
Some(parsed.class)
}
}
fn focus_window(&self, id: &str) -> anyhow::Result<()> {
let target = format!("address:{id}");
debug!("focusing hyprland window: {target}");
let output = Command::new("hyprctl")
.args(["dispatch", "focuswindow", &target])
.output()
.context("failed to run hyprctl dispatch focuswindow")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("hyprctl dispatch focuswindow failed: {stderr}");
}
Ok(())
}
}