Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 2 #51

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
target
*.swp
Cargo.lock
.vscode
.vscode
simple.app
simple.log
.DS_Store
46 changes: 36 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
[package]
name = "mac-notification-sys"
version = "0.5.6"
authors = ["Felix Döring <[email protected]>", "Hendrik Sollich <[email protected]>"]
authors = [
"Felix Döring <[email protected]>",
"Hendrik Sollich <[email protected]>",
]
description = "Thin wrapper around macOS Notifications."
edition = "2018"
rust-version = "1.56"
edition = "2021"

homepage = "https://github.com/h4llow3En/mac-notification-sys"
repository = "https://github.com/h4llow3En/mac-notification-sys"
Expand All @@ -14,15 +16,39 @@ license = "MIT"
keywords = ["notification", "masOS", "osx", "notify"]
readme = "README.md"

include = ["Cargo.toml", "build.rs", "objc/*", "src/*.rs", "tests/*.rs"]
include = ["Cargo.toml", "src/*.rs"]

build = "build.rs"
[[example]]
name = "simple"

[dependencies]
objc-foundation = "0.1.1"
objc_id = "0.1.1"
time = "0.3"
dirs-next = "2.0.0"
objc2 = "0.4.0"
icrate = { version = "0.0.3", features = [
"UserNotifications_all",
"Foundation",
"Foundation_NSArray",
"Foundation_NSString",
"Foundation_NSRunLoop",
"Foundation_NSError",
"Foundation_NSDate",
"Foundation_NSDateComponents",
"Foundation_NSURL",
"Foundation_NSMutableDictionary",
"Foundation_NSNumber",
"Foundation_NSProcessInfo",
"Foundation_NSMutableArray",
"UniformTypeIdentifiers"
] }
bitflags = "^2.3"
cron = "^0.12"
url = "^2.4"
futures = "^0.3"
chrono = "^0.4"
uuid = {version = "^1.4", features = ["v4"]}
lazy_static = "^1.4"

[dev-dependencies]
async-io = "^1.13"

[build-dependencies]
cc = "1.0.17"
cfg_aliases = "^0.1"
63 changes: 37 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,39 +28,50 @@ mac-notification-sys = "0.5"

The documentation can be found [here](https://h4llow3en.github.io/mac-notification-sys/mac_notification_sys/)

## RunLoop

Core Foundation API requiring to run the RunLoop always in the main thread. There will be no effect to the
notification delegates if you ran the NS loop from another thread. `winit` is already running this RunLoop
on their main thread. So you do not have to deal with the run loop if you are using tauri/egui and other
`winit` based application frameworks. Otherwise you have to manually run the run loop by using provided
`run_ns_run_loop_once` method.

## Bundling and Code Signing

You must bundle your application to enable the new User Notification framework. Because
`[UNUserNotificationCenter currentNotificationCenter]` method is looking for the bundle informations and
you will get `bundleProxyForCurrentProcess is nil` error if you didn't run the application from a bundle.

Also you have to code sign the bundle to enable the user notification. Otherwise you will get the
`Notifications are not allowed for this application` error message.

You can use Xcode sandbox entitlements to enable user notifications when you are developing an application.
See the `bundle/run.sh` file to get an idea about the bundling a development app.

## Example

```rust
use mac_notification_sys::*;

fn main() {
let bundle = get_bundle_identifier_or_default("firefox");
set_application(&bundle).unwrap();

send_notification(
"Danger",
Some("Will Robinson"),
"Run away as fast as you can",
None,
)
.unwrap();

send_notification(
"NOW",
None,
"Without subtitle",
Some(Notification::new().sound("Blow")),
)
.unwrap();
}
Please refer the `examples/simple.rs` file.

Use below commands to run the example project:-

```
export CERTIFICATE=mycertificatename
./bundle/run.sh
```

`CERTIFICATE` variable should be a name of a self signed certificate. If you have an apple developer id, modify
the script and run again. This command will create the `simple.app` in the project root folder and open it for you.
Also a file called `simple.log` will create with the output.

## TODO

- [ ] Add timeout option so notifications can be auto-closed
- [ ] Allow NSDictionary to hold various types (perhaps with a union?)
- [ ] Switch to UserNotification if possible
- [] Migrate to no_std environment
- [] Test on all the platforms
- [] MacOS
- [] IOS
- [] WatchOS
- [] IPadOS
- [] CatalystOS

## Contributors

Expand Down
32 changes: 7 additions & 25 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,11 @@
extern crate cc;
use std::env;

const DEPLOYMENT_TARGET_VAR: &str = "MACOSX_DEPLOYMENT_TARGET";
use cfg_aliases::cfg_aliases;

fn main() {
if cfg!(target_os = "macos") {
let min_version = match env::var(DEPLOYMENT_TARGET_VAR) {
Ok(ver) => ver,
Err(_) => String::from(match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() {
"x86_64" => "10.8", // NSUserNotificationCenter first showed up here.
"aarch64" => "11.0", // Apple silicon started here.
arch => panic!("unknown arch: {}", arch),
}),
};

cc::Build::new()
.file("objc/notify.m")
.flag("-fmodules")
.flag("-Wno-deprecated-declarations")
// `cc` doesn't try to pick up on this automatically, but `clang` needs it to
// generate a "correct" Objective-C symbol table which better matches XCode.
// See https://github.com/h4llow3En/mac-notification-sys/issues/45.
.flag(&format!("-mmacos-version-min={}", min_version))
.compile("notify");

println!("cargo:rerun-if-env-changed={}", DEPLOYMENT_TARGET_VAR);
cfg_aliases! {
watchos: { target_os = "watchos" },
macos: { target_os = "macos" },
ios: { all(not(any(target="aarch64-apple-ios-macabi", target="x86_64-apple-ios-macabi")), target_os = "ios") },
catalyst: { any(target="aarch64-apple-ios-macabi", target="x86_64-apple-ios-macabi") },
otheros: {not(any(target_os ="watchos", target_os ="macos", target_os = "ios"))}
}
}
33 changes: 33 additions & 0 deletions bundle/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!--The executable-->
<key>CFBundleExecutable</key>
<string>simple-bin</string><!--This should be the name of the executable inside your MacOS folder-->

<!--Company name-->
<key>CFBundleIdentifier</key>
<string>com.mac-notification-sys.simple</string>

<!--Desired application name-->
<key>CFBundleName</key>
<string>Simple</string>

<!--icons file, located in the Resources folder-->
<key>CFBundleIconFile</key>
<string>rust-logo.icns</string>

<key>CFBundleDisplayName</key>
<string>Mac Notification SYS Simple</string>

<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
14 changes: 14 additions & 0 deletions bundle/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
cargo build --example simple
rm -r simple.app
rm simple.log
mkdir simple.app
mkdir simple.app/Contents
mkdir simple.app/Contents/MacOS
mkdir simple.app/Contents/Resources
cp target/debug/examples/simple simple.app/Contents/MacOS/simple-bin
cp bundle/Info.plist simple.app/Contents/
cp bundle/rust-logo.icns simple.app/Contents/Resources
codesign --force --sign $CERTIFICATE -o runtime --entitlements ./bundle/simple.app.xcent --timestamp\=none --generate-entitlement-der ./simple.app
touch simple.log
open simple.app --stdout ./simple.log --stderr ./simple.log
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been playing around with cargo-bundle, do we want to use that here?

Binary file added bundle/rust-logo.icns
Binary file not shown.
12 changes: 12 additions & 0 deletions bundle/simple.app.xcent
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>
41 changes: 0 additions & 41 deletions examples/actions.rs

This file was deleted.

15 changes: 0 additions & 15 deletions examples/date.rs

This file was deleted.

9 changes: 0 additions & 9 deletions examples/default_app.rs

This file was deleted.

22 changes: 0 additions & 22 deletions examples/send_notification.rs

This file was deleted.

48 changes: 32 additions & 16 deletions examples/simple.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
use mac_notification_sys::*;
use std::time::Duration;

fn main() {
let bundle = get_bundle_identifier_or_default("firefox");
set_application(&bundle).unwrap();
use async_io::block_on;
use mac_notification_sys::{*, notification::AuthorizationOptions};

Notification::default()
.title("Danger")
.subtitle("Will Robinson")
.message("Run away as fast as you can")
.send()
.unwrap();
fn main() {
std::thread::spawn(||{
block_on(async {
println!("Asking for authorization");
let authorized = request_authorization(AuthorizationOptions::Sound|AuthorizationOptions::Badge).await;
println!("Finished authorization");
match authorized {
Ok(()) => {
println!("User authorized for one or many options");
let badge_updated = set_badge_count(30).await;
match badge_updated {
Ok(()) => {
println!("Badge updated");
},
Err(e) => {
println!("Error occured while updating the badge. {:?}", e);
}
}
},
Err(e) => {
println!("Error occured while authorization step. {:?}", e);
}
}
});
});

Notification::default()
.title("NOW")
.message("Without subtitle")
.sound("Submarine")
.send()
.unwrap();
loop {
run_ns_run_loop_once();
std::thread::sleep(Duration::from_millis(100));
}
}
Loading