63 Commits

Author SHA1 Message Date
Szymon
fe00dd8643 Merge pull request #12 from szymon-jozef/szymon-jozef-patch-1
Update issue templates
2026-10-04 19:03:44 +02:00
Szymon
0225f0f2bc Update issue templates 2026-10-04 19:03:35 +02:00
Szymon
1d1a2f7662 Merge pull request #11 from szymon-jozef/chore/onezerozerobump
chore: version bump, metadata update, readme update
2026-10-04 15:44:50 +02:00
16639df081 chore: version bump, metadata update, readme update 2026-10-04 15:43:25 +02:00
Szymon
d839a4a12c Merge pull request #10 from szymon-jozef/docs/examples
docs/examples
2026-10-04 15:36:36 +02:00
df49d4d079 fix: add missing errors section in context docs 2026-10-04 15:34:06 +02:00
4c2acefdfb docs: add doctests to context 2026-10-04 15:29:03 +02:00
71c1dc7bad docs: add doctests to builder 2026-10-04 15:27:48 +02:00
f76226242d docs: write reference for hint and category down 2026-10-04 15:13:04 +02:00
7aa48def4c docs: add doctest to notification::set_timeout 2026-10-04 15:08:47 +02:00
e78c77f12e docs: add doctest to notification::set_urgency 2026-10-04 15:07:52 +02:00
7b37916381 docs: add docs to callback helper functions 2026-10-04 15:05:36 +02:00
fdbf332615 docs: add doctest to notification::update 2026-10-04 15:03:43 +02:00
8172a5f9fb docs: add doctest to notification::show 2026-10-04 15:01:33 +02:00
c231911f6b docs: add doctest to notification::get_close_reasoin 2026-10-04 15:00:13 +02:00
d50bac9e0e docs: add doctest to notification::close 2026-10-04 14:57:55 +02:00
0503fa8e28 docs: add url to icon docs 2026-10-04 14:54:27 +02:00
60026e6fea docs: add doctest to notification::clear_actions 2026-10-04 14:49:37 +02:00
b83c75770a docs: add doctest to notification::add_action 2026-10-04 14:47:13 +02:00
8e88f16912 docs: add doctest to notification::new 2026-10-04 14:43:51 +02:00
db0f9b33a2 fix: lack of url to clap in docs 2026-10-04 14:41:34 +02:00
4acf6a6b87 docs: add doctest to set_app_icon 2026-10-04 14:40:09 +02:00
1f0def6499 docs: add doctest to set_app_name 2026-10-04 14:38:46 +02:00
221812e18d docs: add doctest to get_server_info 2026-10-04 14:37:08 +02:00
fe237ce076 docs: add doctest to get_server_caps 2026-10-04 14:35:55 +02:00
b1a442b566 docs: add doctest to get_app_icon 2026-10-04 14:32:25 +02:00
6d1be544c5 docs: add doctest to get_app_name 2026-10-04 14:29:46 +02:00
be5d8e098c docs: add doctest to is_inited 2026-10-04 14:22:26 +02:00
5c71686b17 docs: add example to uninit 2026-10-04 14:21:08 +02:00
2832dff7f1 docs: add example to init 2026-10-04 14:17:10 +02:00
b6e19b5313 chore: add warnings about missing errors docs 2026-10-04 14:07:59 +02:00
Szymon
22b6ee3ad9 Merge pull request #9 from szymon-jozef/szymon-jozef-patch-1
Add notify-send-rs reference to README
2026-10-03 22:36:50 +02:00
Szymon
47e262357f Add notify-send-rs reference to README
Added a reference to notify-send-rs as an example project.
2026-10-03 22:36:39 +02:00
6e0ecda035 Merge branch 'master' of github.com:szymon-jozef/libnotify-rs 2026-10-03 21:40:05 +02:00
f10ff06a89 chore: version bump 2026-10-03 21:39:58 +02:00
Szymon
f170e8c710 Merge pull request #8 from szymon-jozef/fix/compilation-error
fix: set messages next to crash during build
2026-10-03 21:37:58 +02:00
6d8672537f fix: set messages next to crash during build 2026-10-03 21:37:45 +02:00
Szymon
4f20307a7b Merge pull request #7 from szymon-jozef/docs/error-docs
feat: add specific error docs
2026-10-03 21:36:07 +02:00
632cc49276 feat: add specific error docs 2026-10-03 21:35:34 +02:00
Szymon
edaf6e2787 Merge pull request #6 from szymon-jozef/fix/compile-only-on-linux
fix: compile only on linux
2026-10-03 21:29:45 +02:00
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
Szymon
5b0da1e9c6 Merge pull request #1 from szymon-jozef/feat/builder
feat/builder
2026-10-01 21:25:27 +02:00
6cbb7524cf chore: version bump 2026-10-01 21:22:40 +02:00
5f0b2f199a feat: add example for builder 2026-10-01 21:00:23 +02:00
744efe8ec9 feat: add notifaction builder 2026-10-01 20:59:14 +02:00
7966726534 feat: add libnotify context 2026-10-01 16:31:16 +02:00
da89268e83 fix: badge 2026-09-30 20:34:39 +02:00
21 changed files with 1529 additions and 424 deletions

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
type: Bug
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Crate an object
2. Run a method with following arguments
3. See error
Or something like this.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. nixOS, arch linux]
- Version [e.g 1.0.0]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,21 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
type: Feature
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

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.1.0" version = "1.0.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,16 +1,19 @@
[package] [package]
name = "libnotify-rs" name = "libnotify-rs"
version = "0.1.0" version = "1.0.0"
edition = "2024" edition = "2024"
authors = [ "szymon_jozef"] authors = [ "szymon_jozef"]
description = "Rust bindings for libnotify" description = "Rust bindings for libnotify"
readme = "./README.md" readme = "./README.md"
homepage = "https://github.com/szymon-jozef/libnotify-rs"
repository = "https://github.com/szymon-jozef/libnotify-rs" repository = "https://github.com/szymon-jozef/libnotify-rs"
documentation = "https://szymon-jozef.github.io/libnotify-rs/libnotify_rs/index.html"
license = "MIT" license = "MIT"
keywords = [ "bindings", "libnotify" ] keywords = [ "bindings", "libnotify", "notificatons" ]
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

@@ -1,6 +1,6 @@
![Tests](https://github.com/szymon-jozef/libnotify-rs/actions/workflows/tests.yml/badge.svg) ![Tests](https://github.com/szymon-jozef/libnotify-rs/actions/workflows/tests.yml/badge.svg)
![GitHub License](https://img.shields.io/github/license/szymon-jozef/libnotify-rs) ![GitHub License](https://img.shields.io/github/license/szymon-jozef/libnotify-rs)
![Gitea Release](https://img.shields.io/gitea/v/release/szymon-jozef/libnotify-rs) ![GitHub Release](https://img.shields.io/github/v/release/szymon-jozef/libnotify-rs)
<!--toc:start--> <!--toc:start-->
- [Rust bindings for libnotify](#rust-bindings-for-libnotify) - [Rust bindings for libnotify](#rust-bindings-for-libnotify)
@@ -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,7 +27,7 @@ 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 libnotify-rs
``` ```
### Nix flake ### Nix flake
@@ -56,22 +49,26 @@ packages = [
## Dependencies ## Dependencies
To use this crate you need to have installed: To use this crate you need to have installed:
- [libnotify](https://gitlab.gnome.org/GNOME/libnotify), duh (version 0.8.8 >= is supported) - [libnotify](https://gitlab.gnome.org/GNOME/libnotify), duh (version 0.8.8 >= is tested. Older versions may work, but are unsupported)
- [clang](https://clang.llvm.org/) for bindgen - [clang](https://clang.llvm.org/) for bindgen
- [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/?__goaway_challenge=meta-refresh&__goaway_id=e567f4f3ff21df0fb2b589fc86ed197e&__goaway_referer=https%3A%2F%2Fsearch.brave.com%2F), to find the above - [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/?__goaway_challenge=meta-refresh&__goaway_id=e567f4f3ff21df0fb2b589fc86ed197e&__goaway_referer=https%3A%2F%2Fsearch.brave.com%2F), to find the above
## Examples ## Examples
Refer to [examples dir](./examples) Refer to [examples dir](./examples)
Also have a look at [notify-send-rs](https://github.com/szymon-jozef/notify-send-rs). That's a small notify-send clone I made with this lib, as an example.
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

@@ -2,7 +2,9 @@ use std::env;
use std::path::PathBuf; use std::path::PathBuf;
fn main() { fn main() {
let library = pkg_config::Config::new().probe("libnotify").unwrap(); let library = pkg_config::Config::new()
.probe("libnotify")
.expect("Cannot locate libnotify. Is it installed?");
let paths = library let paths = library
.include_paths .include_paths
@@ -17,7 +19,8 @@ fn main() {
.generate() .generate()
.expect("Unable to generate bindings"); .expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); let out_path =
PathBuf::from(env::var("OUT_DIR").expect("Out $OUT_DIR not set. Perhaps a bindgen error?"));
bindings bindings
.write_to_file(out_path.join("bindings.rs")) .write_to_file(out_path.join("bindings.rs"))

23
examples/builder.rs Normal file
View File

@@ -0,0 +1,23 @@
use libnotify_rs::{builder::NotificationBuilder, context::LibnotifyContext};
/// Builder is recommended way to interact with libnotify notifications
fn main() -> Result<(), Box<dyn std::error::Error>> {
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(())
}

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,16 +1,37 @@
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
* *
*(what a cool section comment) *(what a cool section comment)
*/ */
/// Initialized libnotify. This must be called before any other functions. /// Initializes 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>> ///
/// # Example
///
/// ```
/// use libnotify_rs::api::{
/// functions::init,
/// notification:: Notification,
/// };
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("Test app")?; // we need to init libnotify __before__ any other libnotify code
/// let notification = Notification::new("Test", None, None)?; // Some libnotify call
/// Ok(())
/// }
///
/// ```
///
/// # Errors
/// This function can return LibnotifyError::{InitError, NulError}
pub fn init<'a, T>(app_name: T) -> Result<(), LibnotifyError>
where where
T: Into<Option<&'a str>>, T: Into<Option<&'a str>>,
{ {
@@ -24,7 +45,7 @@ where
) )
} == 0 } == 0
{ {
return Err("Libnotify init failed".into()); return Err(LibnotifyError::InitError);
} }
Ok(()) Ok(())
@@ -33,6 +54,19 @@ where
/// Uninitializes libnotify. /// Uninitializes libnotify.
/// ///
/// This should be called when the program no longer needs libnotify for the rest of its lifecycle, typically just before exitting. /// This should be called when the program no longer needs libnotify for the rest of its lifecycle, typically just before exitting.
///
/// # Example
/// ```
/// use libnotify_rs::api::functions::{init, uninit};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("Test app")?;
/// // let notify = ...
/// uninit(); // __always__ uninit libnotify after use
/// Ok(())
/// }
///
/// ```
pub fn uninit() { pub fn uninit() {
unsafe { unsafe {
notify_uninit(); notify_uninit();
@@ -40,6 +74,20 @@ pub fn uninit() {
} }
/// Gets whether or not libnotify is initialized. /// Gets whether or not libnotify is initialized.
///
/// # Example
/// ```
/// use libnotify_rs::api::functions::{init, is_initted};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// if !is_initted() { // useful for conditional initing
/// init("Test app")?;
/// }
///
/// Ok(())
/// }
///
/// ```
pub fn is_initted() -> bool { pub fn is_initted() -> bool {
unsafe { notify_is_initted() != 0 } unsafe { notify_is_initted() != 0 }
} }
@@ -49,6 +97,20 @@ pub fn is_initted() -> bool {
*/ */
/// Gets the application name registered. /// Gets the application name registered.
///
/// # Example
/// ```
/// use libnotify_rs::api::functions::{get_app_name, init};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let app_name = "Test app";
/// init(app_name)?;
/// let app_name_gotten: String = get_app_name().ok_or::<String>("No name gotten".into())?;
/// assert_eq!(app_name, app_name_gotten);
/// Ok(())
/// }
///
/// ```
pub fn get_app_name() -> Option<String> { pub fn get_app_name() -> Option<String> {
let app_name_c: *const std::os::raw::c_char = unsafe { notify_get_app_name() }; let app_name_c: *const std::os::raw::c_char = unsafe { notify_get_app_name() };
@@ -65,6 +127,20 @@ pub fn get_app_name() -> Option<String> {
/// Gets the application icon registered. /// Gets the application icon registered.
/// Available since: 0.8.4 /// Available since: 0.8.4
///
/// # Example
/// ```
/// use libnotify_rs::api::functions::{get_app_icon, get_app_name, init, set_app_icon};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("Test app")?;
/// let icon = "gimp";
/// set_app_icon(icon)?;
/// let icon_gotten = get_app_icon().ok_or::<String>("No app icon set".into())?;
/// assert_eq!(icon, icon_gotten);
/// Ok(())
/// }
/// ```
pub fn get_app_icon() -> Option<String> { pub fn get_app_icon() -> Option<String> {
let app_icon_c: *const std::os::raw::c_char = unsafe { notify_get_app_icon() }; let app_icon_c: *const std::os::raw::c_char = unsafe { notify_get_app_icon() };
@@ -81,6 +157,27 @@ pub fn get_app_icon() -> Option<String> {
/// Queries the server capabilities. /// Queries the server capabilities.
/// Synchronously queries the server for its capabilities and returns them in a list. /// Synchronously queries the server for its capabilities and returns them in a list.
///
/// # Example
/// ```
/// use libnotify_rs::api::functions::{self, uninit};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// functions::init("Test app")?;
///
/// if let Some(caps) = functions::get_server_caps() {
/// println!("=== Server caps ===");
/// for s in caps {
/// println!("{}", s);
/// }
/// } else {
/// eprintln!("No server caps");
/// }
///
/// Ok(())
/// }
///
/// ```
pub fn get_server_caps() -> Option<Vec<String>> { pub fn get_server_caps() -> Option<Vec<String>> {
let mut vec: Vec<String> = vec![]; let mut vec: Vec<String> = vec![];
@@ -116,6 +213,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>,
@@ -129,6 +227,41 @@ pub struct ServerInfo {
/// Queries the server for information. /// Queries the server for information.
/// Synchronously queries the server for its information, specifically, the name, vendor, server version, and the version of the notifications specification that it is compliant with. /// Synchronously queries the server for its information, specifically, the name, vendor, server version, and the version of the notifications specification that it is compliant with.
///
/// # Example
/// ```
///
/// use libnotify_rs::api::functions::{self, uninit};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// functions::init("Test app")?;
///
/// if let Some(info) = functions::get_server_info() {
/// println!("=== Server info ===");
/// if let Some(name) = info.name {
/// println!("Server name: {}", name);
/// }
///
/// if let Some(vendor) = info.vendor {
/// println!("Server vendor: {}", vendor);
/// }
///
/// if let Some(version) = info.version {
/// println!("Server version: {}", version);
/// }
///
/// if let Some(spec_version) = info.spec_version {
/// println!("Server spec version: {}", spec_version);
/// }
/// } else {
/// eprintln!("No server info");
/// }
///
/// uninit();
///
/// Ok(())
/// }
/// ```
pub fn get_server_info() -> Option<ServerInfo> { pub fn get_server_info() -> Option<ServerInfo> {
let mut ret_name = std::ptr::null_mut() as *mut std::os::raw::c_char; let mut ret_name = std::ptr::null_mut() as *mut std::os::raw::c_char;
let mut ret_vendor = std::ptr::null_mut() as *mut std::os::raw::c_char; let mut ret_vendor = std::ptr::null_mut() as *mut std::os::raw::c_char;
@@ -160,7 +293,23 @@ 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> { ///
/// # Errors
/// This function can return LibnotifyError::NulError
///
/// # Example
/// ```
/// use libnotify_rs::api::functions::{get_app_name, init, set_app_name};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("Test app")?;
/// let app_name = "morbius app";
/// set_app_name(app_name)?;
/// assert_eq!(app_name, get_app_name().unwrap());
/// Ok(())
/// }
/// ```
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 +320,23 @@ 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> { ///
/// # Errors
/// This function can return LibnotifyError::NulError
///
/// # Example
/// ```
/// use libnotify_rs::api::functions::{get_app_icon, init, set_app_icon};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("Test app")?;
/// let app_icon = "morbius app";
/// set_app_icon(app_icon)?;
/// assert_eq!(app_icon, get_app_icon().unwrap());
/// Ok(())
/// }
/// ```
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 +359,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;

628
src/api/notification.rs Normal file
View File

@@ -0,0 +1,628 @@
use crate::api::errors::LibnotifyError;
use super::libnotify::*;
#[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 {
/// Low urgency. Used for unimportant notifications
Low,
/// Normal urgency. Used for most standard notifications
Normal,
/// Critical urgency. Used for very important notifications
Critical,
}
#[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 {
/// 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 {
#[allow(missing_docs)]
String(String),
#[allow(missing_docs)]
Int32(i32),
#[allow(missing_docs)]
Boolean(bool),
}
/// Notification timeout, until close
#[derive(Debug, Clone, Copy)]
pub enum Timeout {
#[allow(missing_docs)]
Default,
#[allow(missing_docs)]
Never,
/// Custom timeout in milliseconds
Custom(i32),
}
/// Represents one notification. Wraps most of [libnotify::notifaction](https://gnome.pages.gitlab.gnome.org/libnotify/class.Notification.html) functions.
pub struct Notification {
inner: *mut NotifyNotification,
}
impl<'a> Notification {
/// Create new notification.
///
/// # Errors
/// This function can return LibnotifyError::{NewNotificationError, NulError}
///
/// # Example
/// ```
///
/// use libnotify_rs::api::{
/// functions::{init, uninit},
/// notification::Notification,
/// };
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("Test app")?;
/// let notification = Notification::new("My notification", "Yap yap", None)?;
/// // ...
/// uninit();
/// Ok(())
/// }
/// ```
pub fn new<T, Y>(summary: &str, body: T, icon: Y) -> Result<Self, LibnotifyError>
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(LibnotifyError::NewNotificationError);
}
Ok(Self { inner })
}
/// Add callback to notification.
///
/// This function needs a [glib mainloop](https://docs.gtk.org/glib/main-loop.html)
///
/// # 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.
///
/// # Errors
/// This function can return LibnotifyError::NulError
///
/// # Example
/// ```no_run(There's no notification deamon on CI and no one to click the notification)
/// use glib::MainLoop;
/// use libnotify_rs::{
/// api::functions::{init, uninit},
/// api::notification::Notification,
/// };
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("Coffee reminder")?;
/// let mut notify = Notification::new("Drink coffee", "Click me when you're done drinking", None)?;
///
/// // We need to have a glib main loop in order to receive callback actions
/// let main_loop = MainLoop::new(None, false);
/// let loop_clone = main_loop.clone();
///
/// notify.add_action("default", "I'm drinking!", move |action_name| {
/// println!("User drunk his coffee!");
/// println!("Action was named: {}", action_name);
/// loop_clone.quit();
/// })?;
///
/// notify.show()?;
/// main_loop.run();
///
/// println!("Glib mainloop has ended");
///
/// uninit();
///
/// Ok(())
/// }
/// ```
pub fn add_action<F>(
&mut self,
action: &str,
label: &str,
callback: F,
) -> Result<(), LibnotifyError>
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
///
/// # Example
/// ```
/// use libnotify_rs::api::{
/// functions::{init, uninit},
/// notification::Notification,
/// };
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("Test app")?;
/// let mut notification = Notification::new("My notification", "Yap yap", None)?;
/// notification.add_action("default", "yap", |action| {
/// // do stuff
/// })?;
///
/// notification.add_action("click", "yappers", |action| {
/// // do some other stuff
/// })?;
///
/// notification.clear_actions(); // now there are no callbacks!
///
/// uninit();
/// Ok(())
/// }
/// ```
///
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
///
/// # Errors
/// This function can return LibnotifyError::{AllocationError, GerrorError}
///
/// # Example
/// ```no_run(Not notification deamon on CI)
/// use libnotify_rs::api::{functions::init, notification::Notification};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("Test app")?;
/// let notification = Notification::new("My notification", "Yap yap", None)?;
/// notification.show()?;
/// std::thread::sleep(std::time::Duration::from_secs(5)); // To do this you can just set duration,
/// // but this is an example
/// notification.close()?; // After 5 seconds no notification
///
/// Ok(())
/// }
/// ```
pub fn close(&self) -> Result<(), LibnotifyError> {
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(LibnotifyError::AllocationError);
}
let e = unsafe { *gerror }.message;
if e.is_null() {
unsafe { g_error_free(gerror) };
return Err(LibnotifyError::AllocationError);
}
let e_str = unsafe { std::ffi::CStr::from_ptr(e) }
.to_string_lossy()
.to_string();
unsafe { g_error_free(gerror) };
return Err(LibnotifyError::GerrorError(e_str));
}
Ok(())
}
/// Returns the closed reason code for the notification.
///
/// This is valid only after the Notification::closed signal is emitted.
///
/// This function needs a [glib mainloop](https://docs.gtk.org/glib/main-loop.html)
///
/// # Example
/// ```no_run(No notificaton daemon on CI)
/// use glib::MainLoop;
/// use libnotify_rs::{
/// api::functions::{init, uninit},
/// api::notification::Notification,
/// };
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("Coffee reminder")?;
/// let mut notify = Notification::new("Drink coffee", "Click me when you're done drinking", None)?;
///
/// // We need to have a glib main loop in order to receive closed_reason
/// let main_loop = MainLoop::new(None, false);
/// let loop_clone = main_loop.clone();
///
/// notify.show()?;
/// main_loop.run();
///
/// // Thanks to the main loop we can also read why the notification was closed
/// println!(
/// "Notification closed, because: {:?}",
/// notify.get_closed_reason()
/// );
///
/// Ok(())
/// }
///```
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.
///
/// Refer to the [documentation](https://specifications.freedesktop.org/icon-naming/latest/) for
/// icon names.
///
/// If this function is not called, the application icon will be set from the value set via
/// `set_app_icon()`. Usage is the same.
///
/// Available since: 0.8.4
///
/// # Errors
/// This function can return LibnotifyError::{NulError}
pub fn set_app_icon(&mut self, app_icon: &str) -> Result<(), LibnotifyError> {
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(). Usage is the same.
///
/// Available since: 0.7.3
///
/// # Errors
/// This function can return LibnotifyError::{NulError}
pub fn set_app_name(&mut self, app_name: &str) -> Result<(), LibnotifyError> {
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
///
/// The only reference to what this even is, is [this thread](https://unix.stackexchange.com/questions/251243/what-do-a-notify-send-notification-category-hint-and-version-parameters-mean). If you know how to use this bad boy, have fun :D
///
/// # Errors
/// This function can return LibnotifyError::{NulError}
pub fn set_category(&mut self, category: &str) -> Result<(), LibnotifyError> {
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
///
///
/// The only reference to what this even is, is [this thread](https://unix.stackexchange.com/questions/251243/what-do-a-notify-send-notification-category-hint-and-version-parameters-mean). If you know how to use this bad boy, have fun :D
///
/// # Errors
/// This function can return LibnotifyError::{NulError, AllocationError}
pub fn set_hint(&mut self, key: &str, value: HintValue) -> Result<(), LibnotifyError> {
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(LibnotifyError::AllocationError);
}
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.
///
/// # Example
/// ```
/// use libnotify_rs::api::{
/// functions::{init, uninit},
/// notification::{self, Notification},
/// };
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("I love coffee")?;
/// let mut notify = Notification::new(
/// "Remember to drink coffe!",
/// "Coffee is very important for your mental health!",
/// None,
/// )?;
///
/// notify.set_timeout(notification::Timeout::Custom(1000 * 10)); // 10 secs
/// Ok(())
/// }
/// ```
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
/// # Example
/// ```
/// use libnotify_rs::api::{
/// functions::{init, uninit},
/// notification::{self, Notification},
/// };
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("I love coffee")?;
/// let mut notify = Notification::new(
/// "Remember to drink coffe!",
/// "Coffee is very important for your mental health!",
/// None,
/// )?;
///
/// notify.set_urgency(notification::Urgency::Critical); // coffee is very important
/// Ok(())
/// }
/// ```
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
///
/// # Errors
/// This function can return LibnotifyError::{GerrorError, AllocationError}
///
/// # Example
/// ```no_run(No notification daemon on CI)
/// use libnotify_rs::api::{
/// functions::{init, uninit},
/// notification::{self, Notification},
/// };
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("I love coffee")?;
/// let mut notify = Notification::new(
/// "Remember to drink coffe!",
/// "Coffee is very important for your mental health!",
/// None,
/// )?;
///
/// notify.show()?;
///
/// Ok(())
/// }
/// ```
pub fn show(&self) -> Result<(), LibnotifyError> {
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(LibnotifyError::AllocationError);
}
let err_msg = unsafe { *gerror }.message;
if err_msg.is_null() {
unsafe { g_error_free(gerror) };
return Err(LibnotifyError::AllocationError);
}
let err_msg_str = unsafe { std::ffi::CStr::from_ptr(err_msg) }
.to_string_lossy()
.to_string();
unsafe { g_error_free(gerror) };
return Err(LibnotifyError::GerrorError(err_msg_str));
}
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()`.
///
/// # Errors
/// This function can return LibnotifyError::{NulError}
///
/// # Example
/// ```
/// use libnotify_rs::api::{functions::init, notification::Notification};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// init("Test app")?;
/// let mut notification = Notification::new("My notification", "Yap yap", None)?;
/// notification.update("My new summary", "My new body", None)?;
///
/// Ok(())
/// }
/// ```
pub fn update<'b, F, I>(
&mut self,
summary: &str,
body: F,
icon: I,
) -> Result<(), LibnotifyError>
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) };
}
}
/// This function translates c callback to rust. Used in `add_action`
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);
}
/// This function tells c how to free memory of our callback
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()
);
}
// I meant to write more tests, but I'm to lazy :3
// Doctests should suffice
}

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,9 +1,29 @@
#![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](https://docs.rs/clap/latest/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)]
#![warn(clippy::missing_errors_doc)]
pub mod functions; #[cfg(not(target_os = "linux"))]
mod libnotify; compile_error!("Libnotify-rs only supports linux");
pub mod notification;
mod utils; /// Basic libnotify rust wrapper
pub mod api;
/// Higher level abstraction over api
pub mod wrappers;
pub use wrappers::{builder, context};

View File

@@ -1,369 +0,0 @@
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<(), Box<dyn std::error::Error>> {
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()
);
}
}

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

@@ -0,0 +1,304 @@
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.
///
/// # Errors
///
/// This function can return LibnotifyError::{NewNotificationError, NulError}
///
/// # Example
/// ```
/// use libnotify_rs::{builder::NotificationBuilder, context::LibnotifyContext};
///
/// // Builder is recommended way to interact with libnotify notifications
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// 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,
/// )?;
///
/// // you don't need to uninitialize libnotify, cause context already does that!
///
/// Ok(())
/// }
///
/// ```
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.
///
/// # Errors
/// This function can return LibnotifyError::{NulError}
///
/// # Example
/// ```ignore(Small example)
/// let notification_builder = NotificationBuilder::new(
/// "Remember about your coffee!",
/// "Only idiots forget about coffee!",
/// None,
/// &ctx,
/// )?
/// .set_app_name("Other app name");
/// ```
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.
///
/// # Errors
/// This function can return LibnotifyError::{NulError}
///
/// # Example
/// ```ignore(Small example)
/// let notification_builder = NotificationBuilder::new(
/// "Remember about your coffee!",
/// "Only idiots forget about coffee!",
/// None,
/// &ctx,
/// )?
/// .set_app_icon("STOCK-HOME")?;
/// ```
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
///
/// # Errors
/// This function can return LibnotifyError::{NulError}
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
/// # Errors
/// This function can return LibnotifyError::{NulError, AllocationError}
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
///
/// # Example
/// ```
/// use libnotify_rs::{builder::NotificationBuilder, context::LibnotifyContext};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// 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_timeout(libnotify_rs::api::notification::Timeout::Never);
///
/// Ok(())
/// }
/// ```
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
///
/// # Example
/// ```
/// use libnotify_rs::{builder::NotificationBuilder, context::LibnotifyContext};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// 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);
///
/// Ok(())
/// }
/// ```
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
///
/// # Errors
/// This function can return LibnotifyError::NulError
///
/// # Example
/// ```
///
/// use libnotify_rs::{builder::NotificationBuilder, context::LibnotifyContext};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// 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,
/// )?
/// .add_action("default", "super cool action", |action| {
/// println!("Action: {} was run!", action);
/// });
///
/// Ok(())
/// }
/// ```
pub fn add_action<F>(
mut self,
action: &str,
label: &str,
callback: F,
) -> Result<Self, LibnotifyError>
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.
///
/// # Errors
/// This function can return LibnotifyError::{GerrorError, AllocationError}
///
/// # Example
/// ```no_run(No notification daemon on CI)
/// use libnotify_rs::{builder::NotificationBuilder, context::LibnotifyContext};
///
/// /// Builder is recommended way to interact with libnotify notifications
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// 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()?;
///
/// Ok(())
/// }
/// ```
pub fn show(&self) -> Result<(), LibnotifyError> {
self.notification.show()?;
Ok(())
}
/// Close notification synchronously.
///
/// Uses `&self` borrow, so you can use this object later, after closing.
///
/// # Errors
/// This function can return LibnotifyError::{GerrorError, AllocationError}
///
/// # Example
/// ```no_run(No notification daemon on CI)
/// use libnotify_rs::{builder::NotificationBuilder, context::LibnotifyContext};
///
/// /// Builder is recommended way to interact with libnotify notifications
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// 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()?;
/// notification_builder.close()?;
/// Ok(())
/// }
/// ```
pub fn close(&self) -> Result<(), LibnotifyError> {
self.notification.close()?;
Ok(())
}
}

71
src/wrappers/context.rs Normal file
View File

@@ -0,0 +1,71 @@
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 {}
static IS_CONTEXT_CREATED: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
impl LibnotifyContext {
/// Try initializing libnotify
///
/// # Errors
/// Returns an error, if libnotify cannot be initialized or if there already exists a context
/// object
///
/// # Example
/// ```
/// use libnotify_rs::context::LibnotifyContext;
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let ctx = LibnotifyContext::new("Coffee addicted reminder")?;
///
/// // you don't need to uninitialize libnotify, cause context already does that!
///
/// Ok(())
/// }
/// ```
pub fn new(app_name: &str) -> Result<LibnotifyContext, Box<dyn std::error::Error>> {
if IS_CONTEXT_CREATED.swap(true, std::sync::atomic::Ordering::SeqCst) {
return Err("You can create only one context at a time".into());
}
if !is_initted() {
init(app_name)?;
}
Ok(Self {})
}
/// Sets global app name
/// # Errors
///
/// This function can return LibnotifyError::NulError
pub fn set_new_name(&self, app_name: &str) -> Result<(), LibnotifyError> {
set_app_name(app_name)?;
Ok(())
}
/// Sets global app icon
///
/// # Errors
///
/// This function can return LibnotifyError::NulError
pub fn set_app_icon(&self, app_icon: &str) -> Result<(), LibnotifyError> {
set_app_icon(app_icon)?;
Ok(())
}
}
impl Drop for LibnotifyContext {
fn drop(&mut self) {
if is_initted() {
uninit();
IS_CONTEXT_CREATED.store(false, std::sync::atomic::Ordering::SeqCst);
}
}
}

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;