diff --git a/examples/applet/src/window.rs b/examples/applet/src/window.rs index 4f34ddfb..34e29a5c 100644 --- a/examples/applet/src/window.rs +++ b/examples/applet/src/window.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, Mutex}; + use cosmic::app::Core; use cosmic::iced::application; use cosmic::iced::platform_specific::shell::commands::popup::{destroy_popup, get_popup}; @@ -6,7 +8,7 @@ use cosmic::iced::{Length, Limits, Task}; use cosmic::iced_runtime::core::window; use cosmic::theme::iced; use cosmic::widget::{list_column, settings, toggler}; -use cosmic::{Element, Theme}; +use cosmic::{applet, Element, Theme}; const ID: &str = "com.system76.CosmicAppletExample"; @@ -14,7 +16,7 @@ const ID: &str = "com.system76.CosmicAppletExample"; pub struct Window { core: Core, popup: Option, - example_row: bool, + example_row: Arc>, } #[derive(Clone, Debug)] @@ -73,6 +75,32 @@ impl cosmic::Application for Window { .height(500) .width(500); popup_settings.positioner.size = Some((500, 500)); + + let applet = self.core.applet.clone(); + let toggled = self.example_row.clone(); + popup_settings = popup_settings.with_view(Box::new( + move || -> Option>> { + { + let guard = toggled.lock().unwrap(); + + let content_list = + list_column().padding(5).spacing(0).add(settings::item( + "Example row", + cosmic::widget::container( + toggler(*guard).on_toggle(|value| { + Message::ToggleExampleRow(value) + }), + ) + .height(Length::Fixed(50.)), + )); + + Some( + Element::from(applet.popup_container(content_list)) + .map(cosmic::app::Message::App), + ) + } + }, + )); get_popup(popup_settings) }; } @@ -81,7 +109,10 @@ impl cosmic::Application for Window { self.popup = None; } } - Message::ToggleExampleRow(toggled) => self.example_row = toggled, + Message::ToggleExampleRow(toggled) => { + let mut guard = self.example_row.lock().unwrap(); + *guard = toggled; + } } Task::none() } @@ -95,15 +126,7 @@ impl cosmic::Application for Window { } fn view_window(&self, _id: Id) -> Element { - let content_list = list_column().padding(5).spacing(0).add(settings::item( - "Example row", - cosmic::widget::container( - toggler(self.example_row).on_toggle(|value| Message::ToggleExampleRow(value)), - ) - .height(Length::Fixed(50.)), - )); - - self.core.applet.popup_container(content_list).into() + "oops".into() } fn style(&self) -> Option { diff --git a/examples/application/src/notif.rs b/examples/application/src/notif.rs new file mode 100644 index 00000000..19f80f43 --- /dev/null +++ b/examples/application/src/notif.rs @@ -0,0 +1,89 @@ +use std::error::Error; +use std::time::Duration; +use std::{collections::HashMap, convert::Infallible}; + +use cosmic::{app::Message, Task}; +use zbus::{export::futures_util::StreamExt, proxy, zvariant::Value, Connection}; + +#[proxy( + default_service = "org.freedesktop.Notifications", + default_path = "/org/freedesktop/Notifications" +)] +trait Notifications { + /// Call the org.freedesktop.Notifications.Notify D-Bus method + fn notify( + &self, + app_name: &str, + replaces_id: u32, + app_icon: &str, + summary: &str, + body: &str, + actions: &[&str], + hints: HashMap<&str, &Value<'_>>, + expire_timeout: i32, + ) -> zbus::Result; + + // We need to define the signal we want to listen for + #[zbus(signal)] + fn action_invoked(&self, id: u32, action_key: String) -> zbus::Result<()>; + + // We need to define the signal we want to listen for + #[zbus(signal)] + fn activation_token(&self, id: u32, activation_token: String) -> zbus::Result<()>; +} + +pub fn notif_task() -> Task> { + Task::future(async { + match notif().await { + Ok(t) => t, + Err(err) => { + tracing::error!("Ooops: {err:?}"); + panic!("oops"); + } + } + }) +} + +pub async fn notif( +) -> Result, Box> { + tokio::time::sleep(Duration::from_secs(2)).await; + let connection = Connection::session().await?; + + let proxy = NotificationsProxy::new(&connection).await?; + let mut actions = proxy.receive_action_invoked().await?; + let mut tokens = proxy.receive_activation_token().await?; + + let _reply = proxy + .notify( + "my-app", + 0, + "dialog-information", + "A summary", + "Some body", + &["default", "asdf"], + HashMap::new(), + 5000, + ) + .await?; + + let token = if let Some(msg) = tokens.next().await { + // struct `JobNewArgs` is generated from `job_new` signal function arguments + let args: ActivationTokenArgs = msg.args().expect("Error parsing message"); + + println!("action: id={} action={}", args.id, args.activation_token,); + args.activation_token + } else { + eprintln!("no token"); + panic!("oops"); + }; + if let Some(msg) = actions.next().await { + // struct `JobNewArgs` is generated from `job_new` signal function arguments + let args: ActionInvokedArgs = msg.args().expect("Error parsing message"); + + println!("action: id={} action={}", args.id, args.action_key,); + } + + Ok(Message::Cosmic(cosmic::app::cosmic::Message::Activate( + token, + ))) +} diff --git a/iced b/iced index a8c31a09..bb5e4f70 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit a8c31a0982b134278e7ecb5a91e0d3ba0fa6d2dd +Subproject commit bb5e4f700de09ca821fe0b1641157ad69d0b184c diff --git a/src/app/core.rs b/src/app/core.rs index 42c4429f..04ab3399 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -13,6 +13,8 @@ use slotmap::Key; use crate::Theme; +use super::{Element, Task}; + /// Status of the nav bar and its panels. #[derive(Clone)] pub struct NavBar { diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 42a16445..fcdee62c 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 use std::borrow::Borrow; +use std::collections::HashMap; use std::sync::Arc; use super::{Application, ApplicationExt, Core, Subscription}; @@ -81,8 +82,11 @@ pub enum Message { } #[derive(Default)] -pub struct Cosmic { +pub struct Cosmic { pub app: App, + #[cfg(feature = "wayland")] + pub subsurfaces: + HashMap Element<'static, super::Message>>>, } impl Cosmic @@ -123,6 +127,20 @@ where super::Message::None => iced::Task::none(), #[cfg(feature = "single-instance")] super::Message::DbusActivation(message) => self.app.dbus_activation(message), + #[cfg(feature = "wayland")] + super::Message::Subsurface(settings, view) => { + if let Some(view) = view.and_then(|view| match view.downcast() { + Ok(v) => Some(v), + Err(err) => { + tracing::error!("Invalid view for subsurface view: {err:?}"); + None + } + }) { + self.get_subsurface(settings, view) + } else { + iced_winit::commands::subsurface::get_subsurface(settings) + } + } }; #[cfg(target_env = "gnu")] @@ -287,6 +305,10 @@ where #[cfg(feature = "multi-window")] pub fn view(&self, id: window::Id) -> Element> { + #[cfg(feature = "wayland")] + if let Some(v) = self.subsurfaces.get(&id) { + return v(&self.app); + } if !self .app .core() @@ -697,6 +719,23 @@ impl Cosmic { impl Cosmic { pub fn new(app: App) -> Self { - Self { app } + Self { + app, + #[cfg(feature = "wayland")] + subsurfaces: HashMap::new(), + } + } + + #[cfg(feature = "wayland")] + /// Create a subsurface + pub fn get_subsurface( + &mut self, + settings: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings, + view: Box Element<'static, super::Message>>, + ) -> Task> { + use iced_winit::commands::subsurface::get_subsurface; + + self.subsurfaces.insert(settings.id, view); + get_subsurface(settings) } } diff --git a/src/app/mod.rs b/src/app/mod.rs index bfb2a142..cd2b31a5 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -27,6 +27,12 @@ pub mod message { DbusActivation(super::DbusActivationMessage), /// Do nothing None, + #[cfg(feature = "wayland")] + /// Create a subsurface with a view function + Subsurface( + iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings, + Option>, + ), } pub const fn app(message: M) -> Message { diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 0ca8a4f1..531f65c3 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -308,6 +308,7 @@ impl Context { }, parent_size: None, grab: true, + popup_view: None, } }