Skip to main content

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}