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 if self.app.core().window.is_maximized {
553            let theme = THEME.lock().unwrap();
554            crate::style::iced::application::style(theme.borrow())
555        } else {
556            let theme = THEME.lock().unwrap();
557
558            theme::Style {
559                background_color: iced_core::Color::TRANSPARENT,
560                icon_color: theme.cosmic().on_bg_color().into(),
561                text_color: theme.cosmic().on_bg_color().into(),
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 && {
900                    let t = theme.cosmic();
901                    match self.app.core().app_type() {
902                        crate::core::AppType::Window => t.frosted_windows,
903                        crate::core::AppType::System => t.frosted_system_interface,
904                        crate::core::AppType::Applet => t.frosted_applets,
905                    }
906                };
907
908                theme.transparent = new_blur;
909                let mut guard = THEME.lock().unwrap();
910                guard.set_theme(theme.theme_type.clone());
911                guard.transparent = new_blur;
912                drop(guard);
913
914                #[cfg(wayland_platform)]
915                {
916                    let core = self.app.core();
917                    let mut cmds = Vec::with_capacity(1 + self.surface_views.len());
918                    let blur = if new_blur {
919                        iced::window::enable_blur
920                    } else {
921                        iced::window::disable_blur
922                    };
923                    if core.blur(&theme, None) {
924                        cmds.push(blur(
925                            self.app
926                                .core()
927                                .main_window_id()
928                                .unwrap_or(window::Id::RESERVED),
929                        ));
930                    }
931                    for (id, wrapper, ..) in &self.surface_views {
932                        let overriden = wrapper.2(&self.app);
933                        if core.blur(&theme, Some(wrapper.1)) && overriden.blur.unwrap_or(true) {
934                            cmds.push(blur(*id));
935                        } else if overriden.blur.is_some_and(|b| !b) {
936                            cmds.push(iced::window::disable_blur(*id));
937                        }
938                    }
939                    return Task::batch(cmds);
940                }
941            }
942
943            Action::SystemThemeChange(keys, mut theme) => {
944                let cur_is_dark = self.app.core().system_theme_mode.is_dark;
945                // Ignore updates if the current theme mode does not match.
946                if cur_is_dark != theme.cosmic().is_dark {
947                    return iced::Task::none();
948                }
949                // update transparent
950                let new_blur = self.blur_enabled && {
951                    let t = theme.cosmic();
952                    match self.app.core().app_type() {
953                        crate::core::AppType::Window => t.frosted_windows,
954                        crate::core::AppType::System => t.frosted_system_interface,
955                        crate::core::AppType::Applet => t.frosted_applets,
956                    }
957                };
958                theme.transparent = new_blur;
959
960                let cmd = self.app.system_theme_update(&keys, theme.cosmic());
961                // Record the last-known system theme in event that the current theme is custom.
962                self.app.core_mut().system_theme = theme.clone();
963                let portal_accent = self.app.core().portal_accent;
964                {
965                    let mut cosmic_theme = THEME.lock().unwrap();
966
967                    // Only apply update if the theme is set to load a system theme
968                    if let ThemeType::System {
969                        theme: _,
970                        prefer_dark,
971                    } = cosmic_theme.theme_type
972                    {
973                        let mut new_theme = if let Some(a) = portal_accent {
974                            let t_inner = theme.cosmic();
975                            if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
976                                Theme::system(Arc::new(t_inner.with_accent(a)))
977                            } else {
978                                theme
979                            }
980                        } else {
981                            theme
982                        };
983                        new_theme.transparent = new_blur;
984                        new_theme.theme_type.prefer_dark(prefer_dark);
985
986                        cosmic_theme.set_theme(new_theme.theme_type);
987                        cosmic_theme.transparent = new_blur;
988
989                        #[cfg(wayland_platform)]
990                        {
991                            use iced_winit::platform_specific::commands::corner_radius::corner_radius;
992
993                            let rounded = self.app.core().sync_window_border_radii_to_theme()
994                                && !self.app.core().window.sharp_corners;
995
996                            let cur_rad = self.app.core().corners(&cosmic_theme, rounded);
997
998                            // Update radius for the main window
999                            let main_window_id = self
1000                                .app
1001                                .core()
1002                                .main_window_id()
1003                                .unwrap_or(window::Id::RESERVED);
1004                            let mut cmds = vec![corner_radius(main_window_id, cur_rad).discard()];
1005                            // Update radius for each tracked view with the window surface type
1006                            for (id, (_, surface_type, live_settings, _)) in &self.surface_views {
1007                                let overriden = live_settings(&self.app);
1008                                let cur_rad = if let Some(c) = overriden.corners {
1009                                    Some(c)
1010                                } else {
1011                                    corners(
1012                                        *surface_type,
1013                                        rounded,
1014                                        &cosmic_theme,
1015                                        self.app.core().auto_corner_radius,
1016                                    )
1017                                };
1018                                if cur_rad.is_none() {
1019                                    continue;
1020                                }
1021                                cmds.push(corner_radius(*id, cur_rad).discard());
1022                            }
1023
1024                            let blur = if new_blur {
1025                                iced::window::enable_blur
1026                            } else {
1027                                iced::window::disable_blur
1028                            };
1029
1030                            cmds.push(blur(
1031                                self.app
1032                                    .core()
1033                                    .main_window_id()
1034                                    .unwrap_or(window::Id::RESERVED),
1035                            ));
1036
1037                            for (id, wrapper, ..) in &self.surface_views {
1038                                let overriden = wrapper.2(&self.app);
1039                                if self.app.core().blur(&cosmic_theme, Some(wrapper.1))
1040                                    && overriden.blur.unwrap_or(true)
1041                                {
1042                                    cmds.push(blur(*id));
1043                                } else if overriden.blur.is_some_and(|b| !b) {
1044                                    cmds.push(iced::window::disable_blur(*id));
1045                                }
1046                            }
1047                            return Task::batch(cmds);
1048                        }
1049                    }
1050                }
1051
1052                return cmd;
1053            }
1054
1055            Action::ScaleFactor(factor) => {
1056                self.app.core_mut().set_scale_factor(factor);
1057            }
1058
1059            Action::Close => {
1060                return match self.app.on_app_exit() {
1061                    Some(message) => self.app.update(message),
1062                    None => self.close(),
1063                };
1064            }
1065            Action::SystemThemeModeChange(keys, mode) => {
1066                if match THEME.lock().unwrap().theme_type {
1067                    ThemeType::System {
1068                        theme: _,
1069                        prefer_dark,
1070                    } => prefer_dark.is_some(),
1071                    _ => false,
1072                } {
1073                    return iced::Task::none();
1074                }
1075
1076                let mut cmds = vec![self.app.system_theme_mode_update(&keys, &mode)];
1077
1078                let core = self.app.core_mut();
1079                core.system_theme_mode = mode;
1080                let is_dark = core.system_is_dark();
1081                let changed = core.system_theme_mode.is_dark != is_dark
1082                    || core.portal_is_dark != Some(is_dark)
1083                    || core.system_theme.cosmic().is_dark != is_dark;
1084                if changed {
1085                    core.theme_sub_counter += 1;
1086                    let mut new_theme = if is_dark {
1087                        crate::theme::system_dark()
1088                    } else {
1089                        crate::theme::system_light()
1090                    };
1091                    cmds.push(self.app.system_theme_update(&[], new_theme.cosmic()));
1092
1093                    let core = self.app.core_mut();
1094                    new_theme = if let Some(a) = core.portal_accent {
1095                        let t_inner = new_theme.cosmic();
1096                        if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
1097                            Theme::system(Arc::new(t_inner.with_accent(a)))
1098                        } else {
1099                            new_theme
1100                        }
1101                    } else {
1102                        new_theme
1103                    };
1104                    let new_blur = self.blur_enabled && {
1105                        let t = new_theme.cosmic();
1106                        match core.app_type() {
1107                            crate::core::AppType::Window => t.frosted_windows,
1108                            crate::core::AppType::System => t.frosted_system_interface,
1109                            crate::core::AppType::Applet => t.frosted_applets,
1110                        }
1111                    };
1112                    new_theme.transparent = new_blur;
1113
1114                    core.system_theme = new_theme.clone();
1115                    {
1116                        let mut cosmic_theme = THEME.lock().unwrap();
1117
1118                        // Only apply update if the theme is set to load a system theme
1119                        if let ThemeType::System { .. } = cosmic_theme.theme_type {
1120                            cosmic_theme.set_theme(new_theme.theme_type);
1121                            cosmic_theme.transparent = new_blur;
1122                            #[cfg(wayland_platform)]
1123                            {
1124                                use iced_winit::platform_specific::commands::corner_radius::corner_radius;
1125
1126                                let rounded = self.app.core().sync_window_border_radii_to_theme()
1127                                    && !self.app.core().window.sharp_corners;
1128                                let cur_rad = self.app.core().corners(&cosmic_theme, rounded);
1129
1130                                // Update radius for the main window
1131                                let main_window_id = self
1132                                    .app
1133                                    .core()
1134                                    .main_window_id()
1135                                    .unwrap_or(window::Id::RESERVED);
1136                                let mut cmds =
1137                                    vec![corner_radius(main_window_id, cur_rad).discard()];
1138                                // Update radius for each tracked view with the window surface type
1139                                for (id, (_, surface_type, live_settings, _)) in &self.surface_views
1140                                {
1141                                    let overriden = live_settings(&self.app);
1142                                    let cur_rad = if let Some(c) = overriden.corners {
1143                                        Some(c)
1144                                    } else {
1145                                        corners(
1146                                            *surface_type,
1147                                            rounded,
1148                                            &cosmic_theme,
1149                                            self.app.core().auto_corner_radius,
1150                                        )
1151                                    };
1152                                    if cur_rad.is_none() {
1153                                        continue;
1154                                    }
1155                                    cmds.push(corner_radius(*id, cur_rad).discard());
1156                                }
1157
1158                                let core = self.app.core();
1159                                let blur = if cosmic_theme.transparent {
1160                                    iced::window::enable_blur
1161                                } else {
1162                                    iced::window::disable_blur
1163                                };
1164
1165                                if core.blur(&cosmic_theme, None) {
1166                                    cmds.push(blur(
1167                                        self.app
1168                                            .core()
1169                                            .main_window_id()
1170                                            .unwrap_or(window::Id::RESERVED),
1171                                    ));
1172                                }
1173                                for (id, wrapper, ..) in &self.surface_views {
1174                                    let overriden = wrapper.2(&self.app);
1175                                    if core.blur(&cosmic_theme, Some(wrapper.1))
1176                                        && overriden.blur.unwrap_or(true)
1177                                    {
1178                                        cmds.push(blur(*id));
1179                                    } else if overriden.blur.is_some_and(|b| !b) {
1180                                        cmds.push(iced::window::disable_blur(*id));
1181                                    }
1182                                }
1183
1184                                return Task::batch(cmds);
1185                            }
1186                        }
1187                    }
1188                }
1189                return Task::batch(cmds);
1190            }
1191            Action::Activate(_token) => {
1192                if let Some(id) = self.app.core().main_window_id() {
1193                    // Unminimize window before requesting to activate it.
1194                    let mut task = iced_runtime::window::minimize(id, false);
1195
1196                    #[cfg(wayland_platform)]
1197                    {
1198                        task = task.chain(
1199                            iced_winit::platform_specific::commands::activation::activate(
1200                                id,
1201                                #[allow(clippy::used_underscore_binding)]
1202                                _token,
1203                            ),
1204                        );
1205                    }
1206
1207                    #[cfg(not(wayland_platform))]
1208                    {
1209                        task = task.chain(iced_runtime::window::gain_focus(id));
1210                    }
1211
1212                    return task;
1213                }
1214            }
1215
1216            Action::Surface(action) => return self.surface_update(action),
1217
1218            Action::SurfaceClosed(id) => {
1219                if self.opened_surfaces.get_mut(&id).is_some_and(|v| {
1220                    *v = v.saturating_sub(1);
1221                    *v == 0
1222                }) {
1223                    self.opened_surfaces.remove(&id);
1224                    self.surface_views.remove(&id);
1225                }
1226                self.surface_views.shrink_to(self.surface_views.len() * 2);
1227
1228                let mut ret = if let Some(msg) = self.app.on_close_requested(id) {
1229                    self.app.update(msg)
1230                } else {
1231                    Task::none()
1232                };
1233                let core = self.app.core();
1234                if core.exit_on_main_window_closed
1235                    && core.main_window_id().is_some_and(|m_id| id == m_id)
1236                {
1237                    ret = Task::batch([iced::exit::<crate::Action<T::Message>>()]);
1238                }
1239                return ret;
1240            }
1241
1242            Action::ShowWindowMenu => {
1243                if let Some(id) = self.app.core().main_window_id() {
1244                    return iced::window::show_system_menu(id);
1245                }
1246            }
1247
1248            #[cfg(feature = "single-instance")]
1249            Action::DbusConnection(conn) => {
1250                return self.app.dbus_connection(conn);
1251            }
1252
1253            #[cfg(xdg_portal)]
1254            Action::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => {
1255                use ashpd::desktop::settings::ColorScheme;
1256                if match THEME.lock().unwrap().theme_type {
1257                    ThemeType::System {
1258                        theme: _,
1259                        prefer_dark,
1260                    } => prefer_dark.is_some(),
1261                    _ => false,
1262                } {
1263                    return iced::Task::none();
1264                }
1265                let is_dark = match s {
1266                    ColorScheme::NoPreference => None,
1267                    ColorScheme::PreferDark => Some(true),
1268                    ColorScheme::PreferLight => Some(false),
1269                };
1270                let core = self.app.core_mut();
1271
1272                core.portal_is_dark = is_dark;
1273                let is_dark = core.system_is_dark();
1274                let changed = core.system_theme_mode.is_dark != is_dark
1275                    || core.portal_is_dark != Some(is_dark)
1276                    || core.system_theme.cosmic().is_dark != is_dark;
1277
1278                if changed {
1279                    core.theme_sub_counter += 1;
1280                    let mut new_theme = if is_dark {
1281                        crate::theme::system_dark()
1282                    } else {
1283                        crate::theme::system_light()
1284                    };
1285                    if let ThemeType::System { .. } = new_theme.theme_type {
1286                        let new_blur = self.blur_enabled && {
1287                            let t = new_theme.cosmic();
1288                            match core.app_type() {
1289                                crate::core::AppType::Window => t.frosted_windows,
1290                                crate::core::AppType::System => t.frosted_system_interface,
1291                                crate::core::AppType::Applet => t.frosted_applets,
1292                            }
1293                        };
1294                        new_theme.transparent = new_blur;
1295                    }
1296                    core.system_theme = new_theme.clone();
1297                    let core = self.app.core();
1298                    {
1299                        let mut cosmic_theme = THEME.lock().unwrap();
1300
1301                        // Only apply update if the theme is set to load a system theme
1302                        if let ThemeType::System { theme: _, .. } = cosmic_theme.theme_type {
1303                            let mut cmds = Vec::with_capacity(1);
1304                            #[cfg(wayland_platform)]
1305                            {
1306                                let blur = if cosmic_theme.transparent {
1307                                    iced::window::enable_blur
1308                                } else {
1309                                    iced::window::disable_blur
1310                                };
1311
1312                                if core.blur(&cosmic_theme, None) {
1313                                    cmds.push(blur(
1314                                        core.main_window_id().unwrap_or(window::Id::RESERVED),
1315                                    ));
1316                                }
1317
1318                                for (id, wrapper, ..) in &self.surface_views {
1319                                    let overriden = wrapper.2(&self.app);
1320                                    if core.blur(&cosmic_theme, Some(wrapper.1))
1321                                        && overriden.blur.unwrap_or(true)
1322                                    {
1323                                        cmds.push(blur(*id));
1324                                    } else if overriden.blur.is_some_and(|b| !b) {
1325                                        cmds.push(iced::window::disable_blur(*id));
1326                                    }
1327                                }
1328                            }
1329                            cosmic_theme.set_theme(new_theme.theme_type);
1330                            return Task::batch(cmds);
1331                        }
1332                    }
1333                }
1334            }
1335            #[cfg(xdg_portal)]
1336            Action::DesktopSettings(crate::theme::portal::Desktop::Accent(c)) => {
1337                use palette::Srgba;
1338                let c = Srgba::new(c.red() as f32, c.green() as f32, c.blue() as f32, 1.0);
1339                let core = self.app.core_mut();
1340                core.portal_accent = Some(c);
1341                let cur_accent = core.system_theme.cosmic().accent_color();
1342
1343                if cur_accent.distance_squared(*c) < 0.00001 {
1344                    // skip calculations if we already have the same color
1345                    return iced::Task::none();
1346                }
1347
1348                {
1349                    let mut cosmic_theme = THEME.lock().unwrap();
1350
1351                    // Only apply update if the theme is set to load a system theme
1352                    if let ThemeType::System {
1353                        theme: t,
1354                        prefer_dark,
1355                    } = cosmic_theme.theme_type.clone()
1356                    {
1357                        cosmic_theme.set_theme(ThemeType::System {
1358                            theme: Arc::new(t.with_accent(c)),
1359                            prefer_dark,
1360                        });
1361                    }
1362                }
1363            }
1364            #[cfg(xdg_portal)]
1365            Action::DesktopSettings(crate::theme::portal::Desktop::Contrast(_)) => {
1366                // TODO when high contrast is integrated in settings and all custom themes
1367            }
1368
1369            Action::ToolkitConfig(config) => {
1370                // Change the icon theme if not defined by the application.
1371                if !self.app.core().icon_theme_override
1372                    && crate::icon_theme::default() != config.icon_theme
1373                {
1374                    crate::icon_theme::set_default(config.icon_theme.clone());
1375                }
1376
1377                *crate::config::COSMIC_TK.write().unwrap() = config;
1378            }
1379
1380            Action::Focus(f) => {
1381                #[cfg(wayland_platform)]
1382                if let Some((
1383                    parent,
1384                    SurfaceIdWrapper::Subsurface(_) | SurfaceIdWrapper::Popup(_),
1385                    _,
1386                    _,
1387                )) = self.surface_views.get(&f)
1388                {
1389                    // If the parent is already focused, push the new focus
1390                    // to the end of the focus chain.
1391                    if parent.is_some_and(|p| self.app.core().focused_window.last() == Some(&p)) {
1392                        self.app.core_mut().focused_window.push(f);
1393                        return iced::Task::none();
1394                    } else {
1395                        // set the whole parent chain to the focus chain
1396                        let mut parent_chain = vec![f];
1397                        let mut cur = *parent;
1398                        while let Some(p) = cur {
1399                            parent_chain.push(p);
1400                            cur = self
1401                                .surface_views
1402                                .get(&p)
1403                                .and_then(|(parent, _, _, _)| *parent);
1404                        }
1405                        parent_chain.reverse();
1406                        self.app.core_mut().focused_window = parent_chain;
1407                        return iced::Task::none();
1408                    }
1409                }
1410                self.app.core_mut().focused_window = vec![f];
1411            }
1412
1413            Action::Unfocus(id) => {
1414                let core = self.app.core_mut();
1415                if core.focused_window().as_ref().is_some_and(|cur| *cur == id) {
1416                    core.focused_window.pop();
1417                }
1418            }
1419            #[cfg(feature = "applet")]
1420            Action::SuggestedBounds(b) => {
1421                tracing::info!("Suggested bounds: {b:?}");
1422                let core = self.app.core_mut();
1423                core.applet.suggested_bounds = b;
1424            }
1425            Action::Opened(id) => {
1426                #[cfg(wayland_platform)]
1427                {
1428                    use iced_winit::platform_specific::commands::corner_radius::corner_radius;
1429
1430                    let mut theme = THEME.lock().unwrap();
1431
1432                    // TODO do we need per window sharp corners?
1433                    let rounded = (!self.app.core().window.sharp_corners
1434                        && self.app.core().sync_window_border_radii_to_theme())
1435                        || self
1436                            .surface_views
1437                            .get(&id)
1438                            .is_some_and(|(_, surface_type, _, _)| {
1439                                matches!(
1440                                    surface_type,
1441                                    SurfaceIdWrapper::Popup(_) | SurfaceIdWrapper::LayerSurface(_)
1442                                )
1443                            });
1444                    let new_blur = self.blur_enabled && {
1445                        let t = theme.cosmic();
1446                        match self.app.core().app_type() {
1447                            crate::core::AppType::Window => t.frosted_windows,
1448                            crate::core::AppType::System => t.frosted_system_interface,
1449                            crate::core::AppType::Applet => t.frosted_applets,
1450                        }
1451                    };
1452
1453                    let wrapper = self.surface_views.get(&id).map(|s| s.1);
1454
1455                    // this will blur untracked windows as if they were the main window
1456                    let blur_cmd = if self.app.core().blur(&theme, wrapper)
1457                        && self
1458                            .surface_views
1459                            .get(&id)
1460                            .and_then(|s| s.2(&self.app).blur)
1461                            .unwrap_or(true)
1462                    {
1463                        let blur = if new_blur {
1464                            iced::window::enable_blur
1465                        } else {
1466                            iced::window::disable_blur
1467                        };
1468                        let mut cmds = Vec::with_capacity(1 + self.surface_views.len());
1469                        cmds.push(blur(id));
1470
1471                        Task::batch(cmds)
1472                    } else {
1473                        Task::none()
1474                    };
1475
1476                    let corner_task = if let Some((_, cur_rad)) =
1477                        self.surface_views.get(&id).map(|s| (s.1, &s.2)).and_then(
1478                            |(s, overriden)| {
1479                                let overriden = overriden(&self.app);
1480                                if let Some(c) = overriden.corners {
1481                                    Some((s, c))
1482                                } else {
1483                                    corners(s, rounded, &theme, self.app.core().auto_corner_radius)
1484                                        .map(|c| (s, c))
1485                                }
1486                            },
1487                        ) {
1488                        corner_radius(id, Some(cur_rad)).discard()
1489                    } else if id
1490                        == self
1491                            .app
1492                            .core()
1493                            .main_window_id()
1494                            .unwrap_or(window::Id::RESERVED)
1495                    {
1496                        corner_radius(id, self.app.core().corners(&theme, rounded)).discard()
1497                    } else {
1498                        Task::none()
1499                    };
1500
1501                    return Task::batch([
1502                        blur_cmd,
1503                        corner_task,
1504                        iced_runtime::window::run_with_handle(id, init_windowing_system),
1505                    ]);
1506                }
1507                return iced_runtime::window::run_with_handle(id, init_windowing_system);
1508            }
1509            #[cfg(wayland_platform)]
1510            Action::BlurEnabled => {
1511                // TODO do this after blur event confirms support instead of for all wayland windows
1512                self.blur_enabled = true;
1513                let mut t = THEME.lock().unwrap();
1514
1515                let new_blur = self.blur_enabled && {
1516                    let t = t.cosmic();
1517                    match self.app.core().app_type() {
1518                        crate::core::AppType::Window => t.frosted_windows,
1519                        crate::core::AppType::System => t.frosted_system_interface,
1520                        crate::core::AppType::Applet => t.frosted_applets,
1521                    }
1522                };
1523
1524                t.transparent = new_blur;
1525
1526                self.app.core_mut().system_theme.transparent = new_blur;
1527                {
1528                    let blur = if new_blur {
1529                        iced::window::enable_blur
1530                    } else {
1531                        iced::window::disable_blur
1532                    };
1533                    let mut cmds = Vec::with_capacity(1 + self.surface_views.len());
1534                    if self.app.core().blur(&t, None) {
1535                        cmds.push(blur(
1536                            self.app
1537                                .core()
1538                                .main_window_id()
1539                                .unwrap_or(window::Id::RESERVED),
1540                        ));
1541                    }
1542                    for (id, wrapper, ..) in &self.surface_views {
1543                        let overriden = wrapper.2(&self.app);
1544                        if self.app.core().blur(&t, Some(wrapper.1))
1545                            && overriden.blur.unwrap_or(true)
1546                        {
1547                            cmds.push(blur(*id));
1548                        } else if overriden.blur.is_some_and(|b| !b) {
1549                            cmds.push(iced::window::disable_blur(*id));
1550                        }
1551                    }
1552                    return Task::batch(cmds);
1553                }
1554            }
1555            _ => (),
1556        }
1557
1558        iced::Task::none()
1559    }
1560}
1561
1562impl<App: Application> Cosmic<App> {
1563    pub fn new(app: App) -> Self {
1564        Self {
1565            app,
1566            surface_views: HashMap::new(),
1567            opened_surfaces: HashMap::new(),
1568            blur_enabled: false,
1569        }
1570    }
1571
1572    #[cfg(feature = "surface-message")]
1573    /// Apply live setting overrides for a surface
1574    pub fn apply_live_settings(
1575        &mut self,
1576        id_wrapper: SurfaceIdWrapper,
1577        live_settings: &crate::surface::action::LiveSettings,
1578    ) -> Task<crate::Action<App::Message>> {
1579        let id = id_wrapper.inner();
1580
1581        let mut cmds = Vec::with_capacity(2);
1582        let t = THEME.try_lock().unwrap();
1583
1584        if let Some(blur) = live_settings.blur {
1585            let blur_cmd = if blur {
1586                if matches!(id_wrapper, SurfaceIdWrapper::Window(_)) {
1587                    window::enable_blur(id)
1588                } else {
1589                    #[cfg(wayland_platform)]
1590                    {
1591                        iced_winit::commands::blur::blur(
1592                            id,
1593                            Some(vec![iced::Rectangle::new(
1594                                iced::Point::ORIGIN,
1595                                iced::Size::INFINITE,
1596                            )]),
1597                        )
1598                        .discard()
1599                    }
1600                    #[cfg(not(wayland_platform))]
1601                    {
1602                        iced::window::enable_blur(id)
1603                    }
1604                }
1605            } else if matches!(id_wrapper, SurfaceIdWrapper::Window(_)) {
1606                window::disable_blur(id)
1607            } else {
1608                #[cfg(wayland_platform)]
1609                {
1610                    iced_winit::commands::blur::blur(id, None).discard()
1611                }
1612                #[cfg(not(wayland_platform))]
1613                {
1614                    iced::window::disable_blur(id)
1615                }
1616            };
1617            cmds.push(blur_cmd);
1618        } else if self.app.core().blur(&t, Some(id_wrapper)) {
1619            cmds.push(if matches!(id_wrapper, SurfaceIdWrapper::Window(_)) {
1620                window::enable_blur(id)
1621            } else {
1622                #[cfg(wayland_platform)]
1623                {
1624                    iced_winit::commands::blur::blur(
1625                        id,
1626                        Some(vec![iced::Rectangle::new(
1627                            iced::Point::ORIGIN,
1628                            iced::Size::INFINITE,
1629                        )]),
1630                    )
1631                    .discard()
1632                }
1633                #[cfg(not(wayland_platform))]
1634                {
1635                    iced::window::enable_blur(id)
1636                }
1637            });
1638        }
1639        #[cfg(wayland_platform)]
1640        if let Some(corners) = live_settings.corners {
1641            cmds.push(
1642                iced_winit::commands::corner_radius::corner_radius(id, Some(corners)).discard(),
1643            );
1644        } else {
1645            let rounded = !self.app.core().window.sharp_corners
1646                && self.app.core().sync_window_border_radii_to_theme();
1647            if let Some(cur_rad) =
1648                corners(id_wrapper, rounded, &t, self.app.core().auto_corner_radius)
1649            {
1650                cmds.push(
1651                    iced_winit::commands::corner_radius::corner_radius(id, Some(cur_rad)).discard(),
1652                );
1653            }
1654        }
1655        #[cfg(wayland_platform)]
1656        if let (SurfaceIdWrapper::LayerSurface(id), Some(padding)) =
1657            (id_wrapper, live_settings.padding)
1658        {
1659            cmds.push(iced_winit::commands::layer_surface::set_padding(
1660                id, padding,
1661            ));
1662        }
1663        Task::batch(cmds)
1664    }
1665
1666    #[cfg(wayland_platform)]
1667    /// Create a subsurface
1668    pub fn get_subsurface(
1669        &mut self,
1670        settings: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings,
1671        view: Option<
1672            Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
1673        >,
1674    ) -> Task<crate::Action<App::Message>> {
1675        use iced_winit::commands::subsurface::get_subsurface;
1676
1677        *self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1;
1678        let live_settings_task = self.apply_live_settings(
1679            SurfaceIdWrapper::Subsurface(settings.id),
1680            &LiveSettings {
1681                blur: None,
1682                corners: None,
1683                padding: None,
1684            },
1685        );
1686        self.surface_views.insert(
1687            settings.id,
1688            (
1689                Some(settings.parent),
1690                SurfaceIdWrapper::Subsurface(settings.id),
1691                Box::new(|_| LiveSettings::default()),
1692                view,
1693            ),
1694        );
1695        Task::batch([live_settings_task, get_subsurface(settings)])
1696    }
1697
1698    #[cfg(wayland_platform)]
1699    /// Create a subsurface
1700    pub fn get_popup(
1701        &mut self,
1702        settings: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
1703        live_settings: Box<dyn for<'a> Fn(&'a App) -> LiveSettings + Send + Sync>,
1704        view: Option<
1705            Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
1706        >,
1707    ) -> Task<crate::Action<App::Message>> {
1708        use iced_winit::commands::popup::get_popup;
1709        *self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1;
1710        let live_settings_task = self.apply_live_settings(
1711            SurfaceIdWrapper::Popup(settings.id),
1712            &live_settings(&self.app),
1713        );
1714        self.surface_views.insert(
1715            settings.id,
1716            (
1717                Some(settings.parent),
1718                SurfaceIdWrapper::Popup(settings.id),
1719                live_settings,
1720                view,
1721            ),
1722        );
1723        live_settings_task.chain(get_popup(settings)).discard()
1724    }
1725
1726    #[cfg(wayland_platform)]
1727    /// Create a window surface
1728    pub fn get_window(
1729        &mut self,
1730        id: iced::window::Id,
1731        settings: iced::window::Settings,
1732        live_settings: Box<dyn for<'a> Fn(&'a App) -> LiveSettings + Send + Sync>,
1733
1734        view: Option<
1735            Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
1736        >,
1737    ) -> Task<crate::Action<App::Message>> {
1738        use iced_winit::SurfaceIdWrapper;
1739        *self.opened_surfaces.entry(id).or_insert(0) += 1;
1740        let live_settings_task =
1741            self.apply_live_settings(SurfaceIdWrapper::Window(id), &live_settings(&self.app));
1742        self.surface_views.insert(
1743            id,
1744            (
1745                None, // TODO parent for window, platform specific option maybe?
1746                SurfaceIdWrapper::Window(id),
1747                live_settings,
1748                view,
1749            ),
1750        );
1751        Task::batch([
1752            iced_runtime::task::oneshot(|channel| {
1753                iced_runtime::Action::Window(iced_runtime::window::Action::Open(
1754                    id, settings, channel,
1755                ))
1756            })
1757            .discard(),
1758            // We don't control window creation in the same way
1759            live_settings_task,
1760        ])
1761    }
1762
1763    #[cfg(wayland_platform)]
1764    pub fn get_layer_shell(
1765        &mut self,
1766        settings: iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings,
1767        live_settings: Box<dyn for<'a> Fn(&'a App) -> LiveSettings + Send + Sync>,
1768        view: Option<
1769            Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync>,
1770        >,
1771    ) -> Task<crate::Action<App::Message>> {
1772        use iced_winit::SurfaceIdWrapper;
1773        use iced_winit::platform_specific::commands::layer_surface::get_layer_surface;
1774        *self.opened_surfaces.entry(settings.id).or_insert(0) += 1;
1775        let live_settings_task = self.apply_live_settings(
1776            SurfaceIdWrapper::LayerSurface(settings.id),
1777            &live_settings(&self.app),
1778        );
1779        self.surface_views.insert(
1780            settings.id,
1781            (
1782                None, // TODO parent for layer shell, platform specific option maybe?
1783                SurfaceIdWrapper::LayerSurface(settings.id),
1784                live_settings,
1785                view,
1786            ),
1787        );
1788        Task::batch([live_settings_task, get_layer_surface(settings)])
1789    }
1790}
1791
1792#[cfg(wayland_platform)]
1793fn corners(
1794    surface_type: SurfaceIdWrapper,
1795    rounded: bool,
1796    theme: &Theme,
1797    auto_corner_radius: BitFlags<Auto>,
1798) -> Option<iced_runtime::platform_specific::wayland::CornerRadius> {
1799    if !match surface_type {
1800        SurfaceIdWrapper::Window(_) => auto_corner_radius.contains(Auto::Window),
1801        SurfaceIdWrapper::LayerSurface(_) => auto_corner_radius.contains(Auto::System),
1802        SurfaceIdWrapper::Popup(_) => auto_corner_radius.contains(Auto::Popup),
1803        _ => false,
1804    } {
1805        return None;
1806    }
1807    let theme = theme.cosmic();
1808    Some(if let SurfaceIdWrapper::Popup(_) = surface_type {
1809        let radius_m = theme.radius_m();
1810        iced_runtime::platform_specific::wayland::CornerRadius {
1811            top_left: radius_m[0].round() as u32,
1812            top_right: radius_m[1].round() as u32,
1813            bottom_right: radius_m[2].round() as u32,
1814            bottom_left: radius_m[3].round() as u32,
1815        }
1816    } else if let SurfaceIdWrapper::Window(_) = surface_type
1817        && !rounded
1818    {
1819        let radius_0 = theme.radius_0();
1820        iced_runtime::platform_specific::wayland::CornerRadius {
1821            top_left: radius_0[0].round() as u32,
1822            top_right: radius_0[1].round() as u32,
1823            bottom_right: radius_0[2].round() as u32,
1824            bottom_left: radius_0[3].round() as u32,
1825        }
1826    } else {
1827        let radius_s = theme.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
1828        iced_runtime::platform_specific::wayland::CornerRadius {
1829            top_left: radius_s[0].round() as u32,
1830            top_right: radius_s[1].round() as u32,
1831            bottom_right: radius_s[2].round() as u32,
1832            bottom_left: radius_s[3].round() as u32,
1833        }
1834    })
1835}