Skip to main content

i_slint_backend_selector/
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#![cfg_attr(
7    not(any(
8        feature = "i-slint-backend-qt",
9        feature = "i-slint-backend-winit",
10        feature = "i-slint-backend-linuxkms"
11    )),
12    no_std
13)]
14#![allow(unused)]
15
16extern crate alloc;
17
18use alloc::boxed::Box;
19pub use i_slint_core::SlintContext;
20use i_slint_core::platform::Platform;
21use i_slint_core::platform::PlatformError;
22
23#[cfg(all(feature = "i-slint-backend-qt", not(no_qt), not(target_os = "android")))]
24fn create_qt_backend() -> Result<Box<dyn Platform + 'static>, PlatformError> {
25    Ok(Box::new(default_backend::Backend::new()))
26}
27
28#[cfg(all(feature = "i-slint-backend-winit", not(target_os = "android")))]
29fn create_winit_backend() -> Result<Box<dyn Platform + 'static>, PlatformError> {
30    Ok(Box::new(i_slint_backend_winit::Backend::new()?))
31}
32
33#[cfg(all(feature = "i-slint-backend-linuxkms", target_os = "linux"))]
34fn create_linuxkms_backend() -> Result<Box<dyn Platform + 'static>, PlatformError> {
35    Ok(Box::new(i_slint_backend_linuxkms::BackendBuilder::default().build()?))
36}
37
38#[cfg(all(feature = "mcp", supports_headless))]
39fn create_headless_backend(renderer: &str) -> Result<Box<dyn Platform + 'static>, PlatformError> {
40    Ok(Box::new(i_slint_backend_testing::TestingBackend::new(
41        i_slint_backend_testing::TestingBackendOptions {
42            mock_time: false,
43            threading: true,
44            renderer_name: Some(renderer.into()),
45        },
46    )))
47}
48
49cfg_if::cfg_if! {
50    if #[cfg(target_os = "android")] {
51        const DEFAULT_BACKEND_NAME: &str = "";
52    } else if #[cfg(all(feature = "i-slint-backend-qt", not(no_qt)))] {
53        use i_slint_backend_qt as default_backend;
54        const DEFAULT_BACKEND_NAME: &str = "qt";
55    } else if #[cfg(feature = "i-slint-backend-winit")] {
56        use i_slint_backend_winit as default_backend;
57        const DEFAULT_BACKEND_NAME: &str = "winit";
58    } else if #[cfg(all(feature = "i-slint-backend-linuxkms", target_os = "linux"))] {
59        use i_slint_backend_linuxkms as default_backend;
60        const DEFAULT_BACKEND_NAME: &str = "linuxkms";
61    } else {
62        const DEFAULT_BACKEND_NAME: &str = "";
63    }
64}
65
66cfg_if::cfg_if! {
67    if #[cfg(all(not(target_os = "android"), any(
68            all(feature = "i-slint-backend-qt", not(no_qt)),
69            feature = "i-slint-backend-winit",
70            all(feature = "i-slint-backend-linuxkms", target_os = "linux")
71        )))] {
72        fn create_default_backend() -> Result<Box<dyn Platform + 'static>, PlatformError> {
73            use alloc::borrow::Cow;
74
75            let backends = [
76                #[cfg(all(feature = "i-slint-backend-qt", not(no_qt)))]
77                ("Qt", create_qt_backend as fn() -> Result<Box<(dyn Platform + 'static)>, PlatformError>),
78                #[cfg(feature = "i-slint-backend-winit")]
79                ("Winit", create_winit_backend as fn() -> Result<Box<(dyn Platform + 'static)>, PlatformError>),
80                #[cfg(all(feature = "i-slint-backend-linuxkms", target_os = "linux"))]
81                ("LinuxKMS", create_linuxkms_backend as fn() -> Result<Box<(dyn Platform + 'static)>, PlatformError>),
82                // Last-resort headless fallback so the MCP server keeps
83                // working when no display is available.
84                #[cfg(all(feature = "mcp", supports_headless))]
85                ("Headless", (|| create_headless_backend("")) as fn() -> Result<Box<(dyn Platform + 'static)>, PlatformError>),
86                ("", || Err(PlatformError::NoPlatform)),
87            ];
88
89            let mut backend_errors: Vec<Cow<str>> = Vec::new();
90
91            for (backend_name, backend_factory) in backends {
92                match backend_factory() {
93                    Ok(platform) => return Ok(platform),
94                    Err(err) => {
95                        backend_errors.push(if !backend_name.is_empty() {
96                            format!("Error from {backend_name} backend: {err}").into()
97                        } else {
98                            "No backends configured.".into()
99                        });
100                    },
101                }
102            }
103
104            Err(PlatformError::Other(format!("Could not initialize backend.\n{}", backend_errors.join("\n"))))
105        }
106
107        pub fn create_backend() -> Result<Box<dyn Platform + 'static>, PlatformError>  {
108
109            let backend_config = std::env::var("SLINT_BACKEND").unwrap_or_default();
110            let backend_config = backend_config.to_lowercase();
111            let (event_loop, _renderer) = parse_backend_env_var(backend_config.as_str());
112
113            match event_loop {
114                #[cfg(all(feature = "i-slint-backend-qt", not(no_qt)))]
115                "qt" => return Ok(Box::new(i_slint_backend_qt::Backend::new())),
116                #[cfg(feature = "i-slint-backend-winit")]
117                "winit" => return i_slint_backend_winit::Backend::new_with_renderer_by_name((!_renderer.is_empty()).then_some(_renderer)).map(|b| Box::new(b) as Box<dyn Platform + 'static>),
118                #[cfg(all(feature = "i-slint-backend-linuxkms", target_os = "linux"))]
119                "linuxkms" => {
120                    let mut builder = i_slint_backend_linuxkms::BackendBuilder::default();
121                    if !_renderer.is_empty() {
122                        builder = builder.with_renderer_name(_renderer.into());
123                    }
124                    return builder.build().map(|b| Box::new(b) as Box<dyn Platform + 'static>)
125                },
126                #[cfg(feature = "backend-testing")]
127                "testing" => return Ok(Box::new(i_slint_backend_testing::TestingBackend::new(
128                    i_slint_backend_testing::TestingBackendOptions { mock_time: false, threading: true, ..Default::default() },
129                ))),
130                #[cfg(all(feature = "mcp", supports_headless))]
131                "headless" => return create_headless_backend(_renderer),
132                _ => {},
133            }
134
135            if !backend_config.is_empty() {
136                eprintln!("Could not load rendering backend {backend_config}, fallback to default")
137            }
138            create_default_backend()
139        }
140        pub use default_backend::{
141            native_widgets, NativeGlobals, NativeWidgets, HAS_NATIVE_STYLE,
142        };
143    } else {
144        pub fn create_backend() -> Result<Box<dyn Platform + 'static>, PlatformError> {
145            Err(PlatformError::NoPlatform)
146        }
147        pub mod native_widgets {}
148        pub type NativeWidgets = ();
149        pub type NativeGlobals = ();
150        pub const HAS_NATIVE_STYLE: bool = false;
151    }
152}
153
154pub fn parse_backend_env_var(backend_config: &str) -> (&str, &str) {
155    backend_config.split_once('-').unwrap_or(match backend_config {
156        "qt" => ("qt", ""),
157        "gl" | "winit" => ("winit", ""),
158        "femtovg" => ("winit", "femtovg"),
159        "skia" => ("winit", "skia"),
160        "sw" | "software" => ("winit", "software"),
161        "vello" => ("winit", "vello"),
162        "linuxkms" => ("linuxkms", ""),
163        x => (x, ""),
164    })
165}
166
167/// Start the system-testing and MCP servers if their features are enabled.
168/// Also called by the bindings that install a platform with `set_platform()`, bypassing the selector.
169#[cfg(any(feature = "system-testing", feature = "mcp"))]
170pub fn init_testing_backends() {
171    #[cfg(feature = "system-testing")]
172    if let Err(e) = i_slint_backend_testing::systest::init() {
173        i_slint_core::debug_log!("System testing init failed: {e:?}");
174    }
175
176    #[cfg(feature = "mcp")]
177    if let Err(e) = i_slint_backend_testing::mcp_server::init() {
178        i_slint_core::debug_log!("MCP server init failed: {e:?}");
179    }
180}
181
182/// Run the callback with the platform abstraction.
183/// Create the backend if it does not exist yet
184pub fn with_platform<R>(
185    f: impl FnOnce(&dyn Platform) -> Result<R, PlatformError>,
186) -> Result<R, PlatformError> {
187    with_global_context(|ctx| f(ctx.platform()))?
188}
189
190/// Run the callback with the [`SlintContext`].
191/// Create the backend if it does not exist yet
192pub fn with_global_context<R>(f: impl FnOnce(&SlintContext) -> R) -> Result<R, PlatformError> {
193    let mut platform_created = false;
194    let result = i_slint_core::with_global_context(
195        || {
196            let backend = create_backend();
197            platform_created = backend.is_ok();
198            backend
199        },
200        f,
201    );
202
203    #[cfg(any(feature = "system-testing", feature = "mcp"))]
204    if result.is_ok() && platform_created {
205        init_testing_backends();
206    }
207
208    result
209}
210
211pub mod api;