libnotify_rs/api/functions.rs
1use crate::api::errors::LibnotifyError;
2
3use super::utils::c_str_to_rs_str_and_free;
4
5use super::libnotify::*;
6
7/* Initting
8 *
9 *(what a cool section comment)
10*/
11
12/// Initializes libnotify. This must be called before any other functions.
13///
14/// Starting from 0.8, if the provided app_name is NULL, libnotify will try to figure it out from the running application. Before it was not allowed, and was causing libnotify not to be initialized.
15///
16/// # Example
17///
18/// ```
19/// use libnotify_rs::api::{
20/// functions::init,
21/// notification:: Notification,
22/// };
23///
24/// fn main() -> Result<(), Box<dyn std::error::Error>> {
25/// init("Test app")?; // we need to init libnotify __before__ any other libnotify code
26/// let notification = Notification::new("Test", None, None)?; // Some libnotify call
27/// Ok(())
28/// }
29///
30/// ```
31///
32/// # Errors
33/// This function can return LibnotifyError::{InitError, NulError}
34pub fn init<'a, T>(app_name: T) -> Result<(), LibnotifyError>
35where
36 T: Into<Option<&'a str>>,
37{
38 let app_name = app_name.into().map(std::ffi::CString::new).transpose()?;
39
40 if unsafe {
41 notify_init(
42 app_name
43 .as_ref()
44 .map_or(std::ptr::null(), |app_name| app_name.as_ptr()),
45 )
46 } == 0
47 {
48 return Err(LibnotifyError::InitError);
49 }
50
51 Ok(())
52}
53
54/// Uninitializes libnotify.
55///
56/// This should be called when the program no longer needs libnotify for the rest of its lifecycle, typically just before exitting.
57///
58/// # Example
59/// ```
60/// use libnotify_rs::api::functions::{init, uninit};
61///
62/// fn main() -> Result<(), Box<dyn std::error::Error>> {
63/// init("Test app")?;
64/// // let notify = ...
65/// uninit(); // __always__ uninit libnotify after use
66/// Ok(())
67/// }
68///
69/// ```
70pub fn uninit() {
71 unsafe {
72 notify_uninit();
73 }
74}
75
76/// Gets whether or not libnotify is initialized.
77///
78/// # Example
79/// ```
80/// use libnotify_rs::api::functions::{init, is_initted};
81///
82/// fn main() -> Result<(), Box<dyn std::error::Error>> {
83/// if !is_initted() { // useful for conditional initing
84/// init("Test app")?;
85/// }
86///
87/// Ok(())
88/// }
89///
90/// ```
91pub fn is_initted() -> bool {
92 unsafe { notify_is_initted() != 0 }
93}
94
95/* Getters
96 *
97*/
98
99/// Gets the application name registered.
100///
101/// # Example
102/// ```
103/// use libnotify_rs::api::functions::{get_app_name, init};
104///
105/// fn main() -> Result<(), Box<dyn std::error::Error>> {
106/// let app_name = "Test app";
107/// init(app_name)?;
108/// let app_name_gotten: String = get_app_name().ok_or::<String>("No name gotten".into())?;
109/// assert_eq!(app_name, app_name_gotten);
110/// Ok(())
111/// }
112///
113/// ```
114pub fn get_app_name() -> Option<String> {
115 let app_name_c: *const std::os::raw::c_char = unsafe { notify_get_app_name() };
116
117 if app_name_c.is_null() {
118 None
119 } else {
120 Some(
121 unsafe { std::ffi::CStr::from_ptr(app_name_c) }
122 .to_string_lossy()
123 .to_string(),
124 )
125 }
126}
127
128/// Gets the application icon registered.
129/// Available since: 0.8.4
130///
131/// # Example
132/// ```
133/// use libnotify_rs::api::functions::{get_app_icon, get_app_name, init, set_app_icon};
134///
135/// fn main() -> Result<(), Box<dyn std::error::Error>> {
136/// init("Test app")?;
137/// let icon = "gimp";
138/// set_app_icon(icon)?;
139/// let icon_gotten = get_app_icon().ok_or::<String>("No app icon set".into())?;
140/// assert_eq!(icon, icon_gotten);
141/// Ok(())
142/// }
143/// ```
144pub fn get_app_icon() -> Option<String> {
145 let app_icon_c: *const std::os::raw::c_char = unsafe { notify_get_app_icon() };
146
147 if app_icon_c.is_null() {
148 None
149 } else {
150 Some(
151 unsafe { std::ffi::CStr::from_ptr(app_icon_c) }
152 .to_string_lossy()
153 .to_string(),
154 )
155 }
156}
157
158/// Queries the server capabilities.
159/// Synchronously queries the server for its capabilities and returns them in a list.
160///
161/// # Example
162/// ```
163/// use libnotify_rs::api::functions::{self, uninit};
164///
165/// fn main() -> Result<(), Box<dyn std::error::Error>> {
166/// functions::init("Test app")?;
167///
168/// if let Some(caps) = functions::get_server_caps() {
169/// println!("=== Server caps ===");
170/// for s in caps {
171/// println!("{}", s);
172/// }
173/// } else {
174/// eprintln!("No server caps");
175/// }
176///
177/// Ok(())
178/// }
179///
180/// ```
181pub fn get_server_caps() -> Option<Vec<String>> {
182 let mut vec: Vec<String> = vec![];
183
184 let list = unsafe { notify_get_server_caps() };
185 if list.is_null() {
186 return None;
187 }
188
189 let mut current = list;
190
191 loop {
192 let data = unsafe { *current }.data;
193 let data_char = data as *const std::os::raw::c_char;
194
195 if !data.is_null() {
196 let s: String = unsafe { std::ffi::CStr::from_ptr(data_char) }
197 .to_string_lossy()
198 .to_string();
199 vec.push(s);
200 }
201
202 unsafe { g_free(data) };
203
204 if unsafe { *current }.next.is_null() {
205 break;
206 }
207
208 current = unsafe { *current }.next;
209 }
210
211 unsafe { g_list_free(list) };
212
213 if vec.is_empty() { None } else { Some(vec) }
214}
215
216/// Contains server information. Refer to [libnotify get_server_info docs](https://gnome.pages.gitlab.gnome.org/libnotify/func.get_server_info.html) to better understand specific fields
217pub struct ServerInfo {
218 /// A location to store the server name, or `None`
219 pub name: Option<String>,
220 /// A location to store the server vendor, or `None`
221 pub vendor: Option<String>,
222 /// A location to store the server version, or `None`
223 pub version: Option<String>,
224 /// A location to store the version the service is compliant with, or `None`
225 pub spec_version: Option<String>,
226}
227
228/// Queries the server for information.
229/// Synchronously queries the server for its information, specifically, the name, vendor, server version, and the version of the notifications specification that it is compliant with.
230///
231/// # Example
232/// ```
233///
234/// use libnotify_rs::api::functions::{self, uninit};
235///
236/// fn main() -> Result<(), Box<dyn std::error::Error>> {
237/// functions::init("Test app")?;
238///
239/// if let Some(info) = functions::get_server_info() {
240/// println!("=== Server info ===");
241/// if let Some(name) = info.name {
242/// println!("Server name: {}", name);
243/// }
244///
245/// if let Some(vendor) = info.vendor {
246/// println!("Server vendor: {}", vendor);
247/// }
248///
249/// if let Some(version) = info.version {
250/// println!("Server version: {}", version);
251/// }
252///
253/// if let Some(spec_version) = info.spec_version {
254/// println!("Server spec version: {}", spec_version);
255/// }
256/// } else {
257/// eprintln!("No server info");
258/// }
259///
260/// uninit();
261///
262/// Ok(())
263/// }
264/// ```
265pub fn get_server_info() -> Option<ServerInfo> {
266 let mut ret_name = std::ptr::null_mut() as *mut std::os::raw::c_char;
267 let mut ret_vendor = std::ptr::null_mut() as *mut std::os::raw::c_char;
268 let mut ret_version = std::ptr::null_mut() as *mut std::os::raw::c_char;
269 let mut ret_spec_version = std::ptr::null_mut() as *mut std::os::raw::c_char;
270
271 if unsafe {
272 notify_get_server_info(
273 &mut ret_name,
274 &mut ret_vendor,
275 &mut ret_version,
276 &mut ret_spec_version,
277 )
278 } == 0
279 {
280 return None;
281 }
282
283 Some(ServerInfo {
284 name: unsafe { c_str_to_rs_str_and_free(ret_name) },
285 vendor: unsafe { c_str_to_rs_str_and_free(ret_vendor) },
286 version: unsafe { c_str_to_rs_str_and_free(ret_version) },
287 spec_version: unsafe { c_str_to_rs_str_and_free(ret_spec_version) },
288 })
289}
290
291/* Setters
292 *
293*/
294
295/// Sets the application name
296///
297/// # Errors
298/// This function can return LibnotifyError::NulError
299///
300/// # Example
301/// ```
302/// use libnotify_rs::api::functions::{get_app_name, init, set_app_name};
303///
304/// fn main() -> Result<(), Box<dyn std::error::Error>> {
305/// init("Test app")?;
306/// let app_name = "morbius app";
307/// set_app_name(app_name)?;
308/// assert_eq!(app_name, get_app_name().unwrap());
309/// Ok(())
310/// }
311/// ```
312pub fn set_app_name(app_name: &str) -> Result<(), LibnotifyError> {
313 let app_name_c = std::ffi::CString::new(app_name)?;
314 unsafe { notify_set_app_name(app_name_c.as_ptr()) };
315
316 Ok(())
317}
318
319/// Sets the application icon.
320/// Available since: 0.8.4
321///
322/// app_icon is icon name or path to an image
323///
324/// # Errors
325/// This function can return LibnotifyError::NulError
326///
327/// # Example
328/// ```
329/// use libnotify_rs::api::functions::{get_app_icon, init, set_app_icon};
330///
331/// fn main() -> Result<(), Box<dyn std::error::Error>> {
332/// init("Test app")?;
333/// let app_icon = "morbius app";
334/// set_app_icon(app_icon)?;
335/// assert_eq!(app_icon, get_app_icon().unwrap());
336/// Ok(())
337/// }
338/// ```
339pub fn set_app_icon(app_icon: &str) -> Result<(), LibnotifyError> {
340 let app_icon_c = std::ffi::CString::new(app_icon)?;
341 unsafe { notify_set_app_icon(app_icon_c.as_ptr()) };
342
343 Ok(())
344}
345
346#[cfg(test)]
347#[serial_test::serial]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn test_initializing() {
353 assert!(!is_initted());
354 assert!(init("Morbius").is_ok());
355 assert!(is_initted());
356 uninit();
357 assert!(!is_initted());
358 }
359
360 #[test]
361 fn test_bad_init() {
362 assert!(matches!(
363 init("Mor\0ius").unwrap_err(),
364 LibnotifyError::NulError(_)
365 ));
366 }
367
368 #[test]
369 fn test_app_name_set_and_get() {
370 let first_name: &str = "Morbius";
371 let _ = init(first_name);
372 assert_eq!(get_app_name().unwrap(), first_name);
373
374 let new_name: &str = "Milo";
375 let _ = set_app_name(new_name);
376 assert_eq!(get_app_name().unwrap(), new_name);
377
378 uninit();
379 }
380
381 #[test]
382 fn test_app_name_bad_name() {
383 let _ = init("Morbius");
384
385 let bad_name: &str = "mor\0ius";
386 assert!(set_app_name(bad_name).is_err());
387 uninit();
388 }
389}