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