Files
libnotify-rs/src/notification.rs
2026-10-01 20:59:14 +02:00

370 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<T, Y>(summary: &str, body: T, icon: Y) -> Result<Self, Box<dyn std::error::Error>>
where
T: Into<Option<&'a str>>,
Y: Into<Option<&'a str>>,
{
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<F>(
&mut self,
action: &str,
label: &str,
callback: F,
) -> Result<(), Box<dyn std::error::Error>>
where
F: Fn(&str) + 'static,
{
let action = std::ffi::CString::new(action)?;
let label = std::ffi::CString::new(label)?;
let callback: Box<F> = Box::new(callback);
let user_data = Box::into_raw(callback) as *mut std::ffi::c_void;
let free_func = drop_box::<F>;
unsafe {
notify_notification_add_action(
self.inner,
action.as_ptr(),
label.as_ptr(),
Some(action_trampoline::<F>),
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<Option<&'b str>>,
I: Into<Option<&'b str>>,
{
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<F>(
_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<F>(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()
);
}
}