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