Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
287 changes: 26 additions & 261 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ hypr-moonshine = { path = "crates/moonshine", package = "moonshine" }
hypr-nango = { path = "crates/nango", package = "nango" }
hypr-network = { path = "crates/network", package = "network" }
hypr-notification = { path = "crates/notification", package = "notification" }
hypr-notification2 = { path = "crates/notification2", package = "notification2" }
hypr-notification-interface = { path = "crates/notification-interface", package = "notification-interface" }
hypr-notification-macos = { path = "crates/notification-macos", package = "notification-macos" }
hypr-notion = { path = "crates/notion", package = "notion" }
hypr-onnx = { path = "crates/onnx", package = "onnx" }
hypr-openai = { path = "crates/openai", package = "openai" }
Expand Down
16 changes: 12 additions & 4 deletions apps/desktop/src/components/settings/views/notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@ export default function NotificationsComponent() {
const eventMutation = useMutation({
mutationFn: async (v: Schema) => {
if (v.event) {
notificationCommands.requestNotificationPermission().then(() => {
notificationCommands.setEventNotification(true);
notificationCommands.setEventNotification(true);
notificationCommands.showNotification({
title: "Test",
message: "Test",
url: "https://hypr.ai",
timeout: { secs: 5, nanos: 0 },
});
} else {
notificationCommands.setEventNotification(false);
Expand All @@ -59,8 +63,12 @@ export default function NotificationsComponent() {
const detectMutation = useMutation({
mutationFn: async (v: Schema) => {
if (v.detect) {
notificationCommands.requestNotificationPermission().then(() => {
notificationCommands.setDetectNotification(true);
notificationCommands.setDetectNotification(true);
notificationCommands.showNotification({
title: "Test",
message: "Test",
url: "https://hypr.ai",
timeout: { secs: 5, nanos: 0 },
});
} else {
notificationCommands.setDetectNotification(false);
Expand Down
8 changes: 8 additions & 0 deletions crates/notification-interface/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "notification-interface"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { workspace = true, features = ["derive"] }
specta = { workspace = true, features = ["derive"] }
7 changes: 7 additions & 0 deletions crates/notification-interface/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[derive(Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct Notification {
pub title: String,
pub message: String,
pub url: Option<String>,
Copy link

Choose a reason for hiding this comment

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

Use a typed URL (e.g., url::Url) instead of String to validate and prevent malformed URLs in the public interface.

Prompt for AI agents
Address the following comment on crates/notification-interface/src/lib.rs at line 5:

<comment>Use a typed URL (e.g., url::Url) instead of String to validate and prevent malformed URLs in the public interface.</comment>

<file context>
@@ -0,0 +1,7 @@
+#[derive(Debug, serde::Serialize, serde::Deserialize, specta::Type)]
+pub struct Notification {
+    pub title: String,
+    pub message: String,
+    pub url: Option&lt;String&gt;,
+    pub timeout: Option&lt;std::time::Duration&gt;,
+}
</file context>

pub timeout: Option<std::time::Duration>,
Copy link

Choose a reason for hiding this comment

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

Represent duration with a primitive (e.g., milliseconds as u64) to avoid interop ambiguity when exporting to TS/JSON via specta.

Prompt for AI agents
Address the following comment on crates/notification-interface/src/lib.rs at line 6:

<comment>Represent duration with a primitive (e.g., milliseconds as u64) to avoid interop ambiguity when exporting to TS/JSON via specta.</comment>

<file context>
@@ -0,0 +1,7 @@
+#[derive(Debug, serde::Serialize, serde::Deserialize, specta::Type)]
+pub struct Notification {
+    pub title: String,
+    pub message: String,
+    pub url: Option&lt;String&gt;,
+    pub timeout: Option&lt;std::time::Duration&gt;,
+}
</file context>

}
11 changes: 11 additions & 0 deletions crates/notification-macos/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "notification-macos"
version = "0.1.0"
edition = "2021"

[build-dependencies]
swift-rs = { workspace = true, features = ["build"] }

[dependencies]
hypr-notification-interface = { workspace = true }
swift-rs = { workspace = true }
13 changes: 13 additions & 0 deletions crates/notification-macos/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
fn main() {
#[cfg(target_os = "macos")]
Copy link

Choose a reason for hiding this comment

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

Using #[cfg(target_os = "macos")] in a build.rs checks the host OS, not the Cargo target; this can misbehave during cross-compilation. Prefer branching on CARGO_CFG_TARGET_OS to make the build script target-aware.

Prompt for AI agents
Address the following comment on crates/notification-macos/build.rs at line 2:

<comment>Using #[cfg(target_os = &quot;macos&quot;)] in a build.rs checks the host OS, not the Cargo target; this can misbehave during cross-compilation. Prefer branching on CARGO_CFG_TARGET_OS to make the build script target-aware.</comment>

<file context>
@@ -0,0 +1,13 @@
+fn main() {
+    #[cfg(target_os = &quot;macos&quot;)]
+    {
+        swift_rs::SwiftLinker::new(&quot;14.2&quot;)
</file context>

{
swift_rs::SwiftLinker::new("14.2")
.with_package("swift-lib", "./swift-lib/")
.link();
}

#[cfg(not(target_os = "macos"))]
{
println!("cargo:warning=Swift linking is only available on macOS");
}
}
44 changes: 44 additions & 0 deletions crates/notification-macos/examples/test_notification.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use notification_macos::*;
use std::time::Duration;

#[cfg(target_os = "macos")]
#[link(name = "AppKit", kind = "framework")]
#[link(name = "Foundation", kind = "framework")]
extern "C" {
fn NSApplicationLoad() -> bool;
fn CFRunLoopRun();
fn CFRunLoopStop(rl: *const std::ffi::c_void);
fn CFRunLoopGetMain() -> *const std::ffi::c_void;
}

fn main() {
#[cfg(target_os = "macos")]
{
unsafe {
NSApplicationLoad();
}

std::thread::spawn(|| {
std::thread::sleep(Duration::from_millis(100));

let notification = Notification {
title: "Test Notification".to_string(),
message: "This is a test message from Rust".to_string(),
url: Some("https://example.com".to_string()),
timeout: Some(Duration::from_secs(3)),
};

show(&notification);

std::thread::sleep(Duration::from_secs(5));
unsafe {
let main_loop = CFRunLoopGetMain();
CFRunLoopStop(main_loop);
}
});

unsafe {
CFRunLoopRun();
}
}
}
47 changes: 47 additions & 0 deletions crates/notification-macos/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
pub use hypr_notification_interface::*;

#[cfg(target_os = "macos")]
use swift_rs::{swift, Bool, SRString};

#[cfg(target_os = "macos")]
swift!(fn _show_notification(
title: &SRString,
message: &SRString,
url: &SRString,
has_url: Bool,
timeout_seconds: f64
) -> Bool);

#[cfg(target_os = "macos")]
pub fn show(notification: &hypr_notification_interface::Notification) {
unsafe {
let title = SRString::from(notification.title.as_str());
let message = SRString::from(notification.message.as_str());
let url = notification
.url
.as_ref()
.map(|u| SRString::from(u.as_str()))
.unwrap_or_else(|| SRString::from(""));
let has_url = notification.url.is_some();
let timeout_seconds = notification.timeout.map(|d| d.as_secs_f64()).unwrap_or(5.0);

_show_notification(&title, &message, &url, has_url, timeout_seconds);
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_notification() {
let notification = hypr_notification_interface::Notification {
title: "Test Title".to_string(),
message: "Test message content".to_string(),
url: Some("https://example.com".to_string()),
timeout: Some(std::time::Duration::from_secs(3)),
};

show(&notification);
}
}
7 changes: 7 additions & 0 deletions crates/notification-macos/swift-lib/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
13 changes: 13 additions & 0 deletions crates/notification-macos/swift-lib/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions crates/notification-macos/swift-lib/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// swift-tools-version:5.9

import PackageDescription

let package = Package(
name: "swift-lib",
platforms: [.macOS("14.2")],
products: [
.library(
name: "swift-lib",
type: .static,
targets: ["swift-lib"])
],
dependencies: [
.package(
url: "https://github.com/Brendonovich/swift-rs",
revision: "01980f981bc642a6da382cc0788f18fdd4cde6df")
],
targets: [
.target(
name: "swift-lib",
dependencies: [
.product(name: "SwiftRs", package: "swift-rs")
],
path: "src"
)
]
)
Loading
Loading