17 Commits

Author SHA1 Message Date
043b6e93c8 fix: compile only on linux 2026-10-03 21:24:05 +02:00
0e2e97bf66 feat: add optional arguments to the builder 2026-10-03 20:50:56 +02:00
025ab6af68 fix: remove clap from timeout 2026-10-03 20:25:33 +02:00
034a302f3b docs: update readme with installation instruction correction 2026-10-03 20:19:51 +02:00
Szymon
987cd449c5 Merge pull request #5 from szymon-jozef/feat/clap
feat: add clap feature
2026-10-03 20:16:49 +02:00
aa12ffe8ff feat: add clap feature 2026-10-03 20:16:12 +02:00
cb9af02a9c docs: update readme 2026-10-03 13:12:21 +02:00
7ae93dca57 fix: change show method to return libnotify error 2026-10-03 13:11:01 +02:00
Szymon
f37bf12370 Merge pull request #4 from szymon-jozef/feat/errors
feat/errors
2026-10-03 13:05:25 +02:00
f850cea72a chore: version bump and deps update 2026-10-03 13:02:43 +02:00
37787a5594 feat: implement all error values 2026-10-03 12:58:08 +02:00
a231712057 feat: add libnotify error 2026-10-03 12:43:34 +02:00
Szymon
21a09f9722 Merge pull request #3 from szymon-jozef/docs/everything
docs/everything
2026-10-02 15:30:25 +02:00
173d04c39c chore: version bump 2026-10-02 15:29:43 +02:00
6be65a249e docs: write super cool documentation 2026-10-02 15:29:17 +02:00
Szymon
2140751d2f Merge pull request #2 from szymon-jozef/refactor/split-modules
refactor: split modules into separate dirs
2026-10-01 21:35:48 +02:00
b4c8bceb4c refactor: split modules into separate dirs 2026-10-01 21:34:47 +02:00
18 changed files with 466 additions and 162 deletions

120
Cargo.lock generated
View File

@@ -11,6 +11,56 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]] [[package]]
name = "bindgen" name = "bindgen"
version = "0.73.2" version = "0.73.2"
@@ -47,9 +97,9 @@ dependencies = [
[[package]] [[package]]
name = "cfg-expr" name = "cfg-expr"
version = "0.20.9" version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4ece8474b5f766c63426647e7b4b316b67431ade1036a8313cee24a03ae917" checksum = "9ba9e9ec16c447027685b1f897b720e18e9a8afd00bd7332c483537e38086c9f"
dependencies = [ dependencies = [
"smallvec", "smallvec",
"target-lexicon", "target-lexicon",
@@ -72,6 +122,39 @@ dependencies = [
"libloading", "libloading",
] ]
[[package]]
name = "clap"
version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_lex"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -230,10 +313,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "libc" name = "is_terminal_polyfill"
version = "0.2.189" version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "libc"
version = "0.2.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
[[package]] [[package]]
name = "libloading" name = "libloading"
@@ -247,9 +336,10 @@ dependencies = [
[[package]] [[package]]
name = "libnotify-rs" name = "libnotify-rs"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"clap",
"glib", "glib",
"pkg-config", "pkg-config",
"serial_test", "serial_test",
@@ -298,6 +388,12 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.5" version = "0.12.5"
@@ -483,6 +579,12 @@ version = "1.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.6" version = "3.0.6"
@@ -558,6 +660,12 @@ version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "version-compare" name = "version-compare"
version = "0.2.1" version = "0.2.1"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "libnotify-rs" name = "libnotify-rs"
version = "0.2.0" version = "0.3.0"
edition = "2024" edition = "2024"
authors = [ "szymon_jozef"] authors = [ "szymon_jozef"]
description = "Rust bindings for libnotify" description = "Rust bindings for libnotify"
@@ -11,6 +11,9 @@ license = "MIT"
keywords = [ "bindings", "libnotify" ] keywords = [ "bindings", "libnotify" ]
build = "./build.rs" build = "./build.rs"
[dependencies]
clap = {version = "4.6.7", optional = true}
[build-dependencies] [build-dependencies]
pkg-config = "0.3.34" pkg-config = "0.3.34"
bindgen = "0.73.2" bindgen = "0.73.2"
@@ -18,3 +21,6 @@ bindgen = "0.73.2"
[dev-dependencies] [dev-dependencies]
glib = "0.22.10" glib = "0.22.10"
serial_test = "4.0.1" serial_test = "4.0.1"
[features]
clap = [ "dep:clap" ]

View File

@@ -19,14 +19,7 @@
This crate exposes a user friendly (at least I hope), Rust api for [libnotify](https://gitlab.gnome.org/GNOME/libnotify). Made using [bindgen](https://github.com/rust-lang/rust-bindgen). This crate exposes a user friendly (at least I hope), Rust api for [libnotify](https://gitlab.gnome.org/GNOME/libnotify). Made using [bindgen](https://github.com/rust-lang/rust-bindgen).
## Structure It comes with basic libnotify calls bindings and higher level abstraction over it.
Project is split into two modules:
- functions
- notifications
Which resembles libnotify structure. Every function is named similarly to its libnotify counterpart and it does the same things.
There are some helper structs and enums to make it more approachable to rust folks.
## Documentation ## Documentation
Available at [github pages](https://szymon-jozef.github.io/libnotify-rs/libnotify_rs/index.html); Available at [github pages](https://szymon-jozef.github.io/libnotify-rs/libnotify_rs/index.html);
@@ -34,9 +27,11 @@ Available at [github pages](https://szymon-jozef.github.io/libnotify-rs/libnotif
## Add to project ## Add to project
Type in: Type in:
```bash ```bash
cargo add --git "https://github.com/szymon-jozef/libnotify-rs" cargo add --git "https://github.com/szymon-jozef/libnotify-rs" --tag <desired version>
``` ```
Tag argument is optional, but encouraged.
### Nix flake ### Nix flake
For nix users, there is also a flake inside. If you want to use it add this to your flake: For nix users, there is also a flake inside. If you want to use it add this to your flake:
@@ -65,13 +60,15 @@ Refer to [examples dir](./examples)
Sneak peek for making a notification: Sneak peek for making a notification:
### Without my super cool wrapper (:()
```rust ```rust
use libnotify_rs::{ use libnotify_rs::api::{
functions::{init, uninit}, functions::{init, uninit},
notification::Notification, notification::{self, Notification},
}; };
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), LibnotifyError> {
init("I love coffee")?; init("I love coffee")?;
let mut notify = Notification::new( let mut notify = Notification::new(
"Remember to drink coffe!", "Remember to drink coffe!",
@@ -79,8 +76,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
None, None,
)?; )?;
notify.set_timeout(libnotify_rs::notification::Timeout::Custom(1000 * 10)); // 10 secs notify.set_timeout(notification::Timeout::Custom(1000 * 10)); // 10 secs
notify.set_urgency(libnotify_rs::notification::Urgency::Critical); // coffee is very important notify.set_urgency(notification::Urgency::Critical); // coffee is very important
notify.set_category("Coffee")?; notify.set_category("Coffee")?;
notify.show()?; notify.show()?;
@@ -91,6 +88,36 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
``` ```
### With my super cool wrapper (:))
```rust
use libnotify_rs::{builder::NotificationBuilder, context::LibnotifyContext};
/// Builder is recommended way to interact with libnotify notifications
fn main() -> Result<(), LibnotifyError> {
let ctx = LibnotifyContext::new("Coffee addicted reminder")?; // builder requires context
let notification_builder = NotificationBuilder::new(
"Remember about your coffee!",
"Only idiots forget about coffee!",
None,
&ctx,
)?
.set_urgency(libnotify_rs::api::notification::Urgency::Critical)
.set_timeout(libnotify_rs::api::notification::Timeout::Never)
.set_category("Coffee")?;
notification_builder.show()?;
// you don't need to uninitialize libnotify, cause context already does that!
Ok(())
}
```
To test those examples use: To test those examples use:
```bash ```bash
cargo r --example <example name> cargo r --example <example name>
@@ -116,6 +143,3 @@ It didn't write any of the actual code, nor any documentation.
## Roadmap ## Roadmap
I will be happy to fix any bugs that you may find! Just create a new issue, or better yet a pull request. I will be happy to fix any bugs that you may find! Just create a new issue, or better yet a pull request.
I'm thinking about a small wrapper around other code, which would initialize libnotify by itself, cleanup after itself, etc. Maybe someday…

View File

@@ -11,8 +11,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
None, None,
&ctx, &ctx,
)? )?
.set_urgency(libnotify_rs::notification::Urgency::Critical) .set_urgency(libnotify_rs::api::notification::Urgency::Critical)
.set_timeout(libnotify_rs::notification::Timeout::Never) .set_timeout(libnotify_rs::api::notification::Timeout::Never)
.set_category("Coffee")?; .set_category("Coffee")?;
notification_builder.show()?; notification_builder.show()?;

View File

@@ -1,7 +1,7 @@
use glib::MainLoop; use glib::MainLoop;
use libnotify_rs::{ use libnotify_rs::{
functions::{init, uninit}, api::functions::{init, uninit},
notification::Notification, api::notification::Notification,
}; };
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {

View File

@@ -1,4 +1,4 @@
use libnotify_rs::functions::{self, uninit}; use libnotify_rs::api::functions::{self, uninit};
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
functions::init("Morbius")?; functions::init("Morbius")?;

View File

@@ -1,6 +1,6 @@
use libnotify_rs::{ use libnotify_rs::api::{
functions::{init, uninit}, functions::{init, uninit},
notification::Notification, notification::{self, Notification},
}; };
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -11,8 +11,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
None, None,
)?; )?;
notify.set_timeout(libnotify_rs::notification::Timeout::Custom(1000 * 10)); // 10 secs notify.set_timeout(notification::Timeout::Custom(1000 * 10)); // 10 secs
notify.set_urgency(libnotify_rs::notification::Urgency::Critical); // coffee is very important notify.set_urgency(notification::Urgency::Critical); // coffee is very important
notify.set_category("Coffee")?; notify.set_category("Coffee")?;
notify.show()?; notify.show()?;

58
src/api/errors.rs Normal file
View File

@@ -0,0 +1,58 @@
/// Enum mapping every possible error that can happen in this lib, with a nice enum.
#[derive(Debug)]
pub enum LibnotifyError {
/// [Libnotify init](https://gnome.pages.gitlab.gnome.org/libnotify/func.init.html) failed
InitError,
/// `&str` contains a null terminator. Read more at [std documentation](https://doc.rust-lang.org/std/ffi/struct.NulError.html)
NulError(std::ffi::NulError),
/// [Libnotify notification constructor](https://gnome.pages.gitlab.gnome.org/libnotify/ctor.Notification.new.html) failed allocating
NewNotificationError,
/// Custom libnotify error with explanation.
GerrorError(String),
/// Data failed allocating. Could indicate that system doesn't have enough memory
AllocationError,
}
impl std::fmt::Display for LibnotifyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LibnotifyError::InitError => {
write!(f, "Libnotify failed to init")
}
LibnotifyError::NulError(_) => {
write!(f, "Argument contains a null terminator")
}
LibnotifyError::NewNotificationError => {
write!(f, "Notification constructor failed")
}
LibnotifyError::GerrorError(e) => {
write!(f, "Libnotify returned an error: {}", e)
}
LibnotifyError::AllocationError => {
write!(f, "Libnotify failed allocating memory")
}
}
}
}
impl std::error::Error for LibnotifyError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
LibnotifyError::InitError => None,
LibnotifyError::NulError(nul_error) => Some(nul_error),
LibnotifyError::NewNotificationError => None,
LibnotifyError::GerrorError(_) => None,
LibnotifyError::AllocationError => None,
}
}
}
impl From<std::ffi::NulError> for LibnotifyError {
fn from(value: std::ffi::NulError) -> Self {
Self::NulError(value)
}
}

View File

@@ -1,6 +1,8 @@
use crate::utils::c_str_to_rs_str_and_free; use crate::api::errors::LibnotifyError;
use crate::libnotify::*; use super::utils::c_str_to_rs_str_and_free;
use super::libnotify::*;
/* Initting /* Initting
* *
@@ -10,7 +12,7 @@ use crate::libnotify::*;
/// Initialized libnotify. This must be called before any other functions. /// Initialized libnotify. This must be called before any other functions.
/// ///
/// Starting from 0.8, if the provided app_name is NULL, libnotify will try to figure it out from the running application. Before it was not allowed, and was causing libnotify not to be initialized. /// Starting from 0.8, if the provided app_name is NULL, libnotify will try to figure it out from the running application. Before it was not allowed, and was causing libnotify not to be initialized.
pub fn init<'a, T>(app_name: T) -> Result<(), Box<dyn std::error::Error>> pub fn init<'a, T>(app_name: T) -> Result<(), LibnotifyError>
where where
T: Into<Option<&'a str>>, T: Into<Option<&'a str>>,
{ {
@@ -24,7 +26,7 @@ where
) )
} == 0 } == 0
{ {
return Err("Libnotify init failed".into()); return Err(LibnotifyError::InitError);
} }
Ok(()) Ok(())
@@ -116,6 +118,7 @@ pub fn get_server_caps() -> Option<Vec<String>> {
if vec.is_empty() { None } else { Some(vec) } if vec.is_empty() { None } else { Some(vec) }
} }
/// Contains server information. Refer to [libnotify get_server_info docs](https://gnome.pages.gitlab.gnome.org/libnotify/func.get_server_info.html) to better understand specific fields
pub struct ServerInfo { pub struct ServerInfo {
/// A location to store the server name, or `None` /// A location to store the server name, or `None`
pub name: Option<String>, pub name: Option<String>,
@@ -160,7 +163,7 @@ pub fn get_server_info() -> Option<ServerInfo> {
*/ */
/// Sets the application name /// Sets the application name
pub fn set_app_name(app_name: &str) -> Result<(), std::ffi::NulError> { pub fn set_app_name(app_name: &str) -> Result<(), LibnotifyError> {
let app_name_c = std::ffi::CString::new(app_name)?; let app_name_c = std::ffi::CString::new(app_name)?;
unsafe { notify_set_app_name(app_name_c.as_ptr()) }; unsafe { notify_set_app_name(app_name_c.as_ptr()) };
@@ -171,7 +174,7 @@ pub fn set_app_name(app_name: &str) -> Result<(), std::ffi::NulError> {
/// Available since: 0.8.4 /// Available since: 0.8.4
/// ///
/// app_icon is icon name or path to an image /// app_icon is icon name or path to an image
pub fn set_app_icon(app_icon: &str) -> Result<(), std::ffi::NulError> { pub fn set_app_icon(app_icon: &str) -> Result<(), LibnotifyError> {
let app_icon_c = std::ffi::CString::new(app_icon)?; let app_icon_c = std::ffi::CString::new(app_icon)?;
unsafe { notify_set_app_icon(app_icon_c.as_ptr()) }; unsafe { notify_set_app_icon(app_icon_c.as_ptr()) };
@@ -194,11 +197,10 @@ mod tests {
#[test] #[test]
fn test_bad_init() { fn test_bad_init() {
assert!( assert!(matches!(
init("Mor\0ius") init("Mor\0ius").unwrap_err(),
.unwrap_err() LibnotifyError::NulError(_)
.is::<std::ffi::c_str::NulError>() ));
);
} }
#[test] #[test]

8
src/api/mod.rs Normal file
View File

@@ -0,0 +1,8 @@
/// Custom error types
pub mod errors;
/// Same as [libnotify global functions](https://gnome.pages.gitlab.gnome.org/libnotify/index.html#functions)
pub mod functions;
mod libnotify;
/// Same as [libnotify notification](https://gnome.pages.gitlab.gnome.org/libnotify/class.Notification.html)
pub mod notification;
mod utils;

View File

@@ -1,13 +1,21 @@
use crate::libnotify::*; use crate::api::errors::LibnotifyError;
use super::libnotify::*;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
/// Notification urgency level. Refer to [libnotify docs](https://gnome.pages.gitlab.gnome.org/libnotify/enum.Urgency.html) for more info
pub enum Urgency { pub enum Urgency {
/// Low urgency. Used for unimportant notifications
Low, Low,
/// Normal urgency. Used for most standard notifications
Normal, Normal,
/// Critical urgency. Used for very important notifications
Critical, Critical,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
/// Reason why notification was closed. Refer to [libnotify docs](https://gnome.pages.gitlab.gnome.org/libnotify/enum.ClosedReason.html)
pub enum ClosedReason { pub enum ClosedReason {
/// Notification not closed /// Notification not closed
Unset, Unset,
@@ -29,25 +37,40 @@ pub enum ClosedReason {
/// Used in set hint. /// Used in set hint.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum HintValue { pub enum HintValue {
#[allow(missing_docs)]
String(String), String(String),
#[allow(missing_docs)]
Int32(i32), Int32(i32),
#[allow(missing_docs)]
Boolean(bool), Boolean(bool),
} }
/// The timeout in milliseconds /// Notification timeout, until close
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Timeout { pub enum Timeout {
#[allow(missing_docs)]
Default, Default,
#[allow(missing_docs)]
Never, Never,
/// Custom timeout in milliseconds
Custom(i32), Custom(i32),
} }
/// Represents one notification. Wraps most of [libnotify::notifaction](https://gnome.pages.gitlab.gnome.org/libnotify/class.Notification.html) functions.
pub struct Notification { pub struct Notification {
inner: *mut NotifyNotification, inner: *mut NotifyNotification,
} }
impl<'a> Notification { impl<'a> Notification {
pub fn new<T, Y>(summary: &str, body: T, icon: Y) -> Result<Self, Box<dyn std::error::Error>> /// Create new notification.
///
/// # Errors
/// This function will return an error if the supplied bytes contain an
/// internal 0 byte. The [`NulError`](https://doc.rust-lang.org/stable/alloc/ffi/c_str/struct.NulError.html) returned will contain the bytes as well as
/// the position of the nul byte.
///
/// On libnotify fail it will return Err(&str.into). May change in the future.
pub fn new<T, Y>(summary: &str, body: T, icon: Y) -> Result<Self, LibnotifyError>
where where
T: Into<Option<&'a str>>, T: Into<Option<&'a str>>,
Y: Into<Option<&'a str>>, Y: Into<Option<&'a str>>,
@@ -67,18 +90,27 @@ impl<'a> Notification {
}; };
if inner.is_null() { if inner.is_null() {
return Err("notify_notification_new returned null".into()); return Err(LibnotifyError::NewNotificationError);
} }
Ok(Self { inner }) Ok(Self { inner })
} }
/// Add callback to notification.
///
/// # Args
/// `action` – action identifier. `default` will work for most use cases. Other text will be
/// displayed as button names.
///
/// `label` – label for user
///
/// `callback` - any closure that accepts &str as an argument, where &str is action name.
pub fn add_action<F>( pub fn add_action<F>(
&mut self, &mut self,
action: &str, action: &str,
label: &str, label: &str,
callback: F, callback: F,
) -> Result<(), Box<dyn std::error::Error>> ) -> Result<(), LibnotifyError>
where where
F: Fn(&str) + 'static, F: Fn(&str) + 'static,
{ {
@@ -115,19 +147,19 @@ impl<'a> Notification {
} }
/// Synchronously tells the notification server to hide the notification on the screen /// Synchronously tells the notification server to hide the notification on the screen
pub fn close(&self) -> Result<(), Box<dyn std::error::Error>> { pub fn close(&self) -> Result<(), LibnotifyError> {
let mut gerror = std::ptr::null_mut() as *mut GError; let mut gerror = std::ptr::null_mut() as *mut GError;
if unsafe { notify_notification_close(self.inner, &mut gerror) } == 0 { if unsafe { notify_notification_close(self.inner, &mut gerror) } == 0 {
if gerror.is_null() { if gerror.is_null() {
return Err("Unknown error".into()); return Err(LibnotifyError::AllocationError);
} }
let e = unsafe { *gerror }.message; let e = unsafe { *gerror }.message;
if e.is_null() { if e.is_null() {
unsafe { g_error_free(gerror) }; unsafe { g_error_free(gerror) };
return Err("Unknown error".into()); return Err(LibnotifyError::AllocationError);
} }
let e_str = unsafe { std::ffi::CStr::from_ptr(e) } let e_str = unsafe { std::ffi::CStr::from_ptr(e) }
@@ -136,7 +168,7 @@ impl<'a> Notification {
unsafe { g_error_free(gerror) }; unsafe { g_error_free(gerror) };
return Err(e_str.into()); return Err(LibnotifyError::GerrorError(e_str));
} }
Ok(()) Ok(())
@@ -162,7 +194,7 @@ impl<'a> Notification {
/// `set_app_icon()`. /// `set_app_icon()`.
/// ///
/// Available since: 0.8.4 /// Available since: 0.8.4
pub fn set_app_icon(&mut self, app_icon: &str) -> Result<(), std::ffi::NulError> { pub fn set_app_icon(&mut self, app_icon: &str) -> Result<(), LibnotifyError> {
let app_icon_c = std::ffi::CString::new(app_icon)?; let app_icon_c = std::ffi::CString::new(app_icon)?;
unsafe { notify_notification_set_app_icon(self.inner, app_icon_c.as_ptr()) }; unsafe { notify_notification_set_app_icon(self.inner, app_icon_c.as_ptr()) };
@@ -174,7 +206,7 @@ impl<'a> 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(). /// 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 /// Available since: 0.7.3
pub fn set_app_name(&mut self, app_name: &str) -> Result<(), std::ffi::NulError> { pub fn set_app_name(&mut self, app_name: &str) -> Result<(), LibnotifyError> {
let app_name_c = std::ffi::CString::new(app_name)?; let app_name_c = std::ffi::CString::new(app_name)?;
unsafe { notify_notification_set_app_name(self.inner, app_name_c.as_ptr()) }; unsafe { notify_notification_set_app_name(self.inner, app_name_c.as_ptr()) };
@@ -184,7 +216,7 @@ impl<'a> Notification {
/// Sets the category of this notification. /// Sets the category of this notification.
/// ///
/// This can be used by the notification server to filter or display the data in a certain way /// 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> { pub fn set_category(&mut self, category: &str) -> Result<(), LibnotifyError> {
let category_c = std::ffi::CString::new(category)?; let category_c = std::ffi::CString::new(category)?;
unsafe { notify_notification_set_category(self.inner, category_c.as_ptr()) }; unsafe { notify_notification_set_category(self.inner, category_c.as_ptr()) };
@@ -194,11 +226,7 @@ impl<'a> Notification {
/// Sets a hint for key with value value /// Sets a hint for key with value value
/// ///
/// Available since: 0.6 /// Available since: 0.6
pub fn set_hint( pub fn set_hint(&mut self, key: &str, value: HintValue) -> Result<(), LibnotifyError> {
&mut self,
key: &str,
value: HintValue,
) -> Result<(), Box<dyn std::error::Error>> {
let key_c = std::ffi::CString::new(key)?; let key_c = std::ffi::CString::new(key)?;
let value_c = match value { let value_c = match value {
@@ -213,7 +241,7 @@ impl<'a> Notification {
}; };
if value_c.is_null() { if value_c.is_null() {
return Err("Couldn't create hint value".into()); return Err(LibnotifyError::AllocationError);
} }
unsafe { notify_notification_set_hint(self.inner, key_c.as_ptr(), value_c) }; unsafe { notify_notification_set_hint(self.inner, key_c.as_ptr(), value_c) };
@@ -246,19 +274,19 @@ impl<'a> Notification {
} }
/// Tells the notification server to display the notification on the screen /// Tells the notification server to display the notification on the screen
pub fn show(&self) -> Result<(), Box<dyn std::error::Error>> { pub fn show(&self) -> Result<(), LibnotifyError> {
let mut gerror = std::ptr::null_mut() as *mut GError; let mut gerror = std::ptr::null_mut() as *mut GError;
if unsafe { notify_notification_show(self.inner, &mut gerror) } == 0 { if unsafe { notify_notification_show(self.inner, &mut gerror) } == 0 {
if gerror.is_null() { if gerror.is_null() {
return Err("Unknown error".into()); return Err(LibnotifyError::AllocationError);
} }
let err_msg = unsafe { *gerror }.message; let err_msg = unsafe { *gerror }.message;
if err_msg.is_null() { if err_msg.is_null() {
unsafe { g_error_free(gerror) }; unsafe { g_error_free(gerror) };
return Err("Unknown error".into()); return Err(LibnotifyError::AllocationError);
} }
let err_msg_str = unsafe { std::ffi::CStr::from_ptr(err_msg) } let err_msg_str = unsafe { std::ffi::CStr::from_ptr(err_msg) }
@@ -266,7 +294,7 @@ impl<'a> Notification {
.to_string(); .to_string();
unsafe { g_error_free(gerror) }; unsafe { g_error_free(gerror) };
return Err(err_msg_str.into()); return Err(LibnotifyError::GerrorError(err_msg_str));
} }
Ok(()) Ok(())

View File

@@ -7,7 +7,7 @@ pub unsafe fn c_str_to_rs_str_and_free(c_str: *mut std::os::raw::c_char) -> Opti
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
unsafe { crate::libnotify::g_free(c_str as *mut std::ffi::c_void) }; unsafe { super::libnotify::g_free(c_str as *mut std::ffi::c_void) };
Some(normal_string) Some(normal_string)
} }

View File

@@ -1,85 +0,0 @@
use crate::{
context::LibnotifyContext,
notification::{HintValue, Notification, Timeout, Urgency},
};
pub struct NotificationBuilder<'a> {
notification: Notification,
_context: &'a LibnotifyContext,
}
impl<'a> NotificationBuilder<'a> {
pub fn new<T, Y>(
summary: &str,
body: T,
icon: Y,
context: &'a LibnotifyContext,
) -> Result<Self, Box<dyn std::error::Error>>
where
T: Into<Option<&'a str>>,
Y: Into<Option<&'a str>>,
{
let notification = Notification::new(summary, body, icon)?;
Ok(Self {
notification,
_context: context,
})
}
pub fn set_app_name(mut self, app_name: &str) -> Result<Self, std::ffi::NulError> {
self.notification.set_app_name(app_name)?;
Ok(self)
}
pub fn set_app_icon(mut self, app_icon: &str) -> Result<Self, std::ffi::NulError> {
self.notification.set_app_icon(app_icon)?;
Ok(self)
}
pub fn set_category(mut self, category: &str) -> Result<Self, std::ffi::NulError> {
self.notification.set_category(category)?;
Ok(self)
}
pub fn set_hint(
mut self,
key: &str,
value: HintValue,
) -> Result<Self, Box<dyn std::error::Error>> {
self.notification.set_hint(key, value)?;
Ok(self)
}
pub fn set_timeout(mut self, timeout: Timeout) -> Self {
self.notification.set_timeout(timeout);
self
}
pub fn set_urgency(mut self, urgency: Urgency) -> Self {
self.notification.set_urgency(urgency);
self
}
pub fn add_action<F>(
mut self,
action: &str,
label: &str,
callback: F,
) -> Result<Self, Box<dyn std::error::Error>>
where
F: Fn(&str) + 'static,
{
self.notification.add_action(action, label, callback)?;
Ok(self)
}
pub fn show(&self) -> Result<(), Box<dyn std::error::Error>> {
self.notification.show()?;
Ok(())
}
pub fn close(&self) -> Result<(), Box<dyn std::error::Error>> {
self.notification.close()?;
Ok(())
}
}

View File

@@ -1,11 +1,28 @@
#![cfg_attr(not(doctest), doc = include_str!("./../README.md"))] //! This crate exposes two modules:
//!
//! # Crate structure
//! ## Api
//! This module contains a basic wrapper for libnotify calls. It just translates data types, etc,
//! so usage is basically the same as libnotify.
//!
//! ## Wrappers
//! This module is a higher level abstraction over api. Recommended to use over api.
//!
//! # Features
//!
//! - clap – enables [clap] support for `Urgency`
//!
#![allow(non_upper_case_globals)] #![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
#![allow(non_snake_case)] #![allow(non_snake_case)]
#![warn(missing_docs)]
pub mod builder; #[cfg(not(target_os = "linux"))]
pub mod context; compile_error!("Libnotify-rs only supports linux");
pub mod functions;
mod libnotify; /// Basic libnotify rust wrapper
pub mod notification; pub mod api;
mod utils; /// Higher level abstraction over api
pub mod wrappers;
pub use wrappers::{builder, context};

123
src/wrappers/builder.rs Normal file
View File

@@ -0,0 +1,123 @@
use crate::{
api::{
errors::LibnotifyError,
notification::{HintValue, Notification, Timeout, Urgency},
},
wrappers::context::LibnotifyContext,
};
/// Higher level abstraction of `Notification`. Allow for builder pattern notification construction.
/// Requires `LibnotifyContext`
pub struct NotificationBuilder<'a> {
notification: Notification,
_context: &'a LibnotifyContext,
}
impl<'a> NotificationBuilder<'a> {
/// Create new NotificationBuilder, with given context.
pub fn new<T, Y>(
summary: &str,
body: T,
icon: Y,
context: &'a LibnotifyContext,
) -> Result<Self, Box<dyn std::error::Error>>
where
T: Into<Option<&'a str>>,
Y: Into<Option<&'a str>>,
{
let notification = Notification::new(summary, body, icon)?;
Ok(Self {
notification,
_context: context,
})
}
/// Sets app name for this specific notification.
pub fn set_app_name<T: Into<Option<&'a str>>>(
mut self,
app_name: T,
) -> Result<Self, LibnotifyError> {
if let Some(app_name) = app_name.into() {
self.notification.set_app_name(app_name)?;
}
Ok(self)
}
/// Sets app icon fot this specific notification.
pub fn set_app_icon<T: Into<Option<&'a str>>>(
mut self,
app_icon: T,
) -> Result<Self, LibnotifyError> {
if let Some(app_icon) = app_icon.into() {
self.notification.set_app_icon(app_icon)?;
}
Ok(self)
}
/// Sets category for this specific notification
pub fn set_category<T: Into<Option<&'a str>>>(
mut self,
category: T,
) -> Result<Self, LibnotifyError> {
if let Some(category) = category.into() {
self.notification.set_category(category)?;
}
Ok(self)
}
/// Set hint for this specific notification
pub fn set_hint(mut self, key: &str, value: HintValue) -> Result<Self, LibnotifyError> {
self.notification.set_hint(key, value)?;
Ok(self)
}
/// Set timeout until close
pub fn set_timeout<T: Into<Option<Timeout>>>(mut self, timeout: T) -> Self {
if let Some(timeout) = timeout.into() {
self.notification.set_timeout(timeout);
}
self
}
/// Set notification urgency level
pub fn set_urgency<T: Into<Option<Urgency>>>(mut self, urgency: T) -> Self {
if let Some(urgency) = urgency.into() {
self.notification.set_urgency(urgency);
}
self
}
/// Add callback
pub fn add_action<F>(
mut self,
action: &str,
label: &str,
callback: F,
) -> Result<Self, Box<dyn std::error::Error>>
where
F: Fn(&str) + 'static,
{
self.notification.add_action(action, label, callback)?;
Ok(self)
}
/// Show notification
///
/// Uses `&self` borrow, so you can use this object later, after showing.
pub fn show(&self) -> Result<(), Box<dyn std::error::Error>> {
self.notification.show()?;
Ok(())
}
/// Close notification synchronously.
pub fn close(&self) -> Result<(), Box<dyn std::error::Error>> {
self.notification.close()?;
Ok(())
}
}

View File

@@ -1,11 +1,20 @@
use crate::functions::{init, is_initted, set_app_icon, set_app_name, uninit}; use crate::api::{
errors::LibnotifyError,
functions::{init, is_initted, set_app_icon, set_app_name, uninit},
};
/// Notification context. Handles libnotify initializing and uninitializing.
pub struct LibnotifyContext {} pub struct LibnotifyContext {}
static IS_CONTEXT_CREATED: std::sync::atomic::AtomicBool = static IS_CONTEXT_CREATED: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false); std::sync::atomic::AtomicBool::new(false);
impl LibnotifyContext { impl LibnotifyContext {
/// Try initializing libnotify
///
/// # Error
/// Returns an error, if libnotify cannot be initialized or if there already exists a context
/// object
pub fn new(app_name: &str) -> Result<LibnotifyContext, Box<dyn std::error::Error>> { pub fn new(app_name: &str) -> Result<LibnotifyContext, Box<dyn std::error::Error>> {
if IS_CONTEXT_CREATED.swap(true, std::sync::atomic::Ordering::SeqCst) { if IS_CONTEXT_CREATED.swap(true, std::sync::atomic::Ordering::SeqCst) {
return Err("You can create only one context at a time".into()); return Err("You can create only one context at a time".into());
@@ -18,12 +27,14 @@ impl LibnotifyContext {
Ok(Self {}) Ok(Self {})
} }
pub fn set_new_name(&self, app_name: &str) -> Result<(), std::ffi::NulError> { /// Sets global app name
pub fn set_new_name(&self, app_name: &str) -> Result<(), LibnotifyError> {
set_app_name(app_name)?; set_app_name(app_name)?;
Ok(()) Ok(())
} }
pub fn set_app_icon(&self, app_icon: &str) -> Result<(), std::ffi::NulError> { /// Sets global app icon
pub fn set_app_icon(&self, app_icon: &str) -> Result<(), LibnotifyError> {
set_app_icon(app_icon)?; set_app_icon(app_icon)?;
Ok(()) Ok(())
} }

4
src/wrappers/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
/// Contains notification builder
pub mod builder;
/// Containx libnotify context
pub mod context;