mod callback_key;
mod map;
use std::{
env,
io::{self, Read},
os::unix::net::UnixListener,
path::PathBuf,
process,
str::FromStr,
sync::{Mutex, Once},
thread,
};
use crate::{options, TermuxNotification};
use self::{callback_key::CallbackKey, map::Map};
static CB_MAP: Mutex<Map<CallbackKey, Box<dyn Fn() + Send>>> =
Mutex::new(Map::new());
pub fn init_socket() {
static INIT: Once = Once::new();
INIT.call_once(|| {
let socket = UnixListener::bind(socket_path()).unwrap();
thread::spawn(move || loop {
let msg = recv_message(&socket).unwrap();
let key = CallbackKey::from_str(&msg);
let Ok(key) = key else { continue };
let mut cb_map = CB_MAP.lock().unwrap();
let Some(f) = cb_map.get(&key) else { continue };
f();
if key.is_finish_trigger() {
cb_map.retain(|(k, _)| k.id() != key.id());
}
});
});
}
fn socket_path() -> PathBuf {
let pid = process::id();
env::temp_dir().join(format!("termux_notification.{pid}.socket"))
}
fn recv_message(socket: &UnixListener) -> io::Result<String> {
let (mut connection, _) = socket.accept()?;
let buf = &mut String::new();
connection.read_to_string(buf)?;
Ok(buf.trim().to_owned())
}
impl TermuxNotification {
pub fn action_fn<F>(&mut self, f: F) -> &mut Self
where
F: Fn() + Send + 'static,
{
let cmd = self.on(options::ACTION, f);
self.action(cmd)
}
pub fn on_delete_fn<F>(&mut self, f: F) -> &mut Self
where
F: Fn() + Send + 'static,
{
let cmd = self.on(options::ON_DELETE, f);
self.on_delete(cmd)
}
pub fn button1_fn<L, F>(&mut self, label: L, f: F) -> &mut Self
where
L: Into<String>,
F: Fn() + Send + 'static,
{
let cmd = self.on(options::BUTTON1, f);
self.button1(label, cmd)
}
pub fn button2_fn<L, F>(&mut self, label: L, f: F) -> &mut Self
where
L: Into<String>,
F: Fn() + Send + 'static,
{
let cmd = self.on(options::BUTTON2, f);
self.button2(label, cmd)
}
pub fn button3_fn<L, F>(&mut self, label: L, f: F) -> &mut Self
where
L: Into<String>,
F: Fn() + Send + 'static,
{
let cmd = self.on(options::BUTTON3, f);
self.button3(label, cmd)
}
fn on<F>(&mut self, trigger: &str, f: F) -> String
where
F: Fn() + Send + 'static,
{
let id = self.get_id_unchecked();
let key = CallbackKey::new(id, trigger.to_owned());
let socket = socket_path().to_string_lossy().to_string();
let cmd = format!(r#"echo "{key}" | nc -UN {socket}"#);
CB_MAP.lock().unwrap().insert(key, Box::new(f));
cmd
}
fn get_id_unchecked(&self) -> String {
self
.args
.get(options::ID)
.cloned()
.flatten()
.expect("id not provided")
}
}