use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, Receiver, Sender};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrayEvent {
TogglePause,
OpenPrefs,
Quit,
}
#[derive(Debug, thiserror::Error)]
pub enum TrayError {
#[error("ksni: {0}")]
Ksni(String),
}
pub struct TrayHandle {
inner: ksni::blocking::Handle<HyprcorrectTray>,
}
impl TrayHandle {
pub fn refresh(&self) {
self.inner.update(|_| {});
}
}
pub struct IconPixmap {
pub width: i32,
pub height: i32,
pub argb: Vec<u8>,
}
pub fn start(
paused: Arc<AtomicBool>,
active_icon: Vec<IconPixmap>,
paused_icon: Vec<IconPixmap>,
) -> Result<(TrayHandle, Receiver<TrayEvent>), TrayError> {
use ksni::blocking::TrayMethods;
let (events_tx, events_rx) = mpsc::channel();
let tray = HyprcorrectTray {
events_tx,
paused,
active_icon,
paused_icon,
};
let inner = tray.spawn().map_err(|e| TrayError::Ksni(e.to_string()))?;
Ok((TrayHandle { inner }, events_rx))
}
struct HyprcorrectTray {
events_tx: Sender<TrayEvent>,
paused: Arc<AtomicBool>,
active_icon: Vec<IconPixmap>,
paused_icon: Vec<IconPixmap>,
}
impl HyprcorrectTray {
fn is_paused(&self) -> bool {
self.paused.load(Ordering::Relaxed)
}
}
impl ksni::Tray for HyprcorrectTray {
fn id(&self) -> String {
"hyprcorrect".to_string()
}
fn title(&self) -> String {
"hyprcorrect".to_string()
}
fn category(&self) -> ksni::Category {
ksni::Category::ApplicationStatus
}
fn status(&self) -> ksni::Status {
ksni::Status::Active
}
fn icon_name(&self) -> String {
String::new()
}
fn icon_pixmap(&self) -> Vec<ksni::Icon> {
let src = if self.is_paused() {
&self.paused_icon
} else {
&self.active_icon
};
src.iter()
.map(|p| ksni::Icon {
width: p.width,
height: p.height,
data: p.argb.clone(),
})
.collect()
}
fn tool_tip(&self) -> ksni::ToolTip {
let description = if self.is_paused() {
"Paused. Click the tray icon to resume.".to_string()
} else {
"Press the trigger chord to fix the last word.".to_string()
};
ksni::ToolTip {
title: "hyprcorrect".into(),
description,
icon_name: String::new(),
icon_pixmap: Vec::new(),
}
}
fn menu(&self) -> Vec<ksni::MenuItem<Self>> {
use ksni::MenuItem;
use ksni::menu::StandardItem;
let pause_label = if self.is_paused() { "Resume" } else { "Pause" };
vec![
StandardItem {
label: pause_label.into(),
activate: Box::new(|this: &mut HyprcorrectTray| {
let _ = this.events_tx.send(TrayEvent::TogglePause);
}),
..Default::default()
}
.into(),
MenuItem::Separator,
StandardItem {
label: "Open Preferences…".into(),
activate: Box::new(|this: &mut HyprcorrectTray| {
let _ = this.events_tx.send(TrayEvent::OpenPrefs);
}),
..Default::default()
}
.into(),
MenuItem::Separator,
StandardItem {
label: "Quit".into(),
activate: Box::new(|this: &mut HyprcorrectTray| {
let _ = this.events_tx.send(TrayEvent::Quit);
}),
..Default::default()
}
.into(),
]
}
}