termux_notification/
callbacks.rs

1//! Provides callbacks to notifications by socket connection.
2//!
3//! # Requiments
4//!
5//! - `netcat-openbsd` package installed
6//! - `callbacks` feature enabled
7//! - `id` provided for notification with callbacks
8//!
9//! # Usage
10//!
11//! On application start initialize callbacks socket.
12//!
13//! ```no_run
14//! termux_notification::callbacks::init_socket();
15//! ```
16//!
17//! # Examples
18//!
19//! ```no_run
20//! use std::{io, process, thread, time::Duration};
21//!
22//! use termux_notification::TermuxNotification;
23//!
24//! fn main() -> io::Result<()> {
25//!   termux_notification::callbacks::init_socket();
26//!
27//!   let remove_handle = TermuxNotification::new()
28//!     .id("example")
29//!     .title("Termux Notification Example")
30//!     .button1_fn("ECHO", || println!("Hello"))
31//!     .on_delete_fn(|| {
32//!       println!("Notification deleted");
33//!       process::exit(0);
34//!     })
35//!     .show()?;
36//!
37//!   thread::sleep(Duration::from_secs(60));
38//!
39//!   remove_handle.remove()
40//! }
41//! ```
42
43mod callback_key;
44mod map;
45
46use std::{
47  env,
48  io::{self, Read},
49  os::unix::net::UnixListener,
50  path::PathBuf,
51  process,
52  str::FromStr,
53  sync::{Mutex, Once},
54  thread,
55};
56
57use crate::{options, TermuxNotification};
58
59use self::{callback_key::CallbackKey, map::Map};
60
61static CB_MAP: Mutex<Map<CallbackKey, Box<dyn Fn() + Send>>> =
62  Mutex::new(Map::new());
63
64/// Creates socket and listen it at new thread to handle notification callbacks
65///
66/// # Panics
67///
68/// Panics if can't create socket.
69/// Spawned thread panics on receive message error.
70pub fn init_socket() {
71  static INIT: Once = Once::new();
72  INIT.call_once(|| {
73    let socket = UnixListener::bind(socket_path()).unwrap();
74    thread::spawn(move || loop {
75      let msg = recv_message(&socket).unwrap();
76      let key = CallbackKey::from_str(&msg);
77      let Ok(key) = key else { continue };
78      let mut cb_map = CB_MAP.lock().unwrap();
79      let Some(f) = cb_map.get(&key) else { continue };
80      f();
81      if key.is_finish_trigger() {
82        cb_map.retain(|(k, _)| k.id() != key.id());
83      }
84    });
85  });
86}
87
88fn socket_path() -> PathBuf {
89  let pid = process::id();
90  env::temp_dir().join(format!("termux_notification.{pid}.socket"))
91}
92
93fn recv_message(socket: &UnixListener) -> io::Result<String> {
94  let (mut connection, _) = socket.accept()?;
95  let buf = &mut String::new();
96  connection.read_to_string(buf)?;
97  Ok(buf.trim().to_owned())
98}
99
100impl TermuxNotification {
101  /// Action to execute when pressing the notification
102  ///
103  /// # Panics
104  ///
105  /// Panics if notification id not provided
106  pub fn action_fn<F>(&mut self, f: F) -> &mut Self
107  where
108    F: Fn() + Send + 'static,
109  {
110    let cmd = self.on(options::ACTION, f);
111    self.action(cmd)
112  }
113
114  /// Action to execute when the the notification is cleared
115  ///
116  /// # Panics
117  ///
118  /// Panics if notification id not provided
119  pub fn on_delete_fn<F>(&mut self, f: F) -> &mut Self
120  where
121    F: Fn() + Send + 'static,
122  {
123    let cmd = self.on(options::ON_DELETE, f);
124    self.on_delete(cmd)
125  }
126
127  /// Text and action for first notification button
128  ///
129  /// # Panics
130  ///
131  /// Panics if notification id not provided
132  pub fn button1_fn<L, F>(&mut self, label: L, f: F) -> &mut Self
133  where
134    L: Into<String>,
135    F: Fn() + Send + 'static,
136  {
137    let cmd = self.on(options::BUTTON1, f);
138    self.button1(label, cmd)
139  }
140
141  /// Text and action for second notification button
142  ///
143  /// # Panics
144  ///
145  /// Panics if notification id not provided
146  pub fn button2_fn<L, F>(&mut self, label: L, f: F) -> &mut Self
147  where
148    L: Into<String>,
149    F: Fn() + Send + 'static,
150  {
151    let cmd = self.on(options::BUTTON2, f);
152    self.button2(label, cmd)
153  }
154
155  /// Text and action for third notification button
156  ///
157  /// # Panics
158  ///
159  /// Panics if notification id not provided
160  pub fn button3_fn<L, F>(&mut self, label: L, f: F) -> &mut Self
161  where
162    L: Into<String>,
163    F: Fn() + Send + 'static,
164  {
165    let cmd = self.on(options::BUTTON3, f);
166    self.button3(label, cmd)
167  }
168
169  fn on<F>(&mut self, trigger: &str, f: F) -> String
170  where
171    F: Fn() + Send + 'static,
172  {
173    let id = self.get_id_unchecked();
174    let key = CallbackKey::new(id, trigger.to_owned());
175    let socket = socket_path().to_string_lossy().to_string();
176    let cmd = format!(r#"echo "{key}" | nc -UN {socket}"#);
177    CB_MAP.lock().unwrap().insert(key, Box::new(f));
178    cmd
179  }
180
181  fn get_id_unchecked(&self) -> String {
182    self
183      .args
184      .get(options::ID)
185      .cloned()
186      .flatten()
187      .expect("id not provided")
188  }
189}