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