use crate::libnotify::*; #[derive(Debug, Clone, Copy)] pub enum Urgency { Low, Normal, Critical, } #[derive(Debug, Clone, Copy)] pub enum ClosedReason { /// Notification not closed Unset, /// Timeout has expired Expired, /// It has been dismissed by the user Dismissed, /// It has been closed by a call to `notify_notification_close()` ApiRequest, /// Closed by undefined/reserved reasons Undefined, /// This variant is returned only if libnotify returns some weird enum value, which should never /// happen Invalid, } /// Enum representing `[GVariant](https://docs.gtk.org/glib/struct.Variant.html)` /// Used in set hint. #[derive(Debug, Clone)] pub enum HintValue { String(String), Int32(i32), Boolean(bool), } /// The timeout in milliseconds #[derive(Debug, Clone, Copy)] pub enum Timeout { Default, Never, Custom(i32), } pub struct Notification { inner: *mut NotifyNotification, } impl<'a> Notification { pub fn new(summary: &str, body: T, icon: Y) -> Result> where T: Into>, Y: Into>, { let summary = std::ffi::CString::new(summary)?; let body = body.into().map(std::ffi::CString::new).transpose()?; let icon = icon.into().map(std::ffi::CString::new).transpose()?; let inner = unsafe { notify_notification_new( summary.as_ptr(), body.as_ref().map_or(std::ptr::null(), |body| body.as_ptr()), icon.as_ref().map_or(std::ptr::null(), |icon| icon.as_ptr()), ) }; if inner.is_null() { return Err("notify_notification_new returned null".into()); } Ok(Self { inner }) } pub fn add_action( &mut self, action: &str, label: &str, callback: F, ) -> Result<(), Box> where F: Fn(&str) + 'static, { let action = std::ffi::CString::new(action)?; let label = std::ffi::CString::new(label)?; let callback: Box = Box::new(callback); let user_data = Box::into_raw(callback) as *mut std::ffi::c_void; let free_func = drop_box::; unsafe { notify_notification_add_action( self.inner, action.as_ptr(), label.as_ptr(), Some(action_trampoline::), user_data, Some(free_func), ); } Ok(()) } /// Clears all actions from the notification pub fn clear_actions(&mut self) { unsafe { notify_notification_clear_actions(self.inner) } } /// Clears all hints from the notification pub fn clear_hints(&mut self) { unsafe { notify_notification_clear_hints(self.inner) } } /// Synchronously tells the notification server to hide the notification on the screen pub fn close(&self) -> Result<(), Box> { let mut gerror = std::ptr::null_mut() as *mut GError; if unsafe { notify_notification_close(self.inner, &mut gerror) } == 0 { if gerror.is_null() { return Err("Unknown error".into()); } let e = unsafe { *gerror }.message; if e.is_null() { unsafe { g_error_free(gerror) }; return Err("Unknown error".into()); } let e_str = unsafe { std::ffi::CStr::from_ptr(e) } .to_string_lossy() .to_string(); unsafe { g_error_free(gerror) }; return Err(e_str.into()); } Ok(()) } /// Returns the closed reason code for the notification. /// /// This is valid only after the Notification::closed signal is emitted. pub fn get_closed_reason(&self) -> ClosedReason { match unsafe { notify_notification_get_closed_reason(self.inner) } { NotifyClosedReason_NOTIFY_CLOSED_REASON_UNSET => ClosedReason::Unset, NotifyClosedReason_NOTIFY_CLOSED_REASON_EXPIRED => ClosedReason::Expired, NotifyClosedReason_NOTIFY_CLOSED_REASON_DISMISSED => ClosedReason::Dismissed, NotifyClosedReason_NOTIFY_CLOSED_REASON_API_REQUEST => ClosedReason::ApiRequest, NotifyClosedReason_NOTIFY_CLOSED_REASON_UNDEFINED => ClosedReason::Undefined, _ => ClosedReason::Invalid, } } /// Sets the application icon for the notification. /// /// If this function is not called, the application icon will be set from the value set via /// `set_app_icon()`. /// /// Available since: 0.8.4 pub fn set_app_icon(&mut self, app_icon: &str) -> Result<(), std::ffi::NulError> { let app_icon_c = std::ffi::CString::new(app_icon)?; unsafe { notify_notification_set_app_icon(self.inner, app_icon_c.as_ptr()) }; Ok(()) } /// Sets the application name for the notification. /// /// If this function is not called, the application name will be set from the value used in init() or overridden with set_app_name(). /// /// Available since: 0.7.3 pub fn set_app_name(&mut self, app_name: &str) -> Result<(), std::ffi::NulError> { let app_name_c = std::ffi::CString::new(app_name)?; unsafe { notify_notification_set_app_name(self.inner, app_name_c.as_ptr()) }; Ok(()) } /// Sets the category of this notification. /// /// This can be used by the notification server to filter or display the data in a certain way pub fn set_category(&mut self, category: &str) -> Result<(), std::ffi::NulError> { let category_c = std::ffi::CString::new(category)?; unsafe { notify_notification_set_category(self.inner, category_c.as_ptr()) }; Ok(()) } /// Sets a hint for key with value value /// /// Available since: 0.6 pub fn set_hint( &mut self, key: &str, value: HintValue, ) -> Result<(), Box> { let key_c = std::ffi::CString::new(key)?; let value_c = match value { HintValue::String(s) => { let s = std::ffi::CString::new(s)?; unsafe { g_variant_new_string(s.as_ptr()) } } HintValue::Int32(i) => unsafe { g_variant_new_int32(i) }, HintValue::Boolean(b) => unsafe { g_variant_new_boolean(b as i32) }, }; if value_c.is_null() { return Err("Couldn't create hint value".into()); } unsafe { notify_notification_set_hint(self.inner, key_c.as_ptr(), value_c) }; Ok(()) } /// Sets the timeout of the notification. /// /// Note that the timeout may be ignored by the server. pub fn set_timeout(&mut self, timeout: Timeout) { let timeout_c = match timeout { Timeout::Default => NOTIFY_EXPIRES_DEFAULT, Timeout::Never => NOTIFY_EXPIRES_NEVER as i32, Timeout::Custom(i) => i, }; unsafe { notify_notification_set_timeout(self.inner, timeout_c) } } /// Sets the urgency level of this notification pub fn set_urgency(&mut self, urgency: Urgency) { let urgency: u32 = match urgency { Urgency::Low => NotifyUrgency_NOTIFY_URGENCY_LOW, Urgency::Normal => NotifyUrgency_NOTIFY_URGENCY_NORMAL, Urgency::Critical => NotifyUrgency_NOTIFY_URGENCY_CRITICAL, }; unsafe { notify_notification_set_urgency(self.inner, urgency) } } /// Tells the notification server to display the notification on the screen pub fn show(&self) -> Result<(), Box> { let mut gerror = std::ptr::null_mut() as *mut GError; if unsafe { notify_notification_show(self.inner, &mut gerror) } == 0 { if gerror.is_null() { return Err("Unknown error".into()); } let err_msg = unsafe { *gerror }.message; if err_msg.is_null() { unsafe { g_error_free(gerror) }; return Err("Unknown error".into()); } let err_msg_str = unsafe { std::ffi::CStr::from_ptr(err_msg) } .to_string_lossy() .to_string(); unsafe { g_error_free(gerror) }; return Err(err_msg_str.into()); } Ok(()) } /// Updates the notification text and icon. /// /// This won’t send the update out and display it on the screen. For that, you will need to call `show()`. pub fn update<'b, F, I>( &mut self, summary: &str, body: F, icon: I, ) -> Result<(), std::ffi::NulError> where F: Into>, I: Into>, { let summary = std::ffi::CString::new(summary)?; let body = body.into().map(std::ffi::CString::new).transpose()?; let icon = icon.into().map(std::ffi::CString::new).transpose()?; unsafe { notify_notification_update( self.inner, summary.as_ptr(), body.as_ref().map_or(std::ptr::null(), |body| body.as_ptr()), icon.as_ref().map_or(std::ptr::null(), |icon| icon.as_ptr()), ); } Ok(()) } } impl Drop for Notification { fn drop(&mut self) { if self.inner.is_null() { return; // shouldn't happen, but check just in case } unsafe { g_object_unref(self.inner as *mut std::os::raw::c_void) }; } } unsafe extern "C" fn action_trampoline( _notification: *mut NotifyNotification, action: *mut ::std::os::raw::c_char, user_data: gpointer, ) where F: Fn(&str) + 'static, { let callback = unsafe { &*(user_data as *const F) }; if action.is_null() { return; } let action = unsafe { std::ffi::CStr::from_ptr(action).to_string_lossy() }; callback(&action); } unsafe extern "C" fn drop_box(data: gpointer) { unsafe { drop(Box::from_raw(data as *mut F)) }; } #[cfg(test)] mod tests { use super::*; #[test] fn new_test() { assert!( !Notification::new("Test", "Test", "Test") .unwrap() .inner .is_null() ); assert!( !Notification::new("Test", None, None) .unwrap() .inner .is_null() ); assert!( !Notification::new("Test", None, "Test") .unwrap() .inner .is_null() ); assert!( !Notification::new("Test", "Test", None) .unwrap() .inner .is_null() ); } }