1pub fn request_authorization() {
5 #[cfg(target_os = "macos")]
6 platform::request_authorization();
7}
8
9pub fn send_notification(title: &str, body: &str) {
17 let title = if title.is_empty() {
18 "Rio".to_string()
19 } else {
20 title.to_string()
21 };
22 let body = body.to_string();
23
24 std::thread::spawn(move || {
25 platform::notify(&title, &body);
26 });
27}
28
29#[cfg(target_os = "macos")]
30mod platform {
31 use block2::RcBlock;
32 use objc::runtime::Object;
33 use objc::{class, msg_send, sel, sel_impl};
34 use objc2::runtime::Bool;
35 use objc2_foundation::{NSError, NSString};
36 use objc2_user_notifications::{
37 UNAuthorizationOptions, UNMutableNotificationContent, UNNotificationRequest,
38 UNUserNotificationCenter,
39 };
40 use std::sync::Once;
41
42 pub(crate) fn request_authorization() {
43 static INIT: Once = Once::new();
44 INIT.call_once(|| unsafe {
45 let bundle: *mut Object = msg_send![class!(NSBundle), mainBundle];
46 if bundle.is_null() {
47 return;
48 }
49 let bundle_id: *mut Object = msg_send![bundle, bundleIdentifier];
50 if bundle_id.is_null() {
51 return;
52 }
53
54 let center = UNUserNotificationCenter::currentNotificationCenter();
55 center.requestAuthorizationWithOptions_completionHandler(
56 UNAuthorizationOptions::UNAuthorizationOptionAlert
57 | UNAuthorizationOptions::UNAuthorizationOptionSound,
58 &RcBlock::new(|_ok: Bool, _err: *mut NSError| {}),
59 );
60 });
61 }
62
63 pub fn notify(title: &str, body: &str) {
64 unsafe {
65 let bundle: *mut Object = msg_send![class!(NSBundle), mainBundle];
68 if bundle.is_null() {
69 return;
70 }
71 let bundle_id: *mut Object = msg_send![bundle, bundleIdentifier];
72 if bundle_id.is_null() {
73 return;
74 }
75
76 let center = UNUserNotificationCenter::currentNotificationCenter();
77
78 let content = UNMutableNotificationContent::new();
79 content.setTitle(&NSString::from_str(title));
80 content.setBody(&NSString::from_str(body));
81
82 let identifier = NSString::from_str("rio-notification");
83 let request = UNNotificationRequest::requestWithIdentifier_content_trigger(
84 &identifier,
85 &content,
86 None,
87 );
88
89 center.addNotificationRequest_withCompletionHandler(&request, None);
90 }
91 }
92}
93
94#[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
95mod platform {
96 use std::collections::HashMap;
97
98 pub fn notify(title: &str, body: &str) {
99 let Ok(connection) = zbus::blocking::Connection::session() else {
100 return;
101 };
102 let Ok(proxy) = zbus::blocking::Proxy::new(
103 &connection,
104 "org.freedesktop.Notifications",
105 "/org/freedesktop/Notifications",
106 "org.freedesktop.Notifications",
107 ) else {
108 return;
109 };
110 let hints: HashMap<&str, zbus::zvariant::Value<'_>> = HashMap::new();
111 let _: Result<u32, _> = proxy.call(
112 "Notify",
113 &(
114 "Rio", 0u32, "rio", title, body, &[] as &[&str], &hints, -1i32, ),
123 );
124 }
125}
126
127#[cfg(target_os = "windows")]
128mod platform {
129 pub fn notify(title: &str, body: &str) {
130 use windows::core::HSTRING;
131 use windows::Data::Xml::Dom::XmlDocument;
132 use windows::UI::Notifications::{ToastNotification, ToastNotificationManager};
133
134 let Ok(xml) = XmlDocument::new() else {
135 return;
136 };
137 let toast_xml = format!(
138 r#"<toast><visual><binding template="ToastGeneric"><text>{}</text><text>{}</text></binding></visual></toast>"#,
139 title, body,
140 );
141 if xml.LoadXml(&HSTRING::from(&toast_xml)).is_err() {
142 return;
143 }
144 let Ok(toast) = ToastNotification::CreateToastNotification(&xml) else {
145 return;
146 };
147 let Ok(notifier) =
148 ToastNotificationManager::CreateToastNotifierWithId(&HSTRING::from("Rio"))
149 else {
150 return;
151 };
152 let _ = notifier.Show(&toast);
153 }
154}