mirror of
https://github.com/szymon-jozef/libnotify-rs.git
synced 2026-10-27 19:20:43 +01:00
370 lines
11 KiB
Rust
370 lines
11 KiB
Rust
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()
|
||
);
|
||
}
|
||
}
|