winit/platform_impl/linux/wayland/window/
mod.rs

1//! The Wayland window.
2
3use std::sync::atomic::{AtomicBool, Ordering};
4use std::sync::{Arc, Mutex};
5
6use sctk::compositor::{CompositorState, Region, SurfaceData};
7use sctk::reexports::client::protocol::wl_display::WlDisplay;
8use sctk::reexports::client::protocol::wl_surface::WlSurface;
9use sctk::reexports::client::{Proxy, QueueHandle};
10use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1;
11use sctk::shell::xdg::window::{Window as SctkWindow, WindowDecorations};
12use sctk::shell::xdg::XdgSurface;
13use sctk::shell::WaylandSurface;
14use tracing::warn;
15
16use super::event_loop::sink::EventSink;
17use super::output::MonitorHandle;
18use super::state::WinitState;
19use super::types::xdg_activation::XdgActivationTokenData;
20use super::{ActiveEventLoop, WindowId};
21use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
22use crate::error::{NotSupportedError, RequestError};
23use crate::event::{Ime, WindowEvent};
24use crate::event_loop::AsyncRequestSerial;
25use crate::monitor::MonitorHandle as CoreMonitorHandle;
26use crate::platform::wayland::{HasXdgSurfaceHandle, XdgSurfaceHandle};
27use crate::platform_impl::{Fullscreen, MonitorHandle as PlatformMonitorHandle};
28use crate::window::{
29    Cursor, CursorGrabMode, Fullscreen as CoreFullscreen, ImePurpose, ResizeDirection, Theme,
30    UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons,
31    WindowId as CoreWindowId, WindowLevel,
32};
33
34pub(crate) mod state;
35
36pub use state::WindowState;
37
38/// The Wayland window.
39pub struct Window {
40    /// Reference to the underlying SCTK window.
41    window: SctkWindow,
42
43    /// Window id.
44    window_id: WindowId,
45
46    /// The state of the window.
47    window_state: Arc<Mutex<WindowState>>,
48
49    /// Compositor to handle WlRegion stuff.
50    compositor: Arc<CompositorState>,
51
52    /// The wayland display used solely for raw window handle.
53    #[allow(dead_code)]
54    display: WlDisplay,
55
56    /// Xdg activation to request user attention.
57    xdg_activation: Option<XdgActivationV1>,
58
59    /// The state of the requested attention from the `xdg_activation`.
60    attention_requested: Arc<AtomicBool>,
61
62    /// Handle to the main queue to perform requests.
63    queue_handle: QueueHandle<WinitState>,
64
65    /// Window requests to the event loop.
66    window_requests: Arc<WindowRequests>,
67
68    /// Observed monitors.
69    monitors: Arc<Mutex<Vec<MonitorHandle>>>,
70
71    /// Source to wake-up the event-loop for window requests.
72    event_loop_awakener: calloop::ping::Ping,
73
74    /// The event sink to deliver synthetic events.
75    window_events_sink: Arc<Mutex<EventSink>>,
76}
77
78impl Window {
79    pub(crate) fn new(
80        event_loop_window_target: &ActiveEventLoop,
81        attributes: WindowAttributes,
82    ) -> Result<Self, RequestError> {
83        let queue_handle = event_loop_window_target.queue_handle.clone();
84        let mut state = event_loop_window_target.state.borrow_mut();
85
86        let monitors = state.monitors.clone();
87
88        let surface = state.compositor_state.create_surface(&queue_handle);
89        let compositor = state.compositor_state.clone();
90        let xdg_activation =
91            state.xdg_activation.as_ref().map(|activation_state| activation_state.global().clone());
92        let display = event_loop_window_target.connection.display();
93
94        let size: Size = attributes.surface_size.unwrap_or(LogicalSize::new(800., 600.).into());
95
96        // We prefer server side decorations, however to not have decorations we ask for client
97        // side decorations instead.
98        let default_decorations = if attributes.decorations {
99            WindowDecorations::RequestServer
100        } else {
101            WindowDecorations::RequestClient
102        };
103
104        let window =
105            state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle);
106
107        let mut window_state = WindowState::new(
108            event_loop_window_target.connection.clone(),
109            &event_loop_window_target.queue_handle,
110            &state,
111            size,
112            window.clone(),
113            attributes.preferred_theme,
114        );
115
116        // Set transparency hint.
117        window_state.set_transparent(attributes.transparent);
118
119        window_state.set_blur(attributes.blur);
120
121        // Set the decorations hint.
122        window_state.set_decorate(attributes.decorations);
123
124        // Set the app_id.
125        if let Some(name) = attributes.platform_specific.name.map(|name| name.general) {
126            window.set_app_id(name);
127        }
128
129        // Set the window title.
130        window_state.set_title(attributes.title);
131
132        // Set the min and max sizes. We must set the hints upon creating a window, so
133        // we use the default `1.` scaling...
134        let min_size = attributes.min_surface_size.map(|size| size.to_logical(1.));
135        let max_size = attributes.max_surface_size.map(|size| size.to_logical(1.));
136        window_state.set_min_surface_size(min_size);
137        window_state.set_max_surface_size(max_size);
138
139        // Non-resizable implies that the min and max sizes are set to the same value.
140        window_state.set_resizable(attributes.resizable);
141
142        // Set startup mode.
143        match attributes.fullscreen.map(Into::into) {
144            Some(Fullscreen::Exclusive(_)) => {
145                warn!("`Fullscreen::Exclusive` is ignored on Wayland");
146            },
147            #[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
148            Some(Fullscreen::Borderless(monitor)) => {
149                let output = monitor.and_then(|monitor| match monitor {
150                    PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
151                    #[cfg(x11_platform)]
152                    PlatformMonitorHandle::X(_) => None,
153                });
154
155                window.set_fullscreen(output.as_ref())
156            },
157            _ if attributes.maximized => window.set_maximized(),
158            _ => (),
159        };
160
161        match attributes.cursor {
162            Cursor::Icon(icon) => window_state.set_cursor(icon),
163            Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
164        }
165
166        // Activate the window when the token is passed.
167        if let (Some(xdg_activation), Some(token)) =
168            (xdg_activation.as_ref(), attributes.platform_specific.activation_token)
169        {
170            xdg_activation.activate(token._token, &surface);
171        }
172
173        // XXX Do initial commit.
174        window.commit();
175
176        // Add the window and window requests into the state.
177        let window_state = Arc::new(Mutex::new(window_state));
178        let window_id = super::make_wid(&surface);
179        state.windows.get_mut().insert(window_id, window_state.clone());
180
181        let window_requests = WindowRequests {
182            redraw_requested: AtomicBool::new(true),
183            closed: AtomicBool::new(false),
184        };
185        let window_requests = Arc::new(window_requests);
186        state.window_requests.get_mut().insert(window_id, window_requests.clone());
187
188        // Setup the event sync to insert `WindowEvents` right from the window.
189        let window_events_sink = state.window_events_sink.clone();
190
191        let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut();
192        let event_queue = wayland_source.queue();
193
194        // Do a roundtrip.
195        event_queue.roundtrip(&mut state).map_err(|err| os_error!(err))?;
196
197        // XXX Wait for the initial configure to arrive.
198        while !window_state.lock().unwrap().is_configured() {
199            event_queue.blocking_dispatch(&mut state).map_err(|err| os_error!(err))?;
200        }
201
202        // Wake-up event loop, so it'll send initial redraw requested.
203        let event_loop_awakener = event_loop_window_target.event_loop_awakener.clone();
204        event_loop_awakener.ping();
205
206        Ok(Self {
207            window,
208            display,
209            monitors,
210            window_id,
211            compositor,
212            window_state,
213            queue_handle,
214            xdg_activation,
215            attention_requested: Arc::new(AtomicBool::new(false)),
216            event_loop_awakener,
217            window_requests,
218            window_events_sink,
219        })
220    }
221}
222
223impl Window {
224    pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
225        let xdg_activation = match self.xdg_activation.as_ref() {
226            Some(xdg_activation) => xdg_activation,
227            None => return Err(NotSupportedError::new("xdg_activation_v1 is not available").into()),
228        };
229
230        let serial = AsyncRequestSerial::get();
231
232        let data = XdgActivationTokenData::Obtain((self.window_id, serial));
233        let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data);
234        xdg_activation_token.set_surface(self.surface());
235        xdg_activation_token.commit();
236
237        Ok(serial)
238    }
239
240    #[inline]
241    pub fn surface(&self) -> &WlSurface {
242        self.window.wl_surface()
243    }
244}
245
246impl Drop for Window {
247    fn drop(&mut self) {
248        self.window_requests.closed.store(true, Ordering::Relaxed);
249        self.event_loop_awakener.ping();
250    }
251}
252
253#[cfg(feature = "rwh_06")]
254impl rwh_06::HasWindowHandle for Window {
255    fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
256        let raw = rwh_06::WaylandWindowHandle::new({
257            let ptr = self.window.wl_surface().id().as_ptr();
258            std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null")
259        });
260
261        unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw.into())) }
262    }
263}
264
265#[cfg(feature = "rwh_06")]
266impl rwh_06::HasDisplayHandle for Window {
267    fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
268        let raw = rwh_06::WaylandDisplayHandle::new({
269            let ptr = self.display.id().as_ptr();
270            std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null")
271        });
272
273        unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw.into())) }
274    }
275}
276
277impl CoreWindow for Window {
278    fn id(&self) -> CoreWindowId {
279        CoreWindowId(self.window_id)
280    }
281
282    fn request_redraw(&self) {
283        // NOTE: try to not wake up the loop when the event was already scheduled and not yet
284        // processed by the loop, because if at this point the value was `true` it could only
285        // mean that the loop still haven't dispatched the value to the client and will do
286        // eventually, resetting it to `false`.
287        if self
288            .window_requests
289            .redraw_requested
290            .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
291            .is_ok()
292        {
293            self.event_loop_awakener.ping();
294        }
295    }
296
297    #[inline]
298    fn title(&self) -> String {
299        self.window_state.lock().unwrap().title().to_owned()
300    }
301
302    fn pre_present_notify(&self) {
303        self.window_state.lock().unwrap().request_frame_callback();
304    }
305
306    fn reset_dead_keys(&self) {
307        crate::platform_impl::common::xkb::reset_dead_keys()
308    }
309
310    fn inner_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
311        Err(NotSupportedError::new("window position information is not available on Wayland")
312            .into())
313    }
314
315    fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
316        Err(NotSupportedError::new("window position information is not available on Wayland")
317            .into())
318    }
319
320    fn set_outer_position(&self, _position: Position) {
321        // Not possible.
322    }
323
324    fn surface_size(&self) -> PhysicalSize<u32> {
325        let window_state = self.window_state.lock().unwrap();
326        let scale_factor = window_state.scale_factor();
327        super::logical_to_physical_rounded(window_state.surface_size(), scale_factor)
328    }
329
330    fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
331        let mut window_state = self.window_state.lock().unwrap();
332        let new_size = window_state.request_surface_size(size);
333        self.request_redraw();
334        Some(new_size)
335    }
336
337    fn outer_size(&self) -> PhysicalSize<u32> {
338        let window_state = self.window_state.lock().unwrap();
339        let scale_factor = window_state.scale_factor();
340        super::logical_to_physical_rounded(window_state.outer_size(), scale_factor)
341    }
342
343    fn set_min_surface_size(&self, min_size: Option<Size>) {
344        let scale_factor = self.scale_factor();
345        let min_size = min_size.map(|size| size.to_logical(scale_factor));
346        self.window_state.lock().unwrap().set_min_surface_size(min_size);
347        // NOTE: Requires commit to be applied.
348        self.request_redraw();
349    }
350
351    /// Set the maximum surface size for the window.
352    #[inline]
353    fn set_max_surface_size(&self, max_size: Option<Size>) {
354        let scale_factor = self.scale_factor();
355        let max_size = max_size.map(|size| size.to_logical(scale_factor));
356        self.window_state.lock().unwrap().set_max_surface_size(max_size);
357        // NOTE: Requires commit to be applied.
358        self.request_redraw();
359    }
360
361    fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
362        None
363    }
364
365    fn set_surface_resize_increments(&self, _increments: Option<Size>) {
366        warn!("`set_surface_resize_increments` is not implemented for Wayland");
367    }
368
369    fn set_title(&self, title: &str) {
370        let new_title = title.to_string();
371        self.window_state.lock().unwrap().set_title(new_title);
372    }
373
374    #[inline]
375    fn set_transparent(&self, transparent: bool) {
376        self.window_state.lock().unwrap().set_transparent(transparent);
377    }
378
379    fn set_visible(&self, _visible: bool) {
380        // Not possible on Wayland.
381    }
382
383    fn is_visible(&self) -> Option<bool> {
384        None
385    }
386
387    fn set_resizable(&self, resizable: bool) {
388        if self.window_state.lock().unwrap().set_resizable(resizable) {
389            // NOTE: Requires commit to be applied.
390            self.request_redraw();
391        }
392    }
393
394    fn is_resizable(&self) -> bool {
395        self.window_state.lock().unwrap().resizable()
396    }
397
398    fn set_enabled_buttons(&self, _buttons: WindowButtons) {
399        // TODO(kchibisov) v5 of the xdg_shell allows that.
400    }
401
402    fn enabled_buttons(&self) -> WindowButtons {
403        // TODO(kchibisov) v5 of the xdg_shell allows that.
404        WindowButtons::all()
405    }
406
407    fn set_minimized(&self, minimized: bool) {
408        // You can't unminimize the window on Wayland.
409        if !minimized {
410            warn!("Unminimizing is ignored on Wayland.");
411            return;
412        }
413
414        self.window.set_minimized();
415    }
416
417    fn is_minimized(&self) -> Option<bool> {
418        // XXX clients don't know whether they are minimized or not.
419        None
420    }
421
422    fn set_maximized(&self, maximized: bool) {
423        if maximized {
424            self.window.set_maximized()
425        } else {
426            self.window.unset_maximized()
427        }
428    }
429
430    fn is_maximized(&self) -> bool {
431        self.window_state
432            .lock()
433            .unwrap()
434            .last_configure
435            .as_ref()
436            .map(|last_configure| last_configure.is_maximized())
437            .unwrap_or_default()
438    }
439
440    fn set_fullscreen(&self, fullscreen: Option<CoreFullscreen>) {
441        match fullscreen {
442            Some(CoreFullscreen::Exclusive(_)) => {
443                warn!("`Fullscreen::Exclusive` is ignored on Wayland");
444            },
445            #[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
446            Some(CoreFullscreen::Borderless(monitor)) => {
447                let output = monitor.and_then(|monitor| match monitor.inner {
448                    PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
449                    #[cfg(x11_platform)]
450                    PlatformMonitorHandle::X(_) => None,
451                });
452
453                self.window.set_fullscreen(output.as_ref())
454            },
455            None => self.window.unset_fullscreen(),
456        }
457    }
458
459    fn fullscreen(&self) -> Option<CoreFullscreen> {
460        let is_fullscreen = self
461            .window_state
462            .lock()
463            .unwrap()
464            .last_configure
465            .as_ref()
466            .map(|last_configure| last_configure.is_fullscreen())
467            .unwrap_or_default();
468
469        if is_fullscreen {
470            let current_monitor = self.current_monitor();
471            Some(CoreFullscreen::Borderless(current_monitor))
472        } else {
473            None
474        }
475    }
476
477    #[inline]
478    fn scale_factor(&self) -> f64 {
479        self.window_state.lock().unwrap().scale_factor()
480    }
481
482    #[inline]
483    fn set_blur(&self, blur: bool) {
484        self.window_state.lock().unwrap().set_blur(blur);
485    }
486
487    #[inline]
488    fn set_decorations(&self, decorate: bool) {
489        self.window_state.lock().unwrap().set_decorate(decorate)
490    }
491
492    #[inline]
493    fn is_decorated(&self) -> bool {
494        self.window_state.lock().unwrap().is_decorated()
495    }
496
497    fn set_window_level(&self, _level: WindowLevel) {}
498
499    fn set_window_icon(&self, _window_icon: Option<crate::window::Icon>) {}
500
501    #[inline]
502    fn set_ime_cursor_area(&self, position: Position, size: Size) {
503        let window_state = self.window_state.lock().unwrap();
504        if window_state.ime_allowed() {
505            let scale_factor = window_state.scale_factor();
506            let position = position.to_logical(scale_factor);
507            let size = size.to_logical(scale_factor);
508            window_state.set_ime_cursor_area(position, size);
509        }
510    }
511
512    #[inline]
513    fn set_ime_allowed(&self, allowed: bool) {
514        let mut window_state = self.window_state.lock().unwrap();
515
516        if window_state.ime_allowed() != allowed && window_state.set_ime_allowed(allowed) {
517            let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled });
518            self.window_events_sink.lock().unwrap().push_window_event(event, self.window_id);
519            self.event_loop_awakener.ping();
520        }
521    }
522
523    #[inline]
524    fn set_ime_purpose(&self, purpose: ImePurpose) {
525        self.window_state.lock().unwrap().set_ime_purpose(purpose);
526    }
527
528    fn focus_window(&self) {}
529
530    fn has_focus(&self) -> bool {
531        self.window_state.lock().unwrap().has_focus()
532    }
533
534    fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
535        let xdg_activation = match self.xdg_activation.as_ref() {
536            Some(xdg_activation) => xdg_activation,
537            None => {
538                warn!("`request_user_attention` isn't supported");
539                return;
540            },
541        };
542
543        // Urgency is only removed by the compositor and there's no need to raise urgency when it
544        // was already raised.
545        if request_type.is_none() || self.attention_requested.load(Ordering::Relaxed) {
546            return;
547        }
548
549        self.attention_requested.store(true, Ordering::Relaxed);
550        let surface = self.surface().clone();
551        let data = XdgActivationTokenData::Attention((
552            surface.clone(),
553            Arc::downgrade(&self.attention_requested),
554        ));
555        let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data);
556        xdg_activation_token.set_surface(&surface);
557        xdg_activation_token.commit();
558    }
559
560    fn set_theme(&self, theme: Option<Theme>) {
561        self.window_state.lock().unwrap().set_theme(theme)
562    }
563
564    fn theme(&self) -> Option<Theme> {
565        self.window_state.lock().unwrap().theme()
566    }
567
568    fn set_content_protected(&self, _protected: bool) {}
569
570    fn set_cursor(&self, cursor: Cursor) {
571        let window_state = &mut self.window_state.lock().unwrap();
572
573        match cursor {
574            Cursor::Icon(icon) => window_state.set_cursor(icon),
575            Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
576        }
577    }
578
579    fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
580        let scale_factor = self.scale_factor();
581        let position = position.to_logical(scale_factor);
582        self.window_state
583            .lock()
584            .unwrap()
585            .set_cursor_position(position)
586            // Request redraw on success, since the state is double buffered.
587            .map(|_| self.request_redraw())
588    }
589
590    fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> {
591        self.window_state.lock().unwrap().set_cursor_grab(mode)
592    }
593
594    fn set_cursor_visible(&self, visible: bool) {
595        self.window_state.lock().unwrap().set_cursor_visible(visible);
596    }
597
598    fn drag_window(&self) -> Result<(), RequestError> {
599        self.window_state.lock().unwrap().drag_window()
600    }
601
602    fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> {
603        self.window_state.lock().unwrap().drag_resize_window(direction)
604    }
605
606    fn show_window_menu(&self, position: Position) {
607        let scale_factor = self.scale_factor();
608        let position = position.to_logical(scale_factor);
609        self.window_state.lock().unwrap().show_window_menu(position);
610    }
611
612    fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
613        let surface = self.window.wl_surface();
614
615        if hittest {
616            surface.set_input_region(None);
617            Ok(())
618        } else {
619            let region = Region::new(&*self.compositor).map_err(|err| os_error!(err))?;
620            region.add(0, 0, 0, 0);
621            surface.set_input_region(Some(region.wl_region()));
622            Ok(())
623        }
624    }
625
626    fn current_monitor(&self) -> Option<CoreMonitorHandle> {
627        let data = self.window.wl_surface().data::<SurfaceData>()?;
628        data.outputs()
629            .next()
630            .map(MonitorHandle::new)
631            .map(crate::platform_impl::MonitorHandle::Wayland)
632            .map(|inner| CoreMonitorHandle { inner })
633    }
634
635    fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
636        Box::new(
637            self.monitors
638                .lock()
639                .unwrap()
640                .clone()
641                .into_iter()
642                .map(crate::platform_impl::MonitorHandle::Wayland)
643                .map(|inner| CoreMonitorHandle { inner }),
644        )
645    }
646
647    fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
648        // NOTE: There's no such concept on Wayland.
649        None
650    }
651
652    /// Get the raw-window-handle v0.6 display handle.
653    #[cfg(feature = "rwh_06")]
654    fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
655        self
656    }
657
658    /// Get the raw-window-handle v0.6 window handle.
659    #[cfg(feature = "rwh_06")]
660    fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
661        self
662    }
663}
664
665impl HasXdgSurfaceHandle for Window {
666    fn xdg_surface_handle(
667        &self,
668    ) -> Result<crate::platform::wayland::XdgSurfaceHandle<'_>, rwh_06::HandleError> {
669        let raw = {
670            let ptr = self.window.xdg_surface().id().as_ptr();
671            std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null")
672        };
673
674        unsafe { Ok(XdgSurfaceHandle::borrow_raw(raw)) }
675    }
676}
677
678/// The request from the window to the event loop.
679#[derive(Debug)]
680pub struct WindowRequests {
681    /// The window was closed.
682    pub closed: AtomicBool,
683
684    /// Redraw Requested.
685    pub redraw_requested: AtomicBool,
686}
687
688impl WindowRequests {
689    pub fn take_closed(&self) -> bool {
690        self.closed.swap(false, Ordering::Relaxed)
691    }
692
693    pub fn take_redraw_requested(&self) -> bool {
694        self.redraw_requested.swap(false, Ordering::Relaxed)
695    }
696}
697
698impl TryFrom<&str> for Theme {
699    type Error = ();
700
701    /// ```
702    /// use winit::window::Theme;
703    ///
704    /// assert_eq!("dark".try_into(), Ok(Theme::Dark));
705    /// assert_eq!("lIghT".try_into(), Ok(Theme::Light));
706    /// ```
707    fn try_from(theme: &str) -> Result<Self, Self::Error> {
708        if theme.eq_ignore_ascii_case("dark") {
709            Ok(Self::Dark)
710        } else if theme.eq_ignore_ascii_case("light") {
711            Ok(Self::Light)
712        } else {
713            Err(())
714        }
715    }
716}