//! Backend input pour le compositor Redox. //! //! Encapsule `inputd::ConsumerHandle` et traduit les `orbclient::Event` //! bruts en un enum `InputEvent` neutre que le compositor peut consommer //! sans être couplé à orbclient ou à inputd directement. //! //! Conception : un seul `ConsumerHandle` est partagé entre `RedoxOutput` //! (qui le tient en vie pour préserver le VT) et `InputBackend` (qui polle //! les events). `RedoxOutput` ne lit pas les events ; `InputBackend` est //! lecteur unique. Ils partagent via `Arc`. use std::io; use std::os::fd::{AsRawFd, BorrowedFd}; use std::sync::Arc; use inputd::{ConsumerHandle, ConsumerHandleEvent}; use orbclient::{Event, EventOption}; /// Event compositor neutre, traduit depuis `orbclient::Event`. /// /// Reste indépendant de Wayland : un compositor peut le mapper vers /// `wl_keyboard.key`, `wl_pointer.motion`, etc. ; un autre frontend /// peut le mapper vers tout autre protocole. #[derive(Debug, Clone, PartialEq, Eq)] pub enum InputEvent { /// Touche clavier pressée ou relâchée. Key { /// Caractère résolu via la keymap active. `'\0'` si touche non-imprimable. character: char, /// Scancode brut envoyé par le driver clavier. scancode: u8, /// `true` = press, `false` = release. pressed: bool, }, /// Caractère issu d'une méthode IME (compose, dead key, etc.). TextInput { character: char }, /// Position absolue de la souris dans les coordonnées display. PointerMotion { x: i32, y: i32 }, /// Mouvement relatif (utile pour les jeux ou modes "grab"). PointerMotionRelative { dx: i32, dy: i32 }, /// État des boutons de la souris (toujours envoyé en bloc côté Redox). PointerButton { left: bool, middle: bool, right: bool, }, /// Événement de scroll (delta). PointerScroll { dx: i32, dy: i32 }, /// L'utilisateur veut sortir (Quit / kill window). Quit, /// Event reçu mais non encore mappé. Conserve le code orbclient pour debug. Unhandled { code: i64, a: i64, b: i64 }, /// inputd nous a retiré le contrôle du VT (handoff vers un autre VT). /// Le compositor doit relâcher le display jusqu'au prochain Resume. Handoff, } /// Capacité maximale du buffer interne de read_events. Aligné sur Orbital. const READ_BUF: usize = 16; pub struct InputBackend { consumer: Arc, } impl InputBackend { /// Construit un backend depuis le `ConsumerHandle` partagé avec le /// `RedoxOutput`. Le caller ne doit pas appeler `read_events` lui-même /// sur le consumer, sinon les events seront répartis entre les lecteurs. pub fn new(consumer: Arc) -> Self { Self { consumer } } /// Borrow le fd des events pour subscription dans une `EventQueue`. pub fn event_fd(&self) -> BorrowedFd<'_> { self.consumer.event_handle() } pub fn event_raw_fd(&self) -> i32 { self.consumer.event_handle().as_raw_fd() } /// Récupère tous les events disponibles maintenant. Non-bloquant /// (`ConsumerHandle::new_vt` ouvre avec `O_NONBLOCK`). /// Retourne aussi `InputEvent::Handoff` si inputd nous notifie un switch VT. pub fn poll(&self) -> io::Result> { let mut out = Vec::new(); let mut buf = [Event::new(); READ_BUF]; loop { match self.consumer.read_events(&mut buf)? { ConsumerHandleEvent::Events(&[]) => break, ConsumerHandleEvent::Events(events) => { for ev in events { out.push(translate(ev)); } } ConsumerHandleEvent::Handoff => { out.push(InputEvent::Handoff); break; // après un handoff on arrête de poller cette fois-ci } } } Ok(out) } } fn translate(ev: &Event) -> InputEvent { match ev.to_option() { EventOption::Key(k) => InputEvent::Key { character: k.character, scancode: k.scancode, pressed: k.pressed, }, EventOption::TextInput(t) => InputEvent::TextInput { character: t.character, }, EventOption::Mouse(m) => InputEvent::PointerMotion { x: m.x, y: m.y }, EventOption::MouseRelative(m) => InputEvent::PointerMotionRelative { dx: m.dx, dy: m.dy }, EventOption::Button(b) => InputEvent::PointerButton { left: b.left, middle: b.middle, right: b.right, }, EventOption::Scroll(s) => InputEvent::PointerScroll { dx: s.x, dy: s.y }, EventOption::Quit(_) => InputEvent::Quit, _ => InputEvent::Unhandled { code: ev.code, a: ev.a, b: ev.b, }, } }