1use std::borrow::Borrow;
5use std::collections::HashMap;
6use std::sync::Arc;
7
8use super::{Action, Application, ApplicationExt, Subscription};
9use crate::theme::{THEME, Theme, ThemeType};
10use crate::{Core, Element, keyboard_nav};
11#[cfg(feature = "wayland")]
12use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
13use cosmic_theme::ThemeMode;
14#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
15use iced::Application as IcedApplication;
16#[cfg(feature = "wayland")]
17use iced::event::wayland;
18use iced::{Task, window};
19use iced_futures::event::listen_with;
20use palette::color_difference::EuclideanDistance;
21
22#[derive(Default)]
23pub struct Cosmic<App: Application> {
24 pub app: App,
25 #[cfg(feature = "wayland")]
26 pub surface_views: HashMap<
27 window::Id,
28 Box<dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>>>,
29 >,
30}
31
32impl<T: Application> Cosmic<T>
33where
34 T::Message: Send + 'static,
35{
36 pub fn init(
37 (mut core, flags): (Core, T::Flags),
38 ) -> (Self, iced::Task<crate::Action<T::Message>>) {
39 #[cfg(feature = "dbus-config")]
40 {
41 use iced_futures::futures::executor::block_on;
42 core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok();
43 }
44
45 let (model, command) = T::init(core, flags);
46
47 (Self::new(model), command)
48 }
49
50 #[cfg(not(feature = "multi-window"))]
51 pub fn title(&self) -> String {
52 self.app.title().to_string()
53 }
54
55 #[cfg(feature = "multi-window")]
56 pub fn title(&self, id: window::Id) -> String {
57 self.app.title(id).to_string()
58 }
59
60 pub fn surface_update(
61 &mut self,
62 _surface_message: crate::surface::Action,
63 ) -> iced::Task<crate::Action<T::Message>> {
64 #[cfg(feature = "surface-message")]
65 match _surface_message {
66 #[cfg(feature = "wayland")]
67 crate::surface::Action::AppSubsurface(settings, view) => {
68 let Some(settings) = std::sync::Arc::try_unwrap(settings)
69 .ok()
70 .and_then(|s| s.downcast::<Box<dyn Fn(&mut T) -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else {
71 tracing::error!("Invalid settings for subsurface");
72 return Task::none();
73 };
74
75 if let Some(view) = view.and_then(|view| {
76 match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
77 dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
78 + Send
79 + Sync,
80 >>() {
81 Ok(v) => Some(v),
82 Err(err) => {
83 tracing::error!("Invalid view for subsurface view: {err:?}");
84
85 None
86 }
87 }
88 }) {
89 let settings = settings(&mut self.app);
90 self.get_subsurface(settings, *view)
91 } else {
92 iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app))
93 }
94 }
95 #[cfg(feature = "wayland")]
96 crate::surface::Action::Subsurface(settings, view) => {
97 let Some(settings) = std::sync::Arc::try_unwrap(settings)
98 .ok()
99 .and_then(|s| s.downcast::<Box<dyn Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync>>().ok()) else {
100 tracing::error!("Invalid settings for subsurface");
101 return Task::none();
102 };
103
104 if let Some(view) = view.and_then(|view| {
105 match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
106 dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
107 >>() {
108 Ok(v) => Some(v),
109 Err(err) => {
110 tracing::error!("Invalid view for subsurface view: {err:?}");
111
112 None
113 }
114 }
115 }) {
116 let settings = settings();
117 self.get_subsurface(settings, Box::new(move |_| view()))
118 } else {
119 iced_winit::commands::subsurface::get_subsurface(settings())
120 }
121 }
122 #[cfg(feature = "wayland")]
123 crate::surface::Action::AppPopup(settings, view) => {
124 let Some(settings) = std::sync::Arc::try_unwrap(settings)
125 .ok()
126 .and_then(|s| s.downcast::<Box<dyn Fn(&mut T) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else {
127 tracing::error!("Invalid settings for popup");
128 return Task::none();
129 };
130
131 if let Some(view) = view.and_then(|view| {
132 match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
133 dyn for<'a> Fn(&'a T) -> Element<'a, crate::Action<T::Message>>
134 + Send
135 + Sync,
136 >>() {
137 Ok(v) => Some(v),
138 Err(err) => {
139 tracing::error!("Invalid view for subsurface view: {err:?}");
140 None
141 }
142 }
143 }) {
144 let settings = settings(&mut self.app);
145
146 self.get_popup(settings, *view)
147 } else {
148 iced_winit::commands::popup::get_popup(settings(&mut self.app))
149 }
150 }
151 #[cfg(feature = "wayland")]
152 crate::surface::Action::DestroyPopup(id) => {
153 iced_winit::commands::popup::destroy_popup(id)
154 }
155 #[cfg(feature = "wayland")]
156 crate::surface::Action::DestroySubsurface(id) => {
157 iced_winit::commands::subsurface::destroy_subsurface(id)
158 }
159 crate::surface::Action::ResponsiveMenuBar {
160 menu_bar,
161 limits,
162 size,
163 } => {
164 let core = self.app.core_mut();
165 core.menu_bars.insert(menu_bar, (limits, size));
166 iced::Task::none()
167 }
168 #[cfg(feature = "wayland")]
169 crate::surface::Action::Popup(settings, view) => {
170 let Some(settings) = std::sync::Arc::try_unwrap(settings)
171 .ok()
172 .and_then(|s| s.downcast::<Box<dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings + Send + Sync>>().ok()) else {
173 tracing::error!("Invalid settings for popup");
174 return Task::none();
175 };
176
177 if let Some(view) = view.and_then(|view| {
178 match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
179 dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
180 >>() {
181 Ok(v) => Some(v),
182 Err(err) => {
183 tracing::error!("Invalid view for subsurface view: {err:?}");
184 None
185 }
186 }
187 }) {
188 let settings = settings();
189
190 self.get_popup(settings, Box::new(move |_| view()))
191 } else {
192 iced_winit::commands::popup::get_popup(settings())
193 }
194 }
195 crate::surface::Action::Ignore => iced::Task::none(),
196 crate::surface::Action::Task(f) => {
197 f().map(|sm| crate::Action::Cosmic(Action::Surface(sm)))
198 }
199 _ => iced::Task::none(),
200 }
201
202 #[cfg(not(feature = "surface-message"))]
203 iced::Task::none()
204 }
205
206 pub fn update(
207 &mut self,
208 message: crate::Action<T::Message>,
209 ) -> iced::Task<crate::Action<T::Message>> {
210 let message = match message {
211 crate::Action::App(message) => self.app.update(message),
212 crate::Action::Cosmic(message) => self.cosmic_update(message),
213 crate::Action::None => iced::Task::none(),
214 #[cfg(feature = "single-instance")]
215 crate::Action::DbusActivation(message) => self.app.dbus_activation(message),
216 };
217
218 #[cfg(all(target_env = "gnu", not(target_os = "windows")))]
219 crate::malloc::trim(0);
220
221 message
222 }
223
224 #[cfg(not(feature = "multi-window"))]
225 pub fn scale_factor(&self) -> f64 {
226 f64::from(self.app.core().scale_factor())
227 }
228
229 #[cfg(feature = "multi-window")]
230 pub fn scale_factor(&self, _id: window::Id) -> f64 {
231 f64::from(self.app.core().scale_factor())
232 }
233
234 pub fn style(&self, theme: &Theme) -> iced_runtime::Appearance {
235 if let Some(style) = self.app.style() {
236 style
237 } else if self.app.core().window.sharp_corners {
238 let theme = THEME.lock().unwrap();
239 crate::style::iced::application::appearance(theme.borrow())
240 } else {
241 let theme = THEME.lock().unwrap();
242 iced_runtime::Appearance {
243 background_color: iced_core::Color::TRANSPARENT,
244 icon_color: theme.cosmic().on_bg_color().into(),
245 text_color: theme.cosmic().on_bg_color().into(),
246 }
247 }
248 }
249
250 #[allow(clippy::too_many_lines)]
251 #[cold]
252 pub fn subscription(&self) -> Subscription<crate::Action<T::Message>> {
253 let window_events = listen_with(|event, _, id| {
254 match event {
255 iced::Event::Window(window::Event::Resized(iced::Size { width, height })) => {
256 return Some(Action::WindowResize(id, width, height));
257 }
258 iced::Event::Window(window::Event::Closed) => {
259 return Some(Action::SurfaceClosed(id));
260 }
261 iced::Event::Window(window::Event::Focused) => return Some(Action::Focus(id)),
262 iced::Event::Window(window::Event::Unfocused) => return Some(Action::Unfocus(id)),
263 #[cfg(feature = "wayland")]
264 iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => {
265 match event {
266 wayland::Event::Popup(wayland::PopupEvent::Done, _, id)
267 | wayland::Event::Layer(wayland::LayerEvent::Done, _, id) => {
268 return Some(Action::SurfaceClosed(id));
269 }
270 #[cfg(feature = "applet")]
271 wayland::Event::Window(
272 iced::event::wayland::WindowEvent::SuggestedBounds(b),
273 ) => {
274 return Some(Action::SuggestedBounds(b));
275 }
276 _ => (),
277 }
278 }
279 _ => (),
280 }
281
282 None
283 });
284
285 let mut subscriptions = vec![
286 self.app.subscription().map(crate::Action::App),
287 self.app
288 .core()
289 .watch_config::<crate::config::CosmicTk>(crate::config::ID)
290 .map(|update| {
291 for why in update
292 .errors
293 .into_iter()
294 .filter(cosmic_config::Error::is_err)
295 {
296 tracing::error!(?why, "cosmic toolkit config update error");
297 }
298
299 crate::Action::Cosmic(Action::ToolkitConfig(update.config))
300 }),
301 self.app
302 .core()
303 .watch_config::<cosmic_theme::Theme>(
304 if if let ThemeType::System { prefer_dark, .. } =
305 THEME.lock().unwrap().theme_type
306 {
307 prefer_dark
308 } else {
309 None
310 }
311 .unwrap_or_else(|| self.app.core().system_theme_mode.is_dark)
312 {
313 cosmic_theme::DARK_THEME_ID
314 } else {
315 cosmic_theme::LIGHT_THEME_ID
316 },
317 )
318 .map(|update| {
319 for why in update
320 .errors
321 .into_iter()
322 .filter(cosmic_config::Error::is_err)
323 {
324 tracing::error!(?why, "cosmic theme config update error");
325 }
326 Action::SystemThemeChange(
327 update.keys,
328 crate::theme::Theme::system(Arc::new(update.config)),
329 )
330 })
331 .map(crate::Action::Cosmic),
332 self.app
333 .core()
334 .watch_config::<ThemeMode>(cosmic_theme::THEME_MODE_ID)
335 .map(|update| {
336 for error in update
337 .errors
338 .into_iter()
339 .filter(cosmic_config::Error::is_err)
340 {
341 tracing::error!(?error, "error reading system theme mode update");
342 }
343 Action::SystemThemeModeChange(update.keys, update.config)
344 })
345 .map(crate::Action::Cosmic),
346 window_events.map(crate::Action::Cosmic),
347 #[cfg(feature = "xdg-portal")]
348 crate::theme::portal::desktop_settings()
349 .map(Action::DesktopSettings)
350 .map(crate::Action::Cosmic),
351 ];
352
353 if self.app.core().keyboard_nav {
354 subscriptions.push(
355 keyboard_nav::subscription()
356 .map(Action::KeyboardNav)
357 .map(crate::Action::Cosmic),
358 );
359 }
360
361 #[cfg(feature = "single-instance")]
362 if self.app.core().single_instance {
363 subscriptions.push(crate::dbus_activation::subscription::<T>());
364 }
365
366 Subscription::batch(subscriptions)
367 }
368
369 #[cfg(not(feature = "multi-window"))]
370 pub fn theme(&self) -> Theme {
371 crate::theme::active()
372 }
373
374 #[cfg(feature = "multi-window")]
375 pub fn theme(&self, _id: window::Id) -> Theme {
376 crate::theme::active()
377 }
378
379 #[cfg(feature = "multi-window")]
380 pub fn view(&self, id: window::Id) -> Element<crate::Action<T::Message>> {
381 #[cfg(feature = "wayland")]
382 if let Some(v) = self.surface_views.get(&id) {
383 return v(&self.app);
384 }
385 if !self
386 .app
387 .core()
388 .main_window_id()
389 .is_some_and(|main_id| main_id == id)
390 {
391 return self.app.view_window(id).map(crate::Action::App);
392 }
393
394 let view = if self.app.core().window.use_template {
395 self.app.view_main()
396 } else {
397 self.app.view().map(crate::Action::App)
398 };
399
400 #[cfg(all(target_env = "gnu", not(target_os = "windows")))]
401 crate::malloc::trim(0);
402
403 view
404 }
405
406 #[cfg(not(feature = "multi-window"))]
407 pub fn view(&self) -> Element<crate::Action<T::Message>> {
408 let view = self.app.view_main();
409
410 #[cfg(all(target_env = "gnu", not(target_os = "windows")))]
411 crate::malloc::trim(0);
412
413 view
414 }
415}
416
417impl<T: Application> Cosmic<T> {
418 #[allow(clippy::unused_self)]
419 #[cold]
420 pub fn close(&mut self) -> iced::Task<crate::Action<T::Message>> {
421 if let Some(id) = self.app.core().main_window_id() {
422 iced::window::close(id)
423 } else {
424 iced::Task::none()
425 }
426 }
427
428 #[allow(clippy::too_many_lines)]
429 fn cosmic_update(&mut self, message: Action) -> iced::Task<crate::Action<T::Message>> {
430 match message {
431 Action::WindowMaximized(id, maximized) => {
432 if self
433 .app
434 .core()
435 .main_window_id()
436 .is_some_and(|main_id| main_id == id)
437 {
438 self.app.core_mut().window.sharp_corners = maximized;
439 }
440 }
441
442 Action::WindowResize(id, width, height) => {
443 if self
444 .app
445 .core()
446 .main_window_id()
447 .is_some_and(|main_id| main_id == id)
448 {
449 self.app.core_mut().set_window_width(width);
450 self.app.core_mut().set_window_height(height);
451 }
452
453 self.app.on_window_resize(id, width, height);
454
455 return iced::window::get_maximized(id).map(move |maximized| {
457 crate::Action::Cosmic(Action::WindowMaximized(id, maximized))
458 });
459 }
460
461 #[cfg(feature = "wayland")]
462 Action::WindowState(id, state) => {
463 if self
464 .app
465 .core()
466 .main_window_id()
467 .is_some_and(|main_id| main_id == id)
468 {
469 self.app.core_mut().window.sharp_corners = state.intersects(
470 WindowState::MAXIMIZED
471 | WindowState::FULLSCREEN
472 | WindowState::TILED
473 | WindowState::TILED_RIGHT
474 | WindowState::TILED_LEFT
475 | WindowState::TILED_TOP
476 | WindowState::TILED_BOTTOM,
477 );
478 }
479 }
480
481 #[cfg(feature = "wayland")]
482 Action::WmCapabilities(id, capabilities) => {
483 if self
484 .app
485 .core()
486 .main_window_id()
487 .is_some_and(|main_id| main_id == id)
488 {
489 self.app.core_mut().window.show_maximize =
490 capabilities.contains(WindowManagerCapabilities::MAXIMIZE);
491 self.app.core_mut().window.show_minimize =
492 capabilities.contains(WindowManagerCapabilities::MINIMIZE);
493 self.app.core_mut().window.show_window_menu =
494 capabilities.contains(WindowManagerCapabilities::WINDOW_MENU);
495 }
496 }
497
498 Action::KeyboardNav(message) => match message {
499 keyboard_nav::Action::FocusNext => {
500 return iced::widget::focus_next().map(crate::Action::Cosmic);
501 }
502 keyboard_nav::Action::FocusPrevious => {
503 return iced::widget::focus_previous().map(crate::Action::Cosmic);
504 }
505 keyboard_nav::Action::Escape => return self.app.on_escape(),
506 keyboard_nav::Action::Search => return self.app.on_search(),
507
508 keyboard_nav::Action::Fullscreen => return self.app.core().toggle_maximize(None),
509 },
510
511 Action::ContextDrawer(show) => {
512 self.app.core_mut().set_show_context(show);
513 return self.app.on_context_drawer();
514 }
515
516 Action::Drag => return self.app.core().drag(None),
517
518 Action::Minimize => return self.app.core().minimize(None),
519
520 Action::Maximize => return self.app.core().toggle_maximize(None),
521
522 Action::NavBar(key) => {
523 self.app.core_mut().nav_bar_set_toggled_condensed(false);
524 return self.app.on_nav_select(key);
525 }
526
527 Action::NavBarContext(key) => {
528 self.app.core_mut().nav_bar_set_context(key);
529 return self.app.on_nav_context(key);
530 }
531
532 Action::ToggleNavBar => {
533 self.app.core_mut().nav_bar_toggle();
534 }
535
536 Action::ToggleNavBarCondensed => {
537 self.app.core_mut().nav_bar_toggle_condensed();
538 }
539
540 Action::AppThemeChange(mut theme) => {
541 if let ThemeType::System { theme: _, .. } = theme.theme_type {
542 self.app.core_mut().theme_sub_counter += 1;
543
544 let portal_accent = self.app.core().portal_accent;
545 if let Some(a) = portal_accent {
546 let t_inner = theme.cosmic();
547 if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
548 theme = Theme::system(Arc::new(t_inner.with_accent(a)));
549 }
550 };
551 }
552
553 THEME.lock().unwrap().set_theme(theme.theme_type);
554 }
555
556 Action::SystemThemeChange(keys, theme) => {
557 let cur_is_dark = THEME.lock().unwrap().theme_type.is_dark();
558 if cur_is_dark != theme.cosmic().is_dark {
560 return iced::Task::none();
561 }
562 let cmd = self.app.system_theme_update(&keys, theme.cosmic());
563 self.app.core_mut().system_theme = theme.clone();
565 let portal_accent = self.app.core().portal_accent;
566 {
567 let mut cosmic_theme = THEME.lock().unwrap();
568
569 if let ThemeType::System {
571 theme: _,
572 prefer_dark,
573 } = cosmic_theme.theme_type
574 {
575 let mut new_theme = if let Some(a) = portal_accent {
576 let t_inner = theme.cosmic();
577 if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
578 Theme::system(Arc::new(t_inner.with_accent(a)))
579 } else {
580 theme
581 }
582 } else {
583 theme
584 };
585 new_theme.theme_type.prefer_dark(prefer_dark);
586
587 cosmic_theme.set_theme(new_theme.theme_type);
588 }
589 }
590
591 return cmd;
592 }
593
594 Action::ScaleFactor(factor) => {
595 self.app.core_mut().set_scale_factor(factor);
596 }
597
598 Action::Close => {
599 return match self.app.on_app_exit() {
600 Some(message) => self.app.update(message),
601 None => self.close(),
602 };
603 }
604 Action::SystemThemeModeChange(keys, mode) => {
605 if match THEME.lock().unwrap().theme_type {
606 ThemeType::System {
607 theme: _,
608 prefer_dark,
609 } => prefer_dark.is_some(),
610 _ => false,
611 } {
612 return iced::Task::none();
613 }
614 let mut cmds = vec![self.app.system_theme_mode_update(&keys, &mode)];
615
616 let core = self.app.core_mut();
617 let prev_is_dark = core.system_is_dark();
618 core.system_theme_mode = mode;
619 let is_dark = core.system_is_dark();
620 let changed = prev_is_dark != is_dark;
621 if changed {
622 core.theme_sub_counter += 1;
623 let mut new_theme = if is_dark {
624 crate::theme::system_dark()
625 } else {
626 crate::theme::system_light()
627 };
628 cmds.push(self.app.system_theme_update(&[], new_theme.cosmic()));
629
630 let core = self.app.core_mut();
631 new_theme = if let Some(a) = core.portal_accent {
632 let t_inner = new_theme.cosmic();
633 if a.distance_squared(*t_inner.accent_color()) > 0.00001 {
634 Theme::system(Arc::new(t_inner.with_accent(a)))
635 } else {
636 new_theme
637 }
638 } else {
639 new_theme
640 };
641
642 core.system_theme = new_theme.clone();
643 {
644 let mut cosmic_theme = THEME.lock().unwrap();
645 if let ThemeType::System { theme: _, .. } = cosmic_theme.theme_type {
647 cosmic_theme.set_theme(new_theme.theme_type);
648 }
649 }
650 }
651 return Task::batch(cmds);
652 }
653 Action::Activate(_token) =>
654 {
655 #[cfg(feature = "wayland")]
656 if let Some(id) = self.app.core().main_window_id() {
657 return iced_winit::platform_specific::commands::activation::activate(
658 id,
659 #[allow(clippy::used_underscore_binding)]
660 _token,
661 );
662 }
663 }
664
665 Action::Surface(action) => return self.surface_update(action),
666
667 Action::SurfaceClosed(id) => {
668 let mut ret = if let Some(msg) = self.app.on_close_requested(id) {
669 self.app.update(msg)
670 } else {
671 Task::none()
672 };
673 let core = self.app.core();
674 if core.exit_on_main_window_closed
675 && core.main_window_id().is_some_and(|m_id| id == m_id)
676 {
677 ret = Task::batch(vec![iced::exit::<crate::Action<T::Message>>()]);
678 }
679 return ret;
680 }
681
682 Action::ShowWindowMenu => {
683 if let Some(id) = self.app.core().main_window_id() {
684 return iced::window::show_system_menu(id);
685 }
686 }
687
688 #[cfg(feature = "xdg-portal")]
689 Action::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => {
690 use ashpd::desktop::settings::ColorScheme;
691 if match THEME.lock().unwrap().theme_type {
692 ThemeType::System {
693 theme: _,
694 prefer_dark,
695 } => prefer_dark.is_some(),
696 _ => false,
697 } {
698 return iced::Task::none();
699 }
700 let is_dark = match s {
701 ColorScheme::NoPreference => None,
702 ColorScheme::PreferDark => Some(true),
703 ColorScheme::PreferLight => Some(false),
704 };
705 let core = self.app.core_mut();
706 let prev_is_dark = core.system_is_dark();
707 core.portal_is_dark = is_dark;
708 let is_dark = core.system_is_dark();
709 let changed = prev_is_dark != is_dark;
710 if changed {
711 core.theme_sub_counter += 1;
712 let new_theme = if is_dark {
713 crate::theme::system_dark()
714 } else {
715 crate::theme::system_light()
716 };
717 core.system_theme = new_theme.clone();
718 {
719 let mut cosmic_theme = THEME.lock().unwrap();
720
721 if let ThemeType::System { theme: _, .. } = cosmic_theme.theme_type {
723 cosmic_theme.set_theme(new_theme.theme_type);
724 }
725 }
726 }
727 }
728 #[cfg(feature = "xdg-portal")]
729 Action::DesktopSettings(crate::theme::portal::Desktop::Accent(c)) => {
730 use palette::Srgba;
731 let c = Srgba::new(c.red() as f32, c.green() as f32, c.blue() as f32, 1.0);
732 let core = self.app.core_mut();
733 core.portal_accent = Some(c);
734 let cur_accent = core.system_theme.cosmic().accent_color();
735
736 if cur_accent.distance_squared(*c) < 0.00001 {
737 return iced::Task::none();
739 }
740
741 {
742 let mut cosmic_theme = THEME.lock().unwrap();
743
744 if let ThemeType::System {
746 theme: t,
747 prefer_dark,
748 } = cosmic_theme.theme_type.clone()
749 {
750 cosmic_theme.set_theme(ThemeType::System {
751 theme: Arc::new(t.with_accent(c)),
752 prefer_dark,
753 });
754 }
755 }
756 }
757 #[cfg(feature = "xdg-portal")]
758 Action::DesktopSettings(crate::theme::portal::Desktop::Contrast(_)) => {
759 }
761
762 Action::ToolkitConfig(config) => {
763 if !self.app.core().icon_theme_override
765 && crate::icon_theme::default() != config.icon_theme
766 {
767 crate::icon_theme::set_default(config.icon_theme.clone());
768 }
769
770 *crate::config::COSMIC_TK.write().unwrap() = config;
771 }
772
773 Action::Focus(f) => {
774 self.app.core_mut().focused_window = Some(f);
775 }
776
777 Action::Unfocus(id) => {
778 let core = self.app.core_mut();
779 if core.focused_window.as_ref().is_some_and(|cur| *cur == id) {
780 core.focused_window = None;
781 }
782 }
783 #[cfg(feature = "applet")]
784 Action::SuggestedBounds(b) => {
785 tracing::info!("Suggested bounds: {b:?}");
786 let core = self.app.core_mut();
787 core.applet.suggested_bounds = b;
788 }
789 _ => {}
790 }
791
792 iced::Task::none()
793 }
794}
795
796impl<App: Application> Cosmic<App> {
797 pub fn new(app: App) -> Self {
798 Self {
799 app,
800 #[cfg(feature = "wayland")]
801 surface_views: HashMap::new(),
802 }
803 }
804
805 #[cfg(feature = "wayland")]
806 pub fn get_subsurface(
808 &mut self,
809 settings: iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings,
810 view: Box<
811 dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync,
812 >,
813 ) -> Task<crate::Action<App::Message>> {
814 use iced_winit::commands::subsurface::get_subsurface;
815
816 self.surface_views.insert(settings.id, view);
817 get_subsurface(settings)
818 }
819
820 #[cfg(feature = "wayland")]
821 pub fn get_popup(
823 &mut self,
824 settings: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
825 view: Box<
826 dyn for<'a> Fn(&'a App) -> Element<'a, crate::Action<App::Message>> + Send + Sync,
827 >,
828 ) -> Task<crate::Action<App::Message>> {
829 use iced_winit::commands::popup::get_popup;
830
831 self.surface_views.insert(settings.id, view);
832 get_popup(settings)
833 }
834}