termux_notification/
lib.rs

1//! Display a system notification.
2//!
3//! ```no_run
4//! use std::io;
5//!
6//! use termux_notification::TermuxNotification;
7//!
8//! fn main() -> io::Result<()> {
9//!   TermuxNotification::new()
10//!     .title("Foo")
11//!     .content("Bar")
12//!     .show()?;
13//!   Ok(())
14//! }
15//! ```
16//!
17//! ## Feature flags
18//!
19//! - `callbacks`
20
21#[cfg(any(doc, feature = "callbacks"))]
22pub mod callbacks;
23mod options;
24pub mod remove_handle;
25
26use std::{
27  collections::HashMap,
28  io,
29  process::{Command, Output},
30};
31
32use remove_handle::RemoveHandle;
33
34/// Builder for `termux-notification` command
35#[derive(Debug, Default, Clone, PartialEq, Eq)]
36pub struct TermuxNotification {
37  args: HashMap<&'static str, Option<String>>,
38}
39
40impl TermuxNotification {
41  #[must_use]
42  pub fn new() -> Self {
43    Self::default()
44  }
45
46  /// Notification id (will overwrite any previous notification with the same id)
47  pub fn id(&mut self, id: impl Into<String>) -> &mut Self {
48    self.args.insert(options::ID, Some(id.into()));
49    self
50  }
51
52  /// Notification title to show
53  pub fn title(&mut self, title: impl Into<String>) -> &mut Self {
54    self.args.insert(options::TITLE, Some(title.into()));
55    self
56  }
57
58  /// Content to show in the notification.
59  pub fn content(&mut self, content: impl Into<String>) -> &mut Self {
60    self.args.insert(options::CONTENT, Some(content.into()));
61    self
62  }
63
64  /// Set the icon that shows up in the status bar.
65  /// View available icons at `https://material.io/resources/icons/`
66  /// (default icon: `event_note`)
67  pub fn icon(&mut self, icon: impl Into<String>) -> &mut Self {
68    self.args.insert(options::ICON, Some(icon.into()));
69    self
70  }
71
72  /// Do not alert when the notification is edited
73  pub fn alert_once(&mut self, alert_once: bool) -> &mut Self {
74    let k = options::ALERT_ONCE;
75    if alert_once {
76      self.args.insert(k, None);
77    } else {
78      self.args.remove(k);
79    }
80    self
81  }
82
83  /// Pin the notification
84  pub fn ongoing(&mut self, ongoing: bool) -> &mut Self {
85    let k = options::ONGOING;
86    if ongoing {
87      self.args.insert(k, None);
88    } else {
89      self.args.remove(k);
90    }
91    self
92  }
93
94  /// Action to execute when pressing the notification
95  pub fn action(&mut self, action: impl Into<String>) -> &mut Self {
96    self.args.insert(options::ACTION, Some(action.into()));
97    self
98  }
99
100  /// Action to execute when the the notification is cleared
101  pub fn on_delete(
102    &mut self,
103    on_delete: impl Into<String>,
104  ) -> &mut Self {
105    self.args.insert(options::ON_DELETE, Some(on_delete.into()));
106    self
107  }
108
109  /// Text and action for first notification button
110  pub fn button1(
111    &mut self,
112    label: impl Into<String>,
113    action: impl Into<String>,
114  ) -> &mut Self {
115    self.args.insert(options::BUTTON1, Some(label.into()));
116    self
117      .args
118      .insert(options::BUTTON1_ACTION, Some(action.into()));
119    self
120  }
121
122  /// Text and action for second notification button
123  pub fn button2(
124    &mut self,
125    label: impl Into<String>,
126    action: impl Into<String>,
127  ) -> &mut Self {
128    self.args.insert(options::BUTTON2, Some(label.into()));
129    self
130      .args
131      .insert(options::BUTTON2_ACTION, Some(action.into()));
132    self
133  }
134
135  /// Text and action for third notification button
136  pub fn button3(
137    &mut self,
138    label: impl Into<String>,
139    action: impl Into<String>,
140  ) -> &mut Self {
141    self.args.insert(options::BUTTON3, Some(label.into()));
142    self
143      .args
144      .insert(options::BUTTON3_ACTION, Some(action.into()));
145    self
146  }
147
148  /// Shows notification via `termux-notification` command
149  ///
150  /// # Errors
151  ///
152  /// Returns an error if command status not success
153  pub fn show(&self) -> io::Result<RemoveHandle> {
154    ensure_success(&self.to_command().output()?)?;
155    let id = self.args.get(options::ID);
156    Ok(RemoveHandle::new(id.cloned().flatten()))
157  }
158
159  /// Builds `termux-notification` command
160  #[must_use]
161  pub fn to_command(&self) -> Command {
162    let mut cmd = Command::new("termux-notification");
163    for (key, val) in &self.args {
164      cmd.arg(key);
165      if let Some(val) = val {
166        cmd.arg(val);
167      }
168    }
169    cmd
170  }
171}
172
173/// # Errors
174///
175/// Returns an error if command status not success
176fn ensure_success(output: &Output) -> io::Result<()> {
177  if output.status.success() {
178    Ok(())
179  } else {
180    Err(io::Error::other(stdout_stderr(output)))
181  }
182}
183
184fn stdout_stderr(output: &Output) -> String {
185  let stdout = String::from_utf8_lossy(&output.stdout);
186  let stderr = String::from_utf8_lossy(&output.stderr);
187  [stdout, stderr]
188    .into_iter()
189    .filter(|s| !s.is_empty())
190    .collect::<Vec<_>>()
191    .join("\n")
192}