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