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