1#![doc = include_str!("README.md")]
6#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
7#![warn(missing_docs)]
8#![cfg_attr(slint_nightly_test, feature(non_exhaustive_omitted_patterns_lint))]
9#![cfg_attr(slint_nightly_test, warn(non_exhaustive_omitted_patterns))]
10
11extern crate alloc;
12
13use event_loop::{CustomEvent, EventLoopState};
14use i_slint_core::api::EventLoopError;
15use i_slint_core::graphics::RequestedGraphicsAPI;
16use i_slint_core::platform::{EventLoopProxy, PlatformError};
17use i_slint_core::window::WindowAdapter;
18use renderer::WinitCompatibleRenderer;
19use std::cell::RefCell;
20use std::collections::HashMap;
21use std::rc::Rc;
22use std::rc::Weak;
23use std::sync::Arc;
24use std::sync::atomic::AtomicUsize;
25use winit::event_loop::ActiveEventLoop;
26
27#[cfg(not(target_arch = "wasm32"))]
28mod clipboard;
29mod drag_resize_window;
30mod winit_compat;
31mod winitwindowadapter;
32use winitwindowadapter::*;
33pub(crate) mod event_loop;
34mod frame_throttle;
35#[cfg(target_os = "ios")]
36mod ios;
37
38pub use winit;
40
41#[non_exhaustive]
45#[derive(Debug)]
46pub struct SlintEvent(CustomEvent);
47
48#[i_slint_core_macros::slint_doc]
49pub type EventLoopBuilder = winit::event_loop::EventLoopBuilder<SlintEvent>;
54
55pub enum EventResult {
58 Propagate,
60 PreventDefault,
62}
63
64mod renderer {
65 use std::sync::Arc;
66
67 use i_slint_core::platform::PlatformError;
68 use i_slint_core::renderer::DrawOutcome;
69 use winit::event_loop::ActiveEventLoop;
70
71 pub trait WinitCompatibleRenderer: std::any::Any {
72 fn render(&self, window: &i_slint_core::api::Window) -> Result<DrawOutcome, PlatformError>;
73
74 fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
75 fn occluded(&self, _: bool) {}
77
78 fn suspend(&self) -> Result<(), PlatformError>;
79
80 fn resume(
82 &self,
83 active_event_loop: &ActiveEventLoop,
84 window_attributes: winit::window::WindowAttributes,
85 ) -> Result<Arc<winit::window::Window>, PlatformError>;
86 }
87
88 #[cfg(enable_femtovg_renderer)]
89 pub(crate) mod femtovg;
90 #[cfg(enable_skia_renderer)]
91 pub(crate) mod skia;
92
93 #[cfg(feature = "renderer-software")]
94 pub(crate) mod sw;
95}
96
97#[cfg(enable_accesskit)]
98mod accesskit;
99#[cfg(muda)]
100mod muda;
101#[cfg(xdg_desktop_settings)]
102mod xdg_desktop_settings;
103
104#[cfg(target_arch = "wasm32")]
105pub(crate) mod wasm_input_helper;
106
107cfg_if::cfg_if! {
108 if #[cfg(enable_femtovg_renderer)] {
109 const DEFAULT_RENDERER_NAME: &str = "FemtoVG";
110 } else if #[cfg(enable_skia_renderer)] {
111 const DEFAULT_RENDERER_NAME: &str = "Skia";
112 } else if #[cfg(feature = "renderer-software")] {
113 const DEFAULT_RENDERER_NAME: &str = "Software";
114 } else {
115 compile_error!("Please select a feature to build with the winit backend: `renderer-femtovg`, `renderer-skia`, `renderer-skia-opengl`, `renderer-skia-vulkan` or `renderer-software`");
116 }
117}
118
119fn default_renderer_factory(
120 shared_backend_data: &Rc<SharedBackendData>,
121) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError> {
122 cfg_if::cfg_if! {
123 if #[cfg(enable_skia_renderer)] {
124 renderer::skia::WinitSkiaRenderer::new_suspended(shared_backend_data)
125 } else if #[cfg(feature = "renderer-femtovg-wgpu")] {
126 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended(shared_backend_data)
127 } else if #[cfg(all(feature = "renderer-femtovg", supports_opengl))] {
128 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(shared_backend_data)
129 } else if #[cfg(feature = "renderer-software")] {
130 renderer::sw::WinitSoftwareRenderer::new_suspended(shared_backend_data)
131 } else {
132 compile_error!("Please select a feature to build with the winit backend: `renderer-femtovg`, `renderer-skia`, `renderer-skia-opengl`, `renderer-skia-vulkan` or `renderer-software`");
133 }
134 }
135}
136
137fn try_create_window_with_fallback_renderer(
138 shared_backend_data: &Rc<SharedBackendData>,
139 attrs: winit::window::WindowAttributes,
140 _proxy: &winit::event_loop::EventLoopProxy<SlintEvent>,
141 #[cfg(all(muda, target_os = "macos"))] muda_enable_default_menu_bar: bool,
142) -> Option<Rc<WinitWindowAdapter>> {
143 [
144 #[cfg(any(
145 feature = "renderer-skia",
146 feature = "renderer-skia-opengl",
147 feature = "renderer-skia-vulkan"
148 ))]
149 renderer::skia::WinitSkiaRenderer::new_suspended,
150 #[cfg(feature = "renderer-femtovg-wgpu")]
151 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended,
152 #[cfg(all(
153 feature = "renderer-femtovg",
154 supports_opengl,
155 not(feature = "renderer-femtovg-wgpu")
156 ))]
157 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended,
158 #[cfg(feature = "renderer-software")]
159 renderer::sw::WinitSoftwareRenderer::new_suspended,
160 ]
161 .into_iter()
162 .find_map(|renderer_factory| {
163 Some(WinitWindowAdapter::new(
164 shared_backend_data.clone(),
165 renderer_factory(shared_backend_data).ok()?,
166 attrs.clone(),
167 #[cfg(any(enable_accesskit, muda))]
168 _proxy.clone(),
169 #[cfg(all(muda, target_os = "macos"))]
170 muda_enable_default_menu_bar,
171 ))
172 })
173}
174
175#[doc(hidden)]
176pub type NativeWidgets = ();
177#[doc(hidden)]
178pub type NativeGlobals = ();
179#[doc(hidden)]
180pub const HAS_NATIVE_STYLE: bool = false;
181#[doc(hidden)]
182pub mod native_widgets {}
183
184#[allow(unused_variables)]
192pub trait CustomApplicationHandler {
193 fn resumed(&mut self, _event_loop: &ActiveEventLoop) -> EventResult {
195 EventResult::Propagate
196 }
197
198 fn window_event(
200 &mut self,
201 event_loop: &ActiveEventLoop,
202 window_id: winit::window::WindowId,
203 winit_window: Option<&winit::window::Window>,
204 slint_window: Option<&i_slint_core::api::Window>,
205 event: &winit::event::WindowEvent,
206 ) -> EventResult {
207 EventResult::Propagate
208 }
209
210 fn new_events(
212 &mut self,
213 event_loop: &ActiveEventLoop,
214 cause: winit::event::StartCause,
215 ) -> EventResult {
216 EventResult::Propagate
217 }
218
219 fn device_event(
221 &mut self,
222 event_loop: &ActiveEventLoop,
223 device_id: winit::event::DeviceId,
224 event: winit::event::DeviceEvent,
225 ) -> EventResult {
226 EventResult::Propagate
227 }
228
229 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
231 EventResult::Propagate
232 }
233
234 fn suspended(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
236 EventResult::Propagate
237 }
238
239 fn exiting(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
241 EventResult::Propagate
242 }
243
244 fn memory_warning(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
246 EventResult::Propagate
247 }
248}
249
250pub struct BackendBuilder {
254 allow_fallback: bool,
256 requested_graphics_api: Option<RequestedGraphicsAPI>,
257 window_attributes_hook:
258 Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
259 renderer_name: Option<String>,
260 event_loop_builder: Option<EventLoopBuilder>,
261 #[cfg(all(muda, target_os = "macos"))]
262 muda_enable_default_menu_bar_bar: bool,
263 #[cfg(target_family = "wasm")]
264 spawn_event_loop: bool,
265 custom_application_handler: Option<Box<dyn CustomApplicationHandler>>,
266}
267
268impl BackendBuilder {
269 #[must_use]
271 pub fn request_graphics_api(mut self, graphics_api: RequestedGraphicsAPI) -> Self {
272 self.requested_graphics_api = Some(graphics_api);
273 self
274 }
275
276 #[must_use]
279 pub fn with_renderer_name(mut self, name: impl Into<String>) -> Self {
280 self.renderer_name = Some(name.into());
281 self
282 }
283
284 #[must_use]
298 pub fn with_window_attributes_hook(
299 mut self,
300 hook: impl Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes + 'static,
301 ) -> Self {
302 self.window_attributes_hook = Some(Box::new(hook));
303 self
304 }
305
306 #[must_use]
309 pub fn with_event_loop_builder(mut self, event_loop_builder: EventLoopBuilder) -> Self {
310 self.event_loop_builder = Some(event_loop_builder);
311 self
312 }
313
314 #[must_use]
320 #[cfg(all(muda, target_os = "macos"))]
321 pub fn with_default_menu_bar(mut self, enable: bool) -> Self {
322 self.muda_enable_default_menu_bar_bar = enable;
323 self
324 }
325
326 #[cfg(target_family = "wasm")]
327 pub fn with_spawn_event_loop(mut self, enable: bool) -> Self {
330 self.spawn_event_loop = enable;
331 self
332 }
333
334 #[must_use]
339 pub fn with_custom_application_handler(
340 mut self,
341 handler: Box<dyn CustomApplicationHandler + 'static>,
342 ) -> Self {
343 self.custom_application_handler = Some(handler);
344 self
345 }
346
347 pub fn build(self) -> Result<Backend, PlatformError> {
360 #[allow(unused_mut)]
361 let mut event_loop_builder =
362 self.event_loop_builder.unwrap_or_else(winit::event_loop::EventLoop::with_user_event);
363
364 #[cfg(all(feature = "muda", target_os = "macos"))]
367 winit::platform::macos::EventLoopBuilderExtMacOS::with_default_menu(
368 &mut event_loop_builder,
369 false,
370 );
371
372 let shared_data = Rc::new(SharedBackendData::new(
375 event_loop_builder,
376 self.renderer_name,
377 self.requested_graphics_api.clone(),
378 self.allow_fallback,
379 )?);
380
381 Ok(Backend {
382 event_loop_state: Default::default(),
383 window_attributes_hook: self.window_attributes_hook,
384 shared_data,
385 #[cfg(all(muda, target_os = "macos"))]
386 muda_enable_default_menu_bar_bar: self.muda_enable_default_menu_bar_bar,
387 #[cfg(target_family = "wasm")]
388 spawn_event_loop: self.spawn_event_loop,
389 custom_application_handler: self.custom_application_handler.into(),
390 #[cfg(xdg_desktop_settings)]
391 xdg_watcher: RefCell::new(None),
392 })
393 }
394}
395
396pub(crate) struct SharedBackendData {
397 allow_fallback: bool,
399 renderer_name: Option<String>,
400 requested_graphics_api: Option<RequestedGraphicsAPI>,
401 #[cfg(enable_skia_renderer)]
402 skia_context: i_slint_renderer_skia::SkiaSharedContext,
403 active_windows: Rc<RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>>,
404 inactive_windows: RefCell<Vec<Weak<WinitWindowAdapter>>>,
407 #[cfg(not(target_arch = "wasm32"))]
408 clipboard: std::cell::RefCell<clipboard::ClipboardPair>,
409 not_running_event_loop: RefCell<Option<winit::event_loop::EventLoop<SlintEvent>>>,
410 event_loop_proxy: winit::event_loop::EventLoopProxy<SlintEvent>,
411 event_loop_generation: Arc<AtomicUsize>,
414 is_wayland: bool,
415 #[cfg(xdg_desktop_settings)]
417 desktop_settings: xdg_desktop_settings::DesktopSettings,
418 #[cfg(target_os = "ios")]
419 #[allow(unused)]
420 keyboard_notifications: ios::KeyboardNotifications,
421}
422
423impl SharedBackendData {
424 fn new(
425 mut builder: EventLoopBuilder,
426 renderer_name: Option<String>,
427 requested_graphics_api: Option<RequestedGraphicsAPI>,
428 allow_fallback: bool,
429 ) -> Result<Self, PlatformError> {
430 #[cfg(not(target_arch = "wasm32"))]
431 use raw_window_handle::HasDisplayHandle;
432
433 #[cfg(all(unix, not(target_vendor = "apple")))]
434 {
435 #[cfg(feature = "wayland")]
436 {
437 use winit::platform::wayland::EventLoopBuilderExtWayland;
438 builder.with_any_thread(true);
439 }
440 #[cfg(feature = "x11")]
441 {
442 use winit::platform::x11::EventLoopBuilderExtX11;
443 builder.with_any_thread(true);
444
445 #[cfg(feature = "wayland")]
449 if std::fs::metadata("/proc/sys/fs/binfmt_misc/WSLInterop").is_ok()
450 || std::fs::metadata("/run/WSL").is_ok()
451 {
452 builder.with_x11();
453 }
454 }
455 }
456 #[cfg(target_family = "windows")]
457 {
458 use winit::platform::windows::EventLoopBuilderExtWindows;
459 builder.with_any_thread(true);
460 }
461
462 let event_loop =
463 builder.build().map_err(|e| format!("Error initializing winit event loop: {e}"))?;
464
465 #[cfg(target_os = "macos")]
466 Self::disable_macos_automatic_shortcut_localization();
467
468 cfg_if::cfg_if! {
469 if #[cfg(all(unix, not(target_vendor = "apple"), feature = "wayland"))] {
470 use winit::platform::wayland::EventLoopExtWayland;
471 let is_wayland = event_loop.is_wayland();
472 } else {
473 let is_wayland = false;
474 }
475 }
476
477 let active_windows =
478 Rc::<RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>>::default();
479
480 #[cfg(target_os = "ios")]
481 let keyboard_notifications =
482 ios::register_keyboard_notifications(Rc::downgrade(&active_windows));
483
484 let event_loop_proxy = event_loop.create_proxy();
485 #[cfg(not(target_arch = "wasm32"))]
486 let clipboard = crate::clipboard::create_clipboard(
487 &event_loop
488 .display_handle()
489 .map_err(|display_err| PlatformError::OtherError(display_err.into()))?,
490 );
491 Ok(Self {
492 allow_fallback,
493 renderer_name,
494 requested_graphics_api,
495 #[cfg(enable_skia_renderer)]
496 skia_context: i_slint_renderer_skia::SkiaSharedContext::default(),
497 active_windows,
498 inactive_windows: Default::default(),
499 #[cfg(not(target_arch = "wasm32"))]
500 clipboard: RefCell::new(clipboard),
501 not_running_event_loop: RefCell::new(Some(event_loop)),
502 event_loop_proxy,
503 event_loop_generation: Default::default(),
504 is_wayland,
505 #[cfg(xdg_desktop_settings)]
506 desktop_settings: xdg_desktop_settings::DesktopSettings::new(),
507 #[cfg(target_os = "ios")]
508 keyboard_notifications,
509 })
510 }
511
512 #[cfg(target_os = "macos")]
520 fn disable_macos_automatic_shortcut_localization() {
521 use objc2::runtime::{AnyClass, AnyObject, Bool, Imp, Sel};
522 use objc2::sel;
523
524 unsafe extern "C-unwind" fn should_not_localize(
525 _this: *mut AnyObject,
526 _cmd: Sel,
527 _app: *mut AnyObject,
528 ) -> Bool {
529 Bool::NO
530 }
531
532 let sel = sel!(applicationShouldAutomaticallyLocalizeKeyEquivalents:);
533 if let Some(cls) = AnyClass::get(c"WinitApplicationDelegate")
534 && cls.instance_method(sel).is_none()
535 {
536 unsafe {
537 objc2::ffi::class_addMethod(
538 (cls as *const AnyClass).cast_mut(),
539 sel,
540 core::mem::transmute::<
541 unsafe extern "C-unwind" fn(*mut AnyObject, Sel, *mut AnyObject) -> Bool,
542 Imp,
543 >(should_not_localize),
544 c"B@:@".as_ptr(),
545 );
546 }
547 }
548 }
549
550 pub fn register_window(&self, id: winit::window::WindowId, window: Rc<WinitWindowAdapter>) {
551 self.active_windows.borrow_mut().insert(id, Rc::downgrade(&window));
552 }
553
554 pub fn register_inactive_window(&self, window: Rc<WinitWindowAdapter>) {
555 let window = Rc::downgrade(&window);
556 let mut inactive_windows = self.inactive_windows.borrow_mut();
557 if !inactive_windows.iter().any(|w| Weak::ptr_eq(w, &window)) {
558 inactive_windows.push(window);
559 }
560 }
561
562 pub fn unregister_window(&self, id: Option<winit::window::WindowId>) {
563 if let Some(id) = id {
564 self.active_windows.borrow_mut().remove(&id);
565 } else {
566 self.inactive_windows
568 .borrow_mut()
569 .retain(|inactive_weak_window| inactive_weak_window.strong_count() > 0)
570 }
571 }
572
573 pub fn create_inactive_windows(
574 &self,
575 event_loop: &winit::event_loop::ActiveEventLoop,
576 ) -> Result<(), PlatformError> {
577 #[cfg(xdg_desktop_settings)]
580 if self.desktop_settings.is_appearance_pending() {
581 return Ok(());
582 }
583 let mut inactive_windows = self.inactive_windows.take();
584 let mut result = Ok(());
585 while let Some(window_weak) = inactive_windows.pop() {
586 if let Some(err) = window_weak.upgrade().and_then(|w| w.ensure_window(event_loop).err())
587 {
588 result = Err(err);
589 break;
590 }
591 }
592 self.inactive_windows.borrow_mut().extend(inactive_windows);
593 result
594 }
595
596 pub fn window_by_id(&self, id: winit::window::WindowId) -> Option<Rc<WinitWindowAdapter>> {
597 self.active_windows.borrow().get(&id).and_then(|weakref| weakref.upgrade())
598 }
599}
600
601#[i_slint_core_macros::slint_doc]
602pub struct Backend {
611 event_loop_state: RefCell<Option<crate::event_loop::EventLoopState>>,
612 shared_data: Rc<SharedBackendData>,
613 custom_application_handler: RefCell<Option<Box<dyn crate::CustomApplicationHandler>>>,
614 #[cfg(xdg_desktop_settings)]
617 xdg_watcher: RefCell<Option<i_slint_core::future::JoinHandle<()>>>,
618
619 pub window_attributes_hook:
633 Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
634
635 #[cfg(all(muda, target_os = "macos"))]
636 muda_enable_default_menu_bar_bar: bool,
637
638 #[cfg(target_family = "wasm")]
639 spawn_event_loop: bool,
640}
641
642impl Backend {
643 #[i_slint_core_macros::slint_doc]
644 pub fn new() -> Result<Self, PlatformError> {
648 Self::builder().build()
649 }
650
651 #[i_slint_core_macros::slint_doc]
652 pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> {
658 let mut builder = Self::builder();
659 if let Some(name) = renderer_name {
660 builder = builder.with_renderer_name(name.to_string());
661 }
662 builder.build()
663 }
664
665 pub fn builder() -> BackendBuilder {
668 BackendBuilder {
669 allow_fallback: true,
670 requested_graphics_api: None,
671 window_attributes_hook: None,
672 renderer_name: None,
673 event_loop_builder: None,
674 #[cfg(all(muda, target_os = "macos"))]
675 muda_enable_default_menu_bar_bar: true,
676 #[cfg(target_family = "wasm")]
677 spawn_event_loop: false,
678 custom_application_handler: None,
679 }
680 }
681}
682
683#[allow(unused)]
684const DEFAULT_CURSOR_FLASH_CYCLE: core::time::Duration = core::time::Duration::from_millis(1000);
685
686#[cfg(any(target_os = "macos", target_os = "ios"))]
687fn prefers_non_blinking_text_insertion_indicator() -> Option<bool> {
688 use core::ffi::{c_char, c_void};
689
690 #[link(name = "Accessibility", kind = "framework")]
691 unsafe extern "C" {}
692
693 unsafe extern "C" {
694 fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
695 }
696
697 type AxPrefersNonBlinkingTextInsertionIndicator =
698 unsafe extern "C" fn() -> objc2::runtime::Bool;
699
700 let symbol = unsafe {
705 dlsym((-2isize) as *mut c_void, c"AXPrefersNonBlinkingTextInsertionIndicator".as_ptr())
706 };
707 if symbol.is_null() {
708 return None;
709 }
710
711 let function: AxPrefersNonBlinkingTextInsertionIndicator =
712 unsafe { core::mem::transmute(symbol) };
713 Some(unsafe { function() }.as_bool())
714}
715
716#[cfg(xdg_desktop_settings)]
717impl Drop for Backend {
718 fn drop(&mut self) {
719 if let Some(handle) = self.xdg_watcher.borrow_mut().take() {
720 handle.abort();
721 }
722 }
723}
724
725impl i_slint_core::platform::Platform for Backend {
726 fn bind_context(&self, _ctx: i_slint_core::SlintContextWeak, _: i_slint_core::InternalToken) {
727 #[cfg(xdg_desktop_settings)]
728 {
729 *self.xdg_watcher.borrow_mut() =
730 crate::xdg_desktop_settings::spawn(&self.shared_data, &_ctx);
731 }
732 #[cfg(target_os = "windows")]
733 if let Some(ctx) = _ctx.upgrade() {
734 use windows::Win32::UI::HiDpi::SystemParametersInfoForDpi;
735 use windows::Win32::UI::WindowsAndMessaging::{
736 NONCLIENTMETRICSW, SPI_GETNONCLIENTMETRICS,
737 };
738 let mut metrics = NONCLIENTMETRICSW {
739 cbSize: core::mem::size_of::<NONCLIENTMETRICSW>() as u32,
740 ..NONCLIENTMETRICSW::default()
741 };
742 let ok = unsafe {
743 SystemParametersInfoForDpi(
744 SPI_GETNONCLIENTMETRICS.0,
745 metrics.cbSize,
746 Some(&mut metrics as *mut _ as *mut core::ffi::c_void),
747 0,
748 96,
749 )
750 }
751 .is_ok();
752 let height = metrics.lfMessageFont.lfHeight.unsigned_abs();
755 if ok && height > 0 {
756 ctx.set_platform_default_font_size(Some(
757 i_slint_core::lengths::LogicalLength::new(height as f32),
758 ));
759 }
760 }
761 }
762
763 fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
764 let mut attrs = WinitWindowAdapter::window_attributes()?;
765
766 if let Some(hook) = &self.window_attributes_hook {
767 attrs = hook(attrs);
768 }
769
770 let adapter = create_renderer(&self.shared_data).map_or_else(
771 |e| {
772 try_create_window_with_fallback_renderer(
773 &self.shared_data,
774 attrs.clone(),
775 &self.shared_data.event_loop_proxy.clone(),
776 #[cfg(all(muda, target_os = "macos"))]
777 self.muda_enable_default_menu_bar_bar,
778 )
779 .ok_or_else(|| format!("Winit backend failed to find a suitable renderer: {e}"))
780 },
781 |renderer| {
782 Ok(WinitWindowAdapter::new(
783 self.shared_data.clone(),
784 renderer,
785 attrs.clone(),
786 #[cfg(any(enable_accesskit, muda))]
787 self.shared_data.event_loop_proxy.clone(),
788 #[cfg(all(muda, target_os = "macos"))]
789 self.muda_enable_default_menu_bar_bar,
790 ))
791 },
792 )?;
793 Ok(adapter)
794 }
795
796 fn run_event_loop(&self) -> Result<(), PlatformError> {
797 let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
798 EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
799 });
800 #[cfg(target_family = "wasm")]
801 {
802 if self.spawn_event_loop {
803 return loop_state.spawn();
804 }
805 }
806 self.shared_data.event_loop_generation.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
808 let new_state = loop_state.run()?;
809 *self.event_loop_state.borrow_mut() = Some(new_state);
810 Ok(())
811 }
812
813 #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]
814 fn process_events(
815 &self,
816 timeout: Option<core::time::Duration>,
817 _: i_slint_core::InternalToken,
818 ) -> Result<core::ops::ControlFlow<()>, PlatformError> {
819 let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
820 EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
821 });
822 let (new_state, status) = loop_state.pump_events(timeout)?;
823 *self.event_loop_state.borrow_mut() = Some(new_state);
824 match status {
825 winit::platform::pump_events::PumpStatus::Continue => {
826 Ok(core::ops::ControlFlow::Continue(()))
827 }
828 winit::platform::pump_events::PumpStatus::Exit(code) => {
829 if code == 0 {
830 Ok(core::ops::ControlFlow::Break(()))
831 } else {
832 Err(format!("Event loop exited with non-zero code {code}").into())
833 }
834 }
835 }
836 }
837
838 fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
839 struct Proxy(winit::event_loop::EventLoopProxy<SlintEvent>, Arc<AtomicUsize>);
840 impl EventLoopProxy for Proxy {
841 fn quit_event_loop(&self) -> Result<(), EventLoopError> {
842 let generation = self.1.load(std::sync::atomic::Ordering::Relaxed);
843 self.0
844 .send_event(SlintEvent(CustomEvent::Exit(generation)))
845 .map_err(|_| EventLoopError::EventLoopTerminated)
846 }
847
848 fn invoke_from_event_loop(
849 &self,
850 event: Box<dyn FnOnce() + Send>,
851 ) -> Result<(), EventLoopError> {
852 #[cfg(target_arch = "wasm32")]
862 self.0
863 .send_event(SlintEvent(CustomEvent::WakeEventLoopWorkaround))
864 .map_err(|_| EventLoopError::EventLoopTerminated)?;
865
866 self.0
867 .send_event(SlintEvent(CustomEvent::UserEvent(event)))
868 .map_err(|_| EventLoopError::EventLoopTerminated)
869 }
870 }
871 Some(Box::new(Proxy(
872 self.shared_data.event_loop_proxy.clone(),
873 Arc::clone(&self.shared_data.event_loop_generation),
874 )))
875 }
876
877 #[cfg(target_arch = "wasm32")]
878 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
879 crate::wasm_input_helper::set_clipboard_text(text.into(), clipboard);
880 }
881
882 #[cfg(not(target_arch = "wasm32"))]
883 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
884 let mut pair = self.shared_data.clipboard.borrow_mut();
885 if let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard) {
886 clipboard.set_contents(text.into()).ok();
887 }
888 }
889
890 #[cfg(target_arch = "wasm32")]
891 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
892 crate::wasm_input_helper::get_clipboard_text(clipboard)
893 }
894
895 #[cfg(not(target_arch = "wasm32"))]
896 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
897 let mut pair = self.shared_data.clipboard.borrow_mut();
898 clipboard::select_clipboard(&mut pair, clipboard).and_then(|c| c.get_contents().ok())
899 }
900
901 #[cfg(target_os = "windows")]
902 fn cursor_flash_cycle(&self) -> core::time::Duration {
903 use windows::Win32::UI::WindowsAndMessaging::GetCaretBlinkTime;
904 let ms = unsafe { GetCaretBlinkTime() };
905 if ms == u32::MAX {
906 core::time::Duration::ZERO
908 } else if ms == 0 {
909 DEFAULT_CURSOR_FLASH_CYCLE
910 } else {
911 core::time::Duration::from_millis(ms as u64 * 2)
913 }
914 }
915
916 #[cfg(target_os = "macos")]
917 fn cursor_flash_cycle(&self) -> core::time::Duration {
918 if prefers_non_blinking_text_insertion_indicator() == Some(true) {
919 return core::time::Duration::ZERO;
920 }
921
922 let defaults = objc2_foundation::NSUserDefaults::standardUserDefaults();
923 let key = objc2_foundation::NSString::from_str("NSTextInsertionPointBlinkPeriod");
924 let period = defaults.integerForKey(&key);
925 if period < 0 {
926 core::time::Duration::ZERO
927 } else if period == 0 {
928 DEFAULT_CURSOR_FLASH_CYCLE
929 } else {
930 core::time::Duration::from_millis(period as u64)
931 }
932 }
933
934 #[cfg(target_os = "ios")]
935 fn cursor_flash_cycle(&self) -> core::time::Duration {
936 if prefers_non_blinking_text_insertion_indicator() == Some(true) {
937 core::time::Duration::ZERO
938 } else {
939 DEFAULT_CURSOR_FLASH_CYCLE
940 }
941 }
942
943 #[cfg(xdg_desktop_settings)]
944 fn cursor_flash_cycle(&self) -> core::time::Duration {
945 self.shared_data.desktop_settings.cursor_flash_cycle()
946 }
947
948 fn open_url(&self, url: &str) -> Result<(), i_slint_core::platform::PlatformError> {
949 webbrowser::open(url).map_err(|e| {
950 i_slint_core::platform::PlatformError::Other(format!("Failed to open URL: {e}"))
951 })
952 }
953}
954
955mod private {
956 pub trait WinitWindowAccessorSealed {}
957}
958
959#[i_slint_core_macros::slint_doc]
960pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed {
973 fn has_winit_window(&self) -> bool;
976 fn with_winit_window<T>(&self, callback: impl FnOnce(&winit::window::Window) -> T)
979 -> Option<T>;
980 fn on_winit_window_event(
988 &self,
989 callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
990 + 'static,
991 );
992
993 fn winit_window(
1036 &self,
1037 ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>>;
1038}
1039
1040impl WinitWindowAccessor for i_slint_core::api::Window {
1041 fn has_winit_window(&self) -> bool {
1042 i_slint_core::window::WindowInner::from_pub(self)
1043 .window_adapter()
1044 .internal(i_slint_core::InternalToken)
1045 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1046 .is_some_and(|adapter| adapter.winit_window().is_some())
1047 }
1048
1049 fn with_winit_window<T>(
1050 &self,
1051 callback: impl FnOnce(&winit::window::Window) -> T,
1052 ) -> Option<T> {
1053 i_slint_core::window::WindowInner::from_pub(self)
1054 .window_adapter()
1055 .internal(i_slint_core::InternalToken)
1056 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1057 .and_then(|adapter| adapter.winit_window().map(|w| callback(&w)))
1058 }
1059
1060 fn winit_window(
1061 &self,
1062 ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>> {
1063 Box::pin(async move {
1064 let adapter_weak = i_slint_core::window::WindowInner::from_pub(self)
1065 .window_adapter()
1066 .internal(i_slint_core::InternalToken)
1067 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1068 .map(|wa| wa.self_weak.clone())
1069 .ok_or_else(|| {
1070 PlatformError::OtherError(
1071 "Slint window is not backed by a Winit window adapter".to_string().into(),
1072 )
1073 })?;
1074 WinitWindowAdapter::async_winit_window(adapter_weak).await
1075 })
1076 }
1077
1078 fn on_winit_window_event(
1079 &self,
1080 mut callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
1081 + 'static,
1082 ) {
1083 if let Some(adapter) = i_slint_core::window::WindowInner::from_pub(self)
1084 .window_adapter()
1085 .internal(i_slint_core::InternalToken)
1086 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1087 {
1088 adapter
1089 .window_event_filter
1090 .set(Some(Box::new(move |window, event| callback(window, event))));
1091 }
1092 }
1093}
1094
1095fn create_renderer(
1097 shared_data: &Rc<SharedBackendData>,
1098) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError> {
1099 match (shared_data.renderer_name.as_deref(), shared_data.requested_graphics_api.as_ref()) {
1100 #[cfg(all(feature = "renderer-femtovg", supports_opengl))]
1101 (Some("gl"), maybe_graphics_api) | (Some("femtovg"), maybe_graphics_api) => {
1102 if let Some(api) = maybe_graphics_api {
1104 i_slint_core::graphics::RequestedOpenGLVersion::try_from(api)?;
1105 }
1106 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(shared_data)
1107 }
1108 #[cfg(feature = "renderer-femtovg-wgpu")]
1109 (Some("femtovg-wgpu"), maybe_graphics_api) => {
1110 if let Some(_api) = maybe_graphics_api {
1111 #[cfg(feature = "unstable-wgpu-29")]
1112 if !matches!(_api, RequestedGraphicsAPI::WGPU29(..)) {
1113 return Err(
1114 "The FemtoVG WGPU renderer only supports the WGPU29 graphics API selection"
1115 .into(),
1116 );
1117 }
1118 }
1119 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended(shared_data)
1120 }
1121 #[cfg(enable_skia_renderer)]
1122 (Some("skia"), maybe_graphics_api) => {
1123 (renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(maybe_graphics_api)?)(
1124 shared_data,
1125 )
1126 }
1127 #[cfg(all(enable_skia_renderer, supports_opengl))]
1128 (Some("skia-opengl"), maybe_graphics_api) => {
1129 if let Some(api) = maybe_graphics_api {
1131 i_slint_core::graphics::RequestedOpenGLVersion::try_from(api)?;
1132 }
1133 renderer::skia::WinitSkiaRenderer::new_opengl_suspended(shared_data)
1134 }
1135 #[cfg(all(
1136 enable_skia_renderer,
1137 any(feature = "unstable-wgpu-28", feature = "unstable-wgpu-29")
1138 ))]
1139 (Some("skia-wgpu"), maybe_graphics_api) => {
1140 if let Some(selected_renderer) = maybe_graphics_api.map_or_else(
1141 || {
1142 #[cfg(feature = "unstable-wgpu-29")]
1143 {
1144 return Some(renderer::skia::WinitSkiaRenderer::new_wgpu_29_suspended(
1145 shared_data,
1146 ));
1147 }
1148 #[cfg(all(feature = "unstable-wgpu-28", not(feature = "unstable-wgpu-29")))]
1149 {
1150 return Some(renderer::skia::WinitSkiaRenderer::new_wgpu_28_suspended(
1151 shared_data,
1152 ));
1153 }
1154 #[allow(unreachable_code)]
1155 None
1156 },
1157 |_api| {
1158 #[cfg(feature = "unstable-wgpu-29")]
1159 if matches!(_api, RequestedGraphicsAPI::WGPU29(..)) {
1160 return Some(renderer::skia::WinitSkiaRenderer::new_wgpu_29_suspended(
1161 shared_data,
1162 ));
1163 }
1164 #[cfg(feature = "unstable-wgpu-28")]
1165 if matches!(_api, RequestedGraphicsAPI::WGPU28(..)) {
1166 return Some(renderer::skia::WinitSkiaRenderer::new_wgpu_28_suspended(
1167 shared_data,
1168 ));
1169 }
1170 None
1171 },
1172 ) {
1173 selected_renderer
1174 } else {
1175 Err("Skia with WGPU doesn't support non-WGPU graphics API".to_string().into())
1176 }
1177 }
1178 #[cfg(all(enable_skia_renderer, not(target_os = "android")))]
1179 (Some("skia-software"), None) => {
1180 renderer::skia::WinitSkiaRenderer::new_software_suspended(shared_data)
1181 }
1182 #[cfg(feature = "renderer-software")]
1183 (Some("sw"), None) | (Some("software"), None) => {
1184 renderer::sw::WinitSoftwareRenderer::new_suspended(shared_data)
1185 }
1186 (None, None) => default_renderer_factory(shared_data),
1187 (Some(renderer_name), _) => {
1188 if shared_data.allow_fallback {
1189 eprintln!(
1190 "slint winit: unrecognized renderer {renderer_name}, falling back to {DEFAULT_RENDERER_NAME}"
1191 );
1192 default_renderer_factory(shared_data)
1193 } else {
1194 Err(PlatformError::NoPlatform)
1195 }
1196 }
1197 #[cfg(feature = "unstable-wgpu-28")]
1198 (None, Some(RequestedGraphicsAPI::WGPU28(..))) => {
1199 cfg_if::cfg_if! {
1200 if #[cfg(enable_skia_renderer)] {
1201 renderer::skia::WinitSkiaRenderer::new_wgpu_28_suspended(shared_data)
1202 } else {
1203 Err("unstable-wgpu-28 was enabled but no renderer was selected. Please select renderer-skia*".into())
1204 }
1205 }
1206 }
1207 #[cfg(feature = "unstable-wgpu-29")]
1208 (None, Some(RequestedGraphicsAPI::WGPU29(..))) => {
1209 cfg_if::cfg_if! {
1210 if #[cfg(enable_skia_renderer)] {
1211 renderer::skia::WinitSkiaRenderer::new_wgpu_29_suspended(shared_data)
1212 } else if #[cfg(feature = "renderer-femtovg-wgpu")] {
1213 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended(shared_data)
1214 } else {
1215 Err("unstable-wgpu-29 was enabled but no renderer was selected. Please select either renderer-skia* or renderer-femtovg-wgpu".into())
1216 }
1217 }
1218 }
1219 (None, Some(_requested_graphics_api)) => {
1220 cfg_if::cfg_if! {
1221 if #[cfg(enable_skia_renderer)] {
1222 renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(Some(_requested_graphics_api))?(shared_data)
1223 } else if #[cfg(all(feature = "renderer-femtovg", supports_opengl))] {
1224 i_slint_core::graphics::RequestedOpenGLVersion::try_from(_requested_graphics_api)?;
1226 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(shared_data)
1227 } else {
1228 return Err(format!("Graphics API use requested by the compile-time enabled renderers don't support that").into())
1229 }
1230 }
1231 }
1232 }
1233}
1234
1235impl private::WinitWindowAccessorSealed for i_slint_core::api::Window {}
1236
1237#[cfg(test)]
1238mod testui {
1239 slint::slint! {
1240 export component App inherits Window {
1241 Text { text: "Ok"; }
1242 }
1243 }
1244}
1245
1246#[cfg(not(any(target_arch = "wasm32", target_vendor = "apple")))]
1248#[test]
1249fn test_window_accessor_and_rwh() {
1250 slint::platform::set_platform(Box::new(crate::Backend::new().unwrap())).unwrap();
1251
1252 use testui::*;
1253
1254 slint::spawn_local(async move {
1255 let app = App::new().unwrap();
1256 let slint_window = app.window();
1257
1258 assert!(!slint_window.has_winit_window());
1259
1260 app.show().unwrap();
1263
1264 let result = slint_window.winit_window().await;
1265 assert!(result.is_ok(), "Failed to get winit window: {:?}", result.err());
1266 assert!(slint_window.has_winit_window());
1267 let handle = slint_window.window_handle();
1268 use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
1269 assert!(handle.window_handle().is_ok());
1270 assert!(handle.display_handle().is_ok());
1271 slint::quit_event_loop().unwrap();
1272 })
1273 .unwrap();
1274
1275 slint::run_event_loop().unwrap();
1276}