diff --git a/src/main.rs b/src/main.rs index 86b1b75..5bd90eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, OnceLock}; use std::time::Duration; -use hinoirisetr::notify::Notification; +use hinoirisetr::notify::InitializedNotificationSystem; use hinoirisetr::time::Time; use hinoirisetr::{Config, apply_settings, compute_settings, debug, error, info, trace, warn}; use tokio::io::{AsyncBufReadExt, BufReader}; @@ -18,20 +18,21 @@ const SOCKET_PATH: &str = "/tmp/hinoirisetr.sock"; static CONFIG: OnceLock>> = OnceLock::new(); enum NotifyState { - Enabled(Arc), + Enabled(InitializedNotificationSystem), Disabled, } async fn socket_server(disabled: Arc, notify: Arc) { let listener = UnixListener::bind(SOCKET_PATH).expect("Failed to bind socket"); trace!("socket server bound"); - let notification: NotifyState = match unsafe { hinoirisetr::notify::Notification::new() } { - Some(not) => NotifyState::Enabled(Arc::new(not)), - None => { - info!("libnotify not found, disabling 'status_notify' command"); - NotifyState::Disabled - } - }; + let notification: NotifyState = + match { hinoirisetr::notify::InitializedNotificationSystem::new("hinoirisetr") } { + Ok(not) => NotifyState::Enabled(not), + Err(_) => { + info!("libnotify not found, disabling 'status_notify' command"); + NotifyState::Disabled + } + }; loop { let (stream, _) = listener.accept().await.unwrap(); @@ -108,24 +109,15 @@ async fn socket_server(disabled: Arc, notify: Arc) { NotifyState::Enabled(ref not) => { trace!("notify notification enabled"); let timeout = config_handle().read().await.notification_timeout; - unsafe { - not.init("hinoirisetr"); - let local_notification = - not.notify_notification_new("Sunsetting", body.as_str(), ""); - not.notify_notification_set_timeout( - local_notification, - timeout as i32, - - ); - not.notify_notification_show( - not.notify_notification_new( - "Sunsetting", - body.as_str(), - "notification-icon", - ), - std::ptr::null_mut(), - ); - } + match not.show_notification( + "Sunsetting", + &body, + "notification-icon", + timeout as i32, + ) { + Ok(_) => {} + Err(e) => error!("Failed to show notification: {e:?}"), + }; } NotifyState::Disabled => { trace!("notify notification disabled"); diff --git a/src/notify.rs b/src/notify.rs index f6a2e47..ec5853d 100644 --- a/src/notify.rs +++ b/src/notify.rs @@ -1,14 +1,20 @@ -#![allow(clippy::missing_safety_doc)] - -use std::ffi::{CString, c_char, c_void}; +use std::ffi::{CString, NulError, c_char, c_void}; +use std::marker::PhantomData; +use std::sync::Arc; +use libloading::Library; use libloading::os::unix::Symbol as UnixSymbol; -use libloading::{Library, Symbol}; -use crate::trace; +type NotifyInit = unsafe extern "C" fn(*const c_char) -> bool; +type NotifyUninit = unsafe extern "C" fn(); +type NotifyNotificationNew = + unsafe extern "C" fn(*const c_char, *const c_char, *const c_char) -> *mut c_void; +type NotifyNotificationShow = unsafe extern "C" fn(*mut c_void, *mut *mut c_void) -> i32; +type NotifyNotificationSetTimeout = unsafe extern "C" fn(*mut c_void, i32); -pub struct Notification { - _lib: Library, // << keep the library alive +/// Represents an uninitialized notification system +struct NotificationSystem { + _lib: Library, notify_init: UnixSymbol, notify_uninit: UnixSymbol, notify_notification_new: UnixSymbol, @@ -16,129 +22,104 @@ pub struct Notification { notify_notification_set_timeout: UnixSymbol, } -type NotifyInit = unsafe extern "C" fn(*const i8) -> bool; -type NotifyUninit = unsafe extern "C" fn(); +/// Represents an initialized notification system with working notifications +pub struct InitializedNotificationSystem { + inner: Arc, + _marker: PhantomData<()>, +} -type NotifyNotificationNew = unsafe extern "C" fn( - summary: *const c_char, - body: *const c_char, - icon: *const c_char, -) -> *mut std::os::raw::c_void; - -type NotifyNotificationShow = unsafe extern "C" fn( - *mut c_void, // NotifyNotification* - *mut *mut c_void, // GError** -) -> i32; - -type NotifyNotificationSetTimeout = unsafe extern "C" fn( - notification: *mut std::os::raw::c_void, // NotifyNotification* - timeout: i32, // gint -); - -impl Notification { - /// Try to load `libnotify.so.4` at runtime. - pub unsafe fn new() -> Option { +impl NotificationSystem { + pub fn new() -> Result { unsafe { - let ( - lib, - notify_init, - notify_uninit, - notify_notification_new, - notify_notification_show, - notify_notification_set_timeout, - ) = { - // 1) Load the library - let lib = Library::new("libnotify.so.4").ok()?; + let lib = Library::new("libnotify.so.4")?; - // 2) Lookup each symbol into its own local - let init_sym: Symbol = lib.get(b"notify_init\0").ok()?; - let uninit_sym: Symbol = lib.get(b"notify_uninit\0").ok()?; - let notify_notification_new_sym: Symbol = - lib.get(b"notify_notification_new\0").ok()?; - let notify_notification_show_sym: Symbol = - lib.get(b"notify_notification_show\0").ok()?; - let notify_notification_set_timeout_sym: Symbol = - lib.get(b"notify_notification_set_timeout\0").ok()?; - - // 3) Transmute their lifetimes to 'static now that we own `lib` forever - let notify_init = init_sym.into_raw(); - let notify_uninit = uninit_sym.into_raw(); - let notify_notification_new = notify_notification_new_sym.into_raw(); - let notify_notification_show = notify_notification_show_sym.into_raw(); - let notify_notification_set_timeout = - notify_notification_set_timeout_sym.into_raw(); - - ( - lib, - notify_init, - notify_uninit, - notify_notification_new, - notify_notification_show, - notify_notification_set_timeout, - ) - }; - - // 3) Now that the borrows are done, move `lib` and the symbols into your struct - Some(Self { + Ok(Self { + notify_init: lib.get::(b"notify_init\0")?.into_raw(), + notify_uninit: lib.get::(b"notify_uninit\0")?.into_raw(), + notify_notification_new: lib + .get::(b"notify_notification_new\0")? + .into_raw(), + notify_notification_show: lib + .get::(b"notify_notification_show\0")? + .into_raw(), + notify_notification_set_timeout: lib + .get::(b"notify_notification_set_timeout\0")? + .into_raw(), _lib: lib, - notify_init, - notify_uninit, - notify_notification_new, - notify_notification_show, - notify_notification_set_timeout, }) } } - pub unsafe fn init(&self, app_name: &str) -> bool { + /// Initialize the notification system. Consumes the uninitialized version + /// and returns an initialized version. + pub fn init(self, app_name: &str) -> Result { + let app_name = CString::new(app_name)?; unsafe { - trace!("Notify::init({app_name:?})"); - let cstr = CString::new(app_name).unwrap(); - (self.notify_init)(cstr.as_ptr()) + if (self.notify_init)(app_name.as_ptr()) { + Ok(InitializedNotificationSystem { + inner: Arc::new(self), // Wrap self in Arc + _marker: PhantomData, + }) + } else { + panic!("notify_init failed") // Convert to proper error type + } } } +} - pub unsafe fn uninit(&self) { - unsafe { - trace!("Notify::uninit()"); - (self.notify_uninit)(); - } +impl Drop for NotificationSystem { + fn drop(&mut self) { + // Safety: Last reference to the library is being dropped + unsafe { (self.notify_uninit)() }; } +} - pub unsafe fn notify_notification_set_timeout( - &self, - notification: *mut std::os::raw::c_void, - timeout: i32, - ) { - trace!("Notify::notify_notification_set_timeout({notification:?}, {timeout})"); - unsafe { - (self.notify_notification_set_timeout)(notification, timeout); - } +impl InitializedNotificationSystem { + pub fn new(app_name: &str) -> Result> { + let lib = NotificationSystem::new()?; + Ok(lib.init(app_name)?) } - - pub unsafe fn notify_notification_new( + pub fn show_notification( &self, summary: &str, body: &str, icon: &str, - ) -> *mut std::os::raw::c_void { - unsafe { - trace!("Notify::notify_notification_new({summary:?}, {body:?}, {icon:?})"); - let summary = CString::new(summary).unwrap(); - let body = CString::new(body).unwrap(); - let icon = CString::new(icon).unwrap(); - (self.notify_notification_new)(summary.as_ptr(), body.as_ptr(), icon.as_ptr()) - } - } + timeout_ms: i32, + ) -> Result<(), NotificationError> { + let summary = CString::new(summary)?; + let body = CString::new(body)?; + let icon = CString::new(icon)?; - pub unsafe fn notify_notification_show( - &self, - notification: *mut std::os::raw::c_void, - error: *mut *mut c_void, - ) { unsafe { - trace!("Notify::notify_notification_show({notification:?}, {error:?})"); - (self.notify_notification_show)(notification, error); + let notification = (self.inner.notify_notification_new)( + summary.as_ptr(), + body.as_ptr(), + icon.as_ptr(), + ); + + (self.inner.notify_notification_set_timeout)(notification, timeout_ms); + + // Handle potential errors from the C library + let mut error_ptr: *mut c_void = std::ptr::null_mut(); + let success = (self.inner.notify_notification_show)(notification, &mut error_ptr); + + if success != 0 { + Ok(()) + } else { + Err(NotificationError::NativeError(error_ptr)) + } } } } + +#[derive(Debug)] +pub enum NotificationError { + NullByteInString(NulError), + NativeError(*mut c_void), +} + +impl From for NotificationError { + fn from(e: NulError) -> Self { + NotificationError::NullByteInString(e) + } +}