use std::process::Command;
use std::sync::mpsc::{self, Receiver, Sender};
use hyprcorrect_core::{Chord, runtime};
use signal_hook::consts::{SIGHUP, SIGINT, SIGTERM, SIGUSR1, SIGUSR2};
use signal_hook::iterator::Signals;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HotkeyEvent {
Trigger,
Reload,
Release,
Shutdown,
}
#[derive(Debug, thiserror::Error)]
pub enum HotkeyError {
#[error("hyprctl could not bind the trigger chord: {0}")]
Hyprctl(String),
#[error("hyprctl could not unbind the trigger chord: {0}")]
HyprctlUnbind(String),
#[error("could not install signal handler: {0}")]
Signal(String),
#[error("could not spawn the signal-listener thread: {0}")]
Thread(String),
}
pub fn install_bind(chord: &Chord, action: &str) -> Result<(), HotkeyError> {
let _ = uninstall_bind(chord);
let pid_path = runtime::pid_path();
let action_path = runtime::action_path();
let bind_value = format!(
"{mods}, {key}, exec, printf %s {action} > {action_path} && kill -USR1 $(cat {pid_path})",
mods = chord.hyprland_modifiers(),
key = chord.hyprland_key(),
action_path = action_path.display(),
pid_path = pid_path.display(),
);
let output = Command::new("hyprctl")
.args(["keyword", "bind", &bind_value])
.output()
.map_err(|e| HotkeyError::Hyprctl(format!("invoke hyprctl: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
return Err(HotkeyError::Hyprctl(format!(
"hyprctl exited non-zero — stdout: {stdout} stderr: {stderr}"
)));
}
Ok(())
}
pub fn uninstall_bind(chord: &Chord) -> Result<(), HotkeyError> {
let unbind_value = format!(
"{mods}, {key}",
mods = chord.hyprland_modifiers(),
key = chord.hyprland_key(),
);
let output = Command::new("hyprctl")
.args(["keyword", "unbind", &unbind_value])
.output()
.map_err(|e| HotkeyError::HyprctlUnbind(format!("invoke hyprctl: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(HotkeyError::HyprctlUnbind(stderr.into_owned()));
}
Ok(())
}
pub fn signal_channel() -> Result<Receiver<HotkeyEvent>, HotkeyError> {
let mut signals = Signals::new([SIGUSR1, SIGUSR2, SIGHUP, SIGTERM, SIGINT])
.map_err(|e| HotkeyError::Signal(e.to_string()))?;
let (tx, rx) = mpsc::channel();
std::thread::Builder::new()
.name("hyprcorrect-signal".into())
.spawn(move || forward_signals(&mut signals, &tx))
.map_err(|e| HotkeyError::Thread(e.to_string()))?;
Ok(rx)
}
fn forward_signals(signals: &mut Signals, tx: &Sender<HotkeyEvent>) {
for signal in signals.forever() {
let event = match signal {
SIGUSR1 => HotkeyEvent::Trigger,
SIGUSR2 => HotkeyEvent::Release,
SIGHUP => HotkeyEvent::Reload,
SIGTERM | SIGINT => HotkeyEvent::Shutdown,
_ => continue,
};
if tx.send(event).is_err() {
break; }
}
}