From 756238feab011635c28bcc2b0b5336987b2b8fa0 Mon Sep 17 00:00:00 2001 From: mars Date: Sat, 19 Nov 2022 20:45:35 -0700 Subject: [PATCH 1/9] Initial notifications crate --- Cargo.toml | 1 + apps/notifications/Cargo.toml | 8 ++++ apps/notifications/src/main.rs | 72 ++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 apps/notifications/Cargo.toml create mode 100644 apps/notifications/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index fe2e0dd..e9a5479 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "apps/magpie", "apps/music-player", + "apps/notifications", "apps/sandbox", "crates/script", "scripts/music-player", diff --git a/apps/notifications/Cargo.toml b/apps/notifications/Cargo.toml new file mode 100644 index 0000000..f4d240e --- /dev/null +++ b/apps/notifications/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "canary-notifications" +version = "0.1.0" +edition = "2021" + +[dependencies] +smol = "1.2" +zbus = "3.5" diff --git a/apps/notifications/src/main.rs b/apps/notifications/src/main.rs new file mode 100644 index 0000000..7952499 --- /dev/null +++ b/apps/notifications/src/main.rs @@ -0,0 +1,72 @@ +use std::collections::HashMap; +use std::future::pending; + +use zbus::{dbus_interface, zvariant::Value, ConnectionBuilder, SignalContext}; + +pub struct Notifications {} + +#[dbus_interface(name = "org.freedesktop.Notifications")] +impl Notifications { + fn get_capabilities(&self) -> Vec { + vec!["body", "body-markup", "actions", "icon-static"] + .into_iter() + .map(ToString::to_string) + .collect() + } + + #[dbus_interface(out_args("name", "vendor", "version", "spec_version"))] + fn get_server_information(&self) -> zbus::fdo::Result<(String, String, String, String)> { + Ok(( + "canary-notifications".to_string(), + "Canary Development Team".to_string(), + "0.1.0".to_string(), + "1.2".to_string(), + )) + } + + fn notify( + &self, + app_name: String, + replaces_id: u32, + app_icon: String, + summary: String, + body: String, + actions: Vec, + hints: HashMap, + expire_timeout: i32, + ) -> u32 { + println!("{}: {}", summary, body); + 0 + } + + fn close_notification(&self, id: u32) {} + + #[dbus_interface(signal)] + async fn notification_closed(ctx: &SignalContext<'_>, id: u32, reason: u32) + -> zbus::Result<()>; + + #[dbus_interface(signal)] + async fn action_invoked( + ctx: &SignalContext<'_>, + id: u32, + action_key: String, + ) -> zbus::Result<()>; +} + +pub fn main() { + smol::block_on(async { + let notifications = Notifications {}; + + let _ = ConnectionBuilder::session() + .unwrap() + .name("org.freedesktop.Notifications") + .unwrap() + .serve_at("/org/freedesktop/Notifications", notifications) + .unwrap() + .build() + .await + .unwrap(); + + pending::<()>().await; + }); +} From 4007c40ba6c04d0a55edc5878c1c2b6d22177101 Mon Sep 17 00:00:00 2001 From: mars Date: Tue, 29 Nov 2022 18:01:09 -0700 Subject: [PATCH 2/9] Add find_socket() to Magpie --- apps/magpie/src/protocol.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/magpie/src/protocol.rs b/apps/magpie/src/protocol.rs index eacf451..a8a016a 100644 --- a/apps/magpie/src/protocol.rs +++ b/apps/magpie/src/protocol.rs @@ -4,7 +4,7 @@ use std::collections::VecDeque; use std::io::{Read, Write}; use std::marker::PhantomData; -use std::path::PathBuf; +use std::path::{PathBuf, Path}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -211,6 +211,16 @@ impl Messenger { } } +/// Acquires the path to the Magpie socket. +/// +/// Currently only joins XDG_RUNTIME_DIR with [MAGPIE_SOCK]. +pub fn find_socket() -> PathBuf { + let sock_dir = std::env::var("XDG_RUNTIME_DIR").expect("XDG_RUNTIME_DIR not set"); + let sock_dir = Path::new(&sock_dir); + let sock_path = sock_dir.join(MAGPIE_SOCK); + sock_path +} + #[cfg(feature = "async")] mod async_messages { use super::*; From ce187a03816356421ff0ff56662eba7ed3cad6db Mon Sep 17 00:00:00 2001 From: mars Date: Tue, 29 Nov 2022 18:01:17 -0700 Subject: [PATCH 3/9] Use find_socket() in music player --- apps/music-player/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/music-player/src/main.rs b/apps/music-player/src/main.rs index fb647ee..f0c045a 100644 --- a/apps/music-player/src/main.rs +++ b/apps/music-player/src/main.rs @@ -196,9 +196,7 @@ fn main() { .to_owned(); smol::block_on(async { - let sock_dir = std::env::var("XDG_RUNTIME_DIR").expect("XDG_RUNTIME_DIR not set"); - let sock_dir = Path::new(&sock_dir); - let sock_path = sock_dir.join(MAGPIE_SOCK); + let sock_path = canary_magpie::protocol::find_socket(); let socket = UnixStream::connect(sock_path).await.unwrap(); let mut magpie = MagpieClient::new(socket); let protocol = "tebibyte-media.desktop.music-player-controller".to_string(); From 72d7e703c1181d85473fa077e0d8c4b74b0045c4 Mon Sep 17 00:00:00 2001 From: mars Date: Tue, 29 Nov 2022 21:56:59 -0700 Subject: [PATCH 4/9] Load Magpie client in notifications --- apps/notifications/Cargo.toml | 1 + apps/notifications/src/main.rs | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/notifications/Cargo.toml b/apps/notifications/Cargo.toml index f4d240e..5f56a81 100644 --- a/apps/notifications/Cargo.toml +++ b/apps/notifications/Cargo.toml @@ -4,5 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] +canary-magpie = { path = "../magpie" } smol = "1.2" zbus = "3.5" diff --git a/apps/notifications/src/main.rs b/apps/notifications/src/main.rs index 7952499..dd59530 100644 --- a/apps/notifications/src/main.rs +++ b/apps/notifications/src/main.rs @@ -1,9 +1,15 @@ use std::collections::HashMap; use std::future::pending; +use canary_magpie::protocol::*; +use smol::net::unix::UnixStream; use zbus::{dbus_interface, zvariant::Value, ConnectionBuilder, SignalContext}; -pub struct Notifications {} +pub type MagpieClient = ClientMessenger; + +pub struct Notifications { + magpie: MagpieClient, +} #[dbus_interface(name = "org.freedesktop.Notifications")] impl Notifications { @@ -55,7 +61,10 @@ impl Notifications { pub fn main() { smol::block_on(async { - let notifications = Notifications {}; + let sock_path = find_socket(); + let socket = UnixStream::connect(sock_path).await.unwrap(); + let magpie = MagpieClient::new(socket); + let notifications = Notifications { magpie }; let _ = ConnectionBuilder::session() .unwrap() From 780f13a015a440a10351ddf29b0c92776021e90c Mon Sep 17 00:00:00 2001 From: mars Date: Tue, 6 Dec 2022 16:59:06 -0700 Subject: [PATCH 5/9] Add init_msg to Magpie CreatePanel message --- apps/magpie/src/protocol.rs | 1 + apps/magpie/src/service/ipc.rs | 2 ++ apps/magpie/src/service/window.rs | 4 +++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/magpie/src/protocol.rs b/apps/magpie/src/protocol.rs index a8a016a..d196a83 100644 --- a/apps/magpie/src/protocol.rs +++ b/apps/magpie/src/protocol.rs @@ -26,6 +26,7 @@ pub struct CreatePanel { pub id: PanelId, pub protocol: String, pub script: PathBuf, + pub init_msg: Vec, } /// Sends a panel a message. diff --git a/apps/magpie/src/service/ipc.rs b/apps/magpie/src/service/ipc.rs index ad9dce2..cbc4b1e 100644 --- a/apps/magpie/src/service/ipc.rs +++ b/apps/magpie/src/service/ipc.rs @@ -130,6 +130,7 @@ impl Client { id, protocol, script, + init_msg, }) => { let mut data = self.data.write(); @@ -146,6 +147,7 @@ impl Client { id: window, protocol, script, + init_msg, }; let _ = self.window_sender.send_event(msg); } diff --git a/apps/magpie/src/service/window.rs b/apps/magpie/src/service/window.rs index fd41d39..53aee3c 100644 --- a/apps/magpie/src/service/window.rs +++ b/apps/magpie/src/service/window.rs @@ -21,6 +21,7 @@ pub enum WindowMessage { id: usize, protocol: String, script: PathBuf, + init_msg: Vec, }, CloseWindow { id: usize, @@ -194,11 +195,12 @@ impl WindowStore { id, protocol, script, + init_msg, } => { println!("Opening window {} with script {:?}", id, script); let module = std::fs::read(script)?; let mut script = self.runtime.load_module(&module)?; - let panel = script.create_panel(&protocol, vec![])?; + let panel = script.create_panel(&protocol, init_msg)?; let window = Window::new(self.ipc_sender.to_owned(), id, panel, &event_loop)?; let window_id = window.get_id(); self.windows.insert(window_id, window); From c06cd2d99917255ff0b4b21eb1b31ab6656e56f4 Mon Sep 17 00:00:00 2001 From: mars Date: Tue, 6 Dec 2022 16:59:19 -0700 Subject: [PATCH 6/9] Update music player to use init_msg --- apps/music-player/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/music-player/src/main.rs b/apps/music-player/src/main.rs index f0c045a..5719cc6 100644 --- a/apps/music-player/src/main.rs +++ b/apps/music-player/src/main.rs @@ -205,6 +205,7 @@ fn main() { id: 0, protocol, script, + init_msg: vec![], }; let msg = MagpieServerMsg::CreatePanel(msg); From d073dde8caf6c786e0204bb9194318c32f96d411 Mon Sep 17 00:00:00 2001 From: mars Date: Tue, 6 Dec 2022 17:09:50 -0700 Subject: [PATCH 7/9] canary-notifications protocol and panel opening --- apps/notifications/Cargo.toml | 16 +++++++++-- apps/notifications/src/lib.rs | 27 ++++++++++++++++++ apps/notifications/src/main.rs | 51 +++++++++++++++++++++++++++++----- 3 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 apps/notifications/src/lib.rs diff --git a/apps/notifications/Cargo.toml b/apps/notifications/Cargo.toml index 5f56a81..615470b 100644 --- a/apps/notifications/Cargo.toml +++ b/apps/notifications/Cargo.toml @@ -3,7 +3,17 @@ name = "canary-notifications" version = "0.1.0" edition = "2021" +[[bin]] +name = "canary-notifications" +path = "src/main.rs" +required-features = ["bin"] + [dependencies] -canary-magpie = { path = "../magpie" } -smol = "1.2" -zbus = "3.5" +canary-magpie = { path = "../magpie", optional = true, features = ["async"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +smol = { version = "1.2", optional = true } +zbus = { version = "3.5", optional = true } + +[features] +bin = ["dep:canary-magpie", "dep:smol", "dep:zbus"] diff --git a/apps/notifications/src/lib.rs b/apps/notifications/src/lib.rs new file mode 100644 index 0000000..fb0ac41 --- /dev/null +++ b/apps/notifications/src/lib.rs @@ -0,0 +1,27 @@ +// Copyright (c) 2022 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later + +use serde::{Deserialize, Serialize}; + +pub use serde; +pub use serde_json; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Contents { + /// The optional name of the application sending the notification. + pub app_name: Option, + + /// The summary text briefly describing the notification. + pub summary: String, + + /// The optional detailed body text. + pub body: Option, + + /// The timeout time in milliseconds since the display of the notification + /// at which the notification should automatically close. + pub timeout: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum OutMsg {} diff --git a/apps/notifications/src/main.rs b/apps/notifications/src/main.rs index dd59530..939c2c3 100644 --- a/apps/notifications/src/main.rs +++ b/apps/notifications/src/main.rs @@ -1,14 +1,18 @@ use std::collections::HashMap; use std::future::pending; +use std::path::PathBuf; use canary_magpie::protocol::*; +use canary_notifications::Contents; use smol::net::unix::UnixStream; use zbus::{dbus_interface, zvariant::Value, ConnectionBuilder, SignalContext}; pub type MagpieClient = ClientMessenger; pub struct Notifications { + module_path: PathBuf, magpie: MagpieClient, + next_id: u32, } #[dbus_interface(name = "org.freedesktop.Notifications")] @@ -30,19 +34,40 @@ impl Notifications { )) } - fn notify( - &self, + async fn notify( + &mut self, app_name: String, replaces_id: u32, app_icon: String, summary: String, body: String, actions: Vec, - hints: HashMap, - expire_timeout: i32, + hints: HashMap>, + timeout: i32, ) -> u32 { - println!("{}: {}", summary, body); - 0 + let contents = Contents { + app_name: Some(app_name).filter(String::is_empty), + summary, + body: Some(body).filter(String::is_empty), + timeout: Some(timeout).filter(|t| *t == 0), + }; + + let id = self.next_id; + self.next_id += 1; + + let msg = CreatePanel { + id, + protocol: "tebibyte-media.desktop.notification".to_string(), + script: self.module_path.to_owned(), + init_msg: serde_json::to_vec(&contents).unwrap(), + }; + + self.magpie + .send_async(&MagpieServerMsg::CreatePanel(msg)) + .await + .unwrap(); + + id } fn close_notification(&self, id: u32) {} @@ -60,11 +85,23 @@ impl Notifications { } pub fn main() { + let args: Vec = std::env::args().collect(); + let module_path = args + .get(1) + .expect("Please pass a path to a Canary script!") + .to_owned() + .into(); + smol::block_on(async { let sock_path = find_socket(); let socket = UnixStream::connect(sock_path).await.unwrap(); let magpie = MagpieClient::new(socket); - let notifications = Notifications { magpie }; + + let notifications = Notifications { + magpie, + next_id: 0, + module_path, + }; let _ = ConnectionBuilder::session() .unwrap() From a6b675dd115013bc072842f656b6e861729810c8 Mon Sep 17 00:00:00 2001 From: mars Date: Tue, 6 Dec 2022 17:54:19 -0700 Subject: [PATCH 8/9] Fix is_empty() Option filtering --- apps/notifications/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/notifications/src/main.rs b/apps/notifications/src/main.rs index 939c2c3..6a7122a 100644 --- a/apps/notifications/src/main.rs +++ b/apps/notifications/src/main.rs @@ -46,9 +46,9 @@ impl Notifications { timeout: i32, ) -> u32 { let contents = Contents { - app_name: Some(app_name).filter(String::is_empty), + app_name: Some(app_name).filter(|s| !s.is_empty()), summary, - body: Some(body).filter(String::is_empty), + body: Some(body).filter(|s| !s.is_empty()), timeout: Some(timeout).filter(|t| *t == 0), }; From 482a0de0300e6e222adc0606b41aa5cfae70cfce Mon Sep 17 00:00:00 2001 From: mars Date: Wed, 7 Dec 2022 15:06:25 -0700 Subject: [PATCH 9/9] Add notifications support to SAO UI --- scripts/sao-ui/Cargo.toml | 2 + scripts/sao-ui/src/lib.rs | 3 ++ scripts/sao-ui/src/notifications.rs | 82 +++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 scripts/sao-ui/src/notifications.rs diff --git a/scripts/sao-ui/Cargo.toml b/scripts/sao-ui/Cargo.toml index 2a09cd0..798db04 100644 --- a/scripts/sao-ui/Cargo.toml +++ b/scripts/sao-ui/Cargo.toml @@ -11,7 +11,9 @@ crate-type = ["cdylib"] glam = "^0.21" keyframe = "1" canary-music-player = { path = "../../apps/music-player" } +canary-notifications = { path = "../../apps/notifications" } canary-script = { path = "../../crates/script" } +canary-textwrap = { path = "../../crates/textwrap" } serde = { version = "1", features = ["derive"] } serde_json = "1" wee_alloc = "^0.4" diff --git a/scripts/sao-ui/src/lib.rs b/scripts/sao-ui/src/lib.rs index 948c2e9..f379d19 100644 --- a/scripts/sao-ui/src/lib.rs +++ b/scripts/sao-ui/src/lib.rs @@ -7,6 +7,7 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; pub mod anim; pub mod main_menu; pub mod music_player; +pub mod notifications; pub mod style; pub mod widgets; @@ -14,6 +15,7 @@ use api::*; use canary_script::*; use main_menu::MainMenuPanel; use music_player::MusicPlayerPanel; +use notifications::NotificationPanel; use widgets::Widget; export_abi!(bind_panel_impl); @@ -24,6 +26,7 @@ fn bind_panel_impl(panel: Panel, protocol: Message, msg: Message) -> Box MusicPlayerPanel::bind(panel, msg), + "tebibyte-media.desktop.notification" => NotificationPanel::bind(panel, msg), "wip-dialog" => ConfirmationDialogPanel::bind(panel, msg), _ => MainMenuPanel::bind(panel, msg), } diff --git a/scripts/sao-ui/src/notifications.rs b/scripts/sao-ui/src/notifications.rs new file mode 100644 index 0000000..4860f93 --- /dev/null +++ b/scripts/sao-ui/src/notifications.rs @@ -0,0 +1,82 @@ +// Copyright (c) 2022 Marceline Crmaer +// SPDX-License-Identifier: AGPL-3.0-or-later + +use super::widgets::prelude::*; +use api::*; +use canary_script::*; +use canary_textwrap::{Content, Layout, TextCache}; + +use shell::Offset; +use text::{Label, LabelText}; + +pub struct NotificationPanel { + panel: Panel, + summary: Offset