174 lines
5.8 KiB
Rust
174 lines
5.8 KiB
Rust
use ksni::{Tray, MenuItem, menu::*};
|
|
use crate::config;
|
|
|
|
fn write_status(state: &str) {
|
|
if let Some(config_dir) = dirs::config_dir() {
|
|
let _ = std::fs::write(config_dir.join("vietc").join("status"), state);
|
|
}
|
|
}
|
|
|
|
fn read_status() -> String {
|
|
let path = dirs::config_dir()
|
|
.map(|d| d.join("vietc").join("status"))
|
|
.unwrap_or_else(|| std::path::PathBuf::from("/tmp/vietc-status"));
|
|
|
|
std::fs::read_to_string(&path)
|
|
.map(|s| s.trim().to_string())
|
|
.unwrap_or_else(|_| {
|
|
let cfg = config::Config::load();
|
|
if cfg.start_enabled { "vn".into() } else { "en".into() }
|
|
})
|
|
}
|
|
|
|
fn current_im() -> String {
|
|
config::Config::load().input_method
|
|
}
|
|
|
|
fn ensure_icons() {
|
|
let Some(config_dir) = dirs::config_dir() else { return };
|
|
let icons_dir = config_dir.join("vietc").join("icons");
|
|
let _ = std::fs::create_dir_all(&icons_dir);
|
|
|
|
let vn_path = icons_dir.join("vietc-vn.svg");
|
|
let en_path = icons_dir.join("vietc-en.svg");
|
|
|
|
if !vn_path.exists() {
|
|
let _ = std::fs::write(&vn_path, r##"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
|
|
<rect x="2" y="2" width="28" height="28" rx="6" fill="#e02424"/>
|
|
<text x="16" y="22" text-anchor="middle" fill="#ffffff" font-size="14" font-weight="900" font-family="system-ui, sans-serif">VN</text>
|
|
</svg>"##);
|
|
}
|
|
|
|
if !en_path.exists() {
|
|
let _ = std::fs::write(&en_path, r##"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
|
|
<rect x="2" y="2" width="28" height="28" rx="6" fill="#4b5563"/>
|
|
<text x="16" y="22" text-anchor="middle" fill="#ffffff" font-size="14" font-weight="900" font-family="system-ui, sans-serif">EN</text>
|
|
</svg>"##);
|
|
}
|
|
}
|
|
|
|
struct VietTray {
|
|
mode: String,
|
|
im: String,
|
|
autostart: bool,
|
|
}
|
|
|
|
impl Tray for VietTray {
|
|
fn id(&self) -> String { "io.github.vietc.Tray".into() }
|
|
fn title(&self) -> String { "Viet+".into() }
|
|
|
|
fn icon_name(&self) -> String {
|
|
if self.mode == "vn" { "vietc-vn".into() } else { "vietc-en".into() }
|
|
}
|
|
|
|
fn icon_theme_path(&self) -> String {
|
|
dirs::config_dir()
|
|
.map(|d| d.join("vietc").join("icons").to_string_lossy().into_owned())
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn activate(&mut self, _x: i32, _y: i32) {
|
|
let next = if self.mode == "vn" { "en" } else { "vn" };
|
|
write_status(&next);
|
|
let mut cfg = config::Config::load();
|
|
cfg.start_enabled = next == "vn";
|
|
let _ = cfg.save();
|
|
self.mode = next.to_string();
|
|
}
|
|
|
|
fn menu(&self) -> Vec<MenuItem<Self>> {
|
|
let is_vn = self.mode == "vn";
|
|
let im_index = if self.im == "telex" { 0 } else { 1 };
|
|
|
|
vec![
|
|
CheckmarkItem {
|
|
label: "Vietnamese Mode".into(),
|
|
checked: is_vn,
|
|
activate: Box::new(|this: &mut VietTray| {
|
|
let next = if this.mode == "vn" { "en" } else { "vn" };
|
|
write_status(&next);
|
|
let mut cfg = config::Config::load();
|
|
cfg.start_enabled = next == "vn";
|
|
let _ = cfg.save();
|
|
this.mode = next.to_string();
|
|
}),
|
|
..Default::default()
|
|
}.into(),
|
|
MenuItem::Separator,
|
|
SubMenu {
|
|
label: "Input Method".into(),
|
|
submenu: vec![
|
|
RadioGroup {
|
|
selected: im_index,
|
|
select: Box::new(|this: &mut VietTray, idx: usize| {
|
|
let im = if idx == 0 { "telex" } else { "vni" };
|
|
let mut cfg = config::Config::load();
|
|
cfg.input_method = im.into();
|
|
let _ = cfg.save();
|
|
this.im = im.into();
|
|
}),
|
|
options: vec![
|
|
RadioItem { label: "Telex".into(), ..Default::default() },
|
|
RadioItem { label: "VNI".into(), ..Default::default() },
|
|
],
|
|
}.into(),
|
|
],
|
|
..Default::default()
|
|
}.into(),
|
|
MenuItem::Separator,
|
|
CheckmarkItem {
|
|
label: "Start with System".into(),
|
|
checked: self.autostart,
|
|
activate: Box::new(|this: &mut VietTray| {
|
|
if this.autostart {
|
|
config::uninstall_autostart();
|
|
} else {
|
|
config::install_autostart();
|
|
}
|
|
}),
|
|
..Default::default()
|
|
}.into(),
|
|
MenuItem::Separator,
|
|
StandardItem {
|
|
label: "Quit".into(),
|
|
activate: Box::new(|_| {
|
|
let _ = std::process::Command::new("pkill")
|
|
.arg("-x").arg("vietc").status();
|
|
std::process::exit(0);
|
|
}),
|
|
..Default::default()
|
|
}.into(),
|
|
]
|
|
}
|
|
}
|
|
|
|
pub fn run() {
|
|
ensure_icons();
|
|
|
|
let tray = VietTray {
|
|
mode: read_status(),
|
|
im: current_im(),
|
|
autostart: config::is_autostart_installed(),
|
|
};
|
|
|
|
let service = ksni::TrayService::new(tray);
|
|
let handle = service.handle();
|
|
service.spawn();
|
|
|
|
// Poll for changes
|
|
std::thread::spawn(move || {
|
|
loop {
|
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
|
let mode = read_status();
|
|
let im = current_im();
|
|
let autostart = config::is_autostart_installed();
|
|
let _ = handle.update(move |t| {
|
|
t.mode = mode;
|
|
t.im = im;
|
|
t.autostart = autostart;
|
|
});
|
|
}
|
|
});
|
|
|
|
loop { std::thread::park(); }
|
|
}
|