Skip to main content

wlib/
lib.rs

1pub mod keys;
2
3use std::collections::{HashMap, HashSet};
4use std::time::Duration;
5
6use smithay_client_toolkit::activation::RequestData;
7use smithay_client_toolkit::reexports::calloop::EventLoop;
8use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
9use smithay_client_toolkit::{
10    activation::{ActivationHandler, ActivationState},
11    compositor::{CompositorHandler, CompositorState},
12    delegate_activation, delegate_compositor, delegate_keyboard, delegate_output, delegate_pointer,
13    delegate_registry, delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window,
14    output::{OutputHandler, OutputState},
15    registry::{ProvidesRegistryState, RegistryState},
16    registry_handlers,
17    seat::{
18        Capability, SeatHandler, SeatState,
19        keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers, RawModifiers},
20        pointer::PointerHandler,
21    },
22    shell::{
23        WaylandSurface,
24        xdg::{
25            XdgShell,
26            window::{Window, WindowConfigure, WindowDecorations, WindowHandler},
27        },
28    },
29    shm::{
30        Shm, ShmHandler,
31        slot::{Buffer, SlotPool},
32    },
33};
34
35use wayland_client::{
36    Connection, QueueHandle,
37    globals::registry_queue_init,
38    protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
39};
40
41pub use smithay_client_toolkit::seat::{
42    keyboard,
43    pointer::{PointerEvent, PointerEventKind},
44};
45
46// Todo.
47// -  More information to update like keyboard state.
48
49/// The trait you must implement to create a window
50pub trait WindowAble {
51    /// Ran before draw so you can set up your scene with information from `context`
52    /// You can include any requests you want wlib to do in in the returned output
53    fn update(&mut self, context: Context) -> Option<WLibRequest>;
54
55    /// Write your pixels to this buffer
56    /// Since the window size is controlled by compositor, the width and height is given here.
57    /// Foramt is always ARGB little endian. (So real byte order is BGRA)
58    /// # Example
59    /// ```rust
60    /// fn draw(buffer: &mut [u8], frame: wlib::WindowSize) {
61    ///     let width = frame.width;
62    ///     let height = frame.height;
63    ///
64    ///     buffer
65    ///         .chunks_exact_mut(4)
66    ///         .enumerate()
67    ///         .for_each(|(index, chunk)| {
68    ///             let x = index as u32 % width;
69    ///             let y = index as u32 / width;
70    ///
71    ///             let a: u8 = 0xFF;
72    ///             let r: u8 = ((x as f32 / (width as f32)) * 255.0) as u8;
73    ///             let g: u8 = 0;
74    ///             let b: u8 = ((y as f32 / (height as f32)) * 255.0) as u8;
75    ///
76    ///             let array: &mut [u8; 4] = chunk.try_into().unwrap();
77    ///             *array = [b, g, r, a]
78    ///         });
79    /// }
80    /// ```
81    fn draw(&mut self, pixel_buffer: &mut [u8], frame_info: WindowSize);
82}
83
84/// The possible event types you can get from `Context::event_queue`
85#[derive(Debug, Clone)]
86pub enum Event {
87    KeyPress(KeyEvent),
88    KeyRelease(KeyEvent),
89    PointerEvent(PointerEvent),
90    CloseRequested,
91}
92
93/// Some information you want to tell WLib.
94pub enum WLibRequest {
95    /// This is so you can prompt for the user to save their work or confirm exit etc, before
96    /// closing the window
97    CloseAccepted,
98}
99
100/// The information passed to your `update()` each frame
101#[derive(Debug, Clone)]
102pub struct Context {
103    /// The currently pressed keys. Just a convinence field for the event_queue
104    pub pressed_keys: HashMap<keys::RawKeyCode, keys::KeySym>,
105
106    /// State of the mouse
107    pub mouse_state: MouseState,
108
109    /// Time since last frame
110    pub delta_time: std::time::Duration,
111
112    /// Has the compositor requested this window been closed?
113    /// To accept, return a `WLibRequest::CloseAccepted`.
114    pub close_requested: bool,
115
116    /// Self explanatory
117    pub is_window_focused: bool,
118
119    /// List of events since the last frame.
120    /// Use it if you specfically need mouse/key(up/down) events or specfic mouse motions.
121    pub event_queue: Vec<Event>,
122
123    /// Current size of the window
124    pub window_size: WindowSize,
125}
126
127/// State of the mouse
128#[derive(Debug, Clone)]
129pub struct MouseState {
130    /// mouse position on window
131    pub position: (f64, f64),
132    /// The mouse buttons currently pressed. Just a convinence field for the event_queue
133    pub mouse_buttons_pressed: HashSet<MouseButton>,
134}
135
136/// Supported MouseButtons.
137/// If you need extra mouse buttons, look at the event queue and use numbers from `/usr/include/linux/input-event-codes.h`
138#[non_exhaustive]
139#[derive(Debug, Clone, Hash, Eq, PartialEq)]
140pub enum MouseButton {
141    BtnLeft,
142    BtnRight,
143    BtnMiddle,
144    BtnSide,
145    BtnExtra,
146    BtnForward,
147    BtnBack,
148}
149
150impl TryFrom<u32> for MouseButton {
151    type Error = ();
152
153    fn try_from(value: u32) -> Result<Self, Self::Error> {
154        match value {
155            0x110 => Ok(MouseButton::BtnLeft),
156            0x111 => Ok(MouseButton::BtnRight),
157            0x112 => Ok(MouseButton::BtnMiddle),
158            0x113 => Ok(MouseButton::BtnSide),
159            0x114 => Ok(MouseButton::BtnExtra),
160            0x115 => Ok(MouseButton::BtnForward),
161            0x116 => Ok(MouseButton::BtnBack),
162            _ => Err(()),
163        }
164    }
165}
166
167struct WindowManager {
168    registry_state: RegistryState,
169    seat_state: SeatState,
170    output_state: OutputState,
171    shm: Shm,
172    xdg_activation: Option<ActivationState>,
173
174    close_accepted: bool,
175    first_configure: bool,
176    pool: SlotPool,
177    width: u32,
178    height: u32,
179    buffer: Option<Buffer>,
180    window: Window,
181    keyboard: Option<wl_keyboard::WlKeyboard>,
182    keyboard_focus: bool,
183    pointer: Option<wl_pointer::WlPointer>,
184    last_frame_time: Option<std::time::Instant>,
185
186    managed_window: Box<dyn WindowAble>,
187    settings: WLibSettings,
188    context: Context,
189}
190
191/// The pixel size of a window
192#[derive(Debug, Clone)]
193pub struct WindowSize {
194    pub width: u32,
195    pub height: u32,
196}
197
198/// The available settings to configure window stuff
199#[derive(Default)]
200pub struct WLibSettings {
201    /// If the window size should be static instead of updating when needed.
202    window_static_size: Option<WindowSize>,
203
204    /// Title of the window to show in in window selectors, decorations etc.
205    window_title: String,
206
207    /// App Id. Should be [reverse domain
208    /// notation](https://en.wikipedia.org/wiki/Reverse_domain_name_notation)
209    app_id: String,
210}
211
212impl WLibSettings {
213    pub fn new() -> Self {
214        Self::default()
215    }
216
217    pub fn with_static_size(mut self, size: WindowSize) -> Self {
218        self.window_static_size = Some(size);
219        self
220    }
221
222    pub fn with_title(mut self, title: &str) -> Self {
223        self.window_title = title.to_string();
224        self
225    }
226
227    pub fn with_app_id(mut self, id: &str) -> Self {
228        self.app_id = id.to_string();
229        self
230    }
231}
232
233/// Runs a struct implementing `WindowAble` by setting up a wayland event loop.
234pub fn run(state: Box<dyn WindowAble>, settings: WLibSettings) {
235    // All Wayland apps start by connecting the compositor (server).
236    let conn = Connection::connect_to_env().unwrap();
237
238    // Enumerate the list of globals to get the protocols the server implements.
239    let (globals, event_queue) = registry_queue_init(&conn).unwrap();
240    let qh = event_queue.handle();
241    let mut event_loop: EventLoop<WindowManager> =
242        EventLoop::try_new().expect("Failed to initialize the event loop!");
243    let loop_handle = event_loop.handle();
244    WaylandSource::new(conn.clone(), event_queue)
245        .insert(loop_handle)
246        .unwrap();
247
248    // The compositor (not to be confused with the server which is commonly called the compositor) allows
249    // configuring surfaces to be presented.
250    let compositor = CompositorState::bind(&globals, &qh).expect("wl_compositor not available");
251    // For desktop platforms, the XDG shell is the standard protocol for creating desktop windows.
252    let xdg_shell = XdgShell::bind(&globals, &qh).expect("xdg shell is not available");
253
254    // Since we are not using the GPU in this example, we use wl_shm to allow software rendering to a buffer
255    // we share with the compositor process.
256    let shm = Shm::bind(&globals, &qh).expect("wl shm is not available.");
257    // If the compositor supports xdg-activation it probably wants us to use it to get focus
258    let xdg_activation = ActivationState::bind(&globals, &qh).ok();
259
260    // A window is created from a surface.
261    let surface = compositor.create_surface(&qh);
262
263    // And then we can create the window.
264    let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh);
265
266    // Configure the window, this may include hints to the compositor about the desired minimum size of the
267    // window, app id for WM identification, the window title, etc.
268    window.set_title(&settings.window_title);
269
270    // GitHub does not let projects use the `org.github` domain but the `io.github` domain is fine.
271    window.set_app_id(&settings.app_id);
272
273    // In order for the window to be mapped, we need to perform an initial commit with no attached buffer.
274    // For more info, see WaylandSurface::commit
275    //
276    // The compositor will respond with an initial configure that we can then use to present to the window with
277    // the correct options.
278    window.commit();
279
280    // To request focus, we first need to request a token
281    if let Some(activation) = xdg_activation.as_ref() {
282        activation.request_token(
283            &qh,
284            RequestData {
285                seat_and_serial: None,
286                surface: Some(window.wl_surface().clone()),
287                app_id: Some(String::from(
288                    "io.github.smithay.client-toolkit.SimpleWindow",
289                )),
290            },
291        )
292    }
293
294    let (width, height) = if let Some(ref dimensions) = settings.window_static_size {
295        // If both min and max size are set to the same value, it means the size is static.
296        // from (niri docs)[https://github.com/YaLTeR/niri/wiki/Floating-Windows], if this is the
297        // case the window is set to be floating in tiling window managers
298        window.set_min_size(Some((dimensions.width, dimensions.height)));
299        window.set_max_size(Some((dimensions.width, dimensions.height)));
300
301        (dimensions.width, dimensions.height)
302    } else {
303        (200, 200)
304    };
305
306    // We don't know how large the window will be yet, so lets assume the minimum size we suggested for the
307    // initial memory allocation.
308    let pool = SlotPool::new((width * height * 4) as usize, &shm).expect("Failed to create pool");
309
310    let mut window_manager = WindowManager {
311        // Seats and outputs may be hotplugged at runtime, therefore we need to setup a registry state to
312        // listen for seats and outputs.
313        registry_state: RegistryState::new(&globals),
314        seat_state: SeatState::new(&globals, &qh),
315        output_state: OutputState::new(&globals, &qh),
316        shm,
317        xdg_activation,
318
319        close_accepted: false,
320        first_configure: true,
321        pool,
322        width,
323        height,
324        buffer: None,
325        window,
326        keyboard: None,
327        keyboard_focus: false,
328        pointer: None,
329        last_frame_time: None,
330
331        managed_window: state,
332        context: Context {
333            delta_time: std::time::Duration::from_millis(0),
334            pressed_keys: HashMap::new(),
335            close_requested: false,
336            event_queue: Vec::new(),
337            is_window_focused: true,
338
339            mouse_state: MouseState {
340                position: (0.0, 0.0),
341                mouse_buttons_pressed: HashSet::new(),
342            },
343            window_size: WindowSize {
344                height: 0,
345                width: 0,
346            },
347        },
348        settings,
349    };
350
351    // We don't draw immediately, the configure will notify us when to first draw.
352    loop {
353        event_loop
354            .dispatch(Duration::ZERO, &mut window_manager)
355            .unwrap();
356
357        if window_manager.close_accepted {
358            break;
359        }
360    }
361}
362
363impl CompositorHandler for WindowManager {
364    fn scale_factor_changed(
365        &mut self,
366        _conn: &Connection,
367        _qh: &QueueHandle<Self>,
368        _surface: &wl_surface::WlSurface,
369        _new_factor: i32,
370    ) {
371        // Not needed for this example.
372    }
373
374    fn transform_changed(
375        &mut self,
376        _conn: &Connection,
377        _qh: &QueueHandle<Self>,
378        _surface: &wl_surface::WlSurface,
379        _new_transform: wl_output::Transform,
380    ) {
381        // Not needed for this example.
382    }
383
384    fn frame(
385        &mut self,
386        conn: &Connection,
387        qh: &QueueHandle<Self>,
388        _surface: &wl_surface::WlSurface,
389        _time: u32,
390    ) {
391        let now = std::time::Instant::now();
392        let delta = self
393            .last_frame_time
394            .map(|last| now - last)
395            .unwrap_or(Duration::ZERO);
396
397        self.last_frame_time = Some(now);
398        self.context.delta_time = delta;
399
400        let request = self.managed_window.update(self.context.clone());
401        self.handle_update(request);
402
403        self.draw(conn, qh);
404
405        self.context.event_queue.clear();
406    }
407
408    fn surface_enter(
409        &mut self,
410        _conn: &Connection,
411        _qh: &QueueHandle<Self>,
412        _surface: &wl_surface::WlSurface,
413        _output: &wl_output::WlOutput,
414    ) {
415        // Not needed for this example.
416    }
417
418    fn surface_leave(
419        &mut self,
420        _conn: &Connection,
421        _qh: &QueueHandle<Self>,
422        _surface: &wl_surface::WlSurface,
423        _output: &wl_output::WlOutput,
424    ) {
425        // Not needed for this example.
426    }
427}
428
429impl OutputHandler for WindowManager {
430    fn output_state(&mut self) -> &mut OutputState {
431        &mut self.output_state
432    }
433
434    fn new_output(
435        &mut self,
436        _conn: &Connection,
437        _qh: &QueueHandle<Self>,
438        _output: wl_output::WlOutput,
439    ) {
440    }
441
442    fn update_output(
443        &mut self,
444        _conn: &Connection,
445        _qh: &QueueHandle<Self>,
446        _output: wl_output::WlOutput,
447    ) {
448    }
449
450    fn output_destroyed(
451        &mut self,
452        _conn: &Connection,
453        _qh: &QueueHandle<Self>,
454        _output: wl_output::WlOutput,
455    ) {
456    }
457}
458
459impl WindowHandler for WindowManager {
460    fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &Window) {
461        self.context.close_requested = true;
462    }
463
464    fn configure(
465        &mut self,
466        conn: &Connection,
467        qh: &QueueHandle<Self>,
468        _window: &Window,
469        configure: WindowConfigure,
470        _serial: u32,
471    ) {
472        // println!("Window configured to: {:?}", configure);
473
474        self.buffer = None;
475
476        if self.settings.window_static_size.is_none() {
477            self.width = configure.new_size.0.map(|v| v.get()).unwrap_or(256);
478            self.height = configure.new_size.1.map(|v| v.get()).unwrap_or(256);
479        }
480
481        self.context.window_size = WindowSize {
482            width: self.width,
483            height: self.height,
484        };
485
486        // self.width = configure.new_size.0.map(|v| v.get()).unwrap();
487        // self.height = configure.new_size.1.map(|v| v.get()).unwrap();
488
489        // Initiate the first draw.
490        if self.first_configure {
491            self.first_configure = false;
492            self.draw(conn, qh);
493        }
494    }
495}
496
497impl ActivationHandler for WindowManager {
498    type RequestData = RequestData;
499
500    fn new_token(&mut self, token: String, _data: &Self::RequestData) {
501        self.xdg_activation
502            .as_ref()
503            .unwrap()
504            .activate::<WindowManager>(self.window.wl_surface(), token);
505    }
506}
507
508impl SeatHandler for WindowManager {
509    fn seat_state(&mut self) -> &mut SeatState {
510        &mut self.seat_state
511    }
512
513    fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
514
515    fn new_capability(
516        &mut self,
517        _conn: &Connection,
518        qh: &QueueHandle<Self>,
519        seat: wl_seat::WlSeat,
520        capability: Capability,
521    ) {
522        if capability == Capability::Keyboard && self.keyboard.is_none() {
523            // println!("Set keyboard capability");
524            let keyboard = self
525                .seat_state
526                .get_keyboard(qh, &seat, None)
527                .expect("Failed to create keyboard");
528
529            self.keyboard = Some(keyboard);
530        }
531
532        if capability == Capability::Pointer && self.pointer.is_none() {
533            // println!("Set pointer capability");
534            let pointer = self
535                .seat_state
536                .get_pointer(qh, &seat)
537                .expect("Failed to create pointer");
538            self.pointer = Some(pointer);
539        }
540    }
541
542    fn remove_capability(
543        &mut self,
544        _conn: &Connection,
545        _: &QueueHandle<Self>,
546        _: wl_seat::WlSeat,
547        capability: Capability,
548    ) {
549        if capability == Capability::Keyboard && self.keyboard.is_some() {
550            // println!("Unset keyboard capability");
551            self.keyboard.take().unwrap().release();
552        }
553
554        if capability == Capability::Pointer && self.pointer.is_some() {
555            // println!("Unset pointer capability");
556            self.pointer.take().unwrap().release();
557        }
558    }
559
560    fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
561}
562
563impl KeyboardHandler for WindowManager {
564    fn enter(
565        &mut self,
566        _: &Connection,
567        _: &QueueHandle<Self>,
568        _: &wl_keyboard::WlKeyboard,
569        surface: &wl_surface::WlSurface,
570        _: u32,
571        raw: &[u32],
572        keysyms: &[Keysym],
573    ) {
574        if self.window.wl_surface() == surface {
575            // println!("Keyboard focus on window with pressed syms: {keysyms:?}");
576            self.keyboard_focus = true;
577            for (rawk, sym) in raw.iter().zip(keysyms.iter()) {
578                self.context.pressed_keys.insert(*rawk, *sym);
579            }
580        }
581    }
582
583    fn leave(
584        &mut self,
585        _: &Connection,
586        _: &QueueHandle<Self>,
587        _: &wl_keyboard::WlKeyboard,
588        surface: &wl_surface::WlSurface,
589        _: u32,
590    ) {
591        if self.window.wl_surface() == surface {
592            // println!("Release keyboard focus on window");
593            self.keyboard_focus = false;
594            self.context.pressed_keys.clear();
595        }
596    }
597
598    fn press_key(
599        &mut self,
600        _conn: &Connection,
601        _qh: &QueueHandle<Self>,
602        _: &wl_keyboard::WlKeyboard,
603        _: u32,
604        event: KeyEvent,
605    ) {
606        self.context
607            .event_queue
608            .push(Event::KeyPress(event.clone()));
609
610        self.context
611            .pressed_keys
612            .insert(event.raw_code, event.keysym);
613    }
614
615    fn release_key(
616        &mut self,
617        _: &Connection,
618        _: &QueueHandle<Self>,
619        _: &wl_keyboard::WlKeyboard,
620        _: u32,
621        event: KeyEvent,
622    ) {
623        self.context
624            .event_queue
625            .push(Event::KeyRelease(event.clone()));
626
627        self.context.pressed_keys.remove(&event.raw_code);
628    }
629
630    fn repeat_key(
631        &mut self,
632        _: &Connection,
633        _: &QueueHandle<Self>,
634        _: &wl_keyboard::WlKeyboard,
635        _: u32,
636        _event: KeyEvent,
637    ) {
638        // println!("Key repeat: {event:?}");
639    }
640
641    fn update_modifiers(
642        &mut self,
643        _: &Connection,
644        _: &QueueHandle<Self>,
645        _: &wl_keyboard::WlKeyboard,
646        _serial: u32,
647        _modifiers: Modifiers,
648        _raw_modifiers: RawModifiers,
649        _layout: u32,
650    ) {
651        // println!("Update modifiers: {modifiers:?}");
652    }
653}
654
655impl PointerHandler for WindowManager {
656    fn pointer_frame(
657        &mut self,
658        _conn: &Connection,
659        _qh: &QueueHandle<Self>,
660        _pointer: &wl_pointer::WlPointer,
661        events: &[PointerEvent],
662    ) {
663        for event in events {
664            // Ignore events for other surfaces
665            if &event.surface != self.window.wl_surface() {
666                continue;
667            }
668
669            use PointerEventKind as PEK;
670            match event.kind {
671                PEK::Press {
672                    time: _,
673                    button: b,
674                    serial: _,
675                } => {
676                    // So this is pretty annoying. The button code is defined in some c header file
677                    // https://wayland.app/protocols/wayland#wl_pointer:event:button
678                    // println!("button press: {b}");
679                    if let Ok(bttn) = MouseButton::try_from(b) {
680                        self.context.mouse_state.mouse_buttons_pressed.insert(bttn);
681                    }
682                }
683                PEK::Release { button: b, .. } => {
684                    // println!("button press: {b}");
685                    if let Ok(bttn) = MouseButton::try_from(b) {
686                        self.context.mouse_state.mouse_buttons_pressed.remove(&bttn);
687                    }
688                }
689                PEK::Enter { .. } => {
690                    self.context.is_window_focused = true;
691                }
692                PEK::Leave { .. } => {
693                    self.context.is_window_focused = false;
694                }
695                _ => {}
696            }
697
698            self.context.mouse_state.position = event.position;
699
700            self.context
701                .event_queue
702                .push(Event::PointerEvent(event.clone()));
703        }
704    }
705}
706
707impl ShmHandler for WindowManager {
708    fn shm_state(&mut self) -> &mut Shm {
709        &mut self.shm
710    }
711}
712
713impl WindowManager {
714    pub fn draw(&mut self, _conn: &Connection, qh: &QueueHandle<Self>) {
715        let width = self.width;
716        let height = self.height;
717        let stride = self.width as i32 * 4;
718
719        let buffer = self.buffer.get_or_insert_with(|| {
720            self.pool
721                .create_buffer(
722                    width as i32,
723                    height as i32,
724                    stride,
725                    wl_shm::Format::Argb8888,
726                )
727                .expect("create buffer")
728                .0
729        });
730        let canvas = match self.pool.canvas(buffer) {
731            Some(canvas) => canvas,
732            None => {
733                // This should be rare, but if the compositor has not released the previous
734                // buffer, we need double-buffering.
735                let (second_buffer, canvas) = self
736                    .pool
737                    .create_buffer(
738                        self.width as i32,
739                        self.height as i32,
740                        stride,
741                        wl_shm::Format::Argb8888,
742                    )
743                    .expect("create buffer");
744                *buffer = second_buffer;
745                canvas
746            }
747        };
748
749        // Draw to the window:
750        self.managed_window.draw(
751            canvas,
752            WindowSize {
753                width: self.width,
754                height: self.height,
755            },
756        );
757
758        // Damage the entire window
759        self.window
760            .wl_surface()
761            .damage_buffer(0, 0, self.width as i32, self.height as i32);
762
763        // Request our next frame
764        self.window
765            .wl_surface()
766            .frame(qh, self.window.wl_surface().clone());
767
768        // Attach and commit to present.
769        buffer
770            .attach_to(self.window.wl_surface())
771            .expect("buffer attach");
772        self.window.commit();
773    }
774
775    fn handle_update(&mut self, request: Option<WLibRequest>) {
776        match request {
777            Some(WLibRequest::CloseAccepted) => self.close_accepted = true,
778            None => {}
779        }
780    }
781}
782
783delegate_compositor!(WindowManager);
784delegate_output!(WindowManager);
785delegate_shm!(WindowManager);
786
787delegate_seat!(WindowManager);
788delegate_keyboard!(WindowManager);
789delegate_pointer!(WindowManager);
790
791delegate_xdg_shell!(WindowManager);
792delegate_xdg_window!(WindowManager);
793delegate_activation!(WindowManager);
794
795delegate_registry!(WindowManager);
796
797impl ProvidesRegistryState for WindowManager {
798    fn registry(&mut self) -> &mut RegistryState {
799        &mut self.registry_state
800    }
801    registry_handlers![OutputState, SeatState,];
802}