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