i_slint_backend_selector/
api.rs1#![warn(missing_docs)]
5
6use 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#[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 #[must_use]
58 pub fn new() -> Self {
59 Self::default()
60 }
61
62 #[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 #[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 #[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 #[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 #[must_use]
96 pub fn require_metal(mut self) -> Self {
97 self.requested_graphics_api = Some(RequestedGraphicsAPI::Metal);
98 self
99 }
100
101 #[must_use]
103 pub fn require_vulkan(mut self) -> Self {
104 self.requested_graphics_api = Some(RequestedGraphicsAPI::Vulkan);
105 self
106 }
107
108 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[must_use]
242 pub fn renderer_name(mut self, name: String) -> Self {
243 self.renderer = Some(name);
244 self
245 }
246
247 #[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 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 #[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 #[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}