cosmic/app/
cosmic.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use std::borrow::Borrow;
5use std::collections::{HashMap, HashSet};
6use std::sync::Arc;
7
8use super::{Action, Application, ApplicationExt, Subscription};
9use crate::theme::{THEME, Theme, ThemeType};
10use crate::{Core, Element, keyboard_nav};
11#[cfg(all(feature = "wayland", target_os = "linux"))]
12use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
13use cosmic_theme::ThemeMode;
14#[cfg(not(any(feature = "multi-window", feature = "wayland", target_os = "linux")))]
15use iced::Application as IcedApplication;
16#[cfg(all(feature = "wayland", target_os = "linux"))]
17use iced::event::wayland;
18use iced::{Task, theme, window};
19use iced_futures::event::listen_with;
20#[cfg(all(feature = "wayland", target_os = "linux"))]
21use iced_winit::SurfaceIdWrapper;
22use palette::color_difference::EuclideanDistance;
23
24#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
25#[non_exhaustive]
26pub enum WindowingSystem {
27    UiKit,
28    AppKit,
29    Orbital,
30    OhosNdk,
31    Xlib,
32    Xcb,
33    Wayland,
34    Drm,
35    Gbm,
36    Win32,
37    WinRt,
38    Web,
39    WebCanvas,
40    WebOffscreenCanvas,
41    AndroidNdk,
42    Haiku,
43}
44
45pub(crate) static WINDOWING_SYSTEM: std::sync::OnceLock<WindowingSystem> =
46    std::sync::OnceLock::new();
47
48pub fn windowing_system() -> Option<WindowingSystem> {
49    WINDOWING_SYSTEM.get().copied()
50}
51
52fn init_windowing_system<M>(handle: window::raw_window_handle::WindowHandle) -> crate::Action<M> {
53    let raw = handle.as_ref();
54    let system = match raw {
55        window::raw_window_handle::RawWindowHandle::UiKit(_) => WindowingSystem::UiKit,
56        window::raw_window_handle::RawWindowHandle::AppKit(_) => WindowingSystem::AppKit,
57        window::raw_window_handle::RawWindowHandle::Orbital(_) => WindowingSystem::Orbital,
58        window::raw_window_handle::RawWindowHandle::OhosNdk(_) => WindowingSystem::OhosNdk,
59        window::raw_window_handle::RawWindowHandle::Xlib(_) => WindowingSystem::Xlib,
60        window::raw_window_handle::RawWindowHandle::Xcb(_) => WindowingSystem::Xcb,
61        window::raw_window_handle::RawWindowHandle::Wayland(_) => WindowingSystem::Wayland,
62        window::raw_window_handle::RawWindowHandle::Web(_) => WindowingSystem::Web,
63        window::raw_window_handle::RawWindowHandle::WebCanvas(_) => WindowingSystem::WebCanvas,
64        window::raw_window_handle::RawWindowHandle::WebOffscreenCanvas(_) => {
65            WindowingSystem::WebOffscreenCanvas
66        }
67        window::raw_window_handle::RawWindowHandle::AndroidNdk(_) => WindowingSystem::AndroidNdk,
68        window::raw_window_handle::RawWindowHandle::Haiku(_) => WindowingSystem::Haiku,
69        window::raw_window_handle::RawWindowHandle::Drm(_) => WindowingSystem::Drm,
70        window::raw_window_handle::RawWindowHandle::Gbm(_) => WindowingSystem::Gbm,
71        window::raw_window_handle::RawWindowHandle::Win32(_) => WindowingSystem::Win32,
72        window::raw_window_handle::RawWindowHandle::WinRt(_) => WindowingSystem::WinRt,
73        _ => {
74            tracing::warn!("Unknown windowing system: {raw:?}");
75            return crate::Action::Cosmic(Action::WindowingSystemInitialized);
76        }
77    };
78
79    _ = WINDOWING_SYSTEM.set(system);
80    crate::Action::Cosmic(Action::WindowingSystemInitialized)
81}
82
83#[derive(Default)]
84pub struct Cosmic<App: Application> {
85    pub app: App,
86    #[cfg(all(feature = "wayland", target_os = "linux"))]
87    pub surface_views: HashMap<
88        window::Id,
89        (
90            Option<window::Id>,
91            SurfaceIdWrapper,
92            Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>>>,
93        ),
94    >,
95    pub tracked_windows: HashSet<window::Id>,
96    pub opened_surfaces: HashMap<window::Id, u32>,
97}
98
99impl<T: Application> Cosmic<T>
100where
101    T::Message: Send + 'static,
102{
103    pub fn init(
104        (mut core, flags): (Core, T::Flags),
105    ) -> (Self, iced::Task<crate::Action<T::Message>>) {
106        #[cfg(all(feature = "dbus-config", target_os = "linux"))]
107        {
108            use iced_futures::futures::executor::block_on;
109            core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok();
110        }
111        let id = core.main_window_id().unwrap_or(window::Id::RESERVED);
112
113        let (model, command) = T::init(core, flags);
114
115        (
116            Self::new(model),
117            Task::batch([
118                command,
119                iced_runtime::window::run_with_handle(id, init_windowing_system),
120            ]),
121        )
122    }
123
124    #[cfg(not(feature = "multi-window"))]
125    pub fn title(&self) -> String {
126        self.app.title().to_string()
127    }
128
129    #[cfg(feature = "multi-window")]
130    pub fn title(&self, id: window::Id) -> String {
131        self.app.title(id).to_string()
132    }
133
134    #[allow(clippy::too_many_lines)]
135    pub fn surface_update(
136        &mut self,
137        _surface_message: crate::surface::Action,
138    ) -> iced::Task<crate::Action<T::Message>> {
139        #[cfg(feature = "surface-message")]
140        match _surface_message {
141            #[cfg(all(feature = "wayland", target_os = "linux"))]
142            crate::surface::Action::AppSubsurface(settings, view) => {
143                let Some(settings) = std::sync::Arc::try_unwrap(settings)
144                    .ok()
145                    .and_then(|s| s.downcast::<Box<dyn Fn(&mut T) -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else {
146                    tracing::error!("Invalid settings for subsurface");
147                    return Task::none();
148                    };
149
150                if let Some(view) = view.and_then(|view| {
151                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
152                        dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
153                            + Send
154                            + Sync,
155                    >>() {
156                        Ok(v) => Some(v),
157                        Err(err) => {
158                            tracing::error!("Invalid view for subsurface view: {err:?}");
159
160                            None
161                        }
162                    }
163                }) {
164                    let settings = settings(&mut self.app);
165
166                    self.get_subsurface(settings, *view)
167                } else {
168                    iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app))
169                }
170            }
171            #[cfg(all(feature = "wayland", target_os = "linux"))]
172            crate::surface::Action::Subsurface(settings, view) => {
173                let Some(settings) = std::sync::Arc::try_unwrap(settings)
174                    .ok()
175                    .and_then(|s| s.downcast::<Box<dyn Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else {
176                    tracing::error!("Invalid settings for subsurface");
177                    return Task::none();
178                };
179
180                if let Some(view) = view.and_then(|view| {
181                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
182                            dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
183                        >>() {
184                            Ok(v) => Some(v),
185                            Err(err) => {
186                                tracing::error!("Invalid view for subsurface view: {err:?}");
187
188                                None
189                            }
190                        }
191                }) {
192                    let settings = settings();
193
194                    self.get_subsurface(settings, Box::new(move |_| view()))
195                } else {
196                    iced_winit::commands::subsurface::get_subsurface(settings())
197                }
198            }
199            #[cfg(all(feature = "wayland", target_os = "linux"))]
200            crate::surface::Action::AppPopup(settings, view) => {
201                let Some(settings) = std::sync::Arc::try_unwrap(settings)
202                    .ok()
203                    .and_then(|s| s.downcast::<Box<dyn Fn(&mut T) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else {
204                    tracing::error!("Invalid settings for popup");
205                    return Task::none();
206                };
207
208                if let Some(view) = view.and_then(|view| {
209                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
210                        dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
211                            + Send
212                            + Sync,
213                    >>() {
214                        Ok(v) => Some(v),
215                        Err(err) => {
216                            tracing::error!("Invalid view for subsurface view: {err:?}");
217                            None
218                        }
219                    }
220                }) {
221                    let settings = settings(&mut self.app);
222
223                    self.get_popup(settings, *view)
224                } else {
225                    iced_winit::commands::popup::get_popup(settings(&mut self.app))
226                }
227            }
228            #[cfg(all(feature = "wayland", target_os = "linux"))]
229            crate::surface::Action::DestroyPopup(id) => {
230                iced_winit::commands::popup::destroy_popup(id)
231            }
232            #[cfg(all(feature = "wayland", target_os = "linux"))]
233            crate::surface::Action::DestroyTooltipPopup => {
234                #[cfg(feature = "applet")]
235                {
236                    iced_winit::commands::popup::destroy_popup(*crate::applet::TOOLTIP_WINDOW_ID)
237                }
238                #[cfg(not(feature = "applet"))]
239                {
240                    Task::none()
241                }
242            }
243            #[cfg(all(feature = "wayland", target_os = "linux"))]
244            crate::surface::Action::DestroySubsurface(id) => {
245                iced_winit::commands::subsurface::destroy_subsurface(id)
246            }
247            #[cfg(all(feature = "wayland", target_os = "linux"))]
248            crate::surface::Action::DestroyWindow(id) => iced::window::close(id),
249            crate::surface::Action::ResponsiveMenuBar {
250                menu_bar,
251                limits,
252                size,
253            } => {
254                let core = self.app.core_mut();
255                core.menu_bars.insert(menu_bar, (limits, size));
256                iced::Task::none()
257            }
258            #[cfg(all(feature = "wayland", target_os = "linux"))]
259            crate::surface::Action::Popup(settings, view) => {
260                let Some(settings) = std::sync::Arc::try_unwrap(settings)
261                    .ok()
262                    .and_then(|s| s.downcast::<Box<dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else {
263                    tracing::error!("Invalid settings for popup");
264                    return Task::none();
265                };
266
267                if let Some(view) = view.and_then(|view| {
268                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
269                            dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
270                        >>() {
271                            Ok(v) => Some(v),
272                            Err(err) => {
273                                tracing::error!("Invalid view for subsurface view: {err:?}");
274                                None
275                            }
276                        }
277                }) {
278                    let settings = settings();
279
280                    self.get_popup(settings, Box::new(move |_| view()))
281                } else {
282                    iced_winit::commands::popup::get_popup(settings())
283                }
284            }
285            #[cfg(all(feature = "wayland", target_os = "linux"))]
286            crate::surface::Action::AppWindow(id, settings, view) => {
287                let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| {
288                    s.downcast::<Box<dyn Fn(&mut T) -> iced::window::Settings + Send + Sync>>()
289                        .ok()
290                }) else {
291                    tracing::error!("Invalid settings for AppWindow");
292                    return Task::none();
293                };
294
295                if let Some(view) = view.and_then(|view| {
296                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
297                        dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
298                            + Send
299                            + Sync,
300                    >>() {
301                        Ok(v) => Some(v),
302                        Err(err) => {
303                            tracing::error!("Invalid view for AppWindow: {err:?}");
304                            None
305                        }
306                    }
307                }) {
308                    let settings = settings(&mut self.app);
309                    self.tracked_windows.insert(id);
310
311                    self.get_window(id, settings, *view)
312                } else {
313                    let settings = settings(&mut self.app);
314
315                    self.tracked_windows.insert(id);
316                    iced_runtime::task::oneshot(|channel| {
317                        iced_runtime::Action::Window(iced_runtime::window::Action::Open(
318                            id, settings, channel,
319                        ))
320                    })
321                    .discard()
322                }
323            }
324            #[cfg(all(feature = "wayland", target_os = "linux"))]
325            crate::surface::Action::Window(id, settings, view) => {
326                let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| {
327                    s.downcast::<Box<dyn Fn() -> iced::window::Settings + Send + Sync>>()
328                        .ok()
329                }) else {
330                    tracing::error!("Invalid settings for Window");
331                    return Task::none();
332                };
333
334                if let Some(view) = view.and_then(|view| {
335                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
336                            dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
337                        >>() {
338                            Ok(v) => Some(v),
339                            Err(err) => {
340                                tracing::error!("Invalid view for Window: {err:?}");
341                                None
342                            }
343                        }
344                }) {
345                    let settings = settings();
346                    self.tracked_windows.insert(id);
347
348                    self.get_window(id, settings, Box::new(move |_| view()))
349                } else {
350                    let settings = settings();
351
352                    self.tracked_windows.insert(id);
353
354                    iced_runtime::task::oneshot(|channel| {
355                        iced_runtime::Action::Window(iced_runtime::window::Action::Open(
356                            id, settings, channel,
357                        ))
358                    })
359                    .discard()
360                }
361            }
362
363            crate::surface::Action::Ignore => iced::Task::none(),
364            crate::surface::Action::Task(f) => {
365                f().map(|sm| crate::Action::Cosmic(Action::Surface(sm)))
366            }
367            _ => iced::Task::none(),
368        }
369
370        #[cfg(not(feature = "surface-message"))]
371        iced::Task::none()
372    }
373
374    pub fn update(
375        &mut self,
376        message: crate::Action<T::Message>,
377    ) -> iced::Task<crate::Action<T::Message>> {
378        let message = match message {
379            crate::Action::App(message) => self.app.update(message),
380            crate::Action::Cosmic(message) => self.cosmic_update(message),
381            crate::Action::None => iced::Task::none(),
382            #[cfg(feature = "single-instance")]
383            crate::Action::DbusActivation(message) => {
384                let mut task = self.app.dbus_activation(message);
385
386                if let Some(id) = self.app.core().main_window_id() {
387                    let unminimize = iced_runtime::window::minimize::<()>(id, false);
388                    task = task.chain(unminimize.discard());
389                }
390
391                task
392            }
393        };
394
395        #[cfg(all(target_env = "gnu", not(target_os = "windows")))]
396        crate::malloc::trim(0);
397
398        message
399    }
400
401    #[cfg(not(feature = "multi-window"))]
402    pub fn scale_factor(&self) -> f64 {
403        f64::from(self.app.core().scale_factor())
404    }
405
406    #[cfg(feature = "multi-window")]
407    pub fn scale_factor(&self, _id: window::Id) -> f64 {
408        f64::from(self.app.core().scale_factor())
409    }
410
411    pub fn style(&self, theme: &Theme) -> theme::Style {
412        if let Some(style) = self.app.style() {
413            style
414        } else if self.app.core().window.is_maximized {
415            let theme = THEME.lock().unwrap();
416            crate::style::iced::application::style(theme.borrow())
417        } else {
418            let theme = THEME.lock().unwrap();
419
420            theme::Style {
421                background_color: iced_core::Color::TRANSPARENT,
422                icon_color: theme.cosmic().on_bg_color().into(),
423                text_color: theme.cosmic().on_bg_color().into(),
424            }
425        }
426    }
427
428    #[allow(clippy::too_many_lines)]
429    #[cold]
430    pub fn subscription(&self) -> Subscription<crate::Action<T::Message>> {
431        let window_events = listen_with(|event, _, id| {
432            match event {
433                iced::Event::Window(window::Event::Resized(iced::Size { width, height })) => {
434                    return Some(Action::WindowResize(id, width, height));
435                }
436                iced::Event::Window(window::Event::Opened { .. }) => {
437                    return Some(Action::Opened(id));
438                }
439                iced::Event::Window(window::Event::Closed) => {
440                    return Some(Action::SurfaceClosed(id));
441                }
442                iced::Event::Window(window::Event::Focused) => return Some(Action::Focus(id)),
443                iced::Event::Window(window::Event::Unfocused) => return Some(Action::Unfocus(id)),
444                #[cfg(all(feature = "wayland", target_os = "linux"))]
445                iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => {
446                    match event {
447                        wayland::Event::Popup(wayland::PopupEvent::Done, _, id)
448                        | wayland::Event::Layer(wayland::LayerEvent::Done, _, id) => {
449                            return Some(Action::SurfaceClosed(id));
450                        }
451                        #[cfg(feature = "applet")]
452                        wayland::Event::Window(
453                            iced::event::wayland::WindowEvent::SuggestedBounds(b),
454                        ) => {
455                            return Some(Action::SuggestedBounds(b));
456                        }
457                        #[cfg(all(feature = "wayland", target_os = "linux"))]
458                        wayland::Event::Window(iced::event::wayland::WindowEvent::WindowState(
459                            s,
460                        )) => {
461                            return Some(Action::WindowState(id, s));
462                        }
463                        _ => (),
464                    }
465                }
466                _ => (),
467            }
468
469            None
470        });
471
472        let mut subscriptions = vec![
473            self.app.subscription().map(crate::Action::App),
474            self.app
475                .core()
476                .watch_config::<crate::config::CosmicTk>(crate::config::ID)
477                .map(|update| {
478                    for why in update
479                        .errors
480                        .into_iter()
481                        .filter(cosmic_config::Error::is_err)
482                    {
483                        if let cosmic_config::Error::GetKey(_, err) = &why {
484                            if err.kind() == std::io::ErrorKind::NotFound {
485                                // No system default config installed; don't error
486                                continue;
487                            }
488                        }
489                        tracing::error!(?why, "cosmic toolkit config update error");
490                    }
491
492                    crate::Action::Cosmic(Action::ToolkitConfig(update.config))
493                }),
494            self.app
495                .core()
496                .watch_config::<cosmic_theme::Theme>(
497                    if if let ThemeType::System { prefer_dark, .. } =
498                        THEME.lock().unwrap().theme_type
499                    {
500                        prefer_dark
501                    } else {
502                        None
503                    }
504                    .unwrap_or_else(|| self.app.core().system_theme_mode.is_dark)
505                    {
506                        cosmic_theme::DARK_THEME_ID
507                    } else {
508                        cosmic_theme::LIGHT_THEME_ID
509                    },
510                )
511                .map(|update| {
512                    for why in update
513                        .errors
514                        .into_iter()
515                        .filter(cosmic_config::Error::is_err)
516                    {
517                        tracing::error!(?why, "cosmic theme config update error");
518                    }
519                    Action::SystemThemeChange(
520                        update.keys,
521                        crate::theme::Theme::system(Arc::new(update.config)),
522                    )
523                })
524                .map(crate::Action::Cosmic),
525            self.app
526                .core()
527                .watch_config::<ThemeMode>(cosmic_theme::THEME_MODE_ID)
528                .map(|update| {
529                    for error in update
530                        .errors
531                        .into_iter()
532                        .filter(cosmic_config::Error::is_err)
533                    {
534                        tracing::error!(?error, "error reading system theme mode update");
535                    }
536                    Action::SystemThemeModeChange(update.keys, update.config)
537                })
538                .map(crate::Action::Cosmic),
539            window_events.map(crate::Action::Cosmic),
540            #[cfg(feature = "xdg-portal")]
541            crate::theme::portal::desktop_settings()
542                .map(Action::DesktopSettings)
543                .map(crate::Action::Cosmic),
544        ];
545
546        if self.app.core().keyboard_nav {
547            subscriptions.push(
548                keyboard_nav::subscription()
549                    .map(Action::KeyboardNav)
550                    .map(crate::Action::Cosmic),
551            );
552        }
553
554        #[cfg(feature = "single-instance")]
555        if self.app.core().single_instance {
556            subscriptions.push(crate::dbus_activation::subscription::<T>());
557        }
558
559        Subscription::batch(subscriptions)
560    }
561
562    #[cfg(not(feature = "multi-window"))]
563    pub fn theme(&self) -> Theme {
564        crate::theme::active()
565    }
566
567    #[cfg(feature = "multi-window")]
568    pub fn theme(&self, _id: window::Id) -> Theme {
569        crate::theme::active()
570    }
571
572    #[cfg(feature = "multi-window")]
573    pub fn view(&self, id: window::Id) -> Element<'_, crate::Action<T::Message>> {
574        #[cfg(all(feature = "wayland", target_os = "linux"))]
575        if let Some((_, _, v)) = self.surface_views.get(&id) {
576            return v(&self.app);
577        }
578        if self
579            .app
580            .core()
581            .main_window_id()
582            .is_none_or(|main_id| main_id != id)
583        {
584            return self.app.view_window(id).map(crate::Action::App);
585        }
586
587        let view = if self.app.core().window.use_template {
588            self.app.view_main()
589        } else {
590            self.app.view().map(crate::Action::App)
591        };
592
593        #[cfg(all(target_env = "gnu", not(target_os = "windows")))]
594        crate::malloc::trim(0);
595
596        view
597    }
598
599    #[cfg(not(feature = "multi-window"))]
600    pub fn view(&self) -> Element<crate::Action<T::Message>> {
601        let view = self.app.view_main();
602
603        #[cfg(all(target_env = "gnu", not(target_os = "windows")))]
604        crate::malloc::trim(0);
605
606        view
607    }
608}
609
610impl<T: Application> Cosmic<T> {
611    #[allow(clippy::unused_self)]
612    #[cold]
613    pub fn close(&mut self) -> iced::Task<crate::Action<T::Message>> {
614        if let Some(id) = self.app.core().main_window_id() {
615            iced::window::close(id)
616        } else {
617            iced::Task::none()
618        }
619    }
620
621    #[allow(clippy::too_many_lines)]
622    fn cosmic_update(&mut self, message: Action) -> iced::Task<crate::Action<T::Message>> {
623        match message {
624            Action::WindowMaximized(id, maximized) => {
625                #[cfg(not(all(feature = "wayland", target_os = "linux")))]
626                if self
627                    .app
628                    .core()
629                    .main_window_id()
630                    .is_some_and(|main_id| main_id == id)
631                {
632                    self.app.core_mut().window.sharp_corners = maximized;
633                }
634            }
635
636            Action::WindowResize(id, width, height) => {
637                if self
638                    .app
639                    .core()
640                    .main_window_id()
641                    .is_some_and(|main_id| main_id == id)
642                {
643                    self.app.core_mut().set_window_width(width);
644                    self.app.core_mut().set_window_height(height);
645                }
646
647                self.app.on_window_resize(id, width, height);
648
649                //TODO: more efficient test of maximized (winit has no event for maximize if set by the OS)
650                return iced::window::is_maximized(id).map(move |maximized| {
651                    crate::Action::Cosmic(Action::WindowMaximized(id, maximized))
652                });
653            }
654
655            #[cfg(all(feature = "wayland", target_os = "linux"))]
656            Action::WindowState(id, state) => {
657                if self
658                    .app
659                    .core()
660                    .main_window_id()
661                    .is_some_and(|main_id| main_id == id)
662                {
663                    self.app.core_mut().window.sharp_corners = state.intersects(
664                        WindowState::MAXIMIZED
665                            | WindowState::FULLSCREEN
666                            | WindowState::TILED
667                            | WindowState::TILED_RIGHT
668                            | WindowState::TILED_LEFT
669                            | WindowState::TILED_TOP
670                            | WindowState::TILED_BOTTOM,
671                    );
672                    self.app.core_mut().window.is_maximized =
673                        state.intersects(WindowState::MAXIMIZED | WindowState::FULLSCREEN);
674                }
675                if self.app.core().sync_window_border_radii_to_theme() {
676                    use iced_runtime::platform_specific::wayland::CornerRadius;
677                    use iced_winit::platform_specific::commands::corner_radius::corner_radius;
678
679                    let theme = THEME.lock().unwrap();
680                    let t = theme.cosmic();
681                    let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
682                    let cur_rad = CornerRadius {
683                        top_left: radii[0].round() as u32,
684                        top_right: radii[1].round() as u32,
685                        bottom_right: radii[2].round() as u32,
686                        bottom_left: radii[3].round() as u32,
687                    };
688                    let rounded = !self.app.core().window.sharp_corners;
689                    return Task::batch([corner_radius(
690                        id,
691                        if rounded {
692                            Some(cur_rad)
693                        } else {
694                            let rad_0 = t.radius_0();
695                            Some(CornerRadius {
696                                top_left: rad_0[0].round() as u32,
697                                top_right: rad_0[1].round() as u32,
698                                bottom_right: rad_0[2].round() as u32,
699                                bottom_left: rad_0[3].round() as u32,
700                            })
701                        },
702                    )
703                    .discard()]);
704                }
705            }
706
707            #[cfg(all(feature = "wayland", target_os = "linux"))]
708            Action::WmCapabilities(id, capabilities) => {
709                if self
710                    .app
711                    .core()
712                    .main_window_id()
713                    .is_some_and(|main_id| main_id == id)
714                {
715                    self.app.core_mut().window.show_maximize =
716                        capabilities.contains(WindowManagerCapabilities::MAXIMIZE);
717                    self.app.core_mut().window.show_minimize =
718                        capabilities.contains(WindowManagerCapabilities::MINIMIZE);
719                    self.app.core_mut().window.show_window_menu =
720                        capabilities.contains(WindowManagerCapabilities::WINDOW_MENU);
721                }
722            }
723
724            Action::KeyboardNav(message) => match message {
725                keyboard_nav::Action::FocusNext => {
726                    return iced::widget::operation::focus_next().map(crate::Action::Cosmic);
727                }
728                keyboard_nav::Action::FocusPrevious => {
729                    return iced::widget::operation::focus_previous().map(crate::Action::Cosmic);
730                }
731                keyboard_nav::Action::Escape => return self.app.on_escape(),
732                keyboard_nav::Action::Search => return self.app.on_search(),
733
734                keyboard_nav::Action::Fullscreen => return self.app.core().toggle_maximize(None),
735            },
736
737            Action::ContextDrawer(show) => {
738                self.app.core_mut().set_show_context(show);
739                return self.app.on_context_drawer();
740            }
741
742            Action::Drag => return self.app.core().drag(None),
743
744            Action::Minimize => return self.app.core().minimize(None),
745
746            Action::Maximize => return self.app.core().toggle_maximize(None),
747
748            Action::NavBar(key) => {
749                self.app.core_mut().nav_bar_set_toggled_condensed(false);
750                return self.app.on_nav_select(key);
751            }
752
753            Action::NavBarContext(key) => {
754                self.app.core_mut().nav_bar_set_context(key);
755                return self.app.on_nav_context(key);
756            }
757
758            Action::ToggleNavBar => {
759                self.app.core_mut().nav_bar_toggle();
760            }
761
762            Action::ToggleNavBarCondensed => {
763                self.app.core_mut().nav_bar_toggle_condensed();
764            }
765
766            Action::AppThemeChange(mut theme) => {
767                if let ThemeType::System { theme: _, .. } = theme.theme_type {
768                    self.app.core_mut().theme_sub_counter += 1;
769
770                    let portal_accent = self.app.core().portal_accent;
771                    if let Some(a) = portal_accent {
772                        let t_inner = theme.cosmic();
773                        if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
774                            theme = Theme::system(Arc::new(t_inner.with_accent(a)));
775                        }
776                    };
777                }
778
779                THEME.lock().unwrap().set_theme(theme.theme_type);
780            }
781
782            Action::SystemThemeChange(keys, theme) => {
783                let cur_is_dark = THEME.lock().unwrap().theme_type.is_dark();
784                // Ignore updates if the current theme mode does not match.
785                if cur_is_dark != theme.cosmic().is_dark {
786                    return iced::Task::none();
787                }
788                let cmd = self.app.system_theme_update(&keys, theme.cosmic());
789                // Record the last-known system theme in event that the current theme is custom.
790                self.app.core_mut().system_theme = theme.clone();
791                let portal_accent = self.app.core().portal_accent;
792                {
793                    let mut cosmic_theme = THEME.lock().unwrap();
794
795                    // Only apply update if the theme is set to load a system theme
796                    if let ThemeType::System {
797                        theme: _,
798                        prefer_dark,
799                    } = cosmic_theme.theme_type
800                    {
801                        let mut new_theme = if let Some(a) = portal_accent {
802                            let t_inner = theme.cosmic();
803                            if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
804                                Theme::system(Arc::new(t_inner.with_accent(a)))
805                            } else {
806                                theme
807                            }
808                        } else {
809                            theme
810                        };
811                        new_theme.theme_type.prefer_dark(prefer_dark);
812
813                        cosmic_theme.set_theme(new_theme.theme_type);
814                        #[cfg(all(feature = "wayland", target_os = "linux"))]
815                        if self.app.core().sync_window_border_radii_to_theme() {
816                            use iced_runtime::platform_specific::wayland::CornerRadius;
817                            use iced_winit::platform_specific::commands::corner_radius::corner_radius;
818
819                            let t = cosmic_theme.cosmic();
820
821                            let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
822                            let cur_rad = CornerRadius {
823                                top_left: radii[0].round() as u32,
824                                top_right: radii[1].round() as u32,
825                                bottom_right: radii[2].round() as u32,
826                                bottom_left: radii[3].round() as u32,
827                            };
828
829                            let rounded = !self.app.core().window.sharp_corners;
830                            // Update radius for the main window
831                            let main_window_id = self
832                                .app
833                                .core()
834                                .main_window_id()
835                                .unwrap_or(window::Id::RESERVED);
836                            let mut cmds = vec![
837                                corner_radius(
838                                    main_window_id,
839                                    if rounded {
840                                        Some(cur_rad)
841                                    } else {
842                                        let rad_0 = t.radius_0();
843                                        Some(CornerRadius {
844                                            top_left: rad_0[0].round() as u32,
845                                            top_right: rad_0[1].round() as u32,
846                                            bottom_right: rad_0[2].round() as u32,
847                                            bottom_left: rad_0[3].round() as u32,
848                                        })
849                                    },
850                                )
851                                .discard(),
852                            ];
853                            // Update radius for each tracked view with the window surface type
854                            for (id, (_, surface_type, _)) in self.surface_views.iter() {
855                                if let SurfaceIdWrapper::Window(_) = surface_type {
856                                    cmds.push(
857                                        corner_radius(
858                                            *id,
859                                            if rounded {
860                                                Some(cur_rad)
861                                            } else {
862                                                let rad_0 = t.radius_0();
863                                                Some(CornerRadius {
864                                                    top_left: rad_0[0].round() as u32,
865                                                    top_right: rad_0[1].round() as u32,
866                                                    bottom_right: rad_0[2].round() as u32,
867                                                    bottom_left: rad_0[3].round() as u32,
868                                                })
869                                            },
870                                        )
871                                        .discard(),
872                                    );
873                                }
874                            }
875                            // Update radius for all tracked windows
876                            for id in self.tracked_windows.iter() {
877                                cmds.push(
878                                    corner_radius(
879                                        *id,
880                                        if rounded {
881                                            Some(cur_rad)
882                                        } else {
883                                            let rad_0 = t.radius_0();
884                                            Some(CornerRadius {
885                                                top_left: rad_0[0].round() as u32,
886                                                top_right: rad_0[1].round() as u32,
887                                                bottom_right: rad_0[2].round() as u32,
888                                                bottom_left: rad_0[3].round() as u32,
889                                            })
890                                        },
891                                    )
892                                    .discard(),
893                                );
894                            }
895
896                            return Task::batch(cmds);
897                        }
898                    }
899                }
900
901                return cmd;
902            }
903
904            Action::ScaleFactor(factor) => {
905                self.app.core_mut().set_scale_factor(factor);
906            }
907
908            Action::Close => {
909                return match self.app.on_app_exit() {
910                    Some(message) => self.app.update(message),
911                    None => self.close(),
912                };
913            }
914            Action::SystemThemeModeChange(keys, mode) => {
915                if match THEME.lock().unwrap().theme_type {
916                    ThemeType::System {
917                        theme: _,
918                        prefer_dark,
919                    } => prefer_dark.is_some(),
920                    _ => false,
921                } {
922                    return iced::Task::none();
923                }
924                let mut cmds = vec![self.app.system_theme_mode_update(&keys, &mode)];
925
926                let core = self.app.core_mut();
927                core.system_theme_mode = mode;
928                let is_dark = core.system_is_dark();
929                let changed = core.system_theme_mode.is_dark != is_dark
930                    || core.portal_is_dark != Some(is_dark)
931                    || core.system_theme.cosmic().is_dark != is_dark;
932                if changed {
933                    core.theme_sub_counter += 1;
934                    let mut new_theme = if is_dark {
935                        crate::theme::system_dark()
936                    } else {
937                        crate::theme::system_light()
938                    };
939                    cmds.push(self.app.system_theme_update(&[], new_theme.cosmic()));
940
941                    let core = self.app.core_mut();
942                    new_theme = if let Some(a) = core.portal_accent {
943                        let t_inner = new_theme.cosmic();
944                        if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
945                            Theme::system(Arc::new(t_inner.with_accent(a)))
946                        } else {
947                            new_theme
948                        }
949                    } else {
950                        new_theme
951                    };
952
953                    core.system_theme = new_theme.clone();
954                    {
955                        let mut cosmic_theme = THEME.lock().unwrap();
956
957                        // Only apply update if the theme is set to load a system theme
958                        if let ThemeType::System { .. } = cosmic_theme.theme_type {
959                            cosmic_theme.set_theme(new_theme.theme_type);
960                            #[cfg(all(feature = "wayland", target_os = "linux"))]
961                            if self.app.core().sync_window_border_radii_to_theme() {
962                                use iced_runtime::platform_specific::wayland::CornerRadius;
963                                use iced_winit::platform_specific::commands::corner_radius::corner_radius;
964
965                                let t = cosmic_theme.cosmic();
966
967                                let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
968                                let cur_rad = CornerRadius {
969                                    top_left: radii[0].round() as u32,
970                                    top_right: radii[1].round() as u32,
971                                    bottom_right: radii[2].round() as u32,
972                                    bottom_left: radii[3].round() as u32,
973                                };
974                                let rounded = !self.app.core().window.sharp_corners;
975
976                                // Update radius for the main window
977                                let main_window_id = self
978                                    .app
979                                    .core()
980                                    .main_window_id()
981                                    .unwrap_or(window::Id::RESERVED);
982                                let mut cmds = vec![
983                                    corner_radius(
984                                        main_window_id,
985                                        if rounded {
986                                            Some(cur_rad)
987                                        } else {
988                                            let rad_0 = t.radius_0();
989                                            Some(CornerRadius {
990                                                top_left: rad_0[0].round() as u32,
991                                                top_right: rad_0[1].round() as u32,
992                                                bottom_right: rad_0[2].round() as u32,
993                                                bottom_left: rad_0[3].round() as u32,
994                                            })
995                                        },
996                                    )
997                                    .discard(),
998                                ];
999                                // Update radius for each tracked view with the window surface type
1000                                for (id, (_, surface_type, _)) in self.surface_views.iter() {
1001                                    if let SurfaceIdWrapper::Window(_) = surface_type {
1002                                        cmds.push(
1003                                            corner_radius(
1004                                                *id,
1005                                                if rounded {
1006                                                    Some(cur_rad)
1007                                                } else {
1008                                                    let rad_0 = t.radius_0();
1009                                                    Some(CornerRadius {
1010                                                        top_left: rad_0[0].round() as u32,
1011                                                        top_right: rad_0[1].round() as u32,
1012                                                        bottom_right: rad_0[2].round() as u32,
1013                                                        bottom_left: rad_0[3].round() as u32,
1014                                                    })
1015                                                },
1016                                            )
1017                                            .discard(),
1018                                        );
1019                                    }
1020                                }
1021                                // Update radius for all tracked windows
1022                                for id in self.tracked_windows.iter() {
1023                                    cmds.push(
1024                                        corner_radius(
1025                                            *id,
1026                                            if rounded {
1027                                                Some(cur_rad)
1028                                            } else {
1029                                                let rad_0 = t.radius_0();
1030                                                Some(CornerRadius {
1031                                                    top_left: rad_0[0].round() as u32,
1032                                                    top_right: rad_0[1].round() as u32,
1033                                                    bottom_right: rad_0[2].round() as u32,
1034                                                    bottom_left: rad_0[3].round() as u32,
1035                                                })
1036                                            },
1037                                        )
1038                                        .discard(),
1039                                    );
1040                                }
1041
1042                                return Task::batch(cmds);
1043                            }
1044                        }
1045                    }
1046                }
1047                return Task::batch(cmds);
1048            }
1049            Action::Activate(_token) => {
1050                if let Some(id) = self.app.core().main_window_id() {
1051                    // Unminimize window before requesting to activate it.
1052                    let mut task = iced_runtime::window::minimize(id, false);
1053
1054                    #[cfg(all(feature = "wayland", target_os = "linux"))]
1055                    {
1056                        task = task.chain(
1057                            iced_winit::platform_specific::commands::activation::activate(
1058                                id,
1059                                #[allow(clippy::used_underscore_binding)]
1060                                _token,
1061                            ),
1062                        )
1063                    }
1064
1065                    #[cfg(not(all(feature = "wayland", target_os = "linux")))]
1066                    {
1067                        task = task.chain(iced_runtime::window::gain_focus(id));
1068                    }
1069
1070                    return task;
1071                }
1072            }
1073
1074            Action::Surface(action) => return self.surface_update(action),
1075
1076            Action::SurfaceClosed(id) => {
1077                if self.opened_surfaces.get_mut(&id).is_some_and(|v| {
1078                    *v = v.saturating_sub(1);
1079                    *v == 0
1080                }) {
1081                    self.opened_surfaces.remove(&id);
1082                    #[cfg(all(feature = "wayland", target_os = "linux"))]
1083                    self.surface_views.remove(&id);
1084                    self.tracked_windows.remove(&id);
1085                }
1086
1087                let mut ret = if let Some(msg) = self.app.on_close_requested(id) {
1088                    self.app.update(msg)
1089                } else {
1090                    Task::none()
1091                };
1092                let core = self.app.core();
1093                if core.exit_on_main_window_closed
1094                    && core.main_window_id().is_some_and(|m_id| id == m_id)
1095                {
1096                    ret = Task::batch([iced::exit::<crate::Action<T::Message>>()]);
1097                }
1098                return ret;
1099            }
1100
1101            Action::ShowWindowMenu => {
1102                if let Some(id) = self.app.core().main_window_id() {
1103                    return iced::window::show_system_menu(id);
1104                }
1105            }
1106
1107            #[cfg(feature = "single-instance")]
1108            Action::DbusConnection(conn) => {
1109                return self.app.dbus_connection(conn);
1110            }
1111
1112            #[cfg(feature = "xdg-portal")]
1113            Action::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => {
1114                use ashpd::desktop::settings::ColorScheme;
1115                if match THEME.lock().unwrap().theme_type {
1116                    ThemeType::System {
1117                        theme: _,
1118                        prefer_dark,
1119                    } => prefer_dark.is_some(),
1120                    _ => false,
1121                } {
1122                    return iced::Task::none();
1123                }
1124                let is_dark = match s {
1125                    ColorScheme::NoPreference => None,
1126                    ColorScheme::PreferDark => Some(true),
1127                    ColorScheme::PreferLight => Some(false),
1128                };
1129                let core = self.app.core_mut();
1130
1131                core.portal_is_dark = is_dark;
1132                let is_dark = core.system_is_dark();
1133                let changed = core.system_theme_mode.is_dark != is_dark
1134                    || core.portal_is_dark != Some(is_dark)
1135                    || core.system_theme.cosmic().is_dark != is_dark;
1136
1137                if changed {
1138                    core.theme_sub_counter += 1;
1139                    let new_theme = if is_dark {
1140                        crate::theme::system_dark()
1141                    } else {
1142                        crate::theme::system_light()
1143                    };
1144                    core.system_theme = new_theme.clone();
1145                    {
1146                        let mut cosmic_theme = THEME.lock().unwrap();
1147
1148                        // Only apply update if the theme is set to load a system theme
1149                        if let ThemeType::System { theme: _, .. } = cosmic_theme.theme_type {
1150                            cosmic_theme.set_theme(new_theme.theme_type);
1151                        }
1152                    }
1153                }
1154            }
1155            #[cfg(feature = "xdg-portal")]
1156            Action::DesktopSettings(crate::theme::portal::Desktop::Accent(c)) => {
1157                use palette::Srgba;
1158                let c = Srgba::new(c.red() as f32, c.green() as f32, c.blue() as f32, 1.0);
1159                let core = self.app.core_mut();
1160                core.portal_accent = Some(c);
1161                let cur_accent = core.system_theme.cosmic().accent_color();
1162
1163                if cur_accent.distance_squared(*c) < 0.00001 {
1164                    // skip calculations if we already have the same color
1165                    return iced::Task::none();
1166                }
1167
1168                {
1169                    let mut cosmic_theme = THEME.lock().unwrap();
1170
1171                    // Only apply update if the theme is set to load a system theme
1172                    if let ThemeType::System {
1173                        theme: t,
1174                        prefer_dark,
1175                    } = cosmic_theme.theme_type.clone()
1176                    {
1177                        cosmic_theme.set_theme(ThemeType::System {
1178                            theme: Arc::new(t.with_accent(c)),
1179                            prefer_dark,
1180                        });
1181                    }
1182                }
1183            }
1184            #[cfg(feature = "xdg-portal")]
1185            Action::DesktopSettings(crate::theme::portal::Desktop::Contrast(_)) => {
1186                // TODO when high contrast is integrated in settings and all custom themes
1187            }
1188
1189            Action::ToolkitConfig(config) => {
1190                // Change the icon theme if not defined by the application.
1191                if !self.app.core().icon_theme_override
1192                    && crate::icon_theme::default() != config.icon_theme
1193                {
1194                    crate::icon_theme::set_default(config.icon_theme.clone());
1195                }
1196
1197                *crate::config::COSMIC_TK.write().unwrap() = config;
1198            }
1199
1200            Action::Focus(f) => {
1201                #[cfg(all(
1202                    feature = "wayland",
1203                    feature = "multi-window",
1204                    feature = "surface-message",
1205                    target_os = "linux"
1206                ))]
1207                if let Some((
1208                    parent,
1209                    SurfaceIdWrapper::Subsurface(_) | SurfaceIdWrapper::Popup(_),
1210                    _,
1211                )) = self.surface_views.get(&f)
1212                {
1213                    // If the parent is already focused, push the new focus
1214                    // to the end of the focus chain.
1215                    if parent.is_some_and(|p| self.app.core().focused_window.last() == Some(&p)) {
1216                        self.app.core_mut().focused_window.push(f);
1217                        return iced::Task::none();
1218                    } else {
1219                        // set the whole parent chain to the focus chain
1220                        let mut parent_chain = vec![f];
1221                        let mut cur = *parent;
1222                        while let Some(p) = cur {
1223                            parent_chain.push(p);
1224                            cur = self
1225                                .surface_views
1226                                .get(&p)
1227                                .and_then(|(parent, _, _)| *parent);
1228                        }
1229                        parent_chain.reverse();
1230                        self.app.core_mut().focused_window = parent_chain;
1231                        return iced::Task::none();
1232                    }
1233                }
1234                self.app.core_mut().focused_window = vec![f];
1235            }
1236
1237            Action::Unfocus(id) => {
1238                let core = self.app.core_mut();
1239                if core.focused_window().as_ref().is_some_and(|cur| *cur == id) {
1240                    core.focused_window.pop();
1241                }
1242            }
1243            #[cfg(feature = "applet")]
1244            Action::SuggestedBounds(b) => {
1245                tracing::info!("Suggested bounds: {b:?}");
1246                let core = self.app.core_mut();
1247                core.applet.suggested_bounds = b;
1248            }
1249            Action::Opened(id) => {
1250                #[cfg(all(feature = "wayland", target_os = "linux"))]
1251                if self.app.core().sync_window_border_radii_to_theme() {
1252                    use iced_runtime::platform_specific::wayland::CornerRadius;
1253                    use iced_winit::platform_specific::commands::corner_radius::corner_radius;
1254
1255                    let theme = THEME.lock().unwrap();
1256                    let t = theme.cosmic();
1257                    let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
1258                    let cur_rad = CornerRadius {
1259                        top_left: radii[0].round() as u32,
1260                        top_right: radii[1].round() as u32,
1261                        bottom_right: radii[2].round() as u32,
1262                        bottom_left: radii[3].round() as u32,
1263                    };
1264                    // TODO do we need per window sharp corners?
1265                    let rounded = !self.app.core().window.sharp_corners;
1266
1267                    return Task::batch([
1268                        corner_radius(
1269                            id,
1270                            if rounded {
1271                                Some(cur_rad)
1272                            } else {
1273                                let rad_0 = t.radius_0();
1274                                Some(CornerRadius {
1275                                    top_left: rad_0[0].round() as u32,
1276                                    top_right: rad_0[1].round() as u32,
1277                                    bottom_right: rad_0[2].round() as u32,
1278                                    bottom_left: rad_0[3].round() as u32,
1279                                })
1280                            },
1281                        )
1282                        .discard(),
1283                        iced_runtime::window::run_with_handle(id, init_windowing_system),
1284                    ]);
1285                }
1286                return iced_runtime::window::run_with_handle(id, init_windowing_system);
1287            }
1288            _ => {}
1289        }
1290
1291        iced::Task::none()
1292    }
1293}
1294
1295impl<App: Application> Cosmic<App> {
1296    pub fn new(app: App) -> Self {
1297        Self {
1298            app,
1299            #[cfg(all(feature = "wayland", target_os = "linux"))]
1300            surface_views: HashMap::new(),
1301            tracked_windows: HashSet::new(),
1302            opened_surfaces: HashMap::new(),
1303        }
1304    }
1305
1306    #[cfg(all(feature = "wayland", target_os = "linux"))]
1307    /// Create a subsurface
1308    pub fn get_subsurface(
1309        &mut self,
1310        settings: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings,
1311        view: Box<
1312            dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync,
1313        >,
1314    ) -> Task<crate::Action<App::Message>> {
1315        use iced_winit::commands::subsurface::get_subsurface;
1316
1317        *self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1;
1318        self.surface_views.insert(
1319            settings.id,
1320            (
1321                Some(settings.parent),
1322                SurfaceIdWrapper::Subsurface(settings.id),
1323                view,
1324            ),
1325        );
1326        get_subsurface(settings)
1327    }
1328
1329    #[cfg(all(feature = "wayland", target_os = "linux"))]
1330    /// Create a subsurface
1331    pub fn get_popup(
1332        &mut self,
1333        settings: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
1334        view: Box<
1335            dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync,
1336        >,
1337    ) -> Task<crate::Action<App::Message>> {
1338        use iced_winit::commands::popup::get_popup;
1339        *self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1;
1340        self.surface_views.insert(
1341            settings.id,
1342            (
1343                Some(settings.parent),
1344                SurfaceIdWrapper::Popup(settings.id),
1345                view,
1346            ),
1347        );
1348        get_popup(settings)
1349    }
1350
1351    #[cfg(all(feature = "wayland", target_os = "linux"))]
1352    /// Create a window surface
1353    pub fn get_window(
1354        &mut self,
1355        id: iced::window::Id,
1356        settings: iced::window::Settings,
1357        view: Box<
1358            dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync,
1359        >,
1360    ) -> Task<crate::Action<App::Message>> {
1361        use iced_winit::SurfaceIdWrapper;
1362        *self.opened_surfaces.entry(id).or_insert(0) += 1;
1363        self.surface_views.insert(
1364            id,
1365            (
1366                None, // TODO parent for window, platform specific option maybe?
1367                SurfaceIdWrapper::Window(id),
1368                view,
1369            ),
1370        );
1371        iced_runtime::task::oneshot(|channel| {
1372            iced_runtime::Action::Window(iced_runtime::window::Action::Open(id, settings, channel))
1373        })
1374        .discard()
1375    }
1376}