Small rewrite:

* Use error-chain for errors and expose them
* Don't use Context object, since the lifetimes get us in trouble
  when used with gtk-rs, which has callback closures with static
  lifetime. Instead we just expose notify_init()/notify_uninit() and
  panic when functions are called before notify_init().
This commit is contained in:
Julian Ospald 2017-07-10 01:01:36 +02:00
parent 172cbd5143
commit fe4d803efe
No known key found for this signature in database
GPG Key ID: 511B62C09D50CD28
4 changed files with 174 additions and 187 deletions

View File

@ -11,6 +11,7 @@ documentation = "http://crumblingstatue.github.io/doc/libnotify/libnotify/"
keywords = ["libnotify", "notification"] keywords = ["libnotify", "notification"]
[dependencies] [dependencies]
error-chain = "0.10.0"
libnotify-sys = "^0.5.0" libnotify-sys = "^0.5.0"
gdk-pixbuf = "^0.1.3" gdk-pixbuf = "^0.1.3"
gdk-pixbuf-sys = "^0.3.4" gdk-pixbuf-sys = "^0.3.4"

View File

@ -1,12 +1,18 @@
extern crate libnotify; extern crate libnotify;
fn main() { fn main() {
// Create a libnotify context // Init libnotify
let notify = libnotify::Context::new("myapp").unwrap(); libnotify::init("myapp").unwrap();
// Create a new notification and show it // Create a new notification and show it
let n = notify.new_notification("Summary", Some("Optional Body"), None) let n = libnotify::Notification::new_notification("Summary",
.unwrap(); Some("Optional Body"),
None).unwrap();
// Show the notification
n.show().unwrap(); n.show().unwrap();
// You can also use the .show() convenience method on the context // You can also use the .show() convenience method on the context
notify.show("I am another notification", None, None).unwrap(); n.update("I am another notification", None, None).unwrap();
// Show the update notification
n.show().unwrap();
// We are done, deinit
libnotify::uninit();
} }

33
src/errors.rs Normal file
View File

@ -0,0 +1,33 @@
use std;
/// Error variants for Libnotify.
error_chain! {
foreign_links {
Utf8(std::str::Utf8Error);
Nul(std::ffi::NulError);
}
errors {
UnknownError {
description("Unknown Error")
display("Unknown Error")
}
InvalidParameter {
description("Invalid parameter")
display("Invalid parameter")
}
NotificationShowError(t: String) {
description("Failed to show notification")
display("Failed to show notification: {}", t)
}
NotifyAlreadyInitialized {
description("Notify system already initialized")
display("Notify system already initialized")
}
NotifyInitError {
description("Notify system initialization error")
display("Notify system initialization error")
}
}
}

View File

@ -19,6 +19,8 @@
#![warn(missing_docs)] #![warn(missing_docs)]
#[macro_use]
extern crate error_chain;
extern crate gdk_pixbuf; extern crate gdk_pixbuf;
extern crate gdk_pixbuf_sys; extern crate gdk_pixbuf_sys;
extern crate glib; extern crate glib;
@ -26,14 +28,24 @@ extern crate glib_sys;
extern crate gtypes; extern crate gtypes;
extern crate libnotify_sys as sys; extern crate libnotify_sys as sys;
pub mod errors;
use errors::*;
use gdk_pixbuf_sys::GdkPixbuf; use gdk_pixbuf_sys::GdkPixbuf;
use glib::translate::*; use glib::translate::ToGlibPtr;
use gtypes::{TRUE, FALSE}; use gtypes::{TRUE, FALSE};
use std::error::Error; use std::ffi::{CStr, CString};
use std::ffi::{self, CStr, CString};
use std::fmt;
use std::marker::PhantomData;
use std::os::raw::c_int; use std::os::raw::c_int;
use std::os::raw::c_char;
macro_rules! init_panic {
() => {
if !is_initted() {
panic!("Notify system not initialized, invalid call of function");
}
}
}
/// The urgency level of the notification. /// The urgency level of the notification.
@ -67,94 +79,14 @@ impl From<Urgency> for sys::NotifyUrgency {
} }
/// Error that can happen on context creation
#[derive(Debug)]
pub enum ContextCreationError { /// A passive pop-up notification
/// Context already exists. pub struct Notification {
AlreadyExists, handle: *mut sys::NotifyNotification,
/// Failed to initialize libnotify.
InitError,
/// A nul byte was found in the provided string.
NulError(ffi::NulError),
} }
impl fmt::Display for ContextCreationError { impl Notification {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ContextCreationError::*;
match *self {
AlreadyExists => write!(f, "A Libnotify context already exists."),
InitError => write!(f, "Failed to initialize libnotify."),
NulError(ref e) => write!(f, "{}", e),
}
}
}
impl From<ffi::NulError> for ContextCreationError {
fn from(src: ffi::NulError) -> Self {
ContextCreationError::NulError(src)
}
}
#[derive(Debug)]
/// An error that can happen when attempting to create a notification.
pub enum NotificationCreationError {
/// A nul byte was found in the provided string.
NulError(ffi::NulError),
/// An unknown error happened.
Unknown,
/// Invalid parameter passed to a glib function
InvalidParameter,
}
impl fmt::Display for NotificationCreationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use NotificationCreationError::*;
match *self {
NulError(ref e) => write!(f, "{}", e),
Unknown => write!(f, "Unknown error"),
InvalidParameter => write!(f, "An invalid parameter was passed"),
}
}
}
impl From<ffi::NulError> for NotificationCreationError {
fn from(src: ffi::NulError) -> Self {
NotificationCreationError::NulError(src)
}
}
impl Error for NotificationCreationError {
fn description(&self) -> &str {
"notification creation error"
}
}
/// The context which within libnotify operates.
///
/// Only one context can exist at a time.
pub struct Context;
impl Context {
/// Create a new context
///
/// Arguments:
///
/// - app_name: The name of the application using the context
pub fn new(app_name: &str) -> Result<Context, ContextCreationError> {
unsafe {
if sys::notify_is_initted() == TRUE {
return Err(ContextCreationError::AlreadyExists);
}
let app_name = try!(CString::new(app_name));
if sys::notify_init(app_name.as_ptr()) == FALSE {
return Err(ContextCreationError::InitError);
}
}
Ok(Context)
}
/// Creates a new Notification. /// Creates a new Notification.
/// ///
/// Arguments: /// Arguments:
@ -162,15 +94,14 @@ impl Context {
/// - summary: Required summary text /// - summary: Required summary text
/// - body: Optional body text /// - body: Optional body text
/// - icon: Optional icon theme icon name or filename /// - icon: Optional icon theme icon name or filename
pub fn new_notification pub fn new_notification(summary: &str,
(&self, body: Option<&str>,
summary: &str, icon: Option<&str>)
body: Option<&str>, -> Result<Notification> {
icon: Option<&str>) init_panic!();
-> Result<Notification, NotificationCreationError> { let summary = CString::new(summary)?;
let summary = try!(CString::new(summary));
let body = match body { let body = match body {
Some(body) => Some(try!(CString::new(body))), Some(body) => Some(CString::new(body)?),
None => None, None => None,
}; };
let body_ptr = match body { let body_ptr = match body {
@ -178,7 +109,7 @@ impl Context {
None => std::ptr::null(), None => std::ptr::null(),
}; };
let icon = match icon { let icon = match icon {
Some(icon) => Some(try!(CString::new(icon))), Some(icon) => Some(CString::new(icon)?),
None => None, None => None,
}; };
let icon_ptr = match icon { let icon_ptr = match icon {
@ -191,61 +122,26 @@ impl Context {
body_ptr, body_ptr,
icon_ptr); icon_ptr);
if n.is_null() { if n.is_null() {
return Err(NotificationCreationError::Unknown); bail!(ErrorKind::UnknownError);
} }
Ok(Notification { Ok(Notification { handle: n })
handle: n,
_phantom: PhantomData,
})
} }
} }
/// Show a notification.
///
/// This is a convenience method that creates a new notification,
/// and shows it in one step.
pub fn show(&self,
summary: &str,
body: Option<&str>,
icon: Option<&str>)
-> Result<(), Box<Error>> {
let notif = try!(self.new_notification(summary, body, icon));
try!(notif.show());
Ok(())
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe {
sys::notify_uninit();
}
}
}
/// A passive pop-up notification
pub struct Notification<'a> {
handle: *mut sys::NotifyNotification,
_phantom: PhantomData<&'a Context>,
}
impl<'a> Notification<'a> {
/// Tells the notification server to display the notification /// Tells the notification server to display the notification
/// on the screen. /// on the screen.
pub fn show(&'a self) -> Result<(), NotificationShowError> { pub fn show(&self) -> Result<()> {
init_panic!();
unsafe { unsafe {
let mut err: *mut glib_sys::GError = std::ptr::null_mut(); let mut err: *mut glib_sys::GError = std::ptr::null_mut();
sys::notify_notification_show(self.handle, &mut err); sys::notify_notification_show(self.handle, &mut err);
if !err.is_null() { if !err.is_null() {
let result = Err(NotificationShowError { let msg = CStr::from_ptr((*err).message)
message: CStr::from_ptr((*err).message) .to_string_lossy()
.to_string_lossy() .into_owned();
.into_owned(),
});
glib_sys::g_error_free(err); glib_sys::g_error_free(err);
return result; bail!(ErrorKind::NotificationShowError(msg));
} }
Ok(()) Ok(())
} }
@ -253,7 +149,8 @@ impl<'a> Notification<'a> {
/// Set the notification timeout. Note that the server might ignore /// Set the notification timeout. Note that the server might ignore
/// the timeout. /// the timeout.
pub fn set_notification_timeout(&'a self, timeout: i32) { pub fn set_notification_timeout(&self, timeout: i32) {
init_panic!();
let _timeout: c_int = From::from(timeout); let _timeout: c_int = From::from(timeout);
unsafe { sys::notify_notification_set_timeout(self.handle, _timeout) } unsafe { sys::notify_notification_set_timeout(self.handle, _timeout) }
@ -262,14 +159,15 @@ impl<'a> Notification<'a> {
/// Updates the notification text and icon. This won't send the update /// 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 /// out and display it on the screen. For that, you will need to
/// call `.show()`. /// call `.show()`.
pub fn update(&'a self, pub fn update(&self,
summary: &str, summary: &str,
body: Option<&str>, body: Option<&str>,
icon: Option<&str>) icon: Option<&str>)
-> Result<(), NotificationCreationError> { -> Result<()> {
let summary = try!(CString::new(summary)); init_panic!();
let summary = CString::new(summary)?;
let body = match body { let body = match body {
Some(body) => Some(try!(CString::new(body))), Some(body) => Some(CString::new(body)?),
None => None, None => None,
}; };
let body_ptr = match body { let body_ptr = match body {
@ -277,7 +175,7 @@ impl<'a> Notification<'a> {
None => std::ptr::null(), None => std::ptr::null(),
}; };
let icon = match icon { let icon = match icon {
Some(icon) => Some(try!(CString::new(icon))), Some(icon) => Some(CString::new(icon)?),
None => None, None => None,
}; };
let icon_ptr = match icon { let icon_ptr = match icon {
@ -291,7 +189,7 @@ impl<'a> Notification<'a> {
body_ptr, body_ptr,
icon_ptr); icon_ptr);
if b == FALSE { if b == FALSE {
return Err(NotificationCreationError::InvalidParameter); bail!(ErrorKind::InvalidParameter);
} }
} }
@ -300,11 +198,12 @@ impl<'a> Notification<'a> {
/// Sets a hint for `key` with value `value`. If value is `None`, /// Sets a hint for `key` with value `value`. If value is `None`,
/// then key is unset. /// then key is unset.
pub fn set_hint(&'a self, pub fn set_hint(&self,
key: &str, key: &str,
value: Option<glib::variant::Variant>) value: Option<glib::variant::Variant>)
-> Result<(), NotificationCreationError> { -> Result<()> {
let key = try!(CString::new(key)); init_panic!();
let key = CString::new(key)?;
let gvalue: *mut glib_sys::GVariant = { let gvalue: *mut glib_sys::GVariant = {
match value { match value {
@ -322,38 +221,40 @@ impl<'a> Notification<'a> {
/// Sets the category of this notification. This can be used by the /// Sets the category of this notification. This can be used by the
/// notification server to filter or display the data in a certain way. /// notification server to filter or display the data in a certain way.
pub fn set_category(&'a self, category: &str) -> Result<(), NotificationCreationError> { pub fn set_category(&self, category: &str) -> Result<()> {
let category = try!(CString::new(category)); init_panic!();
let category = CString::new(category)?;
unsafe { unsafe {
sys::notify_notification_set_category(self.handle, sys::notify_notification_set_category(self.handle,
category.as_ptr()); category.as_ptr());
} }
return Ok(()); return Ok(());
} }
/// Sets the urgency level of this notification. /// Sets the urgency level of this notification.
pub fn set_urgency(&'a self, urgency: Urgency) { pub fn set_urgency(&self, urgency: Urgency) {
init_panic!();
let urgency: sys::NotifyUrgency = From::from(urgency); let urgency: sys::NotifyUrgency = From::from(urgency);
unsafe { unsafe {
sys::notify_notification_set_urgency(self.handle, sys::notify_notification_set_urgency(self.handle, urgency);
urgency);
} }
} }
/// Sets the image in the notification from a Pixbuf. /// Sets the image in the notification from a Pixbuf.
pub fn set_image_from_pixbuf(&'a self, pixbuf: &gdk_pixbuf::Pixbuf) { pub fn set_image_from_pixbuf(&self, pixbuf: &gdk_pixbuf::Pixbuf) {
init_panic!();
let pixbuf: *mut GdkPixbuf = pixbuf.to_glib_none().0; let pixbuf: *mut GdkPixbuf = pixbuf.to_glib_none().0;
unsafe { unsafe {
sys::notify_notification_set_image_from_pixbuf(self.handle, sys::notify_notification_set_image_from_pixbuf(self.handle, pixbuf);
pixbuf);
} }
} }
/// Clears all hints from the notification. /// Clears all hints from the notification.
pub fn clear_hints(&'a self) { pub fn clear_hints(&self) {
init_panic!();
unsafe { unsafe {
sys::notify_notification_clear_hints(self.handle); sys::notify_notification_clear_hints(self.handle);
} }
@ -361,20 +262,18 @@ impl<'a> Notification<'a> {
/// Synchronously tells the notification server to hide the /// Synchronously tells the notification server to hide the
/// notification on the screen. /// notification on the screen.
pub fn close(&'a self) -> Result<(), NotificationShowError> { pub fn close(&self) -> Result<()> {
init_panic!();
unsafe { unsafe {
let mut err: *mut glib_sys::GError = std::ptr::null_mut(); let mut err: *mut glib_sys::GError = std::ptr::null_mut();
sys::notify_notification_close(self.handle, sys::notify_notification_close(self.handle, &mut err);
&mut err);
if !err.is_null() { if !err.is_null() {
let result = Err(NotificationShowError { let msg = CStr::from_ptr((*err).message)
message: CStr::from_ptr((*err).message) .to_string_lossy()
.to_string_lossy() .into_owned();
.into_owned(),
});
glib_sys::g_error_free(err); glib_sys::g_error_free(err);
return result; bail!(ErrorKind::NotificationShowError(msg));
} }
} }
return Ok(()); return Ok(());
@ -382,20 +281,68 @@ impl<'a> Notification<'a> {
} }
/// An error that can happen when attempting to show a notification. /// Initialized libnotify. This must be called before any other functions.
#[derive(Debug)] pub fn init(app_name: &str) -> Result<()> {
pub struct NotificationShowError { let app_name = CString::new(app_name)?;
message: String,
unsafe {
if sys::notify_is_initted() == TRUE {
bail!(ErrorKind::NotifyAlreadyInitialized);
}
let app_name = CString::new(app_name)?;
if sys::notify_init(app_name.as_ptr()) == FALSE {
bail!(ErrorKind::NotifyInitError);
}
}
return Ok(());
} }
impl fmt::Display for NotificationShowError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { /// Uninitialized libnotify.
write!(f, "Error showing notification: {}", self.message) /// This should be called when the program no longer needs libnotify for
/// the rest of its lifecycle, typically just before exitting.
pub fn uninit() {
init_panic!();
unsafe {
sys::notify_uninit();
} }
} }
impl Error for NotificationShowError {
fn description(&self) -> &str { /// Gets whether or not libnotify is initialized.
"notification show error" pub fn is_initted() -> bool {
unsafe {
if sys::notify_is_initted() == TRUE {
return true;
} else {
return false;
}
}
}
/// Sets the application name.
pub fn set_app_name(app_name: &str) -> Result<()> {
init_panic!();
let app_name = CString::new(app_name)?;
unsafe {
sys::notify_set_app_name(app_name.as_ptr());
}
return Ok(());
}
/// Gets the application name registered.
pub fn get_app_name() -> Result<String> {
init_panic!();
unsafe {
let c_name: *const c_char = sys::notify_get_app_name();
let c_str = CStr::from_ptr(c_name);
let string = c_str.to_str()?;
return Ok(String::from(string));
} }
} }