From 00ba16fe01e6d22170d69572505eb282ff0bdec3 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 12 Jun 2025 11:40:50 -0400 Subject: [PATCH] refactor(menu): fallback behavior for non wayland windowing system --- Cargo.toml | 1 + iced | 2 +- src/app/action.rs | 4 + src/app/cosmic.rs | 75 ++++++++++- src/widget/context_menu.rs | 98 ++++++++------- src/widget/menu/menu_bar.rs | 17 ++- src/widget/menu/menu_inner.rs | 231 +++++++++++++++++----------------- 7 files changed, 268 insertions(+), 160 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a90b0e7..132aac8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,6 +120,7 @@ libc = { version = "0.2.171", optional = true } license = { version = "3.6.0", optional = true } mime = { version = "0.3.17", optional = true } palette = "0.7.6" +raw-window-handle = "0.6" rfd = { version = "0.15.3", default-features = false, features = [ "xdg-portal", ], optional = true } diff --git a/iced b/iced index fc9d49e..2d1511d 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit fc9d49eb2f830fe394252ff6799d59ad828243bc +Subproject commit 2d1511d0cf0296e6f0cfcfcd13f2a1aa334c6915 diff --git a/src/app/action.rs b/src/app/action.rs index 44655ff..0f05a6a 100644 --- a/src/app/action.rs +++ b/src/app/action.rs @@ -36,6 +36,8 @@ pub enum Action { NavBar(nav_bar::Id), /// Activates a context menu for an item from the nav bar. NavBarContext(nav_bar::Id), + /// A new window was opened. + Opened(iced::window::Id), /// Set scaling factor ScaleFactor(f32), /// Show the window menu @@ -60,6 +62,8 @@ pub enum Action { ToolkitConfig(CosmicTk), /// Window focus lost Unfocus(iced::window::Id), + /// Windowing system initialized + WindowingSystemInitialized, /// Updates the window maximized state WindowMaximized(iced::window::Id, bool), /// Updates the tracked window geometry. diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 055ff94..f2de0f5 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -19,6 +19,65 @@ use iced::{Task, window}; use iced_futures::event::listen_with; use palette::color_difference::EuclideanDistance; +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[non_exhaustive] +pub enum WindowingSystem { + UiKit, + AppKit, + Orbital, + OhosNdk, + Xlib, + Xcb, + Wayland, + Drm, + Gbm, + Win32, + WinRt, + Web, + WebCanvas, + WebOffscreenCanvas, + AndroidNdk, + Haiku, +} + +pub(crate) static WINDOWING_SYSTEM: std::sync::OnceLock = + std::sync::OnceLock::new(); + +pub fn windowing_system() -> Option { + WINDOWING_SYSTEM.get().copied() +} + +fn init_windowing_system(handle: raw_window_handle::WindowHandle) -> crate::Action { + let raw: &raw_window_handle::RawWindowHandle = handle.as_ref(); + let system = match raw { + window::raw_window_handle::RawWindowHandle::UiKit(_) => WindowingSystem::UiKit, + window::raw_window_handle::RawWindowHandle::AppKit(_) => WindowingSystem::AppKit, + window::raw_window_handle::RawWindowHandle::Orbital(_) => WindowingSystem::Orbital, + window::raw_window_handle::RawWindowHandle::OhosNdk(_) => WindowingSystem::OhosNdk, + window::raw_window_handle::RawWindowHandle::Xlib(_) => WindowingSystem::Xlib, + window::raw_window_handle::RawWindowHandle::Xcb(_) => WindowingSystem::Xcb, + window::raw_window_handle::RawWindowHandle::Wayland(_) => WindowingSystem::Wayland, + window::raw_window_handle::RawWindowHandle::Web(_) => WindowingSystem::Web, + window::raw_window_handle::RawWindowHandle::WebCanvas(_) => WindowingSystem::WebCanvas, + window::raw_window_handle::RawWindowHandle::WebOffscreenCanvas(_) => { + WindowingSystem::WebOffscreenCanvas + } + window::raw_window_handle::RawWindowHandle::AndroidNdk(_) => WindowingSystem::AndroidNdk, + window::raw_window_handle::RawWindowHandle::Haiku(_) => WindowingSystem::Haiku, + window::raw_window_handle::RawWindowHandle::Drm(_) => WindowingSystem::Drm, + window::raw_window_handle::RawWindowHandle::Gbm(_) => WindowingSystem::Gbm, + window::raw_window_handle::RawWindowHandle::Win32(_) => WindowingSystem::Win32, + window::raw_window_handle::RawWindowHandle::WinRt(_) => WindowingSystem::WinRt, + _ => { + tracing::warn!("Unknown windowing system: {raw:?}"); + return crate::Action::Cosmic(Action::WindowingSystemInitialized); + } + }; + + _ = WINDOWING_SYSTEM.set(system); + crate::Action::Cosmic(Action::WindowingSystemInitialized) +} + #[derive(Default)] pub struct Cosmic { pub app: App, @@ -41,10 +100,17 @@ where use iced_futures::futures::executor::block_on; core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok(); } + let id = core.main_window_id().unwrap_or(window::Id::RESERVED); let (model, command) = T::init(core, flags); - (Self::new(model), command) + ( + Self::new(model), + Task::batch(vec![ + command, + iced_runtime::window::run_with_handle(id, init_windowing_system), + ]), + ) } #[cfg(not(feature = "multi-window"))] @@ -57,6 +123,7 @@ where self.app.title(id).to_string() } + #[allow(clippy::too_many_lines)] pub fn surface_update( &mut self, _surface_message: crate::surface::Action, @@ -255,6 +322,9 @@ where iced::Event::Window(window::Event::Resized(iced::Size { width, height })) => { return Some(Action::WindowResize(id, width, height)); } + iced::Event::Window(window::Event::Opened { .. }) => { + return Some(Action::Opened(id)); + } iced::Event::Window(window::Event::Closed) => { return Some(Action::SurfaceClosed(id)); } @@ -786,6 +856,9 @@ impl Cosmic { let core = self.app.core_mut(); core.applet.suggested_bounds = b; } + Action::Opened(id) => { + return iced_runtime::window::run_with_handle(id, init_windowing_system); + } _ => {} } diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index a00ae75..b1014cf 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -3,6 +3,8 @@ //! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. +#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] +use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; use crate::widget::menu::{ self, CloseCondition, Direction, ItemHeight, ItemWidth, MenuBarState, PathHighlight, init_root_menu, menu_roots_diff, @@ -361,21 +363,23 @@ impl Widget feature = "winit", feature = "surface-message" ))] - state.menu_bar_state.inner.with_data_mut(|state| { - if let Some(id) = state.popup_id.remove(&self.window_id) { - state.menu_states.clear(); - state.active_root.clear(); - state.open = false; + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + state.menu_bar_state.inner.with_data_mut(|state| { + if let Some(id) = state.popup_id.remove(&self.window_id) { + state.menu_states.clear(); + state.active_root.clear(); + state.open = false; - { - let surface_action = self.on_surface_action.as_ref().unwrap(); - shell.publish(surface_action( - crate::surface::action::destroy_popup(id), - )); + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + shell.publish(surface_action( + crate::surface::action::destroy_popup(id), + )); + } + state.view_cursor = cursor; } - state.view_cursor = cursor; - } - }); + }); + } } _ => (), @@ -392,7 +396,9 @@ impl Widget state.view_cursor = cursor; }); #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - self.create_popup(layout, cursor, renderer, shell, viewport, state); + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + self.create_popup(layout, cursor, renderer, shell, viewport, state); + } return event::Status::Captured; } else if right_button_released(&event) @@ -400,33 +406,7 @@ impl Widget || left_button_released(&event) { #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - state.menu_bar_state.inner.with_data_mut(|state| { - if let Some(id) = state.popup_id.remove(&self.window_id) { - state.menu_states.clear(); - state.active_root.clear(); - state.open = false; - - { - let surface_action = self.on_surface_action.as_ref().unwrap(); - - shell - .publish(surface_action(crate::surface::action::destroy_popup(id))); - } - state.view_cursor = cursor; - } - }); - } - } else if open { - match event { - Event::Mouse(mouse::Event::ButtonReleased( - mouse::Button::Right | mouse::Button::Left, - )) - | Event::Touch(touch::Event::FingerLifted { .. }) => { - #[cfg(all( - feature = "wayland", - feature = "winit", - feature = "surface-message" - ))] + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { state.menu_bar_state.inner.with_data_mut(|state| { if let Some(id) = state.popup_id.remove(&self.window_id) { state.menu_states.clear(); @@ -444,6 +424,37 @@ impl Widget } }); } + } + } else if open { + match event { + Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Right | mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerLifted { .. }) => { + #[cfg(all( + feature = "wayland", + feature = "winit", + feature = "surface-message" + ))] + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + state.menu_bar_state.inner.with_data_mut(|state| { + if let Some(id) = state.popup_id.remove(&self.window_id) { + state.menu_states.clear(); + state.active_root.clear(); + state.open = false; + + { + let surface_action = self.on_surface_action.as_ref().unwrap(); + + shell.publish(surface_action( + crate::surface::action::destroy_popup(id), + )); + } + state.view_cursor = cursor; + } + }); + } + } _ => (), } } @@ -468,7 +479,10 @@ impl Widget translation: Vector, ) -> Option> { #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) + && self.window_id != window::Id::NONE + && self.on_surface_action.is_some() + { return None; } diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 66a4b9b..707aebd 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -9,6 +9,8 @@ use super::{ }, menu_tree::MenuTree, }; +#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] +use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; use crate::{ Renderer, style::menu_bar::StyleSheet, @@ -629,13 +631,17 @@ where return event::Status::Ignored; } #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); + } } Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) if open && view_cursor.is_over(layout.bounds()) => { #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); + } } _ => (), } @@ -710,7 +716,12 @@ where translation: Vector, ) -> Option> { #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] - return None; + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) + && self.on_surface_action.is_some() + && self.window_id != window::Id::NONE + { + return None; + } let state = tree.state.downcast_ref::(); if state.inner.with_data(|state| !state.open) { diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 595632a..ecf5f8a 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -4,6 +4,8 @@ use std::{borrow::Cow, sync::Arc}; use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; +#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] +use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem}; use crate::style::menu_bar::StyleSheet; use iced::window; @@ -665,21 +667,24 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { feature = "winit", feature = "surface-message" ))] - if let Some(handler) = self.on_surface_action.as_ref() { - let mut root = self.window_id; - let mut depth = self.depth; - while let Some(parent) = - state.popup_id.iter().find(|(_, v)| **v == root) - { - // parent of root popup is the window, so we stop. - if depth == 0 { - break; + if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { + if let Some(handler) = self.on_surface_action.as_ref() { + let mut root = self.window_id; + let mut depth = self.depth; + while let Some(parent) = + state.popup_id.iter().find(|(_, v)| **v == root) + { + // parent of root popup is the window, so we stop. + if depth == 0 { + break; + } + root = *parent.0; + depth = depth.saturating_sub(1); } - root = *parent.0; - depth = depth.saturating_sub(1); + shell.publish((handler)(crate::surface::Action::DestroyPopup( + root, + ))); } - shell - .publish((handler)(crate::surface::Action::DestroyPopup(root))); } state.reset(); @@ -922,72 +927,73 @@ impl Widget Widget Widget