From c3ea6286f53842b190698a2bfcae7153d39dd864 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 20 Nov 2023 11:40:52 -0500 Subject: [PATCH 1/5] fix: warning abount missing assume_defaults key and use name key for dbus_interface macro --- src/app/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 6ce284d2..91fb8d0b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -160,7 +160,7 @@ impl DbusActivation { } #[cfg(feature = "single-instance")] -#[dbus_proxy(interface = "org.freedesktop.DbusActivation")] +#[dbus_proxy(interface = "org.freedesktop.DbusActivation", assume_defaults = true)] pub trait DbusActivationInterface { /// Activate the application. fn activate(&mut self, platform_data: HashMap<&str, Value<'_>>) -> zbus::Result<()>; @@ -182,7 +182,7 @@ pub trait DbusActivationInterface { } #[cfg(feature = "single-instance")] -#[dbus_interface(interface = "org.freedesktop.DbusActivation")] +#[dbus_interface(name = "org.freedesktop.DbusActivation")] impl DbusActivation { async fn activate(&mut self, platform_data: HashMap<&str, Value<'_>>) { if let Some(tx) = &mut self.0 { From 70d54d6f2071fbcd2ab29045a7945b2bb6bc2f22 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 20 Nov 2023 11:41:57 -0500 Subject: [PATCH 2/5] update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 94f8772b..b3ede4f9 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 94f8772b2f05195506f7a454e41290afe02eaa40 +Subproject commit b3ede4f9a72275cfeb29fac80a31546f728783fd From 001fd744c5f80c9ce058eb0e22ae92f19d12c844 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Mon, 20 Nov 2023 10:59:11 -0700 Subject: [PATCH 3/5] Allow custom position in popover --- src/widget/popover.rs | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 754c177e..b0871917 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -9,7 +9,9 @@ use iced_core::mouse; use iced_core::overlay; use iced_core::renderer; use iced_core::widget::{Operation, OperationOutputWrapper, Tree}; -use iced_core::{Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Widget}; +use iced_core::{ + Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, +}; use std::cell::RefCell; pub use iced_style::container::{Appearance, StyleSheet}; @@ -25,19 +27,26 @@ pub struct Popover<'a, Message, Renderer> { content: Element<'a, Message, Renderer>, // XXX Avoid refcell; improve iced overlay API? popup: RefCell>, + position: Option, } impl<'a, Message, Renderer> Popover<'a, Message, Renderer> { - fn new( + pub fn new( content: impl Into>, popup: impl Into>, ) -> Self { Self { content: content.into(), popup: RefCell::new(popup.into()), + position: None, } } + pub fn position(mut self, position: Point) -> Self { + self.position = Some(position); + self + } + // TODO More options for positioning similar to GdkPopup, xdg_popup } @@ -145,9 +154,20 @@ where layout: Layout<'_>, _renderer: &Renderer, ) -> Option> { - // Set position to center of bottom edge let bounds = layout.bounds(); - let position = Point::new(bounds.x + bounds.width / 2.0, bounds.y + bounds.height); + let (position, centered) = match self.position { + Some(relative) => ( + bounds.position() + Vector::new(relative.x, relative.y), + false, + ), + None => { + // Set position to center of bottom edge + ( + Point::new(bounds.x + bounds.width / 2.0, bounds.y + bounds.height), + true, + ) + } + }; // XXX needed to use RefCell to get &mut for popup element Some(overlay::Element::new( @@ -155,6 +175,7 @@ where Box::new(Overlay { tree: &mut tree.children[1], content: &self.popup, + centered, }), )) } @@ -171,9 +192,10 @@ where } } -struct Overlay<'a, 'b, Message, Renderer> { +pub struct Overlay<'a, 'b, Message, Renderer> { tree: &'a mut Tree, content: &'a RefCell>, + centered: bool, } impl<'a, 'b, Message, Renderer> overlay::Overlay @@ -182,13 +204,13 @@ where Renderer: iced_core::Renderer, { fn layout(&self, renderer: &Renderer, bounds: Size, mut position: Point) -> layout::Node { - // Position is set to the center bottom of the lower widget - let limits = layout::Limits::new(Size::UNIT, bounds); let mut node = self.content.borrow().as_widget().layout(renderer, &limits); - - let width = node.size().width; - position.x = (position.x - width / 2.0).clamp(0.0, bounds.width - width); + if self.centered { + // Position is set to the center bottom of the lower widget + let width = node.size().width; + position.x = (position.x - width / 2.0).clamp(0.0, bounds.width - width); + } node.move_to(position); node From 57f4abb8a000c0e554c77807100025301599cf3f Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 21 Nov 2023 15:18:39 -0500 Subject: [PATCH 4/5] refactor: make the single-instance feature additive --- examples/cosmic/Cargo.toml | 2 +- src/app/core.rs | 4 +- src/app/cosmic.rs | 9 ++- src/app/mod.rs | 111 ++++++++++++++----------------------- src/app/settings.rs | 4 -- 5 files changed, 53 insertions(+), 77 deletions(-) diff --git a/examples/cosmic/Cargo.toml b/examples/cosmic/Cargo.toml index 72439a97..a4c3bf3e 100644 --- a/examples/cosmic/Cargo.toml +++ b/examples/cosmic/Cargo.toml @@ -8,7 +8,7 @@ publish = false [dependencies] apply = "0.3.0" fraction = "0.13.0" -libcosmic = { path = "../..", features = ["debug", "winit", "tokio"] } +libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance"] } once_cell = "1.18" slotmap = "1.0.6" env_logger = "0.10" diff --git a/src/app/core.rs b/src/app/core.rs index 02e18d3b..d401f014 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -63,7 +63,8 @@ pub struct Core { #[cfg(feature = "applet")] pub applet: crate::applet::Context, - pub single_instance: bool, + #[cfg(feature = "single-instance")] + pub(crate) single_instance: bool, } impl Default for Core { @@ -106,6 +107,7 @@ impl Default for Core { }, #[cfg(feature = "applet")] applet: crate::applet::Context::default(), + #[cfg(feature = "single-instance")] single_instance: false, } } diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index f820649f..7a89b19e 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -97,6 +97,8 @@ where super::Message::App(message) => self.app.update(message), super::Message::Cosmic(message) => self.cosmic_update(message), super::Message::None => iced::Command::none(), + #[cfg(feature = "single-instance")] + super::Message::DbusActivation(message) => self.app.dbus_activation(message), } } @@ -184,7 +186,7 @@ where .core() .single_instance .then(|| super::single_instance_subscription::()) - .unwrap_or_else(Subscription::none), + .unwrap_or_else(|| Subscription::none()), ]) } @@ -364,11 +366,12 @@ impl Cosmic { }); } } - Message::Activate(token) => { + Message::Activate(_token) => { #[cfg(feature = "wayland")] return iced_sctk::commands::activation::activate( iced::window::Id::default(), - token, + #[allow(clippy::used_underscore_binding)] + _token, ); } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 91fb8d0b..305d3e34 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -19,6 +19,9 @@ pub mod message { App(M), /// Internal messages to be handled by libcosmic. Cosmic(super::cosmic::Message), + #[cfg(feature = "single-instance")] + /// Dbus activation messages + DbusActivation(super::DbusActivationMessage), /// Do nothing None, } @@ -36,8 +39,6 @@ pub mod message { } } -use std::str::FromStr; - pub use self::command::Command; pub use self::core::Core; pub use self::settings::Settings; @@ -57,12 +58,11 @@ use { std::collections::HashMap, zbus::{dbus_interface, dbus_proxy, zvariant::Value}, }; -/// Launch a COSMIC application with the given [`Settings`]. -/// -/// # Errors -/// -/// Returns error on application failure. -pub fn run(settings: Settings, flags: App::Flags) -> iced::Result { + +pub(crate) fn iced_settings( + settings: Settings, + flags: App::Flags, +) -> iced::Settings<(Core, App::Flags)> { if let Some(icon_theme) = settings.default_icon_theme { crate::icon_theme::set_default(icon_theme); } @@ -72,7 +72,6 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res core.set_scale_factor(settings.scale_factor); core.set_window_width(settings.size.0); core.set_window_height(settings.size.1); - core.single_instance = settings.single_instance; THEME.with(move |t| { let mut cosmic_theme = t.borrow_mut(); @@ -120,8 +119,20 @@ pub fn run(settings: Settings, flags: App::Flags) -> iced::Res iced.window.transparent = settings.transparent; } - cosmic::Cosmic::::run(iced) + iced } + +/// Launch a COSMIC application with the given [`Settings`]. +/// +/// # Errors +/// +/// Returns error on application failure. +pub fn run(settings: Settings, flags: App::Flags) -> iced::Result { + let settings = iced_settings::(settings, flags); + + cosmic::Cosmic::::run(settings) +} + #[cfg(feature = "single-instance")] #[derive(Debug, Clone)] pub struct DbusActivationMessage> { @@ -259,16 +270,16 @@ impl DbusActivation { } #[cfg(feature = "single-instance")] - /// Launch a COSMIC application with the given [`Settings`]. /// If the application is already running, the arguments will be passed to the /// running instance. /// # Errors /// Returns error on application failure. -pub fn run_single_instance( - mut settings: Settings, - flags: App::Flags, -) -> iced::Result { +pub fn run_single_instance(settings: Settings, flags: App::Flags) -> iced::Result +where + App::Flags: CosmicFlags + Clone, + App::Message: Clone + std::fmt::Debug + Send + 'static, +{ let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok(); let override_single = std::env::var("COSMIC_SINGLE_INSTANCE") @@ -279,7 +290,6 @@ pub fn run_single_instance( } let path: String = format!("/{}", App::APP_ID.replace('.', "/")); - settings.single_instance = true; let Ok(conn) = zbus::blocking::Connection::session() else { tracing::warn!("Failed to connect to dbus"); @@ -322,13 +332,15 @@ pub fn run_single_instance( tracing::info!("Another instance is running"); Ok(()) } else { - run::(settings, flags) + let mut settings = iced_settings::(settings, flags); + settings.flags.0.single_instance = true; + cosmic::Cosmic::::run(settings) } } pub trait CosmicFlags { - type SubCommand: FromStr + ToString + std::fmt::Debug + Clone + Send + 'static; - type Args: TryFrom> + Into> + std::fmt::Debug + Clone + Send + 'static; + type SubCommand: ToString + std::fmt::Debug + Clone + Send + 'static; + type Args: Into> + std::fmt::Debug + Clone + Send + 'static; #[must_use] fn action(&self) -> Option<&Self::SubCommand> { None @@ -349,27 +361,9 @@ where /// Default async executor to use with the app. type Executor: iced_futures::Executor; - #[cfg(feature = "single-instance")] - /// Argument received [`Application::new`]. - type Flags: Clone + CosmicFlags; - - #[cfg(not(feature = "single-instance"))] /// Argument received [`Application::new`]. type Flags: Clone; - #[cfg(feature = "single-instance")] - /// Message type specific to our app. - type Message: Clone - + From< - DbusActivationDetails< - ::SubCommand, - ::Args, - >, - > + std::fmt::Debug - + Send - + 'static; - - #[cfg(not(feature = "single-instance"))] /// Message type specific to our app. type Message: Clone + std::fmt::Debug + Send + 'static; @@ -465,6 +459,15 @@ where fn style(&self) -> Option<::Style> { None } + + /// Handles dbus activation messages + #[cfg(feature = "single-instance")] + fn dbus_activation( + &mut self, + msg: DbusActivationMessage, + ) -> iced::Command> { + iced::Command::none() + } } /// Methods automatically derived for all types implementing [`Application`]. @@ -633,7 +636,7 @@ fn single_instance_subscription() -> Subscription(), 10, - |mut output| async move { + move |mut output| async move { let mut single_instance: DbusActivation = DbusActivation::new(); let mut rx = single_instance.rx(); if let Ok(builder) = zbus::ConnectionBuilder::session() { @@ -680,36 +683,8 @@ fn single_instance_subscription() -> Subscription { - Some(DbusActivationDetails::Activate) - } - DbusActivationDetails::Open { url } => { - Some(DbusActivationDetails::Open { url }) - } - DbusActivationDetails::ActivateAction { action, args } => { - if let (Ok(action), Ok(args)) = ( - ::SubCommand::from_str(&action), - ::Args::try_from(args), - ) { - Some(DbusActivationDetails::ActivateAction::< - ::SubCommand, - ::Args, - > { - action, - args, - }) - } else { - tracing::error!("Invalid action or args"); - None - } - } - } { - if let Err(err) = - output.send(Message::App(App::Message::from(msg))).await - { - tracing::error!(?err, "Failed to send message"); - } + if let Err(err) = output.send(Message::DbusActivation(msg)).await { + tracing::error!(?err, "Failed to send message"); } } } diff --git a/src/app/settings.rs b/src/app/settings.rs index 1e432235..10ed4dd3 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -62,9 +62,6 @@ pub struct Settings { /// Whether the application should exit when there are no open windows pub(crate) exit_on_close: bool, - - /// Only allow a single instance of the application to run - pub single_instance: bool, } impl Settings { @@ -100,7 +97,6 @@ impl Default for Settings { theme: crate::theme::system_preference(), transparent: false, exit_on_close: true, - single_instance: false, } } } From d1537b4c39bc48b020c9965c0c862207d0060889 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 22 Nov 2023 11:12:59 -0800 Subject: [PATCH 5/5] fix: compilation with latest `taffy` commit Also adds `rev =` to `Cargo.toml` specifying the latest commit. A library like `libcosmic` shouldn't use git dependencies without specifying a commit or tag. --- Cargo.toml | 1 + src/widget/grid/layout.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 678d44d3..e275b7ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,6 +128,7 @@ optional = true [dependencies.taffy] git = "https://github.com/DioxusLabs/taffy" +rev = "7781c70" features = ["grid"] [workspace] diff --git a/src/widget/grid/layout.rs b/src/widget/grid/layout.rs index 1ac77c6b..79f6efcd 100644 --- a/src/widget/grid/layout.rs +++ b/src/widget/grid/layout.rs @@ -9,7 +9,7 @@ use iced_core::{Alignment, Length, Padding, Point, Size}; use taffy::geometry::{Line, Rect}; use taffy::style::{AlignItems, Dimension, Display, GridPlacement, Style}; use taffy::style_helpers::{auto, length}; -use taffy::Taffy; +use taffy::TaffyTree; #[allow(clippy::too_many_lines)] pub fn resolve( @@ -30,7 +30,7 @@ pub fn resolve( let mut leafs = Vec::with_capacity(items.len()); let mut nodes = Vec::with_capacity(items.len()); - let mut taffy = Taffy::<()>::with_capacity(items.len() + 1); + let mut taffy = TaffyTree::<()>::with_capacity(items.len() + 1); // Attach widgets as child nodes. for (child, assignment) in items.iter().zip(assignments.iter()) {