Skip to main content

i_slint_backend_selector/
api.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#![warn(missing_docs)]
5
6/*!
7This module contains types that are public and re-exported in the slint-rs as well as the slint-interpreter crate as public API,
8in particular the `BackendSelector` type.
9*/
10
11use alloc::boxed::Box;
12use alloc::format;
13use alloc::string::{String, ToString};
14
15use i_slint_core::api::PlatformError;
16use i_slint_core::graphics::{RequestedGraphicsAPI, RequestedOpenGLVersion};
17
18#[i_slint_core_macros::slint_doc]
19/// Use the BackendSelector to configure one of Slint's built-in [backends with a renderer](slint:backends_and_renderers)
20/// to accommodate specific needs of your application. This is a programmatic substitute for
21/// the `SLINT_BACKEND` environment variable.
22///
23/// For example, to configure Slint to use a renderer that supports OpenGL ES 3.0, configure
24/// the `BackendSelector` as follows:
25/// ```rust,no_run
26/// # use i_slint_backend_selector::api::BackendSelector;
27/// let selector = BackendSelector::new().require_opengl_es_with_version(3, 0);
28/// if let Err(err) = selector.select() {
29///     eprintln!("Error selecting backend with OpenGL ES support: {err}");
30/// }
31/// ```
32#[derive(Default)]
33pub struct BackendSelector {
34    requested_graphics_api: Option<RequestedGraphicsAPI>,
35    backend: Option<String>,
36    renderer: Option<String>,
37    selected: bool,
38    #[cfg(feature = "unstable-winit-030")]
39    winit_window_attributes_hook: Option<
40        Box<
41            dyn Fn(
42                i_slint_backend_winit::winit::window::WindowAttributes,
43            ) -> i_slint_backend_winit::winit::window::WindowAttributes,
44        >,
45    >,
46    #[cfg(feature = "unstable-winit-030")]
47    winit_event_loop_builder: Option<i_slint_backend_winit::EventLoopBuilder>,
48    #[cfg(feature = "unstable-winit-030")]
49    winit_custom_application_handler:
50        Option<Box<dyn i_slint_backend_winit::CustomApplicationHandler>>,
51    #[cfg(all(target_os = "linux", feature = "unstable-libinput-09"))]
52    libinput_event_hook: Option<Box<dyn Fn(&input::Event) -> bool>>,
53}
54
55impl BackendSelector {
56    /// Creates a new BackendSelector.
57    #[must_use]
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    /// Adds the requirement to the selector that the backend must render with OpenGL ES
63    /// and the specified major and minor version.
64    #[must_use]
65    pub fn require_opengl_es_with_version(mut self, major: u8, minor: u8) -> Self {
66        self.requested_graphics_api =
67            Some(RequestedOpenGLVersion::OpenGLES(Some((major, minor))).into());
68        self
69    }
70
71    /// Adds the requirement to the selector that the backend must render with OpenGL ES.
72    #[must_use]
73    pub fn require_opengl_es(mut self) -> Self {
74        self.requested_graphics_api = Some(RequestedOpenGLVersion::OpenGLES(None).into());
75        self
76    }
77
78    /// Adds the requirement to the selector that the backend must render with OpenGL.
79    #[must_use]
80    pub fn require_opengl(mut self) -> Self {
81        self.requested_graphics_api = Some(RequestedOpenGLVersion::OpenGL(None).into());
82        self
83    }
84
85    /// Adds the requirement to the selector that the backend must render with OpenGL
86    /// and the specified major and minor version.
87    #[must_use]
88    pub fn require_opengl_with_version(mut self, major: u8, minor: u8) -> Self {
89        self.requested_graphics_api =
90            Some(RequestedOpenGLVersion::OpenGL(Some((major, minor))).into());
91        self
92    }
93
94    /// Adds the requirement to the selector that the backend must render with Apple's Metal framework.
95    #[must_use]
96    pub fn require_metal(mut self) -> Self {
97        self.requested_graphics_api = Some(RequestedGraphicsAPI::Metal);
98        self
99    }
100
101    /// Adds the requirement to the selector that the backend must render with Vulkan.
102    #[must_use]
103    pub fn require_vulkan(mut self) -> Self {
104        self.requested_graphics_api = Some(RequestedGraphicsAPI::Vulkan);
105        self
106    }
107
108    /// Adds the requirement to the selector that the backend must render with Direct 3D.
109    #[must_use]
110    pub fn require_d3d(mut self) -> Self {
111        self.requested_graphics_api = Some(RequestedGraphicsAPI::Direct3D);
112        self
113    }
114
115    #[i_slint_core_macros::slint_doc]
116    /// Adds the requirement to the selector that the backend must render using [WGPU](http://wgpu.rs).
117    /// Use this when you integrate other WGPU-based renderers with a Slint UI.
118    ///
119    /// *Note*: This function is behind the [`unstable-wgpu-30` feature flag](slint:rust:slint/docs/cargo_features/#backends)
120    ///         and may be removed or changed in future minor releases, as new major WGPU releases become available.
121    ///
122    /// See also the [`slint::wgpu_30`](slint:rust:slint/wgpu_30) module.
123    #[cfg(feature = "unstable-wgpu-30")]
124    #[must_use]
125    pub fn require_wgpu_30(
126        mut self,
127        configuration: i_slint_core::graphics::wgpu_30::api::WGPUConfiguration,
128    ) -> Self {
129        self.requested_graphics_api = Some(RequestedGraphicsAPI::WGPU30(configuration));
130        self
131    }
132
133    #[i_slint_core_macros::slint_doc]
134    /// Adds the requirement to the selector that the backend must render using [WGPU](http://wgpu.rs).
135    /// Use this when you integrate other WGPU-based renderers with a Slint UI.
136    ///
137    /// *Note*: This function is behind the [`unstable-wgpu-29` feature flag](slint:rust:slint/docs/cargo_features/#backends)
138    ///         and may be removed or changed in future minor releases, as new major WGPU releases become available.
139    ///
140    /// See also the [`slint::wgpu_29`](slint:rust:slint/wgpu_29) module.
141    #[cfg(feature = "unstable-wgpu-29")]
142    #[must_use]
143    pub fn require_wgpu_29(
144        mut self,
145        configuration: i_slint_core::graphics::wgpu_29::api::WGPUConfiguration,
146    ) -> Self {
147        self.requested_graphics_api = Some(RequestedGraphicsAPI::WGPU29(configuration));
148        self
149    }
150
151    #[i_slint_core_macros::slint_doc]
152    /// Configures this builder to use the specified winit hook that will be called before a Window is created.
153    ///
154    /// It can be used to adjust settings of window that will be created.
155    ///
156    /// # Example
157    ///
158    /// ```rust,no_run
159    /// let mut backend = slint::BackendSelector::new()
160    ///     .with_winit_window_attributes_hook(|attributes| attributes.with_content_protected(true))
161    ///     .select()
162    ///     .unwrap();
163    /// ```
164    ///
165    /// *Note*: This function is behind the [`unstable-winit-030` feature flag](slint:rust:slint/docs/cargo_features/#backends)
166    ///         and may be removed or changed in future minor releases, as new major Winit releases become available.
167    ///
168    /// See also the [`slint::winit_030`](slint:rust:slint/winit_030) module
169    #[must_use]
170    #[cfg(feature = "unstable-winit-030")]
171    pub fn with_winit_window_attributes_hook(
172        mut self,
173        hook: impl Fn(
174            i_slint_backend_winit::winit::window::WindowAttributes,
175        ) -> i_slint_backend_winit::winit::window::WindowAttributes
176        + 'static,
177    ) -> Self {
178        self.winit_window_attributes_hook = Some(Box::new(hook));
179        self
180    }
181
182    #[i_slint_core_macros::slint_doc]
183    /// Configures this builder to use the specified winit event loop builder when creating the event
184    /// loop.
185    ///
186    /// *Note*: This function is behind the [`unstable-winit-030` feature flag](slint:rust:slint/docs/cargo_features/#backends)
187    ///         and may be removed or changed in future minor releases, as new major Winit releases become available.
188    ///
189    /// See also the [`slint::winit_030`](slint:rust:slint/winit_030) module
190    #[must_use]
191    #[cfg(feature = "unstable-winit-030")]
192    pub fn with_winit_event_loop_builder(
193        mut self,
194        event_loop_builder: i_slint_backend_winit::EventLoopBuilder,
195    ) -> Self {
196        self.winit_event_loop_builder = Some(event_loop_builder);
197        self
198    }
199
200    #[i_slint_core_macros::slint_doc]
201    /// Configures this builder to invoke the functions on the supplied application handler whenever winit wakes up the
202    /// event loop.
203    ///
204    /// *Note*: This function is behind the [`unstable-winit-030` feature flag](slint:rust:slint/docs/cargo_features/#backends)
205    ///         and may be removed or changed in future minor releases, as new major Winit releases become available.
206    ///
207    /// See also the [`slint::winit_030`](slint:rust:slint/winit_030) module
208    #[must_use]
209    #[cfg(feature = "unstable-winit-030")]
210    pub fn with_winit_custom_application_handler(
211        mut self,
212        custom_application_handler: impl i_slint_backend_winit::CustomApplicationHandler + 'static,
213    ) -> Self {
214        self.winit_custom_application_handler = Some(Box::new(custom_application_handler));
215        self
216    }
217
218    #[i_slint_core_macros::slint_doc]
219    /// Configures this builder to use the specified libinput event filter hook when the LinuxKMS backend
220    /// is selected.
221    ///
222    /// The provided hook is invoked for every event received. If the function returns true, the event is
223    /// not dispatched further.
224    ///
225    /// *Note*: This function is behind the [`unstable-libinput-09` feature flag](slint:rust:slint/docs/cargo_features/#backends)
226    ///         and may be removed or changed in future minor releases, as new major Winit releases become available.
227    #[must_use]
228    #[cfg(all(target_os = "linux", feature = "unstable-libinput-09"))]
229    pub fn with_libinput_event_hook(
230        mut self,
231        event_hook: impl Fn(&input::Event) -> bool + 'static,
232    ) -> Self {
233        self.libinput_event_hook = Some(Box::new(event_hook));
234        self
235    }
236
237    /// Adds the requirement that the selected renderer must match the given name. This is
238    /// equivalent to setting the `SLINT_BACKEND=name` environment variable and requires
239    /// that the corresponding renderer feature is enabled. For example, to select the Skia renderer,
240    /// enable the `renderer-skia` feature and call this function with `skia` as argument.
241    #[must_use]
242    pub fn renderer_name(mut self, name: String) -> Self {
243        self.renderer = Some(name);
244        self
245    }
246
247    /// Adds the requirement that the selected backend must match the given name. This is
248    /// equivalent to setting the `SLINT_BACKEND=name` environment variable and requires
249    /// that the corresponding backend feature is enabled. For example, to select the winit backend,
250    /// enable the `backend-winit` feature and call this function with `winit` as argument.
251    #[must_use]
252    pub fn backend_name(mut self, name: String) -> Self {
253        let lowercase = name.to_lowercase();
254        let (backend, renderer) = crate::parse_backend_env_var(&lowercase);
255        self.backend = Some(backend.to_string());
256        if self.renderer.is_none() && !renderer.is_empty() {
257            self.renderer = Some(renderer.to_string())
258        }
259        self
260    }
261
262    /// Completes the backend selection process and tries to combine with specified requirements
263    /// with the different backends and renderers enabled at compile time. On success, the selected
264    /// backend is automatically set to be active. Returns an error if the requirements could not be met.
265    pub fn select(mut self) -> Result<(), PlatformError> {
266        self.select_internal()
267    }
268
269    #[cfg(not(target_os = "android"))]
270    fn select_internal(&mut self) -> Result<(), PlatformError> {
271        self.selected = true;
272
273        #[cfg(any(
274            feature = "i-slint-backend-qt",
275            feature = "i-slint-backend-winit",
276            feature = "i-slint-backend-linuxkms"
277        ))]
278        if (self.backend.is_none() || self.renderer.is_none())
279            && let Ok(backend_config) = std::env::var("SLINT_BACKEND")
280        {
281            let backend_config = backend_config.to_lowercase();
282            let (backend, renderer) = super::parse_backend_env_var(backend_config.as_str());
283            if !backend.is_empty() {
284                self.backend.get_or_insert_with(|| backend.to_owned());
285            }
286            if !renderer.is_empty() {
287                self.renderer.get_or_insert_with(|| renderer.to_owned());
288            }
289        }
290
291        let backend_name = match self.backend.as_deref() {
292            Some(name) => name,
293            None => {
294                // Only the winit backend supports graphics API requests right now, so prefer that over
295                // aborting.
296                #[cfg(feature = "i-slint-backend-winit")]
297                if self.requested_graphics_api.is_some() {
298                    "winit"
299                } else {
300                    super::DEFAULT_BACKEND_NAME
301                }
302                #[cfg(not(feature = "i-slint-backend-winit"))]
303                super::DEFAULT_BACKEND_NAME
304            }
305        };
306
307        // Fail fast when wgpu rendering was required but no GPU-backed adapter
308        // is available for the requested backends. Otherwise the winit/linuxkms
309        // backends silently fall through to a non-wgpu renderer (e.g. the
310        // standalone software renderer), and the failure surfaces much later
311        // as an Unsupported error from set_rendering_notifier.
312        #[cfg(feature = "unstable-wgpu-30")]
313        if matches!(self.requested_graphics_api, Some(RequestedGraphicsAPI::WGPU30(_)))
314            && !i_slint_core::graphics::wgpu_30::any_wgpu30_adapters_with_gpu(
315                self.requested_graphics_api.clone(),
316                i_slint_core::graphics::wgpu_30::default_backends_to_avoid(),
317            )
318        {
319            return Err(
320                "WGPU 30.x rendering was required but no GPU-backed WGPU adapter is available \
321                 for the requested backends. Set SLINT_WGPU_CPU=1 to allow CPU adapters."
322                    .into(),
323            );
324        }
325        #[cfg(feature = "unstable-wgpu-29")]
326        if matches!(self.requested_graphics_api, Some(RequestedGraphicsAPI::WGPU29(_)))
327            && !i_slint_core::graphics::wgpu_29::any_wgpu29_adapters_with_gpu(
328                self.requested_graphics_api.clone(),
329                i_slint_core::graphics::wgpu_29::default_backends_to_avoid(),
330            )
331        {
332            return Err(
333                "WGPU 29.x rendering was required but no GPU-backed WGPU adapter is available \
334                 for the requested backends. Set SLINT_WGPU_CPU=1 to allow CPU adapters."
335                    .into(),
336            );
337        }
338
339        let backend: Box<dyn i_slint_core::platform::Platform> = match backend_name {
340            #[cfg(all(feature = "i-slint-backend-linuxkms", target_os = "linux"))]
341            "linuxkms" => {
342                let mut builder = i_slint_backend_linuxkms::BackendBuilder::default();
343
344                if let Some(api) = self.requested_graphics_api.take() {
345                    builder = builder.request_graphics_api(api);
346                }
347
348                if let Some(renderer_name) = self.renderer.as_ref() {
349                    builder = builder.with_renderer_name(renderer_name.into());
350                }
351
352                #[cfg(all(target_os = "linux", feature = "unstable-libinput-09"))]
353                if let Some(event_hook) = self.libinput_event_hook.take() {
354                    builder = builder.with_libinput_event_hook(event_hook);
355                }
356
357                Box::new(builder.build()?)
358            }
359            #[cfg(feature = "i-slint-backend-winit")]
360            "winit" => {
361                let builder = i_slint_backend_winit::Backend::builder();
362
363                let builder = match self.requested_graphics_api.as_ref() {
364                    Some(api) => builder.request_graphics_api(api.clone()),
365                    None => builder,
366                };
367
368                let builder = match self.renderer.as_ref() {
369                    Some(name) => builder.with_renderer_name(name),
370                    None => builder,
371                };
372
373                #[cfg(feature = "unstable-winit-030")]
374                let builder = match self.winit_window_attributes_hook.take() {
375                    Some(hook) => builder.with_window_attributes_hook(hook),
376                    None => builder,
377                };
378
379                #[cfg(feature = "unstable-winit-030")]
380                let builder = match self.winit_event_loop_builder.take() {
381                    Some(event_loop_builder) => builder.with_event_loop_builder(event_loop_builder),
382                    None => builder,
383                };
384
385                #[cfg(feature = "unstable-winit-030")]
386                let builder = match self.winit_custom_application_handler.take() {
387                    Some(custom_application_handler) => {
388                        builder.with_custom_application_handler(custom_application_handler)
389                    }
390                    None => builder,
391                };
392
393                Box::new(builder.build()?)
394            }
395            #[cfg(feature = "i-slint-backend-qt")]
396            "qt" => {
397                if self.requested_graphics_api.is_some() {
398                    return Err(
399                        "The qt backend does not implement renderer selection by graphics API"
400                            .into(),
401                    );
402                }
403                if self.renderer.is_some() {
404                    return Err(
405                        "The qt backend does not implement renderer selection by name".into()
406                    );
407                }
408                Box::new(i_slint_backend_qt::Backend::new())
409            }
410            requested_backend => {
411                return Err(format!(
412                    "{requested_backend} backend requested but it is not available"
413                )
414                .into());
415            }
416        };
417
418        let result =
419            i_slint_core::platform::set_platform(backend).map_err(PlatformError::SetPlatformError);
420
421        #[cfg(any(feature = "system-testing", feature = "mcp"))]
422        if result.is_ok() {
423            super::init_testing_backends();
424        }
425
426        result
427    }
428
429    #[cfg(target_os = "android")]
430    fn select_internal(&mut self) -> Result<(), PlatformError> {
431        self.selected = true;
432        if self.backend.as_ref().is_some_and(|b| !b.starts_with("android-activity-")) {
433            return Err(
434                format!("Only the android-activity-* backend is supported on Android").into()
435            );
436        }
437        if self.renderer.as_ref().is_some_and(|r| r != "skia") {
438            return Err(format!("Only the Skia renderer is supported on Android").into());
439        }
440
441        #[cfg(feature = "backend-android-activity")]
442        {
443            i_slint_backend_android_activity::set_requested_graphics_api(
444                self.requested_graphics_api.clone(),
445            )
446        }
447        #[cfg(not(feature = "backend-android-activity"))]
448        {
449            Err(format!(
450                "The BackendSelector is only supported with the backend-android-activity backend"
451            )
452            .into())
453        }
454    }
455}
456
457impl Drop for BackendSelector {
458    fn drop(&mut self) {
459        if !self.selected {
460            self.select_internal().unwrap();
461        }
462    }
463}