Skip to main content

i_slint_backend_winit/
lib.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore binfmt dlsym GETNONCLIENTMETRICS NONCLIENTMETRICSW RTLD testui
5#![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
44/// Re-export of the winit crate.
45pub use winit;
46
47/// Internal type used by the winit backend for thread communication and window system updates.
48///
49/// See also [`EventLoopBuilder`]
50#[non_exhaustive]
51#[derive(Debug)]
52pub struct SlintEvent(CustomEvent);
53
54#[i_slint_core_macros::slint_doc]
55/// Convenience alias for the event loop builder used by Slint.
56///
57/// It can be used to configure the event loop with
58/// [`slint::BackendSelector::with_winit_event_loop_builder()`](slint:rust:slint/struct.BackendSelector.html#method.with_winit_event_loop_builder)
59pub type EventLoopBuilder = winit::event_loop::EventLoopBuilder<SlintEvent>;
60
61/// Returned by callbacks passed to [`Window::on_winit_window_event`](WinitWindowAccessor::on_winit_window_event)
62/// to determine if winit events should propagate to the Slint event loop.
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
64pub enum EventResult {
65    /// The winit event should propagate normally.
66    Propagate,
67    /// The winit event shouldn't be processed further.
68    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        // Got WindowEvent::Occluded
84        fn occluded(&self, _: bool) {}
85
86        fn suspend(&self) -> Result<(), PlatformError>;
87
88        // The window's transparency changed after the window was created. Renderers that pick
89        // their surface's alpha mode up front have to reconfigure it to match.
90        #[cfg(target_os = "macos")]
91        fn set_transparent(&self, _transparent: bool) -> Result<(), PlatformError> {
92            Ok(())
93        }
94
95        // Got winit::Event::Resumed
96        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            // Last in the chain: vello is opt-in and only becomes the default
153            // when it is the only renderer built in.
154            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/// Use this trait to intercept events from winit.
211///
212/// It imitates [`winit::application::ApplicationHandler`] with two changes:
213///   - All functions are invoked before Slint sees them. Use the [`EventResult`] return value to
214///     optionally prevent Slint from seeing the event.
215///   - The [`Self::window_event()`] function has additional parameters to provide access to the Slint Window and
216///     Winit window, if applicable.
217#[allow(unused_variables)]
218pub trait CustomApplicationHandler {
219    /// Re-implement to intercept the [`ApplicationHandler::resumed()`](winit::application::ApplicationHandler::resumed()) event.
220    fn resumed(&mut self, _event_loop: &ActiveEventLoop) -> EventResult {
221        EventResult::Propagate
222    }
223
224    /// Re-implement to intercept the [`ApplicationHandler::window_event()`](winit::application::ApplicationHandler::window_event()) event.
225    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    /// Re-implement to intercept the [`ApplicationHandler::new_events()`](winit::application::ApplicationHandler::new_events()) event.
237    fn new_events(
238        &mut self,
239        event_loop: &ActiveEventLoop,
240        cause: winit::event::StartCause,
241    ) -> EventResult {
242        EventResult::Propagate
243    }
244
245    /// Re-implement to intercept the [`ApplicationHandler::device_event()`](winit::application::ApplicationHandler::device_event()) event.
246    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    /// Re-implement to intercept the [`ApplicationHandler::about_to_wait()`](winit::application::ApplicationHandler::about_to_wait()) event.
256    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
257        EventResult::Propagate
258    }
259
260    /// Re-implement to intercept the [`ApplicationHandler::suspended()`](winit::application::ApplicationHandler::suspended()) event.
261    fn suspended(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
262        EventResult::Propagate
263    }
264
265    /// Re-implement to intercept the [`ApplicationHandler::exiting()`](winit::application::ApplicationHandler::exiting()) event.
266    fn exiting(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
267        EventResult::Propagate
268    }
269
270    /// Re-implement to intercept the [`ApplicationHandler::memory_warning()`](winit::application::ApplicationHandler::memory_warning()) event.
271    fn memory_warning(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
272        EventResult::Propagate
273    }
274}
275
276/// Use the BackendBuilder to configure the properties of the Winit Backend before creating it.
277/// Create the builder using [`Backend::builder()`], then configure it for example with [`Self::with_renderer_name`],
278/// and build the backend using [`Self::build`].
279pub struct BackendBuilder {
280    /// Allow fallback if the desired renderer is not found
281    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    /// Configures this builder to require a renderer that supports the specified graphics API.
296    #[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    /// Configures this builder to use the specified renderer name when building the backend later.
303    /// Pass `renderer-software` for example to configure the backend to use the Slint software renderer.
304    #[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    /// Configures this builder to use the specified hook that will be called before a Window is created.
311    ///
312    /// It can be used to adjust settings of window that will be created.
313    ///
314    /// # Example
315    ///
316    /// ```rust,no_run
317    /// let mut backend = i_slint_backend_winit::Backend::builder()
318    ///     .with_window_attributes_hook(|attributes| attributes.with_content_protected(true))
319    ///     .build()
320    ///     .unwrap();
321    /// slint::platform::set_platform(Box::new(backend));
322    /// ```
323    #[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    /// Configures this builder to use the specified event loop builder when creating the event
333    /// loop during a subsequent call to [`Self::build`].
334    #[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    /// Configures this builder to enable or disable the default menu bar.
341    /// By default, the menu bar is provided by Slint. Set this to false
342    /// if you're providing your own menu bar.
343    /// Note that an application provided menu bar will be overridden by a `MenuBar`
344    /// declared in Slint code.
345    #[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    /// Configures this builder to spawn the event loop using [`winit::platform::web::EventLoopExtWebSys::spawn()`]
354    /// run `run_event_loop()` is called.
355    pub fn with_spawn_event_loop(mut self, enable: bool) -> Self {
356        self.spawn_event_loop = enable;
357        self
358    }
359
360    /// Configures this builder to use the specified [`CustomApplicationHandler`].
361    ///
362    /// This allow application developer to intercept events from winit.
363    /// Similar to [`winit::application::ApplicationHandler`].
364    #[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    /// Builds the backend with the parameters configured previously. Set the resulting backend
374    /// with `slint::platform::set_platform()`:
375    ///
376    /// # Example
377    ///
378    /// ```rust,no_run
379    /// let mut backend = i_slint_backend_winit::Backend::builder()
380    ///     .with_renderer_name("renderer-software")
381    ///     .build()
382    ///     .unwrap();
383    /// slint::platform::set_platform(Box::new(backend));
384    /// ```
385    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        // Never use winit's menu bar. Either we provide one ourselves with muda, or
391        // the user provides one.
392        #[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        // Initialize the winit event loop and propagate errors if for example `DISPLAY` or `WAYLAND_DISPLAY` isn't set.
399
400        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 if the desired renderer is not found
433    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    /// List of visible windows that have been created when without the event loop and
440    /// need to be mapped to a winit Window as soon as the event loop becomes active.
441    inactive_windows: RefCell<Vec<Weak<WinitWindowAdapter>>>,
442    /// Buffered mouse move event pending dispatch. Consecutive `CursorMoved` events are coalesced,
443    /// as winit sends them so frequently that it can cause performance issues (see #9038 and #10912).
444    /// At most one window buffers a move at a time.
445    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    /// The generation is used to determine if a quit_event_loop call is meant for the current
451    /// event loop or is from a stale event.
452    event_loop_generation: Arc<AtomicUsize>,
453    is_wayland: bool,
454    /// Desktop settings read from the XDG portal (cursor blink, appearance query).
455    #[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    /// Panics if the backend is not bound: an event loop only runs inside a live context.
464    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                // Under WSL, the compositor sometimes crashes. Since we cannot reconnect after the compositor
493                // was restarted, the application panics. This does not happen when using XWayland. Therefore,
494                // when running under WSL, try to connect to X11 instead.
495                #[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        // UIKit connects the scene from `UIApplicationMain`, so the class named in
532        // the app's `UIApplicationSceneManifest` has to be registered before then.
533        #[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    // Disable automatic keyboard shortcut localization on macOS by injecting
567    // applicationShouldAutomaticallyLocalizeKeyEquivalents: into winit's delegate class.
568    //
569    // This is necessary to make the keyboard shortcuts declared in Slint work as intended on macOS, instead of being automatically localized by the system.
570    //
571    // This is done at runtime because winit 0.30 doesn't allow replacing its delegate.
572    // TODO: Replace with a proper delegate class when upgrading to the next winit version.
573    #[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            // Use this opportunity of a Window being removed to tidy up.
621            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        // Wait for the appearance query so windows aren't shown with default colors;
632        // the next `about_to_wait` retries once it clears.
633        #[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    /// Buffer a mouse move event for the given window, coalescing it with the previously buffered
655    /// one. A move buffered for another window is dispatched first, to keep the events in order.
656    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    /// Dispatch the buffered mouse move event, if any.
670    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]
678/// This struct implements the Slint Platform trait.
679/// Use this in conjunction with [`slint::platform::set_platform`](slint:rust:slint/platform/fn.set_platform.html) to initialize.
680/// Slint to use winit for all windowing system interaction.
681///
682/// ```rust,no_run
683/// use i_slint_backend_winit::Backend;
684/// slint::platform::set_platform(Box::new(Backend::new().unwrap()));
685/// ```
686pub 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    /// Backend-wide XDG desktop portal watcher. Spawned in `bind_context`
691    /// and aborted on backend drop.
692    #[cfg(xdg_desktop_settings)]
693    xdg_watcher: RefCell<Option<i_slint_core::future::JoinHandle<()>>>,
694
695    /// This hook is called before a Window is created.
696    ///
697    /// It can be used to adjust settings of window that will be created
698    ///
699    /// See also [`BackendBuilder::with_window_attributes_hook`].
700    ///
701    /// # Example
702    ///
703    /// ```rust,no_run
704    /// let mut backend = i_slint_backend_winit::Backend::new().unwrap();
705    /// backend.window_attributes_hook = Some(Box::new(|attributes| attributes.with_content_protected(true)));
706    /// slint::platform::set_platform(Box::new(backend));
707    /// ```
708    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    /// Creates a new winit backend with the default renderer that's compiled in.
721    ///
722    /// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer.
723    pub fn new() -> Result<Self, PlatformError> {
724        Self::builder().build()
725    }
726
727    #[i_slint_core_macros::slint_doc]
728    /// Creates a new winit backend with the renderer specified by name.
729    ///
730    /// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer.
731    ///
732    /// If the renderer name is `None` or the name is not recognized, the default renderer is selected.
733    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    /// Creates a new BackendBuilder for configuring aspects of the Winit backend before
742    /// setting it as the platform backend.
743    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
759/// Proxy of the event loop of the winit backend that was installed as the platform, so
760/// that [`invoke_from_active_event_loop`] can reach it from any thread.
761static GLOBAL_PROXY: std::sync::Mutex<Option<winit::event_loop::EventLoopProxy<SlintEvent>>> =
762    std::sync::Mutex::new(None);
763
764/// Schedules a callback to be invoked in the winit event loop, and passes winit's
765/// [`ActiveEventLoop`] to it.
766///
767/// This is similar to [`slint::invoke_from_event_loop`](i_slint_core::api::invoke_from_event_loop),
768/// but the callback also receives the [`ActiveEventLoop`], which winit only exposes while the
769/// event loop is running. Use it to call winit APIs that need it, for example to create custom
770/// windows.
771///
772/// This function can be called from any thread. It returns an error if the winit backend hasn't
773/// been installed yet, or if the event loop has terminated.
774pub 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    // AXPrefersNonBlinkingTextInsertionIndicator is available starting with macOS 15 and iOS 18,
799    // and the Accessibility framework itself only exists since macOS 11 and iOS 14.
800    // Load both dynamically: a `#[link]` attribute would emit a strong load command that makes
801    // dyld abort before main() on older systems. When the framework or the symbol is
802    // unavailable, the accessibility setting is unavailable and we keep the existing cursor
803    // blink behavior.
804    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            // `lfMessageFont.lfHeight` is in pixels at 96 DPI = Slint logical pixels;
864            // negative means em height, positive means cell height — magnitude is fine here.
865            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        // Note: fetch_add wraps around on overflow, which is what we want.
918        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                // Calling send_event is usually done by winit at the bottom of the stack,
964                // in event handlers, and thus winit might decide to process the event
965                // immediately within that stack.
966                // To prevent re-entrancy issues that might happen by getting the application
967                // event processed on top of the current stack, set winit in Poll mode so that
968                // events are queued and process on top of a clean stack during a requested animation
969                // frame a few moments later.
970                // This also allows batching multiple post_event calls and redraw their state changes
971                // all at once.
972                #[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            // INFINITE — blinking disabled
1019            core::time::Duration::ZERO
1020        } else if ms == 0 {
1021            DEFAULT_CURSOR_FLASH_CYCLE
1022        } else {
1023            // Win32 returns the half-cycle duration
1024            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]
1072/// This helper trait can be used to obtain access to the [`winit::window::Window`] for a given
1073/// [`slint::Window`](slint:rust:slint/struct.window).
1074///
1075/// Note that the association of a Slint window with a winit window relies on two factors:
1076///
1077/// - The winit backend must be in use. You can ensure this programmatically by calling [`slint::BackendSelector::backend_name()`](slint:rust:slint/struct.BackendSelector#method.backend_name)
1078///   with "winit" as argument.
1079/// - The winit window must've been created. Windowing systems, and by extension winit, require that windows can only be properly
1080///   created when certain conditions of the event loop are met. For example, on Android the application can't be suspended. Therefore,
1081///   functions like [`Self::has_winit_window()`] or [`Self::with_winit_window()`] will only succeed when the event loop is active.
1082///   This is typically the case when callbacks are invoked from the event loop, such as through timers, user input events, or when window
1083///   receives events (see also [`Self::on_winit_window_event()`]).
1084pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed {
1085    /// Returns true if a [`winit::window::Window`] exists for this window. This is the case if the window is
1086    /// backed by this winit backend.
1087    fn has_winit_window(&self) -> bool;
1088    /// Invokes the specified callback with a reference to the [`winit::window::Window`] that exists for this Slint window
1089    /// and returns `Some(T)`; otherwise `None`.
1090    fn with_winit_window<T>(&self, callback: impl FnOnce(&winit::window::Window) -> T)
1091    -> Option<T>;
1092    /// Registers a window event filter callback for this Slint window.
1093    ///
1094    /// The callback is invoked in the winit event loop whenever a window event is received with a reference to the
1095    /// [`slint::Window`](i_slint_core::api::Window) and the [`winit::event::WindowEvent`]. The return value of the
1096    /// callback specifies whether Slint should handle this event.
1097    ///
1098    /// If this window [is not backed by winit](WinitWindowAccessor::has_winit_window), this function is a no-op.
1099    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    /// Returns a future that resolves to the [`winit::window::Window`] for this Slint window.
1106    /// When the future is ready, the output it resolves to is either `Ok(Arc<winit::window::Window>)` if the window exists,
1107    /// or an error if the window has been deleted in the meanwhile or isn't backed by the winit backend.
1108    ///
1109    /// ```rust,no_run
1110    /// // Bring winit and accessor traits into scope.
1111    /// use slint::winit_030::{WinitWindowAccessor, winit};
1112    ///
1113    /// slint::slint!{
1114    ///     import { VerticalBox, Button } from "std-widgets.slint";
1115    ///     export component HelloWorld inherits Window {
1116    ///         callback clicked;
1117    ///         VerticalBox {
1118    ///             Text {
1119    ///                 text: "hello world";
1120    ///                 color: green;
1121    ///             }
1122    ///             Button {
1123    ///                 text: "Click me";
1124    ///                 clicked => { root.clicked(); }
1125    ///             }
1126    ///         }
1127    ///     }
1128    /// }
1129    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1130    ///     // Make sure the winit backed is selected:
1131    ///    slint::BackendSelector::new()
1132    ///        .backend_name("winit".into())
1133    ///        .select()?;
1134    ///
1135    ///     let app = HelloWorld::new()?;
1136    ///     let app_weak = app.as_weak();
1137    ///
1138    ///     slint::spawn_local(async move {
1139    ///         let app = app_weak.unwrap();
1140    ///         let winit_window = app.window().winit_window().await.unwrap();
1141    ///         eprintln!("window id = {:#?}", winit_window.id());
1142    ///     }).unwrap();
1143    ///     app.run()?;
1144    ///     Ok(())
1145    /// }
1146    /// ```
1147    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
1207/// Creates a new renderer from the backend properties in `shared_data`
1208fn 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 a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc.
1215            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 a graphics API was requested, double check that it's GL.
1242            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            // this is always enabled when skia is enabled, but rust-analyzer can get confused
1252            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            // vello renders through WGPU 29; anything else was not created by
1274            // this renderer and cannot be adopted.
1275            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                    // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc.
1325                    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// Sorry, can't test with rust test harness and multiple threads.
1347#[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        // Show() won't immediately create the window, the event loop will have to
1361        // spin first.
1362        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}