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::lengths::LogicalPoint;
17use i_slint_core::platform::{EventLoopProxy, PlatformError};
18use i_slint_core::window::WindowAdapter;
19use renderer::WinitCompatibleRenderer;
20use std::cell::Cell;
21use std::cell::OnceCell;
22use std::cell::RefCell;
23use std::collections::HashMap;
24use std::rc::Rc;
25use std::rc::Weak;
26use std::sync::Arc;
27use std::sync::atomic::AtomicUsize;
28use winit::event_loop::ActiveEventLoop;
29
30#[cfg(not(target_arch = "wasm32"))]
31mod clipboard;
32mod drag_resize_window;
33mod winit_compat;
34mod winitwindowadapter;
35use winitwindowadapter::*;
36pub(crate) mod event_loop;
37mod frame_throttle;
38#[cfg(target_os = "ios")]
39mod ios;
40#[cfg(target_os = "macos")]
41mod macos;
42mod touch_finger_id;
43
44pub use winit;
46
47#[non_exhaustive]
51#[derive(Debug)]
52pub struct SlintEvent(CustomEvent);
53
54#[i_slint_core_macros::slint_doc]
55pub type EventLoopBuilder = winit::event_loop::EventLoopBuilder<SlintEvent>;
60
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
64pub enum EventResult {
65 Propagate,
67 PreventDefault,
69}
70
71mod renderer {
72 use std::rc::Weak;
73 use std::sync::Arc;
74
75 use i_slint_core::platform::PlatformError;
76 use i_slint_core::renderer::DrawOutcome;
77 use winit::event_loop::ActiveEventLoop;
78
79 pub trait WinitCompatibleRenderer: std::any::Any {
80 fn render(&self, window: &i_slint_core::api::Window) -> Result<DrawOutcome, PlatformError>;
81
82 fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
83 fn occluded(&self, _: bool) {}
85
86 fn suspend(&self) -> Result<(), PlatformError>;
87
88 #[cfg(target_os = "macos")]
91 fn set_transparent(&self, _transparent: bool) -> Result<(), PlatformError> {
92 Ok(())
93 }
94
95 fn resume(
97 &self,
98 active_event_loop: &ActiveEventLoop,
99 window_attributes: winit::window::WindowAttributes,
100 window_adapter_weak: Weak<crate::winitwindowadapter::WinitWindowAdapter>,
101 ) -> Result<Arc<winit::window::Window>, PlatformError>;
102 }
103
104 #[cfg(enable_femtovg_renderer)]
105 pub(crate) mod femtovg;
106 #[cfg(enable_skia_renderer)]
107 pub(crate) mod skia;
108
109 #[cfg(feature = "renderer-software")]
110 pub(crate) mod sw;
111 #[cfg(feature = "renderer-vello")]
112 pub(crate) mod vello;
113}
114
115#[cfg(enable_accesskit)]
116mod accesskit;
117#[cfg(muda)]
118mod muda;
119#[cfg(xdg_desktop_settings)]
120mod xdg_desktop_settings;
121
122#[cfg(target_arch = "wasm32")]
123pub(crate) mod wasm_input_helper;
124
125cfg_if::cfg_if! {
126 if #[cfg(enable_femtovg_renderer)] {
127 const DEFAULT_RENDERER_NAME: &str = "FemtoVG";
128 } else if #[cfg(enable_skia_renderer)] {
129 const DEFAULT_RENDERER_NAME: &str = "Skia";
130 } else if #[cfg(feature = "renderer-software")] {
131 const DEFAULT_RENDERER_NAME: &str = "Software";
132 } else if #[cfg(feature = "renderer-vello")] {
133 const DEFAULT_RENDERER_NAME: &str = "Vello";
134 } else {
135 compile_error!("Please select a feature to build with the winit backend: `renderer-femtovg`, `renderer-skia`, `renderer-skia-opengl`, `renderer-skia-vulkan`, `renderer-software` or `renderer-vello`");
136 }
137}
138
139fn default_renderer_factory(
140 shared_backend_data: &Rc<SharedBackendData>,
141) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError> {
142 cfg_if::cfg_if! {
143 if #[cfg(enable_skia_renderer)] {
144 renderer::skia::WinitSkiaRenderer::new_suspended(shared_backend_data)
145 } else if #[cfg(feature = "renderer-femtovg-wgpu")] {
146 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended(shared_backend_data)
147 } else if #[cfg(all(feature = "renderer-femtovg", supports_opengl))] {
148 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(shared_backend_data)
149 } else if #[cfg(feature = "renderer-software")] {
150 renderer::sw::WinitSoftwareRenderer::new_suspended(shared_backend_data)
151 } else if #[cfg(feature = "renderer-vello")] {
152 renderer::vello::WinitVelloRenderer::new_suspended(shared_backend_data)
155 } else {
156 compile_error!("Please select a feature to build with the winit backend: `renderer-femtovg`, `renderer-skia`, `renderer-skia-opengl`, `renderer-skia-vulkan`, `renderer-software` or `renderer-vello`");
157 }
158 }
159}
160
161fn try_create_window_with_fallback_renderer(
162 shared_backend_data: &Rc<SharedBackendData>,
163 attrs: winit::window::WindowAttributes,
164 _proxy: &winit::event_loop::EventLoopProxy<SlintEvent>,
165 #[cfg(all(muda, target_os = "macos"))] muda_enable_default_menu_bar: bool,
166) -> Option<Rc<WinitWindowAdapter>> {
167 [
168 #[cfg(any(
169 feature = "renderer-skia",
170 feature = "renderer-skia-opengl",
171 feature = "renderer-skia-vulkan"
172 ))]
173 renderer::skia::WinitSkiaRenderer::new_suspended,
174 #[cfg(feature = "renderer-femtovg-wgpu")]
175 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended,
176 #[cfg(all(
177 feature = "renderer-femtovg",
178 supports_opengl,
179 not(feature = "renderer-femtovg-wgpu")
180 ))]
181 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended,
182 #[cfg(feature = "renderer-software")]
183 renderer::sw::WinitSoftwareRenderer::new_suspended,
184 #[cfg(feature = "renderer-vello")]
185 renderer::vello::WinitVelloRenderer::new_suspended,
186 ]
187 .into_iter()
188 .find_map(|renderer_factory| {
189 Some(WinitWindowAdapter::new(
190 shared_backend_data.clone(),
191 renderer_factory(shared_backend_data).ok()?,
192 attrs.clone(),
193 #[cfg(any(enable_accesskit, muda))]
194 _proxy.clone(),
195 #[cfg(all(muda, target_os = "macos"))]
196 muda_enable_default_menu_bar,
197 ))
198 })
199}
200
201#[doc(hidden)]
202pub type NativeWidgets = ();
203#[doc(hidden)]
204pub type NativeGlobals = ();
205#[doc(hidden)]
206pub const HAS_NATIVE_STYLE: bool = false;
207#[doc(hidden)]
208pub mod native_widgets {}
209
210#[allow(unused_variables)]
218pub trait CustomApplicationHandler {
219 fn resumed(&mut self, _event_loop: &ActiveEventLoop) -> EventResult {
221 EventResult::Propagate
222 }
223
224 fn window_event(
226 &mut self,
227 event_loop: &ActiveEventLoop,
228 window_id: winit::window::WindowId,
229 winit_window: Option<&winit::window::Window>,
230 slint_window: Option<&i_slint_core::api::Window>,
231 event: &winit::event::WindowEvent,
232 ) -> EventResult {
233 EventResult::Propagate
234 }
235
236 fn new_events(
238 &mut self,
239 event_loop: &ActiveEventLoop,
240 cause: winit::event::StartCause,
241 ) -> EventResult {
242 EventResult::Propagate
243 }
244
245 fn device_event(
247 &mut self,
248 event_loop: &ActiveEventLoop,
249 device_id: winit::event::DeviceId,
250 event: winit::event::DeviceEvent,
251 ) -> EventResult {
252 EventResult::Propagate
253 }
254
255 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
257 EventResult::Propagate
258 }
259
260 fn suspended(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
262 EventResult::Propagate
263 }
264
265 fn exiting(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
267 EventResult::Propagate
268 }
269
270 fn memory_warning(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
272 EventResult::Propagate
273 }
274}
275
276pub struct BackendBuilder {
280 allow_fallback: bool,
282 requested_graphics_api: Option<RequestedGraphicsAPI>,
283 window_attributes_hook:
284 Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
285 renderer_name: Option<String>,
286 event_loop_builder: Option<EventLoopBuilder>,
287 #[cfg(all(muda, target_os = "macos"))]
288 muda_enable_default_menu_bar_bar: bool,
289 #[cfg(target_family = "wasm")]
290 spawn_event_loop: bool,
291 custom_application_handler: Option<Box<dyn CustomApplicationHandler>>,
292}
293
294impl BackendBuilder {
295 #[must_use]
297 pub fn request_graphics_api(mut self, graphics_api: RequestedGraphicsAPI) -> Self {
298 self.requested_graphics_api = Some(graphics_api);
299 self
300 }
301
302 #[must_use]
305 pub fn with_renderer_name(mut self, name: impl Into<String>) -> Self {
306 self.renderer_name = Some(name.into());
307 self
308 }
309
310 #[must_use]
324 pub fn with_window_attributes_hook(
325 mut self,
326 hook: impl Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes + 'static,
327 ) -> Self {
328 self.window_attributes_hook = Some(Box::new(hook));
329 self
330 }
331
332 #[must_use]
335 pub fn with_event_loop_builder(mut self, event_loop_builder: EventLoopBuilder) -> Self {
336 self.event_loop_builder = Some(event_loop_builder);
337 self
338 }
339
340 #[must_use]
346 #[cfg(all(muda, target_os = "macos"))]
347 pub fn with_default_menu_bar(mut self, enable: bool) -> Self {
348 self.muda_enable_default_menu_bar_bar = enable;
349 self
350 }
351
352 #[cfg(target_family = "wasm")]
353 pub fn with_spawn_event_loop(mut self, enable: bool) -> Self {
356 self.spawn_event_loop = enable;
357 self
358 }
359
360 #[must_use]
365 pub fn with_custom_application_handler(
366 mut self,
367 handler: Box<dyn CustomApplicationHandler + 'static>,
368 ) -> Self {
369 self.custom_application_handler = Some(handler);
370 self
371 }
372
373 pub fn build(self) -> Result<Backend, PlatformError> {
386 #[allow(unused_mut)]
387 let mut event_loop_builder =
388 self.event_loop_builder.unwrap_or_else(winit::event_loop::EventLoop::with_user_event);
389
390 #[cfg(all(feature = "muda", target_os = "macos"))]
393 winit::platform::macos::EventLoopBuilderExtMacOS::with_default_menu(
394 &mut event_loop_builder,
395 false,
396 );
397
398 let shared_data = Rc::new(SharedBackendData::new(
401 event_loop_builder,
402 self.renderer_name,
403 self.requested_graphics_api.clone(),
404 self.allow_fallback,
405 )?);
406
407 Ok(Backend {
408 event_loop_state: Default::default(),
409 window_attributes_hook: self.window_attributes_hook,
410 shared_data,
411 #[cfg(all(muda, target_os = "macos"))]
412 muda_enable_default_menu_bar_bar: self.muda_enable_default_menu_bar_bar,
413 #[cfg(target_family = "wasm")]
414 spawn_event_loop: self.spawn_event_loop,
415 custom_application_handler: self.custom_application_handler.into(),
416 #[cfg(xdg_desktop_settings)]
417 xdg_watcher: RefCell::new(None),
418 })
419 }
420}
421
422fn dispatch_mouse_move(window: &Weak<WinitWindowAdapter>, position: LogicalPoint) {
423 if let Some(window) = window.upgrade() {
424 window.window().dispatch_event(i_slint_core::platform::WindowEvent::internal(
425 i_slint_core::input::BackendMouseEvent::Moved { position, touch_finger_id: 0 },
426 ));
427 }
428}
429
430pub(crate) struct SharedBackendData {
431 context: OnceCell<i_slint_core::SlintContextWeak>,
432 allow_fallback: bool,
434 renderer_name: Option<String>,
435 requested_graphics_api: Option<RequestedGraphicsAPI>,
436 #[cfg(enable_skia_renderer)]
437 skia_context: i_slint_renderer_skia::SkiaSharedContext,
438 active_windows: Rc<RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>>,
439 inactive_windows: RefCell<Vec<Weak<WinitWindowAdapter>>>,
442 pending_mouse_move: Cell<Option<(Weak<WinitWindowAdapter>, LogicalPoint)>>,
446 #[cfg(not(target_arch = "wasm32"))]
447 clipboard: std::cell::RefCell<clipboard::ClipboardPair>,
448 not_running_event_loop: RefCell<Option<winit::event_loop::EventLoop<SlintEvent>>>,
449 event_loop_proxy: winit::event_loop::EventLoopProxy<SlintEvent>,
450 event_loop_generation: Arc<AtomicUsize>,
453 is_wayland: bool,
454 #[cfg(xdg_desktop_settings)]
456 desktop_settings: xdg_desktop_settings::DesktopSettings,
457 #[cfg(target_os = "ios")]
458 #[allow(unused)]
459 keyboard_notifications: ios::KeyboardNotifications,
460}
461
462impl SharedBackendData {
463 pub(crate) fn context(&self) -> i_slint_core::SlintContext {
465 self.context
466 .get()
467 .and_then(|ctx| ctx.upgrade())
468 .expect("the winit event loop runs inside the context that owns this backend")
469 }
470
471 fn new(
472 mut builder: EventLoopBuilder,
473 renderer_name: Option<String>,
474 requested_graphics_api: Option<RequestedGraphicsAPI>,
475 allow_fallback: bool,
476 ) -> Result<Self, PlatformError> {
477 #[cfg(not(target_arch = "wasm32"))]
478 use raw_window_handle::HasDisplayHandle;
479
480 #[cfg(all(unix, not(target_vendor = "apple")))]
481 {
482 #[cfg(feature = "wayland")]
483 {
484 use winit::platform::wayland::EventLoopBuilderExtWayland;
485 builder.with_any_thread(true);
486 }
487 #[cfg(feature = "x11")]
488 {
489 use winit::platform::x11::EventLoopBuilderExtX11;
490 builder.with_any_thread(true);
491
492 #[cfg(feature = "wayland")]
496 if std::fs::metadata("/proc/sys/fs/binfmt_misc/WSLInterop").is_ok()
497 || std::fs::metadata("/run/WSL").is_ok()
498 {
499 builder.with_x11();
500 }
501 }
502 }
503 #[cfg(target_family = "windows")]
504 {
505 use winit::platform::windows::EventLoopBuilderExtWindows;
506 builder.with_any_thread(true);
507 }
508
509 let event_loop =
510 builder.build().map_err(|e| format!("Error initializing winit event loop: {e}"))?;
511
512 #[cfg(target_os = "macos")]
513 Self::disable_macos_automatic_shortcut_localization();
514
515 cfg_if::cfg_if! {
516 if #[cfg(all(unix, not(target_vendor = "apple"), feature = "wayland"))] {
517 use winit::platform::wayland::EventLoopExtWayland;
518 let is_wayland = event_loop.is_wayland();
519 } else {
520 let is_wayland = false;
521 }
522 }
523
524 let active_windows =
525 Rc::<RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>>::default();
526
527 #[cfg(target_os = "ios")]
528 let keyboard_notifications =
529 ios::register_keyboard_notifications(Rc::downgrade(&active_windows));
530
531 #[cfg(target_os = "ios")]
534 ios::register_scene_delegate_class();
535
536 let event_loop_proxy = event_loop.create_proxy();
537 #[cfg(not(target_arch = "wasm32"))]
538 let clipboard = crate::clipboard::create_clipboard(
539 &event_loop
540 .display_handle()
541 .map_err(|display_err| PlatformError::OtherError(display_err.into()))?,
542 );
543 Ok(Self {
544 context: Default::default(),
545 allow_fallback,
546 renderer_name,
547 requested_graphics_api,
548 #[cfg(enable_skia_renderer)]
549 skia_context: Default::default(),
550 active_windows,
551 inactive_windows: Default::default(),
552 pending_mouse_move: Default::default(),
553 #[cfg(not(target_arch = "wasm32"))]
554 clipboard: RefCell::new(clipboard),
555 not_running_event_loop: RefCell::new(Some(event_loop)),
556 event_loop_proxy,
557 event_loop_generation: Default::default(),
558 is_wayland,
559 #[cfg(xdg_desktop_settings)]
560 desktop_settings: xdg_desktop_settings::DesktopSettings::new(),
561 #[cfg(target_os = "ios")]
562 keyboard_notifications,
563 })
564 }
565
566 #[cfg(target_os = "macos")]
574 fn disable_macos_automatic_shortcut_localization() {
575 use objc2::runtime::{AnyClass, AnyObject, Bool, Imp, Sel};
576 use objc2::sel;
577
578 unsafe extern "C-unwind" fn should_not_localize(
579 _this: *mut AnyObject,
580 _cmd: Sel,
581 _app: *mut AnyObject,
582 ) -> Bool {
583 Bool::NO
584 }
585
586 let sel = sel!(applicationShouldAutomaticallyLocalizeKeyEquivalents:);
587 if let Some(cls) = AnyClass::get(c"WinitApplicationDelegate")
588 && cls.instance_method(sel).is_none()
589 {
590 unsafe {
591 objc2::ffi::class_addMethod(
592 (cls as *const AnyClass).cast_mut(),
593 sel,
594 core::mem::transmute::<
595 unsafe extern "C-unwind" fn(*mut AnyObject, Sel, *mut AnyObject) -> Bool,
596 Imp,
597 >(should_not_localize),
598 c"B@:@".as_ptr(),
599 );
600 }
601 }
602 }
603
604 pub fn register_window(&self, id: winit::window::WindowId, window: Rc<WinitWindowAdapter>) {
605 self.active_windows.borrow_mut().insert(id, Rc::downgrade(&window));
606 }
607
608 pub fn register_inactive_window(&self, window: Rc<WinitWindowAdapter>) {
609 let window = Rc::downgrade(&window);
610 let mut inactive_windows = self.inactive_windows.borrow_mut();
611 if !inactive_windows.iter().any(|w| Weak::ptr_eq(w, &window)) {
612 inactive_windows.push(window);
613 }
614 }
615
616 pub fn unregister_window(&self, id: Option<winit::window::WindowId>) {
617 if let Some(id) = id {
618 self.active_windows.borrow_mut().remove(&id);
619 } else {
620 self.inactive_windows
622 .borrow_mut()
623 .retain(|inactive_weak_window| inactive_weak_window.strong_count() > 0)
624 }
625 }
626
627 pub fn create_inactive_windows(
628 &self,
629 event_loop: &winit::event_loop::ActiveEventLoop,
630 ) -> Result<(), PlatformError> {
631 #[cfg(xdg_desktop_settings)]
634 if self.desktop_settings.is_appearance_pending() {
635 return Ok(());
636 }
637 let mut inactive_windows = self.inactive_windows.take();
638 let mut result = Ok(());
639 while let Some(window_weak) = inactive_windows.pop() {
640 if let Some(err) = window_weak.upgrade().and_then(|w| w.ensure_window(event_loop).err())
641 {
642 result = Err(err);
643 break;
644 }
645 }
646 self.inactive_windows.borrow_mut().extend(inactive_windows);
647 result
648 }
649
650 pub fn window_by_id(&self, id: winit::window::WindowId) -> Option<Rc<WinitWindowAdapter>> {
651 self.active_windows.borrow().get(&id).and_then(|weakref| weakref.upgrade())
652 }
653
654 pub(crate) fn buffer_mouse_move(
657 &self,
658 window: &Weak<WinitWindowAdapter>,
659 position: LogicalPoint,
660 ) {
661 if let Some((pending_window, pending_position)) =
662 self.pending_mouse_move.replace(Some((window.clone(), position)))
663 && !Weak::ptr_eq(&pending_window, window)
664 {
665 dispatch_mouse_move(&pending_window, pending_position);
666 }
667 }
668
669 pub(crate) fn flush_pending_mouse_move(&self) {
671 if let Some((window, position)) = self.pending_mouse_move.take() {
672 dispatch_mouse_move(&window, position);
673 }
674 }
675}
676
677#[i_slint_core_macros::slint_doc]
678pub struct Backend {
687 event_loop_state: RefCell<Option<crate::event_loop::EventLoopState>>,
688 shared_data: Rc<SharedBackendData>,
689 custom_application_handler: RefCell<Option<Box<dyn crate::CustomApplicationHandler>>>,
690 #[cfg(xdg_desktop_settings)]
693 xdg_watcher: RefCell<Option<i_slint_core::future::JoinHandle<()>>>,
694
695 pub window_attributes_hook:
709 Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
710
711 #[cfg(all(muda, target_os = "macos"))]
712 muda_enable_default_menu_bar_bar: bool,
713
714 #[cfg(target_family = "wasm")]
715 spawn_event_loop: bool,
716}
717
718impl Backend {
719 #[i_slint_core_macros::slint_doc]
720 pub fn new() -> Result<Self, PlatformError> {
724 Self::builder().build()
725 }
726
727 #[i_slint_core_macros::slint_doc]
728 pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> {
734 let mut builder = Self::builder();
735 if let Some(name) = renderer_name {
736 builder = builder.with_renderer_name(name.to_string());
737 }
738 builder.build()
739 }
740
741 pub fn builder() -> BackendBuilder {
744 BackendBuilder {
745 allow_fallback: true,
746 requested_graphics_api: None,
747 window_attributes_hook: None,
748 renderer_name: None,
749 event_loop_builder: None,
750 #[cfg(all(muda, target_os = "macos"))]
751 muda_enable_default_menu_bar_bar: true,
752 #[cfg(target_family = "wasm")]
753 spawn_event_loop: false,
754 custom_application_handler: None,
755 }
756 }
757}
758
759static GLOBAL_PROXY: std::sync::Mutex<Option<winit::event_loop::EventLoopProxy<SlintEvent>>> =
762 std::sync::Mutex::new(None);
763
764pub fn invoke_from_active_event_loop(
775 func: impl FnOnce(&ActiveEventLoop) + Send + 'static,
776) -> Result<(), EventLoopError> {
777 let proxy = GLOBAL_PROXY.lock().unwrap().clone().ok_or(EventLoopError::NoEventLoopProvider)?;
778 proxy
779 .send_event(SlintEvent(CustomEvent::UserEventWithEventLoop(Box::new(func))))
780 .map_err(|_| EventLoopError::EventLoopTerminated)
781}
782
783#[allow(unused)]
784const DEFAULT_CURSOR_FLASH_CYCLE: core::time::Duration = core::time::Duration::from_millis(1000);
785
786#[cfg(any(target_os = "macos", target_os = "ios"))]
787fn prefers_non_blinking_text_insertion_indicator() -> Option<bool> {
788 use core::ffi::{c_char, c_int, c_void};
789
790 unsafe extern "C" {
791 fn dlopen(path: *const c_char, mode: c_int) -> *mut c_void;
792 fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
793 }
794
795 type AxPrefersNonBlinkingTextInsertionIndicator =
796 unsafe extern "C" fn() -> objc2::runtime::Bool;
797
798 const RTLD_LAZY: c_int = 0x1;
805 let framework = unsafe {
806 dlopen(
807 c"/System/Library/Frameworks/Accessibility.framework/Accessibility".as_ptr(),
808 RTLD_LAZY,
809 )
810 };
811 if framework.is_null() {
812 return None;
813 }
814
815 let symbol =
816 unsafe { dlsym(framework, c"AXPrefersNonBlinkingTextInsertionIndicator".as_ptr()) };
817 if symbol.is_null() {
818 return None;
819 }
820
821 let function: AxPrefersNonBlinkingTextInsertionIndicator =
822 unsafe { core::mem::transmute(symbol) };
823 Some(unsafe { function() }.as_bool())
824}
825
826#[cfg(xdg_desktop_settings)]
827impl Drop for Backend {
828 fn drop(&mut self) {
829 if let Some(handle) = self.xdg_watcher.borrow_mut().take() {
830 handle.abort();
831 }
832 }
833}
834
835impl i_slint_core::platform::Platform for Backend {
836 fn bind_context(&self, _ctx: i_slint_core::SlintContextWeak, _: i_slint_core::InternalToken) {
837 let _ = self.shared_data.context.set(_ctx.clone());
838 #[cfg(xdg_desktop_settings)]
839 {
840 *self.xdg_watcher.borrow_mut() =
841 crate::xdg_desktop_settings::spawn(&self.shared_data, &_ctx);
842 }
843 #[cfg(target_os = "windows")]
844 if let Some(ctx) = _ctx.upgrade() {
845 use windows::Win32::UI::HiDpi::SystemParametersInfoForDpi;
846 use windows::Win32::UI::WindowsAndMessaging::{
847 NONCLIENTMETRICSW, SPI_GETNONCLIENTMETRICS,
848 };
849 let mut metrics = NONCLIENTMETRICSW {
850 cbSize: core::mem::size_of::<NONCLIENTMETRICSW>() as u32,
851 ..NONCLIENTMETRICSW::default()
852 };
853 let ok = unsafe {
854 SystemParametersInfoForDpi(
855 SPI_GETNONCLIENTMETRICS.0,
856 metrics.cbSize,
857 Some(&mut metrics as *mut _ as *mut core::ffi::c_void),
858 0,
859 96,
860 )
861 }
862 .is_ok();
863 let height = metrics.lfMessageFont.lfHeight.unsigned_abs();
866 if ok && height > 0 {
867 ctx.set_platform_default_font_size(Some(
868 i_slint_core::lengths::LogicalLength::new(height as f32),
869 ));
870 }
871 }
872 }
873
874 fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
875 let mut attrs = WinitWindowAdapter::window_attributes()?;
876
877 if let Some(hook) = &self.window_attributes_hook {
878 attrs = hook(attrs);
879 }
880
881 let adapter = create_renderer(&self.shared_data).map_or_else(
882 |e| {
883 try_create_window_with_fallback_renderer(
884 &self.shared_data,
885 attrs.clone(),
886 &self.shared_data.event_loop_proxy.clone(),
887 #[cfg(all(muda, target_os = "macos"))]
888 self.muda_enable_default_menu_bar_bar,
889 )
890 .ok_or_else(|| format!("Winit backend failed to find a suitable renderer: {e}"))
891 },
892 |renderer| {
893 Ok(WinitWindowAdapter::new(
894 self.shared_data.clone(),
895 renderer,
896 attrs.clone(),
897 #[cfg(any(enable_accesskit, muda))]
898 self.shared_data.event_loop_proxy.clone(),
899 #[cfg(all(muda, target_os = "macos"))]
900 self.muda_enable_default_menu_bar_bar,
901 ))
902 },
903 )?;
904 Ok(adapter)
905 }
906
907 fn run_event_loop(&self) -> Result<(), PlatformError> {
908 let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
909 EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
910 });
911 #[cfg(target_family = "wasm")]
912 {
913 if self.spawn_event_loop {
914 return loop_state.spawn();
915 }
916 }
917 self.shared_data.event_loop_generation.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
919 let new_state = loop_state.run()?;
920 *self.event_loop_state.borrow_mut() = Some(new_state);
921 Ok(())
922 }
923
924 #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]
925 fn process_events(
926 &self,
927 timeout: Option<core::time::Duration>,
928 _: i_slint_core::InternalToken,
929 ) -> Result<core::ops::ControlFlow<()>, PlatformError> {
930 let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
931 EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
932 });
933 let (new_state, status) = loop_state.pump_events(timeout)?;
934 *self.event_loop_state.borrow_mut() = Some(new_state);
935 match status {
936 winit::platform::pump_events::PumpStatus::Continue => {
937 Ok(core::ops::ControlFlow::Continue(()))
938 }
939 winit::platform::pump_events::PumpStatus::Exit(code) => {
940 if code == 0 {
941 Ok(core::ops::ControlFlow::Break(()))
942 } else {
943 Err(format!("Event loop exited with non-zero code {code}").into())
944 }
945 }
946 }
947 }
948
949 fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
950 struct Proxy(winit::event_loop::EventLoopProxy<SlintEvent>, Arc<AtomicUsize>);
951 impl EventLoopProxy for Proxy {
952 fn quit_event_loop(&self) -> Result<(), EventLoopError> {
953 let generation = self.1.load(std::sync::atomic::Ordering::Relaxed);
954 self.0
955 .send_event(SlintEvent(CustomEvent::Exit(generation)))
956 .map_err(|_| EventLoopError::EventLoopTerminated)
957 }
958
959 fn invoke_from_event_loop(
960 &self,
961 event: Box<dyn FnOnce() + Send>,
962 ) -> Result<(), EventLoopError> {
963 #[cfg(target_arch = "wasm32")]
973 self.0
974 .send_event(SlintEvent(CustomEvent::WakeEventLoopWorkaround))
975 .map_err(|_| EventLoopError::EventLoopTerminated)?;
976
977 self.0
978 .send_event(SlintEvent(CustomEvent::UserEvent(event)))
979 .map_err(|_| EventLoopError::EventLoopTerminated)
980 }
981 }
982 *GLOBAL_PROXY.lock().unwrap() = Some(self.shared_data.event_loop_proxy.clone());
983 Some(Box::new(Proxy(
984 self.shared_data.event_loop_proxy.clone(),
985 Arc::clone(&self.shared_data.event_loop_generation),
986 )))
987 }
988
989 #[cfg(target_arch = "wasm32")]
990 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
991 crate::wasm_input_helper::set_clipboard_text(text.into(), clipboard);
992 }
993
994 #[cfg(not(target_arch = "wasm32"))]
995 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
996 let mut pair = self.shared_data.clipboard.borrow_mut();
997 if let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard) {
998 clipboard.set_contents(text.into()).ok();
999 }
1000 }
1001
1002 #[cfg(target_arch = "wasm32")]
1003 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
1004 crate::wasm_input_helper::get_clipboard_text(clipboard)
1005 }
1006
1007 #[cfg(not(target_arch = "wasm32"))]
1008 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
1009 let mut pair = self.shared_data.clipboard.borrow_mut();
1010 clipboard::select_clipboard(&mut pair, clipboard).and_then(|c| c.get_contents().ok())
1011 }
1012
1013 #[cfg(target_os = "windows")]
1014 fn cursor_flash_cycle(&self) -> core::time::Duration {
1015 use windows::Win32::UI::WindowsAndMessaging::GetCaretBlinkTime;
1016 let ms = unsafe { GetCaretBlinkTime() };
1017 if ms == u32::MAX {
1018 core::time::Duration::ZERO
1020 } else if ms == 0 {
1021 DEFAULT_CURSOR_FLASH_CYCLE
1022 } else {
1023 core::time::Duration::from_millis(ms as u64 * 2)
1025 }
1026 }
1027
1028 #[cfg(target_os = "macos")]
1029 fn cursor_flash_cycle(&self) -> core::time::Duration {
1030 if prefers_non_blinking_text_insertion_indicator() == Some(true) {
1031 return core::time::Duration::ZERO;
1032 }
1033
1034 let defaults = objc2_foundation::NSUserDefaults::standardUserDefaults();
1035 let key = objc2_foundation::NSString::from_str("NSTextInsertionPointBlinkPeriod");
1036 let period = defaults.integerForKey(&key);
1037 if period < 0 {
1038 core::time::Duration::ZERO
1039 } else if period == 0 {
1040 DEFAULT_CURSOR_FLASH_CYCLE
1041 } else {
1042 core::time::Duration::from_millis(period as u64)
1043 }
1044 }
1045
1046 #[cfg(target_os = "ios")]
1047 fn cursor_flash_cycle(&self) -> core::time::Duration {
1048 if prefers_non_blinking_text_insertion_indicator() == Some(true) {
1049 core::time::Duration::ZERO
1050 } else {
1051 DEFAULT_CURSOR_FLASH_CYCLE
1052 }
1053 }
1054
1055 #[cfg(xdg_desktop_settings)]
1056 fn cursor_flash_cycle(&self) -> core::time::Duration {
1057 self.shared_data.desktop_settings.cursor_flash_cycle()
1058 }
1059
1060 fn open_url(&self, url: &str) -> Result<(), i_slint_core::platform::PlatformError> {
1061 webbrowser::open(url).map_err(|e| {
1062 i_slint_core::platform::PlatformError::Other(format!("Failed to open URL: {e}"))
1063 })
1064 }
1065}
1066
1067mod private {
1068 pub trait WinitWindowAccessorSealed {}
1069}
1070
1071#[i_slint_core_macros::slint_doc]
1072pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed {
1085 fn has_winit_window(&self) -> bool;
1088 fn with_winit_window<T>(&self, callback: impl FnOnce(&winit::window::Window) -> T)
1091 -> Option<T>;
1092 fn on_winit_window_event(
1100 &self,
1101 callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
1102 + 'static,
1103 );
1104
1105 fn winit_window(
1148 &self,
1149 ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>>;
1150}
1151
1152impl WinitWindowAccessor for i_slint_core::api::Window {
1153 fn has_winit_window(&self) -> bool {
1154 i_slint_core::window::WindowInner::from_pub(self)
1155 .window_adapter()
1156 .internal(i_slint_core::InternalToken)
1157 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1158 .is_some_and(|adapter| adapter.winit_window().is_some())
1159 }
1160
1161 fn with_winit_window<T>(
1162 &self,
1163 callback: impl FnOnce(&winit::window::Window) -> T,
1164 ) -> Option<T> {
1165 i_slint_core::window::WindowInner::from_pub(self)
1166 .window_adapter()
1167 .internal(i_slint_core::InternalToken)
1168 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1169 .and_then(|adapter| adapter.winit_window().map(|w| callback(&w)))
1170 }
1171
1172 fn winit_window(
1173 &self,
1174 ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>> {
1175 Box::pin(async move {
1176 let adapter_weak = i_slint_core::window::WindowInner::from_pub(self)
1177 .window_adapter()
1178 .internal(i_slint_core::InternalToken)
1179 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1180 .map(|wa| wa.self_weak.clone())
1181 .ok_or_else(|| {
1182 PlatformError::OtherError(
1183 "Slint window is not backed by a Winit window adapter".to_string().into(),
1184 )
1185 })?;
1186 WinitWindowAdapter::async_winit_window(adapter_weak).await
1187 })
1188 }
1189
1190 fn on_winit_window_event(
1191 &self,
1192 mut callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
1193 + 'static,
1194 ) {
1195 if let Some(adapter) = i_slint_core::window::WindowInner::from_pub(self)
1196 .window_adapter()
1197 .internal(i_slint_core::InternalToken)
1198 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1199 {
1200 adapter
1201 .window_event_filter
1202 .set(Some(Box::new(move |window, event| callback(window, event))));
1203 }
1204 }
1205}
1206
1207fn create_renderer(
1209 shared_data: &Rc<SharedBackendData>,
1210) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError> {
1211 match (shared_data.renderer_name.as_deref(), shared_data.requested_graphics_api.as_ref()) {
1212 #[cfg(all(feature = "renderer-femtovg", supports_opengl))]
1213 (Some("gl"), maybe_graphics_api) | (Some("femtovg"), maybe_graphics_api) => {
1214 if let Some(api) = maybe_graphics_api {
1216 i_slint_core::graphics::RequestedOpenGLVersion::try_from(api)?;
1217 }
1218 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(shared_data)
1219 }
1220 #[cfg(feature = "renderer-femtovg-wgpu")]
1221 (Some("femtovg-wgpu"), maybe_graphics_api) => {
1222 if let Some(_api) = maybe_graphics_api {
1223 #[cfg(feature = "unstable-wgpu-30")]
1224 if !matches!(_api, RequestedGraphicsAPI::WGPU30(..)) {
1225 return Err(
1226 "The FemtoVG WGPU renderer only supports the WGPU30 graphics API selection"
1227 .into(),
1228 );
1229 }
1230 }
1231 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended(shared_data)
1232 }
1233 #[cfg(enable_skia_renderer)]
1234 (Some("skia"), maybe_graphics_api) => {
1235 (renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(maybe_graphics_api)?)(
1236 shared_data,
1237 )
1238 }
1239 #[cfg(all(enable_skia_renderer, supports_opengl))]
1240 (Some("skia-opengl"), maybe_graphics_api) => {
1241 if let Some(api) = maybe_graphics_api {
1243 i_slint_core::graphics::RequestedOpenGLVersion::try_from(api)?;
1244 }
1245 renderer::skia::WinitSkiaRenderer::new_opengl_suspended(shared_data)
1246 }
1247 #[cfg(enable_skia_renderer)]
1248 (Some("skia-wgpu"), maybe_graphics_api) => match maybe_graphics_api {
1249 None => renderer::skia::WinitSkiaRenderer::new_wgpu_30_suspended(shared_data),
1250 #[cfg(feature = "unstable-wgpu-30")]
1251 Some(RequestedGraphicsAPI::WGPU30(..)) => {
1253 renderer::skia::WinitSkiaRenderer::new_wgpu_30_suspended(shared_data)
1254 }
1255 #[cfg(feature = "unstable-wgpu-29")]
1256 Some(RequestedGraphicsAPI::WGPU29(..)) => {
1257 renderer::skia::WinitSkiaRenderer::new_wgpu_29_suspended(shared_data)
1258 }
1259 Some(_) => {
1260 Err("Skia with WGPU doesn't support non-WGPU graphics API".to_string().into())
1261 }
1262 },
1263 #[cfg(all(enable_skia_renderer, not(target_os = "android")))]
1264 (Some("skia-software"), None) => {
1265 renderer::skia::WinitSkiaRenderer::new_software_suspended(shared_data)
1266 }
1267 #[cfg(feature = "renderer-software")]
1268 (Some("sw"), None) | (Some("software"), None) => {
1269 renderer::sw::WinitSoftwareRenderer::new_suspended(shared_data)
1270 }
1271 #[cfg(feature = "renderer-vello")]
1272 (Some("vello"), maybe_graphics_api) => {
1273 if let Some(api) = maybe_graphics_api
1276 && !matches!(api, RequestedGraphicsAPI::WGPU29(..))
1277 {
1278 return Err(
1279 "The vello renderer only supports the WGPU29 graphics API selection".into()
1280 );
1281 }
1282 renderer::vello::WinitVelloRenderer::new_suspended(shared_data)
1283 }
1284 (None, None) => default_renderer_factory(shared_data),
1285 (Some(renderer_name), _) => {
1286 if shared_data.allow_fallback {
1287 eprintln!(
1288 "slint winit: unrecognized renderer {renderer_name}, falling back to {DEFAULT_RENDERER_NAME}"
1289 );
1290 default_renderer_factory(shared_data)
1291 } else {
1292 Err(PlatformError::NoPlatform)
1293 }
1294 }
1295 #[cfg(feature = "unstable-wgpu-29")]
1296 (None, Some(RequestedGraphicsAPI::WGPU29(..))) => {
1297 cfg_if::cfg_if! {
1298 if #[cfg(enable_skia_renderer)] {
1299 renderer::skia::WinitSkiaRenderer::new_wgpu_29_suspended(shared_data)
1300 } else if #[cfg(feature = "renderer-vello")] {
1301 renderer::vello::WinitVelloRenderer::new_suspended(shared_data)
1302 } else {
1303 Err("unstable-wgpu-29 was enabled but no renderer was selected. Please select renderer-skia* or renderer-vello".into())
1304 }
1305 }
1306 }
1307 #[cfg(feature = "unstable-wgpu-30")]
1308 (None, Some(RequestedGraphicsAPI::WGPU30(..))) => {
1309 cfg_if::cfg_if! {
1310 if #[cfg(enable_skia_renderer)] {
1311 renderer::skia::WinitSkiaRenderer::new_wgpu_30_suspended(shared_data)
1312 } else if #[cfg(feature = "renderer-femtovg-wgpu")] {
1313 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended(shared_data)
1314 } else {
1315 Err("unstable-wgpu-30 was enabled but no renderer was selected. Please select either renderer-skia* or renderer-femtovg-wgpu".into())
1316 }
1317 }
1318 }
1319 (None, Some(_requested_graphics_api)) => {
1320 cfg_if::cfg_if! {
1321 if #[cfg(enable_skia_renderer)] {
1322 renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(Some(_requested_graphics_api))?(shared_data)
1323 } else if #[cfg(all(feature = "renderer-femtovg", supports_opengl))] {
1324 i_slint_core::graphics::RequestedOpenGLVersion::try_from(_requested_graphics_api)?;
1326 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(shared_data)
1327 } else {
1328 return Err(format!("Graphics API use requested by the compile-time enabled renderers don't support that").into())
1329 }
1330 }
1331 }
1332 }
1333}
1334
1335impl private::WinitWindowAccessorSealed for i_slint_core::api::Window {}
1336
1337#[cfg(test)]
1338mod testui {
1339 slint::slint! {
1340 export component App inherits Window {
1341 Text { text: "Ok"; }
1342 }
1343 }
1344}
1345
1346#[cfg(not(any(target_arch = "wasm32", target_vendor = "apple")))]
1348#[test]
1349fn test_window_accessor_and_rwh() {
1350 slint::platform::set_platform(Box::new(crate::Backend::new().unwrap())).unwrap();
1351
1352 use testui::*;
1353
1354 slint::spawn_local(async move {
1355 let app = App::new().unwrap();
1356 let slint_window = app.window();
1357
1358 assert!(!slint_window.has_winit_window());
1359
1360 app.show().unwrap();
1363
1364 let result = slint_window.winit_window().await;
1365 assert!(result.is_ok(), "Failed to get winit window: {:?}", result.err());
1366 assert!(slint_window.has_winit_window());
1367 let handle = slint_window.window_handle();
1368 use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
1369 assert!(handle.window_handle().is_ok());
1370 assert!(handle.display_handle().is_ok());
1371 slint::quit_event_loop().unwrap();
1372 })
1373 .unwrap();
1374
1375 slint::run_event_loop().unwrap();
1376}