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