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