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 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::platform::{EventLoopProxy, PlatformError};
17use i_slint_core::window::WindowAdapter;
18use renderer::WinitCompatibleRenderer;
19use std::cell::RefCell;
20use std::collections::HashMap;
21use std::rc::Rc;
22use std::rc::Weak;
23use std::sync::Arc;
24use std::sync::atomic::AtomicUsize;
25use winit::event_loop::ActiveEventLoop;
26
27#[cfg(not(target_arch = "wasm32"))]
28mod clipboard;
29mod drag_resize_window;
30mod winit_compat;
31mod winitwindowadapter;
32use winitwindowadapter::*;
33pub(crate) mod event_loop;
34mod frame_throttle;
35#[cfg(target_os = "ios")]
36mod ios;
37
38/// Re-export of the winit crate.
39pub use winit;
40
41/// Internal type used by the winit backend for thread communication and window system updates.
42///
43/// See also [`EventLoopBuilder`]
44#[non_exhaustive]
45#[derive(Debug)]
46pub struct SlintEvent(CustomEvent);
47
48#[i_slint_core_macros::slint_doc]
49/// Convenience alias for the event loop builder used by Slint.
50///
51/// It can be used to configure the event loop with
52/// [`slint::BackendSelector::with_winit_event_loop_builder()`](slint:rust:slint/struct.BackendSelector.html#method.with_winit_event_loop_builder)
53pub type EventLoopBuilder = winit::event_loop::EventLoopBuilder<SlintEvent>;
54
55/// Returned by callbacks passed to [`Window::on_winit_window_event`](WinitWindowAccessor::on_winit_window_event)
56/// to determine if winit events should propagate to the Slint event loop.
57pub enum EventResult {
58    /// The winit event should propagate normally.
59    Propagate,
60    /// The winit event shouldn't be processed further.
61    PreventDefault,
62}
63
64mod renderer {
65    use std::sync::Arc;
66
67    use i_slint_core::platform::PlatformError;
68    use i_slint_core::renderer::DrawOutcome;
69    use winit::event_loop::ActiveEventLoop;
70
71    pub trait WinitCompatibleRenderer: std::any::Any {
72        fn render(&self, window: &i_slint_core::api::Window) -> Result<DrawOutcome, PlatformError>;
73
74        fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
75        // Got WindowEvent::Occluded
76        fn occluded(&self, _: bool) {}
77
78        fn suspend(&self) -> Result<(), PlatformError>;
79
80        // Got winit::Event::Resumed
81        fn resume(
82            &self,
83            active_event_loop: &ActiveEventLoop,
84            window_attributes: winit::window::WindowAttributes,
85        ) -> Result<Arc<winit::window::Window>, PlatformError>;
86    }
87
88    #[cfg(enable_femtovg_renderer)]
89    pub(crate) mod femtovg;
90    #[cfg(enable_skia_renderer)]
91    pub(crate) mod skia;
92
93    #[cfg(feature = "renderer-software")]
94    pub(crate) mod sw;
95}
96
97#[cfg(enable_accesskit)]
98mod accesskit;
99#[cfg(muda)]
100mod muda;
101#[cfg(xdg_desktop_settings)]
102mod xdg_desktop_settings;
103
104#[cfg(target_arch = "wasm32")]
105pub(crate) mod wasm_input_helper;
106
107cfg_if::cfg_if! {
108    if #[cfg(enable_femtovg_renderer)] {
109        const DEFAULT_RENDERER_NAME: &str = "FemtoVG";
110    } else if #[cfg(enable_skia_renderer)] {
111        const DEFAULT_RENDERER_NAME: &str = "Skia";
112    } else if #[cfg(feature = "renderer-software")] {
113        const DEFAULT_RENDERER_NAME: &str = "Software";
114    } else {
115        compile_error!("Please select a feature to build with the winit backend: `renderer-femtovg`, `renderer-skia`, `renderer-skia-opengl`, `renderer-skia-vulkan` or `renderer-software`");
116    }
117}
118
119fn default_renderer_factory(
120    shared_backend_data: &Rc<SharedBackendData>,
121) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError> {
122    cfg_if::cfg_if! {
123        if #[cfg(enable_skia_renderer)] {
124            renderer::skia::WinitSkiaRenderer::new_suspended(shared_backend_data)
125        } else if #[cfg(feature = "renderer-femtovg-wgpu")] {
126            renderer::femtovg::WGPUFemtoVGRenderer::new_suspended(shared_backend_data)
127        } else if #[cfg(all(feature = "renderer-femtovg", supports_opengl))] {
128            renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(shared_backend_data)
129        } else if #[cfg(feature = "renderer-software")] {
130            renderer::sw::WinitSoftwareRenderer::new_suspended(shared_backend_data)
131        } else {
132            compile_error!("Please select a feature to build with the winit backend: `renderer-femtovg`, `renderer-skia`, `renderer-skia-opengl`, `renderer-skia-vulkan` or `renderer-software`");
133        }
134    }
135}
136
137fn try_create_window_with_fallback_renderer(
138    shared_backend_data: &Rc<SharedBackendData>,
139    attrs: winit::window::WindowAttributes,
140    _proxy: &winit::event_loop::EventLoopProxy<SlintEvent>,
141    #[cfg(all(muda, target_os = "macos"))] muda_enable_default_menu_bar: bool,
142) -> Option<Rc<WinitWindowAdapter>> {
143    [
144        #[cfg(any(
145            feature = "renderer-skia",
146            feature = "renderer-skia-opengl",
147            feature = "renderer-skia-vulkan"
148        ))]
149        renderer::skia::WinitSkiaRenderer::new_suspended,
150        #[cfg(feature = "renderer-femtovg-wgpu")]
151        renderer::femtovg::WGPUFemtoVGRenderer::new_suspended,
152        #[cfg(all(
153            feature = "renderer-femtovg",
154            supports_opengl,
155            not(feature = "renderer-femtovg-wgpu")
156        ))]
157        renderer::femtovg::GlutinFemtoVGRenderer::new_suspended,
158        #[cfg(feature = "renderer-software")]
159        renderer::sw::WinitSoftwareRenderer::new_suspended,
160    ]
161    .into_iter()
162    .find_map(|renderer_factory| {
163        Some(WinitWindowAdapter::new(
164            shared_backend_data.clone(),
165            renderer_factory(shared_backend_data).ok()?,
166            attrs.clone(),
167            #[cfg(any(enable_accesskit, muda))]
168            _proxy.clone(),
169            #[cfg(all(muda, target_os = "macos"))]
170            muda_enable_default_menu_bar,
171        ))
172    })
173}
174
175#[doc(hidden)]
176pub type NativeWidgets = ();
177#[doc(hidden)]
178pub type NativeGlobals = ();
179#[doc(hidden)]
180pub const HAS_NATIVE_STYLE: bool = false;
181#[doc(hidden)]
182pub mod native_widgets {}
183
184/// Use this trait to intercept events from winit.
185///
186/// It imitates [`winit::application::ApplicationHandler`] with two changes:
187///   - All functions are invoked before Slint sees them. Use the [`EventResult`] return value to
188///     optionally prevent Slint from seeing the event.
189///   - The [`Self::window_event()`] function has additional parameters to provide access to the Slint Window and
190///     Winit window, if applicable.
191#[allow(unused_variables)]
192pub trait CustomApplicationHandler {
193    /// Re-implement to intercept the [`ApplicationHandler::resumed()`](winit::application::ApplicationHandler::resumed()) event.
194    fn resumed(&mut self, _event_loop: &ActiveEventLoop) -> EventResult {
195        EventResult::Propagate
196    }
197
198    /// Re-implement to intercept the [`ApplicationHandler::window_event()`](winit::application::ApplicationHandler::window_event()) event.
199    fn window_event(
200        &mut self,
201        event_loop: &ActiveEventLoop,
202        window_id: winit::window::WindowId,
203        winit_window: Option<&winit::window::Window>,
204        slint_window: Option<&i_slint_core::api::Window>,
205        event: &winit::event::WindowEvent,
206    ) -> EventResult {
207        EventResult::Propagate
208    }
209
210    /// Re-implement to intercept the [`ApplicationHandler::new_events()`](winit::application::ApplicationHandler::new_events()) event.
211    fn new_events(
212        &mut self,
213        event_loop: &ActiveEventLoop,
214        cause: winit::event::StartCause,
215    ) -> EventResult {
216        EventResult::Propagate
217    }
218
219    /// Re-implement to intercept the [`ApplicationHandler::device_event()`](winit::application::ApplicationHandler::device_event()) event.
220    fn device_event(
221        &mut self,
222        event_loop: &ActiveEventLoop,
223        device_id: winit::event::DeviceId,
224        event: winit::event::DeviceEvent,
225    ) -> EventResult {
226        EventResult::Propagate
227    }
228
229    /// Re-implement to intercept the [`ApplicationHandler::about_to_wait()`](winit::application::ApplicationHandler::about_to_wait()) event.
230    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
231        EventResult::Propagate
232    }
233
234    /// Re-implement to intercept the [`ApplicationHandler::suspended()`](winit::application::ApplicationHandler::suspended()) event.
235    fn suspended(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
236        EventResult::Propagate
237    }
238
239    /// Re-implement to intercept the [`ApplicationHandler::exiting()`](winit::application::ApplicationHandler::exiting()) event.
240    fn exiting(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
241        EventResult::Propagate
242    }
243
244    /// Re-implement to intercept the [`ApplicationHandler::memory_warning()`](winit::application::ApplicationHandler::memory_warning()) event.
245    fn memory_warning(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
246        EventResult::Propagate
247    }
248}
249
250/// Use the BackendBuilder to configure the properties of the Winit Backend before creating it.
251/// Create the builder using [`Backend::builder()`], then configure it for example with [`Self::with_renderer_name`],
252/// and build the backend using [`Self::build`].
253pub struct BackendBuilder {
254    /// Allow fallback if the desired renderer is not found
255    allow_fallback: bool,
256    requested_graphics_api: Option<RequestedGraphicsAPI>,
257    window_attributes_hook:
258        Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
259    renderer_name: Option<String>,
260    event_loop_builder: Option<EventLoopBuilder>,
261    #[cfg(all(muda, target_os = "macos"))]
262    muda_enable_default_menu_bar_bar: bool,
263    #[cfg(target_family = "wasm")]
264    spawn_event_loop: bool,
265    custom_application_handler: Option<Box<dyn CustomApplicationHandler>>,
266}
267
268impl BackendBuilder {
269    /// Configures this builder to require a renderer that supports the specified graphics API.
270    #[must_use]
271    pub fn request_graphics_api(mut self, graphics_api: RequestedGraphicsAPI) -> Self {
272        self.requested_graphics_api = Some(graphics_api);
273        self
274    }
275
276    /// Configures this builder to use the specified renderer name when building the backend later.
277    /// Pass `renderer-software` for example to configure the backend to use the Slint software renderer.
278    #[must_use]
279    pub fn with_renderer_name(mut self, name: impl Into<String>) -> Self {
280        self.renderer_name = Some(name.into());
281        self
282    }
283
284    /// Configures this builder to use the specified hook that will be called before a Window is created.
285    ///
286    /// It can be used to adjust settings of window that will be created.
287    ///
288    /// # Example
289    ///
290    /// ```rust,no_run
291    /// let mut backend = i_slint_backend_winit::Backend::builder()
292    ///     .with_window_attributes_hook(|attributes| attributes.with_content_protected(true))
293    ///     .build()
294    ///     .unwrap();
295    /// slint::platform::set_platform(Box::new(backend));
296    /// ```
297    #[must_use]
298    pub fn with_window_attributes_hook(
299        mut self,
300        hook: impl Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes + 'static,
301    ) -> Self {
302        self.window_attributes_hook = Some(Box::new(hook));
303        self
304    }
305
306    /// Configures this builder to use the specified event loop builder when creating the event
307    /// loop during a subsequent call to [`Self::build`].
308    #[must_use]
309    pub fn with_event_loop_builder(mut self, event_loop_builder: EventLoopBuilder) -> Self {
310        self.event_loop_builder = Some(event_loop_builder);
311        self
312    }
313
314    /// Configures this builder to enable or disable the default menu bar.
315    /// By default, the menu bar is provided by Slint. Set this to false
316    /// if you're providing your own menu bar.
317    /// Note that an application provided menu bar will be overridden by a `MenuBar`
318    /// declared in Slint code.
319    #[must_use]
320    #[cfg(all(muda, target_os = "macos"))]
321    pub fn with_default_menu_bar(mut self, enable: bool) -> Self {
322        self.muda_enable_default_menu_bar_bar = enable;
323        self
324    }
325
326    #[cfg(target_family = "wasm")]
327    /// Configures this builder to spawn the event loop using [`winit::platform::web::EventLoopExtWebSys::spawn()`]
328    /// run `run_event_loop()` is called.
329    pub fn with_spawn_event_loop(mut self, enable: bool) -> Self {
330        self.spawn_event_loop = enable;
331        self
332    }
333
334    /// Configures this builder to use the specified [`CustomApplicationHandler`].
335    ///
336    /// This allow application developer to intercept events from winit.
337    /// Similar to [`winit::application::ApplicationHandler`].
338    #[must_use]
339    pub fn with_custom_application_handler(
340        mut self,
341        handler: Box<dyn CustomApplicationHandler + 'static>,
342    ) -> Self {
343        self.custom_application_handler = Some(handler);
344        self
345    }
346
347    /// Builds the backend with the parameters configured previously. Set the resulting backend
348    /// with `slint::platform::set_platform()`:
349    ///
350    /// # Example
351    ///
352    /// ```rust,no_run
353    /// let mut backend = i_slint_backend_winit::Backend::builder()
354    ///     .with_renderer_name("renderer-software")
355    ///     .build()
356    ///     .unwrap();
357    /// slint::platform::set_platform(Box::new(backend));
358    /// ```
359    pub fn build(self) -> Result<Backend, PlatformError> {
360        #[allow(unused_mut)]
361        let mut event_loop_builder =
362            self.event_loop_builder.unwrap_or_else(winit::event_loop::EventLoop::with_user_event);
363
364        // Never use winit's menu bar. Either we provide one ourselves with muda, or
365        // the user provides one.
366        #[cfg(all(feature = "muda", target_os = "macos"))]
367        winit::platform::macos::EventLoopBuilderExtMacOS::with_default_menu(
368            &mut event_loop_builder,
369            false,
370        );
371
372        // Initialize the winit event loop and propagate errors if for example `DISPLAY` or `WAYLAND_DISPLAY` isn't set.
373
374        let shared_data = Rc::new(SharedBackendData::new(
375            event_loop_builder,
376            self.renderer_name,
377            self.requested_graphics_api.clone(),
378            self.allow_fallback,
379        )?);
380
381        Ok(Backend {
382            event_loop_state: Default::default(),
383            window_attributes_hook: self.window_attributes_hook,
384            shared_data,
385            #[cfg(all(muda, target_os = "macos"))]
386            muda_enable_default_menu_bar_bar: self.muda_enable_default_menu_bar_bar,
387            #[cfg(target_family = "wasm")]
388            spawn_event_loop: self.spawn_event_loop,
389            custom_application_handler: self.custom_application_handler.into(),
390            #[cfg(xdg_desktop_settings)]
391            xdg_watcher: RefCell::new(None),
392        })
393    }
394}
395
396pub(crate) struct SharedBackendData {
397    /// Allow fallback if the desired renderer is not found
398    allow_fallback: bool,
399    renderer_name: Option<String>,
400    requested_graphics_api: Option<RequestedGraphicsAPI>,
401    #[cfg(enable_skia_renderer)]
402    skia_context: i_slint_renderer_skia::SkiaSharedContext,
403    active_windows: Rc<RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>>,
404    /// List of visible windows that have been created when without the event loop and
405    /// need to be mapped to a winit Window as soon as the event loop becomes active.
406    inactive_windows: RefCell<Vec<Weak<WinitWindowAdapter>>>,
407    #[cfg(not(target_arch = "wasm32"))]
408    clipboard: std::cell::RefCell<clipboard::ClipboardPair>,
409    not_running_event_loop: RefCell<Option<winit::event_loop::EventLoop<SlintEvent>>>,
410    event_loop_proxy: winit::event_loop::EventLoopProxy<SlintEvent>,
411    /// The generation is used to determine if a quit_event_loop call is meant for the current
412    /// event loop or is from a stale event.
413    event_loop_generation: Arc<AtomicUsize>,
414    is_wayland: bool,
415    /// Desktop settings read from the XDG portal (cursor blink, appearance query).
416    #[cfg(xdg_desktop_settings)]
417    desktop_settings: xdg_desktop_settings::DesktopSettings,
418    #[cfg(target_os = "ios")]
419    #[allow(unused)]
420    keyboard_notifications: ios::KeyboardNotifications,
421}
422
423impl SharedBackendData {
424    fn new(
425        mut builder: EventLoopBuilder,
426        renderer_name: Option<String>,
427        requested_graphics_api: Option<RequestedGraphicsAPI>,
428        allow_fallback: bool,
429    ) -> Result<Self, PlatformError> {
430        #[cfg(not(target_arch = "wasm32"))]
431        use raw_window_handle::HasDisplayHandle;
432
433        #[cfg(all(unix, not(target_vendor = "apple")))]
434        {
435            #[cfg(feature = "wayland")]
436            {
437                use winit::platform::wayland::EventLoopBuilderExtWayland;
438                builder.with_any_thread(true);
439            }
440            #[cfg(feature = "x11")]
441            {
442                use winit::platform::x11::EventLoopBuilderExtX11;
443                builder.with_any_thread(true);
444
445                // Under WSL, the compositor sometimes crashes. Since we cannot reconnect after the compositor
446                // was restarted, the application panics. This does not happen when using XWayland. Therefore,
447                // when running under WSL, try to connect to X11 instead.
448                #[cfg(feature = "wayland")]
449                if std::fs::metadata("/proc/sys/fs/binfmt_misc/WSLInterop").is_ok()
450                    || std::fs::metadata("/run/WSL").is_ok()
451                {
452                    builder.with_x11();
453                }
454            }
455        }
456        #[cfg(target_family = "windows")]
457        {
458            use winit::platform::windows::EventLoopBuilderExtWindows;
459            builder.with_any_thread(true);
460        }
461
462        let event_loop =
463            builder.build().map_err(|e| format!("Error initializing winit event loop: {e}"))?;
464
465        #[cfg(target_os = "macos")]
466        Self::disable_macos_automatic_shortcut_localization();
467
468        cfg_if::cfg_if! {
469            if #[cfg(all(unix, not(target_vendor = "apple"), feature = "wayland"))] {
470                use winit::platform::wayland::EventLoopExtWayland;
471                let is_wayland = event_loop.is_wayland();
472            } else {
473                let is_wayland = false;
474            }
475        }
476
477        let active_windows =
478            Rc::<RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>>::default();
479
480        #[cfg(target_os = "ios")]
481        let keyboard_notifications =
482            ios::register_keyboard_notifications(Rc::downgrade(&active_windows));
483
484        let event_loop_proxy = event_loop.create_proxy();
485        #[cfg(not(target_arch = "wasm32"))]
486        let clipboard = crate::clipboard::create_clipboard(
487            &event_loop
488                .display_handle()
489                .map_err(|display_err| PlatformError::OtherError(display_err.into()))?,
490        );
491        Ok(Self {
492            allow_fallback,
493            renderer_name,
494            requested_graphics_api,
495            #[cfg(enable_skia_renderer)]
496            skia_context: i_slint_renderer_skia::SkiaSharedContext::default(),
497            active_windows,
498            inactive_windows: Default::default(),
499            #[cfg(not(target_arch = "wasm32"))]
500            clipboard: RefCell::new(clipboard),
501            not_running_event_loop: RefCell::new(Some(event_loop)),
502            event_loop_proxy,
503            event_loop_generation: Default::default(),
504            is_wayland,
505            #[cfg(xdg_desktop_settings)]
506            desktop_settings: xdg_desktop_settings::DesktopSettings::new(),
507            #[cfg(target_os = "ios")]
508            keyboard_notifications,
509        })
510    }
511
512    // Disable automatic keyboard shortcut localization on macOS by injecting
513    // applicationShouldAutomaticallyLocalizeKeyEquivalents: into winit's delegate class.
514    //
515    // This is necessary to make the keyboard shortcuts declared in Slint work as intended on macOS, instead of being automatically localized by the system.
516    //
517    // This is done at runtime because winit 0.30 doesn't allow replacing its delegate.
518    // TODO: Replace with a proper delegate class when upgrading to the next winit version.
519    #[cfg(target_os = "macos")]
520    fn disable_macos_automatic_shortcut_localization() {
521        use objc2::runtime::{AnyClass, AnyObject, Bool, Imp, Sel};
522        use objc2::sel;
523
524        unsafe extern "C-unwind" fn should_not_localize(
525            _this: *mut AnyObject,
526            _cmd: Sel,
527            _app: *mut AnyObject,
528        ) -> Bool {
529            Bool::NO
530        }
531
532        let sel = sel!(applicationShouldAutomaticallyLocalizeKeyEquivalents:);
533        if let Some(cls) = AnyClass::get(c"WinitApplicationDelegate")
534            && cls.instance_method(sel).is_none()
535        {
536            unsafe {
537                objc2::ffi::class_addMethod(
538                    (cls as *const AnyClass).cast_mut(),
539                    sel,
540                    core::mem::transmute::<
541                        unsafe extern "C-unwind" fn(*mut AnyObject, Sel, *mut AnyObject) -> Bool,
542                        Imp,
543                    >(should_not_localize),
544                    c"B@:@".as_ptr(),
545                );
546            }
547        }
548    }
549
550    pub fn register_window(&self, id: winit::window::WindowId, window: Rc<WinitWindowAdapter>) {
551        self.active_windows.borrow_mut().insert(id, Rc::downgrade(&window));
552    }
553
554    pub fn register_inactive_window(&self, window: Rc<WinitWindowAdapter>) {
555        let window = Rc::downgrade(&window);
556        let mut inactive_windows = self.inactive_windows.borrow_mut();
557        if !inactive_windows.iter().any(|w| Weak::ptr_eq(w, &window)) {
558            inactive_windows.push(window);
559        }
560    }
561
562    pub fn unregister_window(&self, id: Option<winit::window::WindowId>) {
563        if let Some(id) = id {
564            self.active_windows.borrow_mut().remove(&id);
565        } else {
566            // Use this opportunity of a Window being removed to tidy up.
567            self.inactive_windows
568                .borrow_mut()
569                .retain(|inactive_weak_window| inactive_weak_window.strong_count() > 0)
570        }
571    }
572
573    pub fn create_inactive_windows(
574        &self,
575        event_loop: &winit::event_loop::ActiveEventLoop,
576    ) -> Result<(), PlatformError> {
577        // Wait for the appearance query so windows aren't shown with default colors;
578        // the next `about_to_wait` retries once it clears.
579        #[cfg(xdg_desktop_settings)]
580        if self.desktop_settings.is_appearance_pending() {
581            return Ok(());
582        }
583        let mut inactive_windows = self.inactive_windows.take();
584        let mut result = Ok(());
585        while let Some(window_weak) = inactive_windows.pop() {
586            if let Some(err) = window_weak.upgrade().and_then(|w| w.ensure_window(event_loop).err())
587            {
588                result = Err(err);
589                break;
590            }
591        }
592        self.inactive_windows.borrow_mut().extend(inactive_windows);
593        result
594    }
595
596    pub fn window_by_id(&self, id: winit::window::WindowId) -> Option<Rc<WinitWindowAdapter>> {
597        self.active_windows.borrow().get(&id).and_then(|weakref| weakref.upgrade())
598    }
599}
600
601#[i_slint_core_macros::slint_doc]
602/// This struct implements the Slint Platform trait.
603/// Use this in conjunction with [`slint::platform::set_platform`](slint:rust:slint/platform/fn.set_platform.html) to initialize.
604/// Slint to use winit for all windowing system interaction.
605///
606/// ```rust,no_run
607/// use i_slint_backend_winit::Backend;
608/// slint::platform::set_platform(Box::new(Backend::new().unwrap()));
609/// ```
610pub struct Backend {
611    event_loop_state: RefCell<Option<crate::event_loop::EventLoopState>>,
612    shared_data: Rc<SharedBackendData>,
613    custom_application_handler: RefCell<Option<Box<dyn crate::CustomApplicationHandler>>>,
614    /// Backend-wide XDG desktop portal watcher. Spawned in `bind_context`
615    /// and aborted on backend drop.
616    #[cfg(xdg_desktop_settings)]
617    xdg_watcher: RefCell<Option<i_slint_core::future::JoinHandle<()>>>,
618
619    /// This hook is called before a Window is created.
620    ///
621    /// It can be used to adjust settings of window that will be created
622    ///
623    /// See also [`BackendBuilder::with_window_attributes_hook`].
624    ///
625    /// # Example
626    ///
627    /// ```rust,no_run
628    /// let mut backend = i_slint_backend_winit::Backend::new().unwrap();
629    /// backend.window_attributes_hook = Some(Box::new(|attributes| attributes.with_content_protected(true)));
630    /// slint::platform::set_platform(Box::new(backend));
631    /// ```
632    pub window_attributes_hook:
633        Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
634
635    #[cfg(all(muda, target_os = "macos"))]
636    muda_enable_default_menu_bar_bar: bool,
637
638    #[cfg(target_family = "wasm")]
639    spawn_event_loop: bool,
640}
641
642impl Backend {
643    #[i_slint_core_macros::slint_doc]
644    /// Creates a new winit backend with the default renderer that's compiled in.
645    ///
646    /// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer.
647    pub fn new() -> Result<Self, PlatformError> {
648        Self::builder().build()
649    }
650
651    #[i_slint_core_macros::slint_doc]
652    /// Creates a new winit backend with the renderer specified by name.
653    ///
654    /// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer.
655    ///
656    /// If the renderer name is `None` or the name is not recognized, the default renderer is selected.
657    pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> {
658        let mut builder = Self::builder();
659        if let Some(name) = renderer_name {
660            builder = builder.with_renderer_name(name.to_string());
661        }
662        builder.build()
663    }
664
665    /// Creates a new BackendBuilder for configuring aspects of the Winit backend before
666    /// setting it as the platform backend.
667    pub fn builder() -> BackendBuilder {
668        BackendBuilder {
669            allow_fallback: true,
670            requested_graphics_api: None,
671            window_attributes_hook: None,
672            renderer_name: None,
673            event_loop_builder: None,
674            #[cfg(all(muda, target_os = "macos"))]
675            muda_enable_default_menu_bar_bar: true,
676            #[cfg(target_family = "wasm")]
677            spawn_event_loop: false,
678            custom_application_handler: None,
679        }
680    }
681}
682
683#[allow(unused)]
684const DEFAULT_CURSOR_FLASH_CYCLE: core::time::Duration = core::time::Duration::from_millis(1000);
685
686#[cfg(any(target_os = "macos", target_os = "ios"))]
687fn prefers_non_blinking_text_insertion_indicator() -> Option<bool> {
688    use core::ffi::{c_char, c_void};
689
690    #[link(name = "Accessibility", kind = "framework")]
691    unsafe extern "C" {}
692
693    unsafe extern "C" {
694        fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
695    }
696
697    type AxPrefersNonBlinkingTextInsertionIndicator =
698        unsafe extern "C" fn() -> objc2::runtime::Bool;
699
700    // AXPrefersNonBlinkingTextInsertionIndicator is available starting with macOS 15 and iOS 18.
701    // Look it up dynamically so older systems don't fail to load because of a strong symbol
702    // reference. On older systems dlsym returns null, so the accessibility setting is
703    // unavailable and we keep the existing cursor blink behavior.
704    let symbol = unsafe {
705        dlsym((-2isize) as *mut c_void, c"AXPrefersNonBlinkingTextInsertionIndicator".as_ptr())
706    };
707    if symbol.is_null() {
708        return None;
709    }
710
711    let function: AxPrefersNonBlinkingTextInsertionIndicator =
712        unsafe { core::mem::transmute(symbol) };
713    Some(unsafe { function() }.as_bool())
714}
715
716#[cfg(xdg_desktop_settings)]
717impl Drop for Backend {
718    fn drop(&mut self) {
719        if let Some(handle) = self.xdg_watcher.borrow_mut().take() {
720            handle.abort();
721        }
722    }
723}
724
725impl i_slint_core::platform::Platform for Backend {
726    fn bind_context(&self, _ctx: i_slint_core::SlintContextWeak, _: i_slint_core::InternalToken) {
727        #[cfg(xdg_desktop_settings)]
728        {
729            *self.xdg_watcher.borrow_mut() =
730                crate::xdg_desktop_settings::spawn(&self.shared_data, &_ctx);
731        }
732        #[cfg(target_os = "windows")]
733        if let Some(ctx) = _ctx.upgrade() {
734            use windows::Win32::UI::HiDpi::SystemParametersInfoForDpi;
735            use windows::Win32::UI::WindowsAndMessaging::{
736                NONCLIENTMETRICSW, SPI_GETNONCLIENTMETRICS,
737            };
738            let mut metrics = NONCLIENTMETRICSW {
739                cbSize: core::mem::size_of::<NONCLIENTMETRICSW>() as u32,
740                ..NONCLIENTMETRICSW::default()
741            };
742            let ok = unsafe {
743                SystemParametersInfoForDpi(
744                    SPI_GETNONCLIENTMETRICS.0,
745                    metrics.cbSize,
746                    Some(&mut metrics as *mut _ as *mut core::ffi::c_void),
747                    0,
748                    96,
749                )
750            }
751            .is_ok();
752            // `lfMessageFont.lfHeight` is in pixels at 96 DPI = Slint logical pixels;
753            // negative means em height, positive means cell height — magnitude is fine here.
754            let height = metrics.lfMessageFont.lfHeight.unsigned_abs();
755            if ok && height > 0 {
756                ctx.set_platform_default_font_size(Some(
757                    i_slint_core::lengths::LogicalLength::new(height as f32),
758                ));
759            }
760        }
761    }
762
763    fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
764        let mut attrs = WinitWindowAdapter::window_attributes()?;
765
766        if let Some(hook) = &self.window_attributes_hook {
767            attrs = hook(attrs);
768        }
769
770        let adapter = create_renderer(&self.shared_data).map_or_else(
771            |e| {
772                try_create_window_with_fallback_renderer(
773                    &self.shared_data,
774                    attrs.clone(),
775                    &self.shared_data.event_loop_proxy.clone(),
776                    #[cfg(all(muda, target_os = "macos"))]
777                    self.muda_enable_default_menu_bar_bar,
778                )
779                .ok_or_else(|| format!("Winit backend failed to find a suitable renderer: {e}"))
780            },
781            |renderer| {
782                Ok(WinitWindowAdapter::new(
783                    self.shared_data.clone(),
784                    renderer,
785                    attrs.clone(),
786                    #[cfg(any(enable_accesskit, muda))]
787                    self.shared_data.event_loop_proxy.clone(),
788                    #[cfg(all(muda, target_os = "macos"))]
789                    self.muda_enable_default_menu_bar_bar,
790                ))
791            },
792        )?;
793        Ok(adapter)
794    }
795
796    fn run_event_loop(&self) -> Result<(), PlatformError> {
797        let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
798            EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
799        });
800        #[cfg(target_family = "wasm")]
801        {
802            if self.spawn_event_loop {
803                return loop_state.spawn();
804            }
805        }
806        // Note: fetch_add wraps around on overflow, which is what we want.
807        self.shared_data.event_loop_generation.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
808        let new_state = loop_state.run()?;
809        *self.event_loop_state.borrow_mut() = Some(new_state);
810        Ok(())
811    }
812
813    #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]
814    fn process_events(
815        &self,
816        timeout: Option<core::time::Duration>,
817        _: i_slint_core::InternalToken,
818    ) -> Result<core::ops::ControlFlow<()>, PlatformError> {
819        let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
820            EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
821        });
822        let (new_state, status) = loop_state.pump_events(timeout)?;
823        *self.event_loop_state.borrow_mut() = Some(new_state);
824        match status {
825            winit::platform::pump_events::PumpStatus::Continue => {
826                Ok(core::ops::ControlFlow::Continue(()))
827            }
828            winit::platform::pump_events::PumpStatus::Exit(code) => {
829                if code == 0 {
830                    Ok(core::ops::ControlFlow::Break(()))
831                } else {
832                    Err(format!("Event loop exited with non-zero code {code}").into())
833                }
834            }
835        }
836    }
837
838    fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
839        struct Proxy(winit::event_loop::EventLoopProxy<SlintEvent>, Arc<AtomicUsize>);
840        impl EventLoopProxy for Proxy {
841            fn quit_event_loop(&self) -> Result<(), EventLoopError> {
842                let generation = self.1.load(std::sync::atomic::Ordering::Relaxed);
843                self.0
844                    .send_event(SlintEvent(CustomEvent::Exit(generation)))
845                    .map_err(|_| EventLoopError::EventLoopTerminated)
846            }
847
848            fn invoke_from_event_loop(
849                &self,
850                event: Box<dyn FnOnce() + Send>,
851            ) -> Result<(), EventLoopError> {
852                // Calling send_event is usually done by winit at the bottom of the stack,
853                // in event handlers, and thus winit might decide to process the event
854                // immediately within that stack.
855                // To prevent re-entrancy issues that might happen by getting the application
856                // event processed on top of the current stack, set winit in Poll mode so that
857                // events are queued and process on top of a clean stack during a requested animation
858                // frame a few moments later.
859                // This also allows batching multiple post_event calls and redraw their state changes
860                // all at once.
861                #[cfg(target_arch = "wasm32")]
862                self.0
863                    .send_event(SlintEvent(CustomEvent::WakeEventLoopWorkaround))
864                    .map_err(|_| EventLoopError::EventLoopTerminated)?;
865
866                self.0
867                    .send_event(SlintEvent(CustomEvent::UserEvent(event)))
868                    .map_err(|_| EventLoopError::EventLoopTerminated)
869            }
870        }
871        Some(Box::new(Proxy(
872            self.shared_data.event_loop_proxy.clone(),
873            Arc::clone(&self.shared_data.event_loop_generation),
874        )))
875    }
876
877    #[cfg(target_arch = "wasm32")]
878    fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
879        crate::wasm_input_helper::set_clipboard_text(text.into(), clipboard);
880    }
881
882    #[cfg(not(target_arch = "wasm32"))]
883    fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
884        let mut pair = self.shared_data.clipboard.borrow_mut();
885        if let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard) {
886            clipboard.set_contents(text.into()).ok();
887        }
888    }
889
890    #[cfg(target_arch = "wasm32")]
891    fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
892        crate::wasm_input_helper::get_clipboard_text(clipboard)
893    }
894
895    #[cfg(not(target_arch = "wasm32"))]
896    fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
897        let mut pair = self.shared_data.clipboard.borrow_mut();
898        clipboard::select_clipboard(&mut pair, clipboard).and_then(|c| c.get_contents().ok())
899    }
900
901    #[cfg(target_os = "windows")]
902    fn cursor_flash_cycle(&self) -> core::time::Duration {
903        use windows::Win32::UI::WindowsAndMessaging::GetCaretBlinkTime;
904        let ms = unsafe { GetCaretBlinkTime() };
905        if ms == u32::MAX {
906            // INFINITE — blinking disabled
907            core::time::Duration::ZERO
908        } else if ms == 0 {
909            DEFAULT_CURSOR_FLASH_CYCLE
910        } else {
911            // Win32 returns the half-cycle duration
912            core::time::Duration::from_millis(ms as u64 * 2)
913        }
914    }
915
916    #[cfg(target_os = "macos")]
917    fn cursor_flash_cycle(&self) -> core::time::Duration {
918        if prefers_non_blinking_text_insertion_indicator() == Some(true) {
919            return core::time::Duration::ZERO;
920        }
921
922        let defaults = objc2_foundation::NSUserDefaults::standardUserDefaults();
923        let key = objc2_foundation::NSString::from_str("NSTextInsertionPointBlinkPeriod");
924        let period = defaults.integerForKey(&key);
925        if period < 0 {
926            core::time::Duration::ZERO
927        } else if period == 0 {
928            DEFAULT_CURSOR_FLASH_CYCLE
929        } else {
930            core::time::Duration::from_millis(period as u64)
931        }
932    }
933
934    #[cfg(target_os = "ios")]
935    fn cursor_flash_cycle(&self) -> core::time::Duration {
936        if prefers_non_blinking_text_insertion_indicator() == Some(true) {
937            core::time::Duration::ZERO
938        } else {
939            DEFAULT_CURSOR_FLASH_CYCLE
940        }
941    }
942
943    #[cfg(xdg_desktop_settings)]
944    fn cursor_flash_cycle(&self) -> core::time::Duration {
945        self.shared_data.desktop_settings.cursor_flash_cycle()
946    }
947
948    fn open_url(&self, url: &str) -> Result<(), i_slint_core::platform::PlatformError> {
949        webbrowser::open(url).map_err(|e| {
950            i_slint_core::platform::PlatformError::Other(format!("Failed to open URL: {e}"))
951        })
952    }
953}
954
955mod private {
956    pub trait WinitWindowAccessorSealed {}
957}
958
959#[i_slint_core_macros::slint_doc]
960/// This helper trait can be used to obtain access to the [`winit::window::Window`] for a given
961/// [`slint::Window`](slint:rust:slint/struct.window).
962///
963/// Note that the association of a Slint window with a winit window relies on two factors:
964///
965/// - 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)
966///   with "winit" as argument.
967/// - The winit window must've been created. Windowing systems, and by extension winit, require that windows can only be properly
968///   created when certain conditions of the event loop are met. For example, on Android the application can't be suspended. Therefore,
969///   functions like [`Self::has_winit_window()`] or [`Self::with_winit_window()`] will only succeed when the event loop is active.
970///   This is typically the case when callbacks are invoked from the event loop, such as through timers, user input events, or when window
971///   receives events (see also [`Self::on_winit_window_event()`]).
972pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed {
973    /// Returns true if a [`winit::window::Window`] exists for this window. This is the case if the window is
974    /// backed by this winit backend.
975    fn has_winit_window(&self) -> bool;
976    /// Invokes the specified callback with a reference to the [`winit::window::Window`] that exists for this Slint window
977    /// and returns `Some(T)`; otherwise `None`.
978    fn with_winit_window<T>(&self, callback: impl FnOnce(&winit::window::Window) -> T)
979    -> Option<T>;
980    /// Registers a window event filter callback for this Slint window.
981    ///
982    /// The callback is invoked in the winit event loop whenever a window event is received with a reference to the
983    /// [`slint::Window`](i_slint_core::api::Window) and the [`winit::event::WindowEvent`]. The return value of the
984    /// callback specifies whether Slint should handle this event.
985    ///
986    /// If this window [is not backed by winit](WinitWindowAccessor::has_winit_window), this function is a no-op.
987    fn on_winit_window_event(
988        &self,
989        callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
990        + 'static,
991    );
992
993    /// Returns a future that resolves to the [`winit::window::Window`] for this Slint window.
994    /// When the future is ready, the output it resolves to is either `Ok(Arc<winit::window::Window>)` if the window exists,
995    /// or an error if the window has been deleted in the meanwhile or isn't backed by the winit backend.
996    ///
997    /// ```rust,no_run
998    /// // Bring winit and accessor traits into scope.
999    /// use slint::winit_030::{WinitWindowAccessor, winit};
1000    ///
1001    /// slint::slint!{
1002    ///     import { VerticalBox, Button } from "std-widgets.slint";
1003    ///     export component HelloWorld inherits Window {
1004    ///         callback clicked;
1005    ///         VerticalBox {
1006    ///             Text {
1007    ///                 text: "hello world";
1008    ///                 color: green;
1009    ///             }
1010    ///             Button {
1011    ///                 text: "Click me";
1012    ///                 clicked => { root.clicked(); }
1013    ///             }
1014    ///         }
1015    ///     }
1016    /// }
1017    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1018    ///     // Make sure the winit backed is selected:
1019    ///    slint::BackendSelector::new()
1020    ///        .backend_name("winit".into())
1021    ///        .select()?;
1022    ///
1023    ///     let app = HelloWorld::new()?;
1024    ///     let app_weak = app.as_weak();
1025    ///
1026    ///     slint::spawn_local(async move {
1027    ///         let app = app_weak.unwrap();
1028    ///         let winit_window = app.window().winit_window().await.unwrap();
1029    ///         eprintln!("window id = {:#?}", winit_window.id());
1030    ///     }).unwrap();
1031    ///     app.run()?;
1032    ///     Ok(())
1033    /// }
1034    /// ```
1035    fn winit_window(
1036        &self,
1037    ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>>;
1038}
1039
1040impl WinitWindowAccessor for i_slint_core::api::Window {
1041    fn has_winit_window(&self) -> bool {
1042        i_slint_core::window::WindowInner::from_pub(self)
1043            .window_adapter()
1044            .internal(i_slint_core::InternalToken)
1045            .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1046            .is_some_and(|adapter| adapter.winit_window().is_some())
1047    }
1048
1049    fn with_winit_window<T>(
1050        &self,
1051        callback: impl FnOnce(&winit::window::Window) -> T,
1052    ) -> Option<T> {
1053        i_slint_core::window::WindowInner::from_pub(self)
1054            .window_adapter()
1055            .internal(i_slint_core::InternalToken)
1056            .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1057            .and_then(|adapter| adapter.winit_window().map(|w| callback(&w)))
1058    }
1059
1060    fn winit_window(
1061        &self,
1062    ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>> {
1063        Box::pin(async move {
1064            let adapter_weak = i_slint_core::window::WindowInner::from_pub(self)
1065                .window_adapter()
1066                .internal(i_slint_core::InternalToken)
1067                .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1068                .map(|wa| wa.self_weak.clone())
1069                .ok_or_else(|| {
1070                    PlatformError::OtherError(
1071                        "Slint window is not backed by a Winit window adapter".to_string().into(),
1072                    )
1073                })?;
1074            WinitWindowAdapter::async_winit_window(adapter_weak).await
1075        })
1076    }
1077
1078    fn on_winit_window_event(
1079        &self,
1080        mut callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
1081        + 'static,
1082    ) {
1083        if let Some(adapter) = i_slint_core::window::WindowInner::from_pub(self)
1084            .window_adapter()
1085            .internal(i_slint_core::InternalToken)
1086            .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1087        {
1088            adapter
1089                .window_event_filter
1090                .set(Some(Box::new(move |window, event| callback(window, event))));
1091        }
1092    }
1093}
1094
1095/// Creates a new renderer from the backend properties in `shared_data`
1096fn create_renderer(
1097    shared_data: &Rc<SharedBackendData>,
1098) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError> {
1099    match (shared_data.renderer_name.as_deref(), shared_data.requested_graphics_api.as_ref()) {
1100        #[cfg(all(feature = "renderer-femtovg", supports_opengl))]
1101        (Some("gl"), maybe_graphics_api) | (Some("femtovg"), maybe_graphics_api) => {
1102            // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc.
1103            if let Some(api) = maybe_graphics_api {
1104                i_slint_core::graphics::RequestedOpenGLVersion::try_from(api)?;
1105            }
1106            renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(shared_data)
1107        }
1108        #[cfg(feature = "renderer-femtovg-wgpu")]
1109        (Some("femtovg-wgpu"), maybe_graphics_api) => {
1110            if let Some(_api) = maybe_graphics_api {
1111                #[cfg(feature = "unstable-wgpu-29")]
1112                if !matches!(_api, RequestedGraphicsAPI::WGPU29(..)) {
1113                    return Err(
1114                        "The FemtoVG WGPU renderer only supports the WGPU29 graphics API selection"
1115                            .into(),
1116                    );
1117                }
1118            }
1119            renderer::femtovg::WGPUFemtoVGRenderer::new_suspended(shared_data)
1120        }
1121        #[cfg(enable_skia_renderer)]
1122        (Some("skia"), maybe_graphics_api) => {
1123            (renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(maybe_graphics_api)?)(
1124                shared_data,
1125            )
1126        }
1127        #[cfg(all(enable_skia_renderer, supports_opengl))]
1128        (Some("skia-opengl"), maybe_graphics_api) => {
1129            // If a graphics API was requested, double check that it's GL.
1130            if let Some(api) = maybe_graphics_api {
1131                i_slint_core::graphics::RequestedOpenGLVersion::try_from(api)?;
1132            }
1133            renderer::skia::WinitSkiaRenderer::new_opengl_suspended(shared_data)
1134        }
1135        #[cfg(all(
1136            enable_skia_renderer,
1137            any(feature = "unstable-wgpu-28", feature = "unstable-wgpu-29")
1138        ))]
1139        (Some("skia-wgpu"), maybe_graphics_api) => {
1140            if let Some(selected_renderer) = maybe_graphics_api.map_or_else(
1141                || {
1142                    #[cfg(feature = "unstable-wgpu-29")]
1143                    {
1144                        return Some(renderer::skia::WinitSkiaRenderer::new_wgpu_29_suspended(
1145                            shared_data,
1146                        ));
1147                    }
1148                    #[cfg(all(feature = "unstable-wgpu-28", not(feature = "unstable-wgpu-29")))]
1149                    {
1150                        return Some(renderer::skia::WinitSkiaRenderer::new_wgpu_28_suspended(
1151                            shared_data,
1152                        ));
1153                    }
1154                    #[allow(unreachable_code)]
1155                    None
1156                },
1157                |_api| {
1158                    #[cfg(feature = "unstable-wgpu-29")]
1159                    if matches!(_api, RequestedGraphicsAPI::WGPU29(..)) {
1160                        return Some(renderer::skia::WinitSkiaRenderer::new_wgpu_29_suspended(
1161                            shared_data,
1162                        ));
1163                    }
1164                    #[cfg(feature = "unstable-wgpu-28")]
1165                    if matches!(_api, RequestedGraphicsAPI::WGPU28(..)) {
1166                        return Some(renderer::skia::WinitSkiaRenderer::new_wgpu_28_suspended(
1167                            shared_data,
1168                        ));
1169                    }
1170                    None
1171                },
1172            ) {
1173                selected_renderer
1174            } else {
1175                Err("Skia with WGPU doesn't support non-WGPU graphics API".to_string().into())
1176            }
1177        }
1178        #[cfg(all(enable_skia_renderer, not(target_os = "android")))]
1179        (Some("skia-software"), None) => {
1180            renderer::skia::WinitSkiaRenderer::new_software_suspended(shared_data)
1181        }
1182        #[cfg(feature = "renderer-software")]
1183        (Some("sw"), None) | (Some("software"), None) => {
1184            renderer::sw::WinitSoftwareRenderer::new_suspended(shared_data)
1185        }
1186        (None, None) => default_renderer_factory(shared_data),
1187        (Some(renderer_name), _) => {
1188            if shared_data.allow_fallback {
1189                eprintln!(
1190                    "slint winit: unrecognized renderer {renderer_name}, falling back to {DEFAULT_RENDERER_NAME}"
1191                );
1192                default_renderer_factory(shared_data)
1193            } else {
1194                Err(PlatformError::NoPlatform)
1195            }
1196        }
1197        #[cfg(feature = "unstable-wgpu-28")]
1198        (None, Some(RequestedGraphicsAPI::WGPU28(..))) => {
1199            cfg_if::cfg_if! {
1200                if #[cfg(enable_skia_renderer)] {
1201                    renderer::skia::WinitSkiaRenderer::new_wgpu_28_suspended(shared_data)
1202                } else {
1203                    Err("unstable-wgpu-28 was enabled but no renderer was selected. Please select renderer-skia*".into())
1204                }
1205            }
1206        }
1207        #[cfg(feature = "unstable-wgpu-29")]
1208        (None, Some(RequestedGraphicsAPI::WGPU29(..))) => {
1209            cfg_if::cfg_if! {
1210                if #[cfg(enable_skia_renderer)] {
1211                    renderer::skia::WinitSkiaRenderer::new_wgpu_29_suspended(shared_data)
1212                } else if #[cfg(feature = "renderer-femtovg-wgpu")] {
1213                    renderer::femtovg::WGPUFemtoVGRenderer::new_suspended(shared_data)
1214                } else {
1215                    Err("unstable-wgpu-29 was enabled but no renderer was selected. Please select either renderer-skia* or renderer-femtovg-wgpu".into())
1216                }
1217            }
1218        }
1219        (None, Some(_requested_graphics_api)) => {
1220            cfg_if::cfg_if! {
1221                if #[cfg(enable_skia_renderer)] {
1222                    renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(Some(_requested_graphics_api))?(shared_data)
1223                } else if #[cfg(all(feature = "renderer-femtovg", supports_opengl))] {
1224                    // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc.
1225                    i_slint_core::graphics::RequestedOpenGLVersion::try_from(_requested_graphics_api)?;
1226                    renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(shared_data)
1227                } else {
1228                    return Err(format!("Graphics API use requested by the compile-time enabled renderers don't support that").into())
1229                }
1230            }
1231        }
1232    }
1233}
1234
1235impl private::WinitWindowAccessorSealed for i_slint_core::api::Window {}
1236
1237#[cfg(test)]
1238mod testui {
1239    slint::slint! {
1240        export component App inherits Window {
1241            Text { text: "Ok"; }
1242        }
1243    }
1244}
1245
1246// Sorry, can't test with rust test harness and multiple threads.
1247#[cfg(not(any(target_arch = "wasm32", target_vendor = "apple")))]
1248#[test]
1249fn test_window_accessor_and_rwh() {
1250    slint::platform::set_platform(Box::new(crate::Backend::new().unwrap())).unwrap();
1251
1252    use testui::*;
1253
1254    slint::spawn_local(async move {
1255        let app = App::new().unwrap();
1256        let slint_window = app.window();
1257
1258        assert!(!slint_window.has_winit_window());
1259
1260        // Show() won't immediately create the window, the event loop will have to
1261        // spin first.
1262        app.show().unwrap();
1263
1264        let result = slint_window.winit_window().await;
1265        assert!(result.is_ok(), "Failed to get winit window: {:?}", result.err());
1266        assert!(slint_window.has_winit_window());
1267        let handle = slint_window.window_handle();
1268        use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
1269        assert!(handle.window_handle().is_ok());
1270        assert!(handle.display_handle().is_ok());
1271        slint::quit_event_loop().unwrap();
1272    })
1273    .unwrap();
1274
1275    slint::run_event_loop().unwrap();
1276}