Skip to main content

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;
6use std::sync::Arc;
7
8use super::{Action, Application, ApplicationExt, Subscription};
9#[cfg(wayland_platform)]
10use crate::core::Auto;
11#[cfg(wayland_platform)]
12use crate::surface::action::LiveSettings;
13use crate::theme::{THEME, Theme, ThemeType};
14use crate::{Core, Element, keyboard_nav};
15#[cfg(wayland_platform)]
16use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
17use cosmic_theme::ThemeMode;
18#[cfg(wayland_platform)]
19use enumflags2::BitFlags;
20#[cfg(not(any(feature = "multi-window", wayland_platform)))]
21use iced::Application as IcedApplication;
22#[cfg(wayland_platform)]
23use iced::event::wayland;
24use iced::{Task, theme, window};
25use iced_futures::event::listen_with;
26#[cfg(feature = "winit")]
27use iced_winit::SurfaceIdWrapper;
28use palette::color_difference::EuclideanDistance;
29
30#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
31#[non_exhaustive]
32pub enum WindowingSystem {
33    UiKit,
34    AppKit,
35    Orbital,
36    OhosNdk,
37    Xlib,
38    Xcb,
39    Wayland,
40    Drm,
41    Gbm,
42    Win32,
43    WinRt,
44    Web,
45    WebCanvas,
46    WebOffscreenCanvas,
47    AndroidNdk,
48    Haiku,
49}
50
51pub(crate) static WINDOWING_SYSTEM: std::sync::OnceLock<WindowingSystem> =
52    std::sync::OnceLock::new();
53
54pub fn windowing_system() -> Option<WindowingSystem> {
55    WINDOWING_SYSTEM.get().copied()
56}
57
58fn init_windowing_system<M>(handle: window::raw_window_handle::WindowHandle) -> crate::Action<M> {
59    let raw = handle.as_ref();
60    let system = match raw {
61        window::raw_window_handle::RawWindowHandle::UiKit(_) => WindowingSystem::UiKit,
62        window::raw_window_handle::RawWindowHandle::AppKit(_) => WindowingSystem::AppKit,
63        window::raw_window_handle::RawWindowHandle::Orbital(_) => WindowingSystem::Orbital,
64        window::raw_window_handle::RawWindowHandle::OhosNdk(_) => WindowingSystem::OhosNdk,
65        window::raw_window_handle::RawWindowHandle::Xlib(_) => WindowingSystem::Xlib,
66        window::raw_window_handle::RawWindowHandle::Xcb(_) => WindowingSystem::Xcb,
67        window::raw_window_handle::RawWindowHandle::Wayland(_) => WindowingSystem::Wayland,
68        window::raw_window_handle::RawWindowHandle::Web(_) => WindowingSystem::Web,
69        window::raw_window_handle::RawWindowHandle::WebCanvas(_) => WindowingSystem::WebCanvas,
70        window::raw_window_handle::RawWindowHandle::WebOffscreenCanvas(_) => {
71            WindowingSystem::WebOffscreenCanvas
72        }
73        window::raw_window_handle::RawWindowHandle::AndroidNdk(_) => WindowingSystem::AndroidNdk,
74        window::raw_window_handle::RawWindowHandle::Haiku(_) => WindowingSystem::Haiku,
75        window::raw_window_handle::RawWindowHandle::Drm(_) => WindowingSystem::Drm,
76        window::raw_window_handle::RawWindowHandle::Gbm(_) => WindowingSystem::Gbm,
77        window::raw_window_handle::RawWindowHandle::Win32(_) => WindowingSystem::Win32,
78        window::raw_window_handle::RawWindowHandle::WinRt(_) => WindowingSystem::WinRt,
79        _ => {
80            tracing::warn!("Unknown windowing system: {raw:?}");
81            return crate::Action::Cosmic(Action::WindowingSystemInitialized);
82        }
83    };
84
85    _ = WINDOWING_SYSTEM.set(system);
86    crate::Action::Cosmic(Action::WindowingSystemInitialized)
87}
88
89#[derive(Default)]
90pub struct Cosmic<App: Application> {
91    pub app: App,
92    pub surface_views: HashMap<
93        window::Id,
94        (
95            Option<window::Id>,
96            SurfaceIdWrapper,
97            Box<dyn for<'a> Fn(&'a App) -> crate::surface::action::LiveSettings>,
98            Option<
99                Box<
100                    dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>>
101                        + Send
102                        + Sync
103                        + 'static,
104                >,
105            >,
106        ),
107    >,
108    pub opened_surfaces: HashMap<window::Id, u32>,
109    blur_enabled: bool,
110}
111
112impl<T: Application> Cosmic<T>
113where
114    T::Message: Send + 'static,
115{
116    pub fn init(
117        (mut core, flags): (Core, T::Flags),
118    ) -> (Self, iced::Task<crate::Action<T::Message>>) {
119        #[cfg(all(feature = "dbus-config", target_os = "linux"))]
120        {
121            use iced_futures::futures::executor::block_on;
122            core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok();
123        }
124        let id = core.main_window_id().unwrap_or(window::Id::RESERVED);
125
126        let (model, command) = T::init(core, flags);
127        let existing_theme = THEME.lock().unwrap();
128        let blur = existing_theme.transparent;
129        drop(existing_theme);
130
131        let mut cmds = vec![
132            iced_runtime::window::run_with_handle(id, init_windowing_system),
133            command,
134        ];
135
136        if blur {
137            cmds.push(crate::task::message(crate::action::cosmic(
138                Action::BlurEnabled,
139            )));
140        }
141        (Self::new(model), Task::batch(cmds))
142    }
143
144    #[cfg(not(feature = "multi-window"))]
145    pub fn title(&self) -> String {
146        self.app.title().to_string()
147    }
148
149    #[cfg(feature = "multi-window")]
150    pub fn title(&self, id: window::Id) -> String {
151        self.app.title(id).to_string()
152    }
153
154    #[allow(clippy::too_many_lines)]
155    pub fn surface_update(
156        &mut self,
157        _surface_message: crate::surface::Action,
158    ) -> iced::Task<crate::Action<T::Message>> {
159        #[cfg(feature = "surface-message")]
160        match _surface_message {
161            #[cfg(wayland_platform)]
162            crate::surface::Action::AppSubsurface(settings, live_settings, view) => {
163                let Some(settings) = std::sync::Arc::try_unwrap(settings)
164                    .ok()
165                    .and_then(|s| s.downcast::<Box<dyn Fn(&mut T) -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else {
166                    tracing::error!("Invalid settings for subsurface");
167                    return Task::none();
168                    };
169
170                let settings = settings(&mut self.app);
171
172                let view = view.and_then(|view| {
173                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
174                        dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
175                            + Send
176                            + Sync,
177                    >>() {
178                        Ok(v) => Some(v),
179                        Err(err) => {
180                            tracing::error!("Invalid view for subsurface view: {err:?}");
181
182                            None
183                        }
184                    }
185                });
186                self.get_subsurface(settings, view.map(|v| *v))
187            }
188            #[cfg(wayland_platform)]
189            crate::surface::Action::Subsurface(settings, live_settings, view) => {
190                let Some(settings) = std::sync::Arc::try_unwrap(settings)
191                    .ok()
192                    .and_then(|s| s.downcast::<Box<dyn Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else {
193                    tracing::error!("Invalid settings for subsurface");
194                    return Task::none();
195                };
196                let settings = settings();
197
198                if let Some(view) = view.and_then(|view| {
199                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
200                            dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
201                        >>() {
202                            Ok(v) => Some(v),
203                            Err(err) => {
204                                tracing::error!("Invalid view for subsurface view: {err:?}");
205
206                                None
207                            }
208                        }
209                }) {
210                    self.get_subsurface(settings, Some(Box::new(move |_| view())))
211                } else {
212                    self.get_subsurface(settings, None)
213                }
214            }
215            #[cfg(wayland_platform)]
216            crate::surface::Action::AppPopup(settings, live_settings, view) => {
217                let Some(settings) = std::sync::Arc::try_unwrap(settings)
218                    .ok()
219                    .and_then(|s| s.downcast::<Box<dyn Fn(&mut T) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else {
220                    tracing::error!("Invalid settings for popup");
221                    return Task::none();
222                };
223                let Some(live_settings) =
224                    std::sync::Arc::try_unwrap(live_settings)
225                        .ok()
226                        .and_then(|s| {
227                            s.downcast::<Box<dyn Fn(&T) -> LiveSettings + Send + Sync>>()
228                                .ok()
229                        })
230                else {
231                    tracing::error!("Invalid live settings for popup");
232                    return Task::none();
233                };
234
235                let view = view.and_then(|view| {
236                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
237                        dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
238                            + Send
239                            + Sync,
240                    >>() {
241                        Ok(v) => Some(v),
242                        Err(err) => {
243                            tracing::error!("Invalid view for subsurface view: {err:?}");
244                            None
245                        }
246                    }
247                });
248                let settings = settings(&mut self.app);
249
250                self.get_popup(settings, *live_settings, view.map(|v| *v))
251            }
252            #[cfg(wayland_platform)]
253            crate::surface::Action::DestroyPopup(id) => {
254                iced_winit::commands::popup::destroy_popup(id)
255            }
256            #[cfg(wayland_platform)]
257            crate::surface::Action::DestroyTooltipPopup => {
258                #[cfg(feature = "applet")]
259                {
260                    iced_winit::commands::popup::destroy_popup(*crate::applet::TOOLTIP_WINDOW_ID)
261                }
262                #[cfg(not(feature = "applet"))]
263                {
264                    Task::none()
265                }
266            }
267            #[cfg(wayland_platform)]
268            crate::surface::Action::DestroySubsurface(id) => {
269                iced_winit::commands::subsurface::destroy_subsurface(id)
270            }
271            #[cfg(wayland_platform)]
272            crate::surface::Action::DestroyWindow(id) => iced::window::close(id),
273            crate::surface::Action::ResponsiveMenuBar {
274                menu_bar,
275                limits,
276                size,
277            } => {
278                let core = self.app.core_mut();
279                core.menu_bars.insert(menu_bar, (limits, size));
280                iced::Task::none()
281            }
282            #[cfg(wayland_platform)]
283            crate::surface::Action::Popup(settings, live_settings, view) => {
284                let Some(settings) = std::sync::Arc::try_unwrap(settings)
285                    .ok()
286                    .and_then(|s| s.downcast::<Box<dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else {
287                    tracing::error!("Invalid settings for popup");
288                    return Task::none();
289                };
290
291                let Some(live_settings) =
292                    std::sync::Arc::try_unwrap(live_settings)
293                        .ok()
294                        .and_then(|s| {
295                            s.downcast::<Box<dyn Fn() -> LiveSettings + Send + Sync>>()
296                                .ok()
297                        })
298                else {
299                    tracing::error!("Invalid live settings for popup");
300                    return Task::none();
301                };
302                let settings = settings();
303                let live_settings = Box::new(move |_: &T| live_settings());
304
305                if let Some(view) = view.and_then(|view| {
306                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
307                            dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
308                        >>() {
309                            Ok(v) => Some(v),
310                            Err(err) => {
311                                tracing::error!("Invalid view for subsurface view: {err:?}");
312                                None
313                            }
314                        }
315                }) {
316                    self.get_popup(settings, live_settings, Some(Box::new(move |_| view())))
317                } else {
318                    self.get_popup(settings, live_settings, None)
319                }
320            }
321            #[cfg(wayland_platform)]
322            crate::surface::Action::AppWindow(id, settings, live_settings, view) => {
323                let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| {
324                    s.downcast::<Box<dyn Fn(&mut T) -> iced::window::Settings + Send + Sync>>()
325                        .ok()
326                }) else {
327                    tracing::error!("Invalid settings for AppWindow");
328                    return Task::none();
329                };
330                let Some(live_settings) =
331                    std::sync::Arc::try_unwrap(live_settings)
332                        .ok()
333                        .and_then(|s| {
334                            s.downcast::<Box<dyn Fn(&T) -> LiveSettings + Send + Sync>>()
335                                .ok()
336                        })
337                else {
338                    tracing::error!("Invalid live settings for popup");
339                    return Task::none();
340                };
341
342                let view = view.and_then(|view| {
343                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
344                        dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
345                            + Send
346                            + Sync,
347                    >>() {
348                        Ok(v) => Some(v),
349                        Err(err) => {
350                            tracing::error!("Invalid view for AppWindow: {err:?}");
351                            None
352                        }
353                    }
354                });
355                let settings = settings(&mut self.app);
356
357                self.get_window(id, settings, *live_settings, view.map(|v| *v))
358            }
359            #[cfg(wayland_platform)]
360            crate::surface::Action::Window(id, settings, live_settings, view) => {
361                let Some(settings) = std::sync::Arc::try_unwrap(settings).ok().and_then(|s| {
362                    s.downcast::<Box<dyn Fn() -> iced::window::Settings + Send + Sync>>()
363                        .ok()
364                }) else {
365                    tracing::error!("Invalid settings for Window");
366                    return Task::none();
367                };
368
369                let Some(live_settings) =
370                    std::sync::Arc::try_unwrap(live_settings)
371                        .ok()
372                        .and_then(|s| {
373                            s.downcast::<Box<dyn Fn() -> LiveSettings + Send + Sync>>()
374                                .ok()
375                        })
376                else {
377                    tracing::error!("Invalid live settings for popup");
378                    return Task::none();
379                };
380
381                if let Some(view) = view.and_then(|view| {
382                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
383                            dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
384                        >>() {
385                            Ok(v) => Some(v),
386                            Err(err) => {
387                                tracing::error!("Invalid view for Window: {err:?}");
388                                None
389                            }
390                        }
391                }) {
392                    let settings = settings();
393
394                    self.get_window(
395                        id,
396                        settings,
397                        Box::new(move |_| live_settings()),
398                        Some(Box::new(move |_| view())),
399                    )
400                } else {
401                    let settings = settings();
402
403                    iced_runtime::task::oneshot(|channel| {
404                        iced_runtime::Action::Window(iced_runtime::window::Action::Open(
405                            id, settings, channel,
406                        ))
407                    })
408                    .discard()
409                }
410            }
411
412            crate::surface::Action::Ignore => iced::Task::none(),
413            crate::surface::Action::Task(f) => {
414                f().map(|sm| crate::Action::Cosmic(Action::Surface(sm)))
415            }
416            #[cfg(wayland_platform)]
417            crate::surface::Action::AppLayerShell(settings, live_settings, view) => {
418                let Some(settings) = std::sync::Arc::try_unwrap(settings)
419                    .ok()
420                    .and_then(|s| s.downcast::<Box<dyn Fn(&mut T) -> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings + Send + Sync>>().ok()) else {
421                    tracing::error!("Invalid settings for layer surface");
422                    return Task::none();
423                };
424                let Some(live_settings) =
425                    std::sync::Arc::try_unwrap(live_settings)
426                        .ok()
427                        .and_then(|s| {
428                            s.downcast::<Box<dyn Fn(&T) -> LiveSettings + Send + Sync>>()
429                                .ok()
430                        })
431                else {
432                    tracing::error!("Invalid live settings for popup");
433                    return Task::none();
434                };
435
436                let view = view.and_then(|view| {
437                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
438                        dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
439                            + Send
440                            + Sync,
441                    >>() {
442                        Ok(v) => Some(v),
443                        Err(err) => {
444                            tracing::error!("Invalid view for layer surface: {err:?}");
445                            None
446                        }
447                    }
448                });
449
450                let settings = settings(&mut self.app);
451
452                self.get_layer_shell(settings, *live_settings, view.map(|v| *v))
453            }
454            #[cfg(wayland_platform)]
455            crate::surface::Action::LayerShell(settings, live_settings, view) => {
456                let Some(settings) = std::sync::Arc::try_unwrap(settings)
457                    .ok()
458                    .and_then(|s| s.downcast::<Box<dyn Fn() -> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings + Send + Sync>>().ok()) else {
459                    tracing::error!("Invalid settings for layer surface");
460                    return Task::none();
461                };
462
463                let Some(live_settings) =
464                    std::sync::Arc::try_unwrap(live_settings)
465                        .ok()
466                        .and_then(|s| {
467                            s.downcast::<Box<dyn Fn() -> LiveSettings + Send + Sync>>()
468                                .ok()
469                        })
470                else {
471                    tracing::error!("Invalid live settings for popup");
472                    return Task::none();
473                };
474                let settings = settings();
475                let live_settings = live_settings();
476                let live_settings = Box::new(move |_app: &T| live_settings);
477
478                if let Some(view) = view.and_then(|view| {
479                    match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
480                            dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
481                        >>() {
482                            Ok(v) => Some(v),
483                            Err(err) => {
484                                tracing::error!("Invalid view for layer surface: {err:?}");
485                                None
486                            }
487                        }
488                }) {
489                    self.get_layer_shell(settings, live_settings, Some(Box::new(move |_| view())))
490                } else {
491                    self.get_layer_shell(settings, live_settings, None)
492                }
493            }
494            #[cfg(wayland_platform)]
495            crate::surface::Action::DestroyLayerShell(id) => {
496                iced_winit::commands::layer_surface::destroy_layer_surface(id)
497            }
498            crate::surface::Action::SyncLiveSettings(id) => {
499                if let Some((_, id, live_settings, _)) = self.surface_views.get(&id) {
500                    let live_settings = live_settings(&self.app);
501                    return self.apply_live_settings(*id, &live_settings);
502                }
503                Task::none()
504            }
505            _ => iced::Task::none(),
506        }
507
508        #[cfg(not(feature = "surface-message"))]
509        iced::Task::none()
510    }
511
512    pub fn update(
513        &mut self,
514        message: crate::Action<T::Message>,
515    ) -> iced::Task<crate::Action<T::Message>> {
516        let message = match message {
517            crate::Action::App(message) => self.app.update(message),
518            crate::Action::Cosmic(message) => self.cosmic_update(message),
519            crate::Action::None => iced::Task::none(),
520            #[cfg(feature = "single-instance")]
521            crate::Action::DbusActivation(message) => {
522                let mut task = self.app.dbus_activation(message);
523
524                if let Some(id) = self.app.core().main_window_id() {
525                    let unminimize = iced_runtime::window::minimize::<()>(id, false);
526                    task = task.chain(unminimize.discard());
527                }
528
529                task
530            }
531        };
532
533        #[cfg(all(target_env = "gnu", not(target_os = "windows")))]
534        crate::malloc::trim(0);
535
536        message
537    }
538
539    #[cfg(not(feature = "multi-window"))]
540    pub fn scale_factor(&self) -> f64 {
541        f64::from(self.app.core().scale_factor())
542    }
543
544    #[cfg(feature = "multi-window")]
545    pub fn scale_factor(&self, _id: window::Id) -> f64 {
546        f64::from(self.app.core().scale_factor())
547    }
548
549    pub fn style(&self, theme: &Theme) -> theme::Style {
550        if let Some(style) = self.app.style() {
551            style
552        } else {
553            let theme = THEME.lock().unwrap();
554            if self.app.core().window.is_maximized && !theme.cosmic().frosted_maximized_apps {
555                crate::style::iced::application::style(theme.borrow())
556            } else {
557                theme::Style {
558                    background_color: iced_core::Color::TRANSPARENT,
559                    icon_color: theme.cosmic().on_bg_color().into(),
560                    text_color: theme.cosmic().on_bg_color().into(),
561                }
562            }
563        }
564    }
565
566    #[allow(clippy::too_many_lines)]
567    #[cold]
568    pub fn subscription(&self) -> Subscription<crate::Action<T::Message>> {
569        let window_events = listen_with(|event, _, id| {
570            match event {
571                iced::Event::Window(window::Event::Resized(iced::Size { width, height })) => {
572                    return Some(Action::WindowResize(id, width, height));
573                }
574                iced::Event::Window(window::Event::Opened { .. }) => {
575                    return Some(Action::Opened(id));
576                }
577                iced::Event::Window(window::Event::Closed) => {
578                    return Some(Action::SurfaceClosed(id));
579                }
580                iced::Event::Window(window::Event::Focused) => return Some(Action::Focus(id)),
581                iced::Event::Window(window::Event::Unfocused) => return Some(Action::Unfocus(id)),
582                #[cfg(wayland_platform)]
583                iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => {
584                    match event {
585                        wayland::Event::Popup(wayland::PopupEvent::Done, _, id)
586                        | wayland::Event::Layer(wayland::LayerEvent::Done, _, id) => {
587                            return Some(Action::SurfaceClosed(id));
588                        }
589                        #[cfg(feature = "applet")]
590                        wayland::Event::Window(
591                            iced::event::wayland::WindowEvent::SuggestedBounds(b),
592                        ) => {
593                            return Some(Action::SuggestedBounds(b));
594                        }
595                        #[cfg(wayland_platform)]
596                        wayland::Event::Window(iced::event::wayland::WindowEvent::WindowState(
597                            s,
598                        )) => {
599                            return Some(Action::WindowState(id, s));
600                        }
601                        wayland::Event::BlurEnabled => {
602                            return Some(Action::BlurEnabled);
603                        }
604                        _ => (),
605                    }
606                }
607                _ => (),
608            }
609
610            None
611        });
612
613        let mut subscriptions = vec![
614            self.app.subscription().map(crate::Action::App),
615            self.app
616                .core()
617                .watch_config::<crate::config::CosmicTk>(crate::config::ID)
618                .map(|update| {
619                    for why in update
620                        .errors
621                        .into_iter()
622                        .filter(cosmic_config::Error::is_err)
623                    {
624                        if let cosmic_config::Error::GetKey(_, err) = &why {
625                            if err.kind() == std::io::ErrorKind::NotFound {
626                                // No system default config installed; don't error
627                                continue;
628                            }
629                        }
630                        tracing::error!(?why, "cosmic toolkit config update error");
631                    }
632
633                    crate::Action::Cosmic(Action::ToolkitConfig(update.config))
634                }),
635            self.app
636                .core()
637                .watch_config::<cosmic_theme::Theme>(
638                    if if let ThemeType::System { prefer_dark, .. } =
639                        THEME.lock().unwrap().theme_type
640                    {
641                        prefer_dark
642                    } else {
643                        None
644                    }
645                    .unwrap_or_else(|| self.app.core().system_theme_mode.is_dark)
646                    {
647                        cosmic_theme::DARK_THEME_ID
648                    } else {
649                        cosmic_theme::LIGHT_THEME_ID
650                    },
651                )
652                .map(|update| {
653                    for why in update
654                        .errors
655                        .into_iter()
656                        .filter(cosmic_config::Error::is_err)
657                    {
658                        tracing::error!(?why, "cosmic theme config update error");
659                    }
660                    Action::SystemThemeChange(
661                        update.keys,
662                        crate::theme::Theme::system(Arc::new(update.config)),
663                    )
664                })
665                .map(crate::Action::Cosmic),
666            self.app
667                .core()
668                .watch_config::<ThemeMode>(cosmic_theme::THEME_MODE_ID)
669                .map(|update| {
670                    for error in update
671                        .errors
672                        .into_iter()
673                        .filter(cosmic_config::Error::is_err)
674                    {
675                        tracing::error!(?error, "error reading system theme mode update");
676                    }
677                    Action::SystemThemeModeChange(update.keys, update.config)
678                })
679                .map(crate::Action::Cosmic),
680            window_events.map(crate::Action::Cosmic),
681            #[cfg(xdg_portal)]
682            crate::theme::portal::desktop_settings()
683                .map(Action::DesktopSettings)
684                .map(crate::Action::Cosmic),
685        ];
686
687        if self.app.core().keyboard_nav {
688            subscriptions.push(
689                keyboard_nav::subscription()
690                    .map(Action::KeyboardNav)
691                    .map(crate::Action::Cosmic),
692            );
693        }
694
695        #[cfg(feature = "single-instance")]
696        if self.app.core().single_instance {
697            subscriptions.push(crate::dbus_activation::subscription::<T>());
698        }
699
700        Subscription::batch(subscriptions)
701    }
702
703    #[cfg(not(feature = "multi-window"))]
704    pub fn theme(&self) -> Theme {
705        crate::theme::active()
706    }
707
708    #[cfg(feature = "multi-window")]
709    pub fn theme(&self, _id: window::Id) -> Theme {
710        crate::theme::active()
711    }
712
713    #[cfg(feature = "multi-window")]
714    pub fn view(&self, id: window::Id) -> Element<'_, crate::Action<T::Message>> {
715        if let Some((_, _, _, Some(v))) = self.surface_views.get(&id) {
716            return v(&self.app);
717        }
718        if self
719            .app
720            .core()
721            .main_window_id()
722            .is_none_or(|main_id| main_id != id)
723        {
724            return self.app.view_window(id).map(crate::Action::App);
725        }
726
727        let view = if self.app.core().window.use_template {
728            self.app.view_main()
729        } else {
730            self.app.view().map(crate::Action::App)
731        };
732
733        #[cfg(all(target_env = "gnu", not(target_os = "windows")))]
734        crate::malloc::trim(0);
735
736        view
737    }
738
739    #[cfg(not(feature = "multi-window"))]
740    pub fn view(&self) -> Element<crate::Action<T::Message>> {
741        let view = self.app.view_main();
742
743        #[cfg(all(target_env = "gnu", not(target_os = "windows")))]
744        crate::malloc::trim(0);
745
746        view
747    }
748}
749
750impl<T: Application> Cosmic<T> {
751    #[allow(clippy::unused_self)]
752    #[cold]
753    pub fn close(&mut self) -> iced::Task<crate::Action<T::Message>> {
754        if let Some(id) = self.app.core().main_window_id() {
755            iced::window::close(id)
756        } else {
757            iced::Task::none()
758        }
759    }
760
761    #[allow(clippy::too_many_lines)]
762    fn cosmic_update(&mut self, message: Action) -> iced::Task<crate::Action<T::Message>> {
763        match message {
764            Action::WindowMaximized(id, maximized) => {
765                #[cfg(not(wayland_platform))]
766                if self
767                    .app
768                    .core()
769                    .main_window_id()
770                    .is_some_and(|main_id| main_id == id)
771                {
772                    self.app.core_mut().window.sharp_corners = maximized;
773                }
774            }
775
776            Action::WindowResize(id, width, height) => {
777                if self
778                    .app
779                    .core()
780                    .main_window_id()
781                    .is_some_and(|main_id| main_id == id)
782                {
783                    self.app.core_mut().set_window_width(width);
784                    self.app.core_mut().set_window_height(height);
785                }
786
787                self.app.on_window_resize(id, width, height);
788
789                //TODO: more efficient test of maximized (winit has no event for maximize if set by the OS)
790                return iced::window::is_maximized(id).map(move |maximized| {
791                    crate::Action::Cosmic(Action::WindowMaximized(id, maximized))
792                });
793            }
794
795            #[cfg(wayland_platform)]
796            Action::WindowState(id, state) => {
797                if self
798                    .app
799                    .core()
800                    .main_window_id()
801                    .is_some_and(|main_id| main_id == id)
802                {
803                    self.app.core_mut().window.sharp_corners = state.intersects(
804                        WindowState::MAXIMIZED
805                            | WindowState::FULLSCREEN
806                            | WindowState::TILED
807                            | WindowState::TILED_RIGHT
808                            | WindowState::TILED_LEFT
809                            | WindowState::TILED_TOP
810                            | WindowState::TILED_BOTTOM,
811                    );
812                    self.app.core_mut().window.is_maximized =
813                        state.intersects(WindowState::MAXIMIZED | WindowState::FULLSCREEN);
814                }
815                {
816                    use iced_winit::platform_specific::commands::corner_radius::corner_radius;
817
818                    let theme = THEME.lock().unwrap();
819                    let rounded = !self.app.core().window.sharp_corners
820                        && self.app.core().sync_window_border_radii_to_theme();
821
822                    let cur_rad = self.app.core().corners(&theme, rounded);
823                    return Task::batch([corner_radius(id, cur_rad).discard()]);
824                }
825            }
826
827            #[cfg(wayland_platform)]
828            Action::WmCapabilities(id, capabilities) => {
829                if self
830                    .app
831                    .core()
832                    .main_window_id()
833                    .is_some_and(|main_id| main_id == id)
834                {
835                    self.app.core_mut().window.show_maximize =
836                        capabilities.contains(WindowManagerCapabilities::MAXIMIZE);
837                    self.app.core_mut().window.show_minimize =
838                        capabilities.contains(WindowManagerCapabilities::MINIMIZE);
839                    self.app.core_mut().window.show_window_menu =
840                        capabilities.contains(WindowManagerCapabilities::WINDOW_MENU);
841                }
842            }
843
844            Action::KeyboardNav(message) => match message {
845                keyboard_nav::Action::FocusNext => {
846                    return iced::widget::operation::focus_next().map(crate::Action::Cosmic);
847                }
848                keyboard_nav::Action::FocusPrevious => {
849                    return iced::widget::operation::focus_previous().map(crate::Action::Cosmic);
850                }
851                keyboard_nav::Action::Escape => return self.app.on_escape(),
852                keyboard_nav::Action::Search => return self.app.on_search(),
853
854                keyboard_nav::Action::Fullscreen => return self.app.core().toggle_maximize(None),
855            },
856
857            Action::ContextDrawer(show) => {
858                self.app.core_mut().set_show_context(show);
859                return self.app.on_context_drawer();
860            }
861
862            Action::Drag => return self.app.core().drag(None),
863
864            Action::Minimize => return self.app.core().minimize(None),
865
866            Action::Maximize => return self.app.core().toggle_maximize(None),
867
868            Action::NavBar(key) => {
869                self.app.core_mut().nav_bar_set_toggled_condensed(false);
870                return self.app.on_nav_select(key);
871            }
872
873            Action::NavBarContext(key) => {
874                self.app.core_mut().nav_bar_set_context(key);
875                return self.app.on_nav_context(key);
876            }
877
878            Action::ToggleNavBar => {
879                self.app.core_mut().nav_bar_toggle();
880            }
881
882            Action::ToggleNavBarCondensed => {
883                self.app.core_mut().nav_bar_toggle_condensed();
884            }
885
886            Action::AppThemeChange(mut theme) => {
887                if let ThemeType::System { theme: _, .. } = theme.theme_type {
888                    self.app.core_mut().theme_sub_counter += 1;
889
890                    let portal_accent = self.app.core().portal_accent;
891                    if let Some(a) = portal_accent {
892                        let t_inner = theme.cosmic();
893                        if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
894                            theme = Theme::system(Arc::new(t_inner.with_accent(a)));
895                        }
896                    }
897                }
898
899                let new_blur = self.blur_enabled && self.app.core().frosted(theme.cosmic());
900
901                theme.transparent = new_blur;
902                let mut guard = THEME.lock().unwrap();
903                guard.set_theme(theme.theme_type.clone());
904                guard.transparent = new_blur;
905                drop(guard);
906
907                #[cfg(wayland_platform)]
908                {
909                    let core = self.app.core();
910                    let mut cmds = Vec::with_capacity(1 + self.surface_views.len());
911                    let blur = if new_blur {
912                        iced::window::enable_blur
913                    } else {
914                        iced::window::disable_blur
915                    };
916                    if core.blur(&theme, None) {
917                        cmds.push(blur(
918                            self.app
919                                .core()
920                                .main_window_id()
921                                .unwrap_or(window::Id::RESERVED),
922                        ));
923                    }
924                    for (id, wrapper, ..) in &self.surface_views {
925                        let overriden = wrapper.2(&self.app);
926                        if core.blur(&theme, Some(wrapper.1)) && overriden.blur.unwrap_or(true) {
927                            cmds.push(blur(*id));
928                        } else if overriden.blur.is_some_and(|b| !b) {
929                            cmds.push(iced::window::disable_blur(*id));
930                        }
931                    }
932                    return Task::batch(cmds);
933                }
934            }
935
936            Action::SystemThemeChange(keys, mut theme) => {
937                let cur_is_dark = self.app.core().system_theme_mode.is_dark;
938                // Ignore updates if the current theme mode does not match.
939                if cur_is_dark != theme.cosmic().is_dark {
940                    return iced::Task::none();
941                }
942                // update transparent
943                let new_blur = self.blur_enabled && self.app.core().frosted(theme.cosmic());
944                theme.transparent = new_blur;
945
946                let cmd = self.app.system_theme_update(&keys, theme.cosmic());
947                // Record the last-known system theme in event that the current theme is custom.
948                self.app.core_mut().system_theme = theme.clone();
949                let portal_accent = self.app.core().portal_accent;
950                {
951                    let mut cosmic_theme = THEME.lock().unwrap();
952
953                    // Only apply update if the theme is set to load a system theme
954                    if let ThemeType::System {
955                        theme: _,
956                        prefer_dark,
957                    } = cosmic_theme.theme_type
958                    {
959                        let mut new_theme = if let Some(a) = portal_accent {
960                            let t_inner = theme.cosmic();
961                            if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
962                                Theme::system(Arc::new(t_inner.with_accent(a)))
963                            } else {
964                                theme
965                            }
966                        } else {
967                            theme
968                        };
969                        new_theme.transparent = new_blur;
970                        new_theme.theme_type.prefer_dark(prefer_dark);
971
972                        cosmic_theme.set_theme(new_theme.theme_type);
973                        cosmic_theme.transparent = new_blur;
974
975                        #[cfg(wayland_platform)]
976                        {
977                            use iced_winit::platform_specific::commands::corner_radius::corner_radius;
978
979                            let rounded = self.app.core().sync_window_border_radii_to_theme()
980                                && !self.app.core().window.sharp_corners;
981
982                            let cur_rad = self.app.core().corners(&cosmic_theme, rounded);
983
984                            // Update radius for the main window
985                            let main_window_id = self
986                                .app
987                                .core()
988                                .main_window_id()
989                                .unwrap_or(window::Id::RESERVED);
990                            let mut cmds = vec![corner_radius(main_window_id, cur_rad).discard()];
991                            // Update radius for each tracked view with the window surface type
992                            for (id, (_, surface_type, live_settings, _)) in &self.surface_views {
993                                let overriden = live_settings(&self.app);
994                                let cur_rad = if let Some(c) = overriden.corners {
995                                    Some(c)
996                                } else {
997                                    corners(
998                                        *surface_type,
999                                        rounded,
1000                                        &cosmic_theme,
1001                                        self.app.core().auto_corner_radius,
1002                                    )
1003                                };
1004                                if cur_rad.is_none() {
1005                                    continue;
1006                                }
1007                                cmds.push(corner_radius(*id, cur_rad).discard());
1008                            }
1009
1010                            let blur = if new_blur {
1011                                iced::window::enable_blur
1012                            } else {
1013                                iced::window::disable_blur
1014                            };
1015
1016                            cmds.push(blur(
1017                                self.app
1018                                    .core()
1019                                    .main_window_id()
1020                                    .unwrap_or(window::Id::RESERVED),
1021                            ));
1022
1023                            for (id, wrapper, ..) in &self.surface_views {
1024                                let overriden = wrapper.2(&self.app);
1025                                if self.app.core().blur(&cosmic_theme, Some(wrapper.1))
1026                                    && overriden.blur.unwrap_or(true)
1027                                {
1028                                    cmds.push(blur(*id));
1029                                } else if overriden.blur.is_some_and(|b| !b) {
1030                                    cmds.push(iced::window::disable_blur(*id));
1031                                }
1032                            }
1033                            return Task::batch(cmds);
1034                        }
1035                    }
1036                }
1037
1038                return cmd;
1039            }
1040
1041            Action::ScaleFactor(factor) => {
1042                self.app.core_mut().set_scale_factor(factor);
1043            }
1044
1045            Action::Close => {
1046                return match self.app.on_app_exit() {
1047                    Some(message) => self.app.update(message),
1048                    None => self.close(),
1049                };
1050            }
1051            Action::SystemThemeModeChange(keys, mode) => {
1052                if match THEME.lock().unwrap().theme_type {
1053                    ThemeType::System {
1054                        theme: _,
1055                        prefer_dark,
1056                    } => prefer_dark.is_some(),
1057                    _ => false,
1058                } {
1059                    return iced::Task::none();
1060                }
1061
1062                let mut cmds = vec![self.app.system_theme_mode_update(&keys, &mode)];
1063
1064                let core = self.app.core_mut();
1065                core.system_theme_mode = mode;
1066                let is_dark = core.system_is_dark();
1067                let changed = core.system_theme_mode.is_dark != is_dark
1068                    || core.portal_is_dark != Some(is_dark)
1069                    || core.system_theme.cosmic().is_dark != is_dark;
1070                if changed {
1071                    core.theme_sub_counter += 1;
1072                    let mut new_theme = if is_dark {
1073                        crate::theme::system_dark()
1074                    } else {
1075                        crate::theme::system_light()
1076                    };
1077                    cmds.push(self.app.system_theme_update(&[], new_theme.cosmic()));
1078
1079                    let core = self.app.core_mut();
1080                    new_theme = if let Some(a) = core.portal_accent {
1081                        let t_inner = new_theme.cosmic();
1082                        if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
1083                            Theme::system(Arc::new(t_inner.with_accent(a)))
1084                        } else {
1085                            new_theme
1086                        }
1087                    } else {
1088                        new_theme
1089                    };
1090                    let new_blur = self.blur_enabled && core.frosted(new_theme.cosmic());
1091                    new_theme.transparent = new_blur;
1092
1093                    core.system_theme = new_theme.clone();
1094                    {
1095                        let mut cosmic_theme = THEME.lock().unwrap();
1096
1097                        // Only apply update if the theme is set to load a system theme
1098                        if let ThemeType::System { .. } = cosmic_theme.theme_type {
1099                            cosmic_theme.set_theme(new_theme.theme_type);
1100                            cosmic_theme.transparent = new_blur;
1101                            #[cfg(wayland_platform)]
1102                            {
1103                                use iced_winit::platform_specific::commands::corner_radius::corner_radius;
1104
1105                                let rounded = self.app.core().sync_window_border_radii_to_theme()
1106                                    && !self.app.core().window.sharp_corners;
1107                                let cur_rad = self.app.core().corners(&cosmic_theme, rounded);
1108
1109                                // Update radius for the main window
1110                                let main_window_id = self
1111                                    .app
1112                                    .core()
1113                                    .main_window_id()
1114                                    .unwrap_or(window::Id::RESERVED);
1115                                let mut cmds =
1116                                    vec![corner_radius(main_window_id, cur_rad).discard()];
1117                                // Update radius for each tracked view with the window surface type
1118                                for (id, (_, surface_type, live_settings, _)) in &self.surface_views
1119                                {
1120                                    let overriden = live_settings(&self.app);
1121                                    let cur_rad = if let Some(c) = overriden.corners {
1122                                        Some(c)
1123                                    } else {
1124                                        corners(
1125                                            *surface_type,
1126                                            rounded,
1127                                            &cosmic_theme,
1128                                            self.app.core().auto_corner_radius,
1129                                        )
1130                                    };
1131                                    if cur_rad.is_none() {
1132                                        continue;
1133                                    }
1134                                    cmds.push(corner_radius(*id, cur_rad).discard());
1135                                }
1136
1137                                let core = self.app.core();
1138                                let blur = if cosmic_theme.transparent {
1139                                    iced::window::enable_blur
1140                                } else {
1141                                    iced::window::disable_blur
1142                                };
1143
1144                                if core.blur(&cosmic_theme, None) {
1145                                    cmds.push(blur(
1146                                        self.app
1147                                            .core()
1148                                            .main_window_id()
1149                                            .unwrap_or(window::Id::RESERVED),
1150                                    ));
1151                                }
1152                                for (id, wrapper, ..) in &self.surface_views {
1153                                    let overriden = wrapper.2(&self.app);
1154                                    if core.blur(&cosmic_theme, Some(wrapper.1))
1155                                        && overriden.blur.unwrap_or(true)
1156                                    {
1157                                        cmds.push(blur(*id));
1158                                    } else if overriden.blur.is_some_and(|b| !b) {
1159                                        cmds.push(iced::window::disable_blur(*id));
1160                                    }
1161                                }
1162
1163                                return Task::batch(cmds);
1164                            }
1165                        }
1166                    }
1167                }
1168                return Task::batch(cmds);
1169            }
1170            Action::Activate(_token) => {
1171                if let Some(id) = self.app.core().main_window_id() {
1172                    // Unminimize window before requesting to activate it.
1173                    let mut task = iced_runtime::window::minimize(id, false);
1174
1175                    #[cfg(wayland_platform)]
1176                    {
1177                        task = task.chain(
1178                            iced_winit::platform_specific::commands::activation::activate(
1179                                id,
1180                                #[allow(clippy::used_underscore_binding)]
1181                                _token,
1182                            ),
1183                        );
1184                    }
1185
1186                    #[cfg(not(wayland_platform))]
1187                    {
1188                        task = task.chain(iced_runtime::window::gain_focus(id));
1189                    }
1190
1191                    return task;
1192                }
1193            }
1194
1195            Action::Surface(action) => return self.surface_update(action),
1196
1197            Action::SurfaceClosed(id) => {
1198                if self.opened_surfaces.get_mut(&id).is_some_and(|v| {
1199                    *v = v.saturating_sub(1);
1200                    *v == 0
1201                }) {
1202                    self.opened_surfaces.remove(&id);
1203                    self.surface_views.remove(&id);
1204                }
1205                self.surface_views.shrink_to(self.surface_views.len() * 2);
1206
1207                let mut ret = if let Some(msg) = self.app.on_close_requested(id) {
1208                    self.app.update(msg)
1209                } else {
1210                    Task::none()
1211                };
1212                let core = self.app.core();
1213                if core.exit_on_main_window_closed
1214                    && core.main_window_id().is_some_and(|m_id| id == m_id)
1215                {
1216                    ret = Task::batch([iced::exit::<crate::Action<T::Message>>()]);
1217                }
1218                return ret;
1219            }
1220
1221            Action::ShowWindowMenu => {
1222                if let Some(id) = self.app.core().main_window_id() {
1223                    return iced::window::show_system_menu(id);
1224                }
1225            }
1226
1227            #[cfg(feature = "single-instance")]
1228            Action::DbusConnection(conn) => {
1229                return self.app.dbus_connection(conn);
1230            }
1231
1232            #[cfg(xdg_portal)]
1233            Action::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => {
1234                use ashpd::desktop::settings::ColorScheme;
1235                if match THEME.lock().unwrap().theme_type {
1236                    ThemeType::System {
1237                        theme: _,
1238                        prefer_dark,
1239                    } => prefer_dark.is_some(),
1240                    _ => false,
1241                } {
1242                    return iced::Task::none();
1243                }
1244                let is_dark = match s {
1245                    ColorScheme::NoPreference => None,
1246                    ColorScheme::PreferDark => Some(true),
1247                    ColorScheme::PreferLight => Some(false),
1248                };
1249                let core = self.app.core_mut();
1250
1251                core.portal_is_dark = is_dark;
1252                let is_dark = core.system_is_dark();
1253                let changed = core.system_theme_mode.is_dark != is_dark
1254                    || core.portal_is_dark != Some(is_dark)
1255                    || core.system_theme.cosmic().is_dark != is_dark;
1256
1257                if changed {
1258                    core.theme_sub_counter += 1;
1259                    let mut new_theme = if is_dark {
1260                        crate::theme::system_dark()
1261                    } else {
1262                        crate::theme::system_light()
1263                    };
1264                    if let ThemeType::System { .. } = new_theme.theme_type {
1265                        let new_blur = self.blur_enabled && core.frosted(new_theme.cosmic());
1266                        new_theme.transparent = new_blur;
1267                    }
1268                    core.system_theme = new_theme.clone();
1269                    let core = self.app.core();
1270                    {
1271                        let mut cosmic_theme = THEME.lock().unwrap();
1272
1273                        // Only apply update if the theme is set to load a system theme
1274                        if let ThemeType::System { theme: _, .. } = cosmic_theme.theme_type {
1275                            let mut cmds = Vec::with_capacity(1);
1276                            #[cfg(wayland_platform)]
1277                            {
1278                                let blur = if cosmic_theme.transparent {
1279                                    iced::window::enable_blur
1280                                } else {
1281                                    iced::window::disable_blur
1282                                };
1283
1284                                if core.blur(&cosmic_theme, None) {
1285                                    cmds.push(blur(
1286                                        core.main_window_id().unwrap_or(window::Id::RESERVED),
1287                                    ));
1288                                }
1289
1290                                for (id, wrapper, ..) in &self.surface_views {
1291                                    let overriden = wrapper.2(&self.app);
1292                                    if core.blur(&cosmic_theme, Some(wrapper.1))
1293                                        && overriden.blur.unwrap_or(true)
1294                                    {
1295                                        cmds.push(blur(*id));
1296                                    } else if overriden.blur.is_some_and(|b| !b) {
1297                                        cmds.push(iced::window::disable_blur(*id));
1298                                    }
1299                                }
1300                            }
1301                            cosmic_theme.set_theme(new_theme.theme_type);
1302                            return Task::batch(cmds);
1303                        }
1304                    }
1305                }
1306            }
1307            #[cfg(xdg_portal)]
1308            Action::DesktopSettings(crate::theme::portal::Desktop::Accent(c)) => {
1309                use palette::Srgba;
1310                let c = Srgba::new(c.red() as f32, c.green() as f32, c.blue() as f32, 1.0);
1311                let core = self.app.core_mut();
1312                core.portal_accent = Some(c);
1313                let cur_accent = core.system_theme.cosmic().accent_color();
1314
1315                if cur_accent.distance_squared(*c) < 0.00001 {
1316                    // skip calculations if we already have the same color
1317                    return iced::Task::none();
1318                }
1319
1320                {
1321                    let mut cosmic_theme = THEME.lock().unwrap();
1322
1323                    // Only apply update if the theme is set to load a system theme
1324                    if let ThemeType::System {
1325                        theme: t,
1326                        prefer_dark,
1327                    } = cosmic_theme.theme_type.clone()
1328                    {
1329                        cosmic_theme.set_theme(ThemeType::System {
1330                            theme: Arc::new(t.with_accent(c)),
1331                            prefer_dark,
1332                        });
1333                    }
1334                }
1335            }
1336            #[cfg(xdg_portal)]
1337            Action::DesktopSettings(crate::theme::portal::Desktop::Contrast(_)) => {
1338                // TODO when high contrast is integrated in settings and all custom themes
1339            }
1340
1341            Action::ToolkitConfig(config) => {
1342                // Change the icon theme if not defined by the application.
1343                if !self.app.core().icon_theme_override
1344                    && crate::icon_theme::default() != config.icon_theme
1345                {
1346                    crate::icon_theme::set_default(config.icon_theme.clone());
1347                }
1348
1349                *crate::config::COSMIC_TK.write().unwrap() = config;
1350            }
1351
1352            Action::Focus(f) => {
1353                #[cfg(wayland_platform)]
1354                if let Some((
1355                    parent,
1356                    SurfaceIdWrapper::Subsurface(_) | SurfaceIdWrapper::Popup(_),
1357                    _,
1358                    _,
1359                )) = self.surface_views.get(&f)
1360                {
1361                    // If the parent is already focused, push the new focus
1362                    // to the end of the focus chain.
1363                    if parent.is_some_and(|p| self.app.core().focused_window.last() == Some(&p)) {
1364                        self.app.core_mut().focused_window.push(f);
1365                        return iced::Task::none();
1366                    } else {
1367                        // set the whole parent chain to the focus chain
1368                        let mut parent_chain = vec![f];
1369                        let mut cur = *parent;
1370                        while let Some(p) = cur {
1371                            parent_chain.push(p);
1372                            cur = self
1373                                .surface_views
1374                                .get(&p)
1375                                .and_then(|(parent, _, _, _)| *parent);
1376                        }
1377                        parent_chain.reverse();
1378                        self.app.core_mut().focused_window = parent_chain;
1379                        return iced::Task::none();
1380                    }
1381                }
1382                self.app.core_mut().focused_window = vec![f];
1383            }
1384
1385            Action::Unfocus(id) => {
1386                let core = self.app.core_mut();
1387                if core.focused_window().as_ref().is_some_and(|cur| *cur == id) {
1388                    core.focused_window.pop();
1389                }
1390            }
1391            #[cfg(feature = "applet")]
1392            Action::SuggestedBounds(b) => {
1393                tracing::info!("Suggested bounds: {b:?}");
1394                let core = self.app.core_mut();
1395                core.applet.suggested_bounds = b;
1396            }
1397            Action::Opened(id) => {
1398                #[cfg(wayland_platform)]
1399                {
1400                    use iced_winit::platform_specific::commands::corner_radius::corner_radius;
1401
1402                    let mut theme = THEME.lock().unwrap();
1403
1404                    // TODO do we need per window sharp corners?
1405                    let rounded = (!self.app.core().window.sharp_corners
1406                        && self.app.core().sync_window_border_radii_to_theme())
1407                        || self
1408                            .surface_views
1409                            .get(&id)
1410                            .is_some_and(|(_, surface_type, _, _)| {
1411                                matches!(
1412                                    surface_type,
1413                                    SurfaceIdWrapper::Popup(_) | SurfaceIdWrapper::LayerSurface(_)
1414                                )
1415                            });
1416                    let new_blur = self.blur_enabled && self.app.core().frosted(theme.cosmic());
1417
1418                    let wrapper = self.surface_views.get(&id).map(|s| s.1);
1419
1420                    // this will blur untracked windows as if they were the main window
1421                    let blur_cmd = if self.app.core().blur(&theme, wrapper)
1422                        && self
1423                            .surface_views
1424                            .get(&id)
1425                            .and_then(|s| s.2(&self.app).blur)
1426                            .unwrap_or(true)
1427                    {
1428                        let blur = if new_blur {
1429                            iced::window::enable_blur
1430                        } else {
1431                            iced::window::disable_blur
1432                        };
1433                        let mut cmds = Vec::with_capacity(1 + self.surface_views.len());
1434                        cmds.push(blur(id));
1435
1436                        Task::batch(cmds)
1437                    } else {
1438                        Task::none()
1439                    };
1440
1441                    let corner_task = if let Some((_, cur_rad)) =
1442                        self.surface_views.get(&id).map(|s| (s.1, &s.2)).and_then(
1443                            |(s, overriden)| {
1444                                let overriden = overriden(&self.app);
1445                                if let Some(c) = overriden.corners {
1446                                    Some((s, c))
1447                                } else {
1448                                    corners(s, rounded, &theme, self.app.core().auto_corner_radius)
1449                                        .map(|c| (s, c))
1450                                }
1451                            },
1452                        ) {
1453                        corner_radius(id, Some(cur_rad)).discard()
1454                    } else if id
1455                        == self
1456                            .app
1457                            .core()
1458                            .main_window_id()
1459                            .unwrap_or(window::Id::RESERVED)
1460                    {
1461                        corner_radius(id, self.app.core().corners(&theme, rounded)).discard()
1462                    } else {
1463                        Task::none()
1464                    };
1465
1466                    return Task::batch([
1467                        blur_cmd,
1468                        corner_task,
1469                        iced_runtime::window::run_with_handle(id, init_windowing_system),
1470                    ]);
1471                }
1472                return iced_runtime::window::run_with_handle(id, init_windowing_system);
1473            }
1474            #[cfg(wayland_platform)]
1475            Action::BlurEnabled => {
1476                // TODO do this after blur event confirms support instead of for all wayland windows
1477                self.blur_enabled = true;
1478                let mut t = THEME.lock().unwrap();
1479
1480                let new_blur = self.blur_enabled && self.app.core().frosted(t.cosmic());
1481
1482                t.transparent = new_blur;
1483
1484                self.app.core_mut().system_theme.transparent = new_blur;
1485                {
1486                    let blur = if new_blur {
1487                        iced::window::enable_blur
1488                    } else {
1489                        iced::window::disable_blur
1490                    };
1491                    let mut cmds = Vec::with_capacity(1 + self.surface_views.len());
1492                    if self.app.core().blur(&t, None) {
1493                        cmds.push(blur(
1494                            self.app
1495                                .core()
1496                                .main_window_id()
1497                                .unwrap_or(window::Id::RESERVED),
1498                        ));
1499                    }
1500                    for (id, wrapper, ..) in &self.surface_views {
1501                        let overriden = wrapper.2(&self.app);
1502                        if self.app.core().blur(&t, Some(wrapper.1))
1503                            && overriden.blur.unwrap_or(true)
1504                        {
1505                            cmds.push(blur(*id));
1506                        } else if overriden.blur.is_some_and(|b| !b) {
1507                            cmds.push(iced::window::disable_blur(*id));
1508                        }
1509                    }
1510                    return Task::batch(cmds);
1511                }
1512            }
1513            _ => (),
1514        }
1515
1516        iced::Task::none()
1517    }
1518}
1519
1520impl<App: Application> Cosmic<App> {
1521    pub fn new(app: App) -> Self {
1522        Self {
1523            app,
1524            surface_views: HashMap::new(),
1525            opened_surfaces: HashMap::new(),
1526            blur_enabled: false,
1527        }
1528    }
1529
1530    #[cfg(feature = "surface-message")]
1531    /// Apply live setting overrides for a surface
1532    pub fn apply_live_settings(
1533        &mut self,
1534        id_wrapper: SurfaceIdWrapper,
1535        live_settings: &crate::surface::action::LiveSettings,
1536    ) -> Task<crate::Action<App::Message>> {
1537        let id = id_wrapper.inner();
1538
1539        let mut cmds = Vec::with_capacity(2);
1540        let t = THEME.try_lock().unwrap();
1541
1542        if let Some(blur) = live_settings.blur {
1543            let blur_cmd = if blur {
1544                if matches!(id_wrapper, SurfaceIdWrapper::Window(_)) {
1545                    window::enable_blur(id)
1546                } else {
1547                    #[cfg(wayland_platform)]
1548                    {
1549                        iced_winit::commands::blur::blur(
1550                            id,
1551                            Some(vec![iced::Rectangle::new(
1552                                iced::Point::ORIGIN,
1553                                iced::Size::INFINITE,
1554                            )]),
1555                        )
1556                        .discard()
1557                    }
1558                    #[cfg(not(wayland_platform))]
1559                    {
1560                        iced::window::enable_blur(id)
1561                    }
1562                }
1563            } else if matches!(id_wrapper, SurfaceIdWrapper::Window(_)) {
1564                window::disable_blur(id)
1565            } else {
1566                #[cfg(wayland_platform)]
1567                {
1568                    iced_winit::commands::blur::blur(id, None).discard()
1569                }
1570                #[cfg(not(wayland_platform))]
1571                {
1572                    iced::window::disable_blur(id)
1573                }
1574            };
1575            cmds.push(blur_cmd);
1576        } else if self.app.core().blur(&t, Some(id_wrapper)) {
1577            cmds.push(if matches!(id_wrapper, SurfaceIdWrapper::Window(_)) {
1578                window::enable_blur(id)
1579            } else {
1580                #[cfg(wayland_platform)]
1581                {
1582                    iced_winit::commands::blur::blur(
1583                        id,
1584                        Some(vec![iced::Rectangle::new(
1585                            iced::Point::ORIGIN,
1586                            iced::Size::INFINITE,
1587                        )]),
1588                    )
1589                    .discard()
1590                }
1591                #[cfg(not(wayland_platform))]
1592                {
1593                    iced::window::enable_blur(id)
1594                }
1595            });
1596        }
1597        #[cfg(wayland_platform)]
1598        if let Some(corners) = live_settings.corners {
1599            cmds.push(
1600                iced_winit::commands::corner_radius::corner_radius(id, Some(corners)).discard(),
1601            );
1602        } else {
1603            let rounded = !self.app.core().window.sharp_corners
1604                && self.app.core().sync_window_border_radii_to_theme();
1605            if let Some(cur_rad) =
1606                corners(id_wrapper, rounded, &t, self.app.core().auto_corner_radius)
1607            {
1608                cmds.push(
1609                    iced_winit::commands::corner_radius::corner_radius(id, Some(cur_rad)).discard(),
1610                );
1611            }
1612        }
1613        #[cfg(wayland_platform)]
1614        if let (SurfaceIdWrapper::LayerSurface(id), Some(padding)) =
1615            (id_wrapper, live_settings.padding)
1616        {
1617            cmds.push(iced_winit::commands::layer_surface::set_padding(
1618                id, padding,
1619            ));
1620        }
1621        Task::batch(cmds)
1622    }
1623
1624    #[cfg(wayland_platform)]
1625    /// Create a subsurface
1626    pub fn get_subsurface(
1627        &mut self,
1628        settings: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings,
1629        view: Option<
1630            Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
1631        >,
1632    ) -> Task<crate::Action<App::Message>> {
1633        use iced_winit::commands::subsurface::get_subsurface;
1634
1635        *self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1;
1636        let live_settings_task = self.apply_live_settings(
1637            SurfaceIdWrapper::Subsurface(settings.id),
1638            &LiveSettings {
1639                blur: None,
1640                corners: None,
1641                padding: None,
1642            },
1643        );
1644        self.surface_views.insert(
1645            settings.id,
1646            (
1647                Some(settings.parent),
1648                SurfaceIdWrapper::Subsurface(settings.id),
1649                Box::new(|_| LiveSettings::default()),
1650                view,
1651            ),
1652        );
1653        Task::batch([live_settings_task, get_subsurface(settings)])
1654    }
1655
1656    #[cfg(wayland_platform)]
1657    /// Create a subsurface
1658    pub fn get_popup(
1659        &mut self,
1660        settings: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
1661        live_settings: Box<dyn for<'a> Fn(&'a App) -> LiveSettings + Send + Sync>,
1662        view: Option<
1663            Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
1664        >,
1665    ) -> Task<crate::Action<App::Message>> {
1666        use iced_winit::commands::popup::get_popup;
1667        *self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1;
1668        let live_settings_task = self.apply_live_settings(
1669            SurfaceIdWrapper::Popup(settings.id),
1670            &live_settings(&self.app),
1671        );
1672        self.surface_views.insert(
1673            settings.id,
1674            (
1675                Some(settings.parent),
1676                SurfaceIdWrapper::Popup(settings.id),
1677                live_settings,
1678                view,
1679            ),
1680        );
1681        live_settings_task.chain(get_popup(settings)).discard()
1682    }
1683
1684    #[cfg(wayland_platform)]
1685    /// Create a window surface
1686    pub fn get_window(
1687        &mut self,
1688        id: iced::window::Id,
1689        settings: iced::window::Settings,
1690        live_settings: Box<dyn for<'a> Fn(&'a App) -> LiveSettings + Send + Sync>,
1691
1692        view: Option<
1693            Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
1694        >,
1695    ) -> Task<crate::Action<App::Message>> {
1696        use iced_winit::SurfaceIdWrapper;
1697        *self.opened_surfaces.entry(id).or_insert(0) += 1;
1698        let live_settings_task =
1699            self.apply_live_settings(SurfaceIdWrapper::Window(id), &live_settings(&self.app));
1700        self.surface_views.insert(
1701            id,
1702            (
1703                None, // TODO parent for window, platform specific option maybe?
1704                SurfaceIdWrapper::Window(id),
1705                live_settings,
1706                view,
1707            ),
1708        );
1709        Task::batch([
1710            iced_runtime::task::oneshot(|channel| {
1711                iced_runtime::Action::Window(iced_runtime::window::Action::Open(
1712                    id, settings, channel,
1713                ))
1714            })
1715            .discard(),
1716            // We don't control window creation in the same way
1717            live_settings_task,
1718        ])
1719    }
1720
1721    #[cfg(wayland_platform)]
1722    pub fn get_layer_shell(
1723        &mut self,
1724        settings: iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings,
1725        live_settings: Box<dyn for<'a> Fn(&'a App) -> LiveSettings + Send + Sync>,
1726        view: Option<
1727            Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
1728        >,
1729    ) -> Task<crate::Action<App::Message>> {
1730        use iced_winit::SurfaceIdWrapper;
1731        use iced_winit::platform_specific::commands::layer_surface::get_layer_surface;
1732        *self.opened_surfaces.entry(settings.id).or_insert(0) += 1;
1733        let live_settings_task = self.apply_live_settings(
1734            SurfaceIdWrapper::LayerSurface(settings.id),
1735            &live_settings(&self.app),
1736        );
1737        self.surface_views.insert(
1738            settings.id,
1739            (
1740                None, // TODO parent for layer shell, platform specific option maybe?
1741                SurfaceIdWrapper::LayerSurface(settings.id),
1742                live_settings,
1743                view,
1744            ),
1745        );
1746        Task::batch([live_settings_task, get_layer_surface(settings)])
1747    }
1748}
1749
1750#[cfg(wayland_platform)]
1751fn corners(
1752    surface_type: SurfaceIdWrapper,
1753    rounded: bool,
1754    theme: &Theme,
1755    auto_corner_radius: BitFlags<Auto>,
1756) -> Option<iced_runtime::platform_specific::wayland::CornerRadius> {
1757    if !match surface_type {
1758        SurfaceIdWrapper::Window(_) => auto_corner_radius.contains(Auto::Window),
1759        SurfaceIdWrapper::LayerSurface(_) => auto_corner_radius.contains(Auto::System),
1760        SurfaceIdWrapper::Popup(_) => auto_corner_radius.contains(Auto::Popup),
1761        _ => false,
1762    } {
1763        return None;
1764    }
1765    let theme = theme.cosmic();
1766    Some(if let SurfaceIdWrapper::Popup(_) = surface_type {
1767        let radius_m = theme.radius_m();
1768        iced_runtime::platform_specific::wayland::CornerRadius {
1769            top_left: radius_m[0].round() as u32,
1770            top_right: radius_m[1].round() as u32,
1771            bottom_right: radius_m[2].round() as u32,
1772            bottom_left: radius_m[3].round() as u32,
1773        }
1774    } else if let SurfaceIdWrapper::Window(_) = surface_type
1775        && !rounded
1776    {
1777        let radius_0 = theme.radius_0();
1778        iced_runtime::platform_specific::wayland::CornerRadius {
1779            top_left: radius_0[0].round() as u32,
1780            top_right: radius_0[1].round() as u32,
1781            bottom_right: radius_0[2].round() as u32,
1782            bottom_left: radius_0[3].round() as u32,
1783        }
1784    } else {
1785        let radius_s = theme.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
1786        iced_runtime::platform_specific::wayland::CornerRadius {
1787            top_left: radius_s[0].round() as u32,
1788            top_right: radius_s[1].round() as u32,
1789            bottom_right: radius_s[2].round() as u32,
1790            bottom_left: radius_s[3].round() as u32,
1791        }
1792    })
1793}