Skip to main content

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