Merge pull request 'Notification daemon' (#55) from notifications into main
Reviewed-on: #55
This commit is contained in:
commit
7adc356c41
|
@ -2,6 +2,7 @@
|
|||
members = [
|
||||
"apps/magpie",
|
||||
"apps/music-player",
|
||||
"apps/notifications",
|
||||
"apps/sandbox",
|
||||
"crates/script",
|
||||
"crates/textwrap",
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
@ -26,6 +26,7 @@ pub struct CreatePanel {
|
|||
pub id: PanelId,
|
||||
pub protocol: String,
|
||||
pub script: PathBuf,
|
||||
pub init_msg: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Sends a panel a message.
|
||||
|
@ -210,6 +211,16 @@ impl<T: Read, I: DeserializeOwned, O> Messenger<T, I, O> {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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::*;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ pub enum WindowMessage {
|
|||
id: usize,
|
||||
protocol: String,
|
||||
script: PathBuf,
|
||||
init_msg: Vec<u8>,
|
||||
},
|
||||
CloseWindow {
|
||||
id: usize,
|
||||
|
@ -194,13 +195,14 @@ impl WindowStore {
|
|||
id,
|
||||
protocol,
|
||||
script,
|
||||
init_msg,
|
||||
} => {
|
||||
log::debug!("Opening window {} with script {:?}...", id, script);
|
||||
let start = std::time::Instant::now();
|
||||
let module = std::fs::read(script)?;
|
||||
let mut script = self.runtime.load_module(&module)?;
|
||||
log::debug!("Instantiated window {} script in {:?}", id, start.elapsed());
|
||||
let panel = script.create_panel(&protocol, vec![])?;
|
||||
let panel = script.create_panel(&protocol, init_msg)?;
|
||||
log::debug!("Created window {} panel in {:?}", id, start.elapsed());
|
||||
let window = Window::new(self.ipc_sender.to_owned(), id, panel, &event_loop)?;
|
||||
let window_id = window.get_id();
|
||||
|
|
|
@ -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();
|
||||
|
@ -207,6 +205,7 @@ fn main() {
|
|||
id: 0,
|
||||
protocol,
|
||||
script,
|
||||
init_msg: vec![],
|
||||
};
|
||||
|
||||
let msg = MagpieServerMsg::CreatePanel(msg);
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
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", 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"]
|
|
@ -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<String>,
|
||||
|
||||
/// The summary text briefly describing the notification.
|
||||
pub summary: String,
|
||||
|
||||
/// The optional detailed body text.
|
||||
pub body: Option<String>,
|
||||
|
||||
/// The timeout time in milliseconds since the display of the notification
|
||||
/// at which the notification should automatically close.
|
||||
pub timeout: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum OutMsg {}
|
|
@ -0,0 +1,118 @@
|
|||
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<UnixStream>;
|
||||
|
||||
pub struct Notifications {
|
||||
module_path: PathBuf,
|
||||
magpie: MagpieClient,
|
||||
next_id: u32,
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.freedesktop.Notifications")]
|
||||
impl Notifications {
|
||||
fn get_capabilities(&self) -> Vec<String> {
|
||||
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(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn notify(
|
||||
&mut self,
|
||||
app_name: String,
|
||||
replaces_id: u32,
|
||||
app_icon: String,
|
||||
summary: String,
|
||||
body: String,
|
||||
actions: Vec<String>,
|
||||
hints: HashMap<String, Value<'_>>,
|
||||
timeout: i32,
|
||||
) -> u32 {
|
||||
let contents = Contents {
|
||||
app_name: Some(app_name).filter(|s| !s.is_empty()),
|
||||
summary,
|
||||
body: Some(body).filter(|s| !s.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) {}
|
||||
|
||||
#[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() {
|
||||
let args: Vec<String> = 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,
|
||||
next_id: 0,
|
||||
module_path,
|
||||
};
|
||||
|
||||
let _ = ConnectionBuilder::session()
|
||||
.unwrap()
|
||||
.name("org.freedesktop.Notifications")
|
||||
.unwrap()
|
||||
.serve_at("/org/freedesktop/Notifications", notifications)
|
||||
.unwrap()
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pending::<()>().await;
|
||||
});
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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<dyn Pan
|
|||
|
||||
match protocol.as_str() {
|
||||
"tebibyte-media.desktop.music-player-controller" => MusicPlayerPanel::bind(panel, msg),
|
||||
"tebibyte-media.desktop.notification" => NotificationPanel::bind(panel, msg),
|
||||
"wip-dialog" => ConfirmationDialogPanel::bind(panel, msg),
|
||||
_ => MainMenuPanel::bind(panel, msg),
|
||||
}
|
||||
|
|
|
@ -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<Label>,
|
||||
text_cache: TextCache,
|
||||
body: Content,
|
||||
body_layout: Layout,
|
||||
body_rect: Rect,
|
||||
}
|
||||
|
||||
impl PanelImpl for NotificationPanel {
|
||||
fn update(&mut self, dt: f32) {}
|
||||
|
||||
fn draw(&mut self) {
|
||||
let ctx = DrawContext::new(self.panel);
|
||||
ctx.draw_rounded_rect(self.body_rect, 5.0, THEME.palette.base);
|
||||
self.summary.draw(&ctx);
|
||||
|
||||
let ctx = ctx.with_offset(Vec2::new(5.0, 20.0));
|
||||
self.body_layout
|
||||
.draw(&self.text_cache, &ctx, 5.0, 8.0, THEME.palette.text);
|
||||
}
|
||||
|
||||
fn on_resize(&mut self, new_size: Vec2) {
|
||||
self.body_rect = Rect::from_xy_size(Vec2::ZERO, new_size);
|
||||
|
||||
let width = (new_size.x - 10.0) / 5.0;
|
||||
self.body_layout = self.body.layout(&mut self.text_cache, width);
|
||||
}
|
||||
|
||||
fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {}
|
||||
|
||||
fn on_message(&mut self, msg: Message) {}
|
||||
}
|
||||
|
||||
impl NotificationPanel {
|
||||
pub fn bind(panel: Panel, msg: Message) -> Box<dyn PanelImpl> {
|
||||
let msg = msg.to_vec();
|
||||
let msg: canary_notifications::Contents = serde_json::from_slice(&msg).unwrap();
|
||||
|
||||
let font = Font::new(crate::DISPLAY_FONT);
|
||||
let text = msg.summary;
|
||||
let text = LabelText { font, text };
|
||||
let summary = Label::new(
|
||||
text,
|
||||
text::HorizontalAlignment::Left,
|
||||
10.0,
|
||||
THEME.palette.text,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
);
|
||||
let summary = Offset::new(summary, Vec2::new(5.0, 12.0));
|
||||
|
||||
let font = Font::new(crate::CONTENT_FONT);
|
||||
let text = msg.body.unwrap_or(String::new());
|
||||
let mut text_cache = TextCache::default();
|
||||
let body = Content::from_plain(&mut text_cache, font, &text);
|
||||
let body_layout = body.layout(&text_cache, 0.0);
|
||||
|
||||
let body_rect = Rect::from_xy_size(Vec2::ZERO, Vec2::ZERO);
|
||||
|
||||
Box::new(Self {
|
||||
panel,
|
||||
summary,
|
||||
text_cache,
|
||||
body,
|
||||
body_layout,
|
||||
body_rect,
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue