🎉 Phase 5 — input backend Redox validé runtime
Crates ajoutés :
- redox-wl-input (lib) : InputBackend wrappe Arc<ConsumerHandle>
+ enum InputEvent { Key, TextInput, PointerMotion(Relative)?,
PointerButton, PointerScroll, Quit, Unhandled, Handoff }
- redox-wl-test-input (bin) : ouvre display + take CRTC + peint bleu
marine + polle events 30s en logguant chaque event
Modifs redox-wl-display :
- _consumer: ConsumerHandle → consumer: Arc<ConsumerHandle>
- + pub fn consumer() -> Arc<ConsumerHandle> pour partage avec input
Validation runtime sur Redox bootée via QEMU + monitor unix socket :
20 events injectés via `sendkey` et `mouse_button` HMP commands, tous
reçus et traduits correctement :
- a/b/c PRESS+RELEASE — keymap directe
- shift+a → 'A' uppercase — modificateur fonctionnel
- ctrl+c → ctrl PRESS + 'c' PRESS — composition fonctionnelle
- mouse_button 1/0 → PointerButton L=true/false
- Esc, Enter, Shift, Ctrl reçus avec scancode brut
Décision architecturale : un seul ConsumerHandle partagé via Arc entre
RedoxOutput (pour vie du VT) et InputBackend (lecteur unique d'events).
Sinon deux consumers = deux VTs distincts dont un seul reçoit les events.
Capture preuve : docs/phase5-blue-screen-with-input.png — bleu marine
plein écran 1280x800 confirmant que display + input fonctionnent
ensemble dans le même binaire.
docs/phase5-input-backend.md : compte-rendu complet.
Image restaurée à boot Orbital normal après session.
Leyoda 2026 – GPLv3
This commit is contained in:
parent
753a30757b
commit
a9bb88d9f3
7 changed files with 542 additions and 2 deletions
|
|
@ -18,6 +18,7 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::os::fd::AsRawFd;
|
use std::os::fd::AsRawFd;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use drm::Device as _;
|
use drm::Device as _;
|
||||||
use drm::buffer::{Buffer as _, DrmFourcc};
|
use drm::buffer::{Buffer as _, DrmFourcc};
|
||||||
|
|
@ -32,7 +33,10 @@ pub struct RedoxOutput {
|
||||||
/// Consumer handle GARDÉ EN VIE pour que le VT alloué par inputd persiste.
|
/// Consumer handle GARDÉ EN VIE pour que le VT alloué par inputd persiste.
|
||||||
/// Si on drop ce handle, inputd retire le VT de sa table et toute opération
|
/// Si on drop ce handle, inputd retire le VT de sa table et toute opération
|
||||||
/// `inputd -A <vt>` ultérieure dira "non-existent VT".
|
/// `inputd -A <vt>` ultérieure dira "non-existent VT".
|
||||||
_consumer: ConsumerHandle,
|
/// Exposé via `consumer()` pour partage avec un éventuel `InputBackend`
|
||||||
|
/// (qui polle les events sur le même fd, sans réouvrir un consumer
|
||||||
|
/// supplémentaire — sinon on aurait deux VTs et un seul recevrait les events).
|
||||||
|
consumer: Arc<ConsumerHandle>,
|
||||||
handle: V2GraphicsHandle,
|
handle: V2GraphicsHandle,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
|
@ -101,7 +105,7 @@ impl RedoxOutput {
|
||||||
.ok_or_else(|| io::Error::other("no crtc compatible with encoder"))?;
|
.ok_or_else(|| io::Error::other("no crtc compatible with encoder"))?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
_consumer: consumer,
|
consumer: Arc::new(consumer),
|
||||||
handle,
|
handle,
|
||||||
width: w as u32,
|
width: w as u32,
|
||||||
height: h as u32,
|
height: h as u32,
|
||||||
|
|
@ -128,6 +132,13 @@ impl RedoxOutput {
|
||||||
pub fn vt(&self) -> usize {
|
pub fn vt(&self) -> usize {
|
||||||
self.vt
|
self.vt
|
||||||
}
|
}
|
||||||
|
/// Renvoie un Arc partagé du `ConsumerHandle` pour un `InputBackend` qui
|
||||||
|
/// veut polle les events sur le même VT que le display.
|
||||||
|
/// Le caller ne doit PAS lire les events depuis ce handle (RedoxOutput
|
||||||
|
/// ne lit pas non plus) — c'est l'`InputBackend` qui sera lecteur unique.
|
||||||
|
pub fn consumer(&self) -> Arc<ConsumerHandle> {
|
||||||
|
Arc::clone(&self.consumer)
|
||||||
|
}
|
||||||
|
|
||||||
/// Alloue un CpuBackedBuffer ARGB8888 plein écran, l'ajoute comme
|
/// Alloue un CpuBackedBuffer ARGB8888 plein écran, l'ajoute comme
|
||||||
/// framebuffer DRM et appelle `set_crtc` pour qu'il soit affiché.
|
/// framebuffer DRM et appelle `set_crtc` pour qu'il soit affiché.
|
||||||
|
|
|
||||||
9
crates/redox-wl-input/Cargo.toml
Normal file
9
crates/redox-wl-input/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "redox-wl-input"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Input backend wrapper around inputd ConsumerHandle for Redox"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
inputd = { git = "https://gitlab.redox-os.org/redox-os/base.git" }
|
||||||
|
orbclient = "0.3"
|
||||||
134
crates/redox-wl-input/src/lib.rs
Normal file
134
crates/redox-wl-input/src/lib.rs
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
//! 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<ConsumerHandle>`.
|
||||||
|
|
||||||
|
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<ConsumerHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<ConsumerHandle>) -> 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<Vec<InputEvent>> {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
8
crates/redox-wl-test-input/Cargo.toml
Normal file
8
crates/redox-wl-test-input/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "redox-wl-test-input"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
redox-wl-display = { path = "../redox-wl-display" }
|
||||||
|
redox-wl-input = { path = "../redox-wl-input" }
|
||||||
176
crates/redox-wl-test-input/src/main.rs
Normal file
176
crates/redox-wl-test-input/src/main.rs
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
//! Phase 5 — Test input backend.
|
||||||
|
//!
|
||||||
|
//! Boote en remplaçant Orbital sur le VT 3 (cf init), prend le CRTC, peint
|
||||||
|
//! un fond uniforme bleu marine pour confirmer qu'on contrôle le display,
|
||||||
|
//! puis polle les events input pendant 30 secondes en logguant chaque
|
||||||
|
//! event reçu vers /scheme/debug (visible côté host via le serial QEMU).
|
||||||
|
//!
|
||||||
|
//! Côté host on peut envoyer des touches via le monitor QEMU :
|
||||||
|
//! printf "sendkey a\n" | ncat -U /tmp/qmp.sock
|
||||||
|
//! printf "sendkey ret\n" | ncat -U /tmp/qmp.sock
|
||||||
|
//! printf "mouse_move 100 100\n" | ncat -U /tmp/qmp.sock (HMP)
|
||||||
|
//! printf "mouse_button 1\n" | ncat -U /tmp/qmp.sock
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::{Command, ExitCode};
|
||||||
|
use std::sync::{Mutex, OnceLock};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use redox_wl_display::RedoxOutput;
|
||||||
|
use redox_wl_input::{InputBackend, InputEvent};
|
||||||
|
|
||||||
|
struct DebugSink(Mutex<Option<std::fs::File>>);
|
||||||
|
impl DebugSink {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self(Mutex::new(
|
||||||
|
OpenOptions::new().write(true).open("/scheme/debug").ok(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn writeln(&self, s: &str) {
|
||||||
|
println!("{s}");
|
||||||
|
if let Ok(mut g) = self.0.lock() {
|
||||||
|
if let Some(f) = g.as_mut() {
|
||||||
|
let _ = writeln!(f, "{s}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn dlog(s: &str) {
|
||||||
|
static SINK: OnceLock<DebugSink> = OnceLock::new();
|
||||||
|
SINK.get_or_init(DebugSink::new).writeln(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
dlog("[input] Phase 5 — test input backend");
|
||||||
|
|
||||||
|
// Ouvre le display (ce qui alloue notre VT côté inputd)
|
||||||
|
let mut output = RedoxOutput::open().map_err(|e| format!("RedoxOutput::open: {e}"))?;
|
||||||
|
let our_vt = output.vt();
|
||||||
|
dlog(&format!(
|
||||||
|
"[input] display {}x{}, VT={our_vt}",
|
||||||
|
output.width(),
|
||||||
|
output.height()
|
||||||
|
));
|
||||||
|
|
||||||
|
// Active notre VT (sinon set_crtc no-op cf phase 4)
|
||||||
|
let _ = Command::new("inputd")
|
||||||
|
.arg("-A")
|
||||||
|
.arg(our_vt.to_string())
|
||||||
|
.status();
|
||||||
|
thread::sleep(Duration::from_millis(300));
|
||||||
|
|
||||||
|
// Prend le CRTC, peint un fond bleu marine pour signaler "on est là"
|
||||||
|
output
|
||||||
|
.take_crtc()
|
||||||
|
.map_err(|e| format!("take_crtc: {e}"))?;
|
||||||
|
{
|
||||||
|
let pixels = output.pixels_mut()?;
|
||||||
|
for p in pixels.iter_mut() {
|
||||||
|
*p = 0xFF_10_30_60; // ARGB bleu marine sombre
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output
|
||||||
|
.present_with_takeover()
|
||||||
|
.map_err(|e| format!("present init: {e}"))?;
|
||||||
|
dlog("[input] CRTC pris, fond bleu marine peint");
|
||||||
|
|
||||||
|
// Crée le backend input partageant le ConsumerHandle de RedoxOutput
|
||||||
|
let input = InputBackend::new(output.consumer());
|
||||||
|
dlog(&format!(
|
||||||
|
"[input] InputBackend prêt, fd events={}",
|
||||||
|
input.event_raw_fd()
|
||||||
|
));
|
||||||
|
|
||||||
|
// Boucle de polling pendant 30 secondes
|
||||||
|
let start = Instant::now();
|
||||||
|
let total_duration = Duration::from_secs(30);
|
||||||
|
let mut event_count: usize = 0;
|
||||||
|
let mut quit_requested = false;
|
||||||
|
|
||||||
|
while start.elapsed() < total_duration && !quit_requested {
|
||||||
|
let events = input.poll().map_err(|e| format!("poll: {e}"))?;
|
||||||
|
for ev in events {
|
||||||
|
event_count += 1;
|
||||||
|
match &ev {
|
||||||
|
InputEvent::Key {
|
||||||
|
character,
|
||||||
|
scancode,
|
||||||
|
pressed,
|
||||||
|
} => {
|
||||||
|
let c = if character.is_control() || *character == '\0' {
|
||||||
|
'·'
|
||||||
|
} else {
|
||||||
|
*character
|
||||||
|
};
|
||||||
|
dlog(&format!(
|
||||||
|
"[input] #{event_count:04} Key '{c}' scan={scancode:#x} {}",
|
||||||
|
if *pressed { "PRESS" } else { "RELEASE" }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
InputEvent::TextInput { character } => {
|
||||||
|
dlog(&format!("[input] #{event_count:04} TextInput '{character}'"));
|
||||||
|
}
|
||||||
|
InputEvent::PointerMotion { x, y } => {
|
||||||
|
dlog(&format!("[input] #{event_count:04} PointerMotion abs=({x},{y})"));
|
||||||
|
}
|
||||||
|
InputEvent::PointerMotionRelative { dx, dy } => {
|
||||||
|
dlog(&format!(
|
||||||
|
"[input] #{event_count:04} PointerMotionRelative d=({dx},{dy})"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
InputEvent::PointerButton {
|
||||||
|
left,
|
||||||
|
middle,
|
||||||
|
right,
|
||||||
|
} => {
|
||||||
|
dlog(&format!(
|
||||||
|
"[input] #{event_count:04} PointerButton L={left} M={middle} R={right}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
InputEvent::PointerScroll { dx, dy } => {
|
||||||
|
dlog(&format!("[input] #{event_count:04} PointerScroll d=({dx},{dy})"));
|
||||||
|
}
|
||||||
|
InputEvent::Quit => {
|
||||||
|
dlog(&format!("[input] #{event_count:04} Quit demandé"));
|
||||||
|
quit_requested = true;
|
||||||
|
}
|
||||||
|
InputEvent::Unhandled { code, a, b } => {
|
||||||
|
dlog(&format!(
|
||||||
|
"[input] #{event_count:04} Unhandled code={code} a={a} b={b}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
InputEvent::Handoff => {
|
||||||
|
dlog(&format!("[input] #{event_count:04} Handoff (VT switch)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Re-flush pour tenir l'écran si fbcond/fbbootlogd tente de reprendre
|
||||||
|
let _ = output.present_with_takeover();
|
||||||
|
thread::sleep(Duration::from_millis(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
dlog(&format!(
|
||||||
|
"[input] fin — {event_count} events reçus en {:.1}s",
|
||||||
|
start.elapsed().as_secs_f32()
|
||||||
|
));
|
||||||
|
let _ = env::var("VT");
|
||||||
|
drop(output);
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> ExitCode {
|
||||||
|
match run() {
|
||||||
|
Ok(()) => {
|
||||||
|
dlog("[input] PASS");
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dlog(&format!("[input] FAIL: {e}"));
|
||||||
|
ExitCode::FAILURE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
docs/phase5-blue-screen-with-input.png
Normal file
BIN
docs/phase5-blue-screen-with-input.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 449 B |
202
docs/phase5-input-backend.md
Normal file
202
docs/phase5-input-backend.md
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
# Phase 5 — Input backend Redox : résultats
|
||||||
|
|
||||||
|
> Document produit le 2026-05-09 dans le cadre du plan directeur
|
||||||
|
> `REDOX_COSMIC_XWAYLAND_RS_PLAN.md`.
|
||||||
|
>
|
||||||
|
> **Périmètre** : encapsuler `inputd` proprement, exposer un enum
|
||||||
|
> `InputEvent` neutre côté compositor, valider runtime sur Redox bootée
|
||||||
|
> avec injection d'events keyboard/mouse via le monitor QEMU.
|
||||||
|
|
||||||
|
## Verdict global
|
||||||
|
|
||||||
|
**✅ Pipeline input complet validé runtime.**
|
||||||
|
|
||||||
|
Le binaire `redox-wl-test-input` a :
|
||||||
|
1. Ouvert le display via `RedoxOutput` (VT alloué = 2)
|
||||||
|
2. Activé le VT via `inputd -A`
|
||||||
|
3. Pris le CRTC et peint un fond bleu marine `0xFF103060` (preuve display)
|
||||||
|
4. Créé un `InputBackend` partageant le même `ConsumerHandle`
|
||||||
|
5. Pollé en boucle pendant 30 s, traduisant chaque event en `InputEvent`
|
||||||
|
6. Logué chaque event sur `/scheme/debug` (capté côté host via serial QEMU)
|
||||||
|
|
||||||
|
20 events injectés via le monitor QEMU (`sendkey`, `mouse_button`) ont
|
||||||
|
tous été reçus, avec les **modificateurs interprétés correctement par la
|
||||||
|
keymap** Redox (shift+a → `'A'`, ctrl+c → ctrl press + `c` press).
|
||||||
|
|
||||||
|
Capture preuve display : 
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ binaire compositor │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────┐ Arc<ConsumerHandle> │
|
||||||
|
│ │ RedoxOutput │ ─────┐ │
|
||||||
|
│ │ (display) │ ▼ │
|
||||||
|
│ └──────────────┘ ┌─────────────────┐ │
|
||||||
|
│ │ InputBackend │ │
|
||||||
|
│ │ poll() ────────┼──▶ Vec<InputEvent>
|
||||||
|
│ └─────────────────┘ │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
/scheme/input/consumer (inputd, un seul VT, un seul fd)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Décision architecturale clé** : un seul `ConsumerHandle` partagé via
|
||||||
|
`Arc`, pour 2 raisons :
|
||||||
|
|
||||||
|
1. **Un consumer = un VT**. Si on en ouvrait deux, on aurait deux VTs
|
||||||
|
distincts et inputd routerait les events vers le VT actif (un seul
|
||||||
|
à la fois). Le second consumer serait perpétuellement muet.
|
||||||
|
2. **`RedoxOutput` doit garder le consumer en vie** pour préserver le VT
|
||||||
|
(cf phase 4 — sinon `inputd -A <vt>` retourne "non-existent"). Mais
|
||||||
|
c'est `InputBackend` qui consomme les events. Le partage Arc résout
|
||||||
|
cette tension.
|
||||||
|
|
||||||
|
## API publique
|
||||||
|
|
||||||
|
### `redox-wl-input::InputEvent`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum InputEvent {
|
||||||
|
Key { character: char, scancode: u8, pressed: bool },
|
||||||
|
TextInput { character: char },
|
||||||
|
PointerMotion { x: i32, y: i32 }, // absolu
|
||||||
|
PointerMotionRelative { dx: i32, dy: i32 },
|
||||||
|
PointerButton { left: bool, middle: bool, right: bool },
|
||||||
|
PointerScroll { dx: i32, dy: i32 },
|
||||||
|
Quit,
|
||||||
|
Unhandled { code: i64, a: i64, b: i64 }, // events orbclient pas encore mappés
|
||||||
|
Handoff, // VT switch côté inputd
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
L'enum est volontairement **agnostique vis-à-vis de Wayland**. Un
|
||||||
|
compositor Wayland le mappera en `wl_keyboard.key`, `wl_pointer.motion`,
|
||||||
|
etc. ; un autre frontend pourra le mapper différemment.
|
||||||
|
|
||||||
|
### `redox-wl-input::InputBackend`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl InputBackend {
|
||||||
|
pub fn new(consumer: Arc<ConsumerHandle>) -> Self;
|
||||||
|
pub fn event_fd(&self) -> BorrowedFd<'_>; // pour subscribe à une EventQueue
|
||||||
|
pub fn event_raw_fd(&self) -> i32;
|
||||||
|
pub fn poll(&self) -> io::Result<Vec<InputEvent>>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`poll()` est non-bloquant et draine tous les events disponibles. À
|
||||||
|
intégrer plus tard dans une `event::EventQueue` Redox pour event-driven.
|
||||||
|
|
||||||
|
## Validation runtime — extrait du log
|
||||||
|
|
||||||
|
Injection via le monitor QEMU :
|
||||||
|
```bash
|
||||||
|
for k in a b c ret; do printf "sendkey $k\n" | ncat -U /tmp/qmp.sock; done
|
||||||
|
printf "sendkey shift-a\n" | ncat -U /tmp/qmp.sock
|
||||||
|
printf "sendkey ctrl-c\n" | ncat -U /tmp/qmp.sock
|
||||||
|
printf "sendkey esc\n" | ncat -U /tmp/qmp.sock
|
||||||
|
printf "mouse_button 1\n" | ncat -U /tmp/qmp.sock
|
||||||
|
printf "mouse_button 0\n" | ncat -U /tmp/qmp.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
Sortie côté binaire (lue via /scheme/debug → serial → stdout host) :
|
||||||
|
```
|
||||||
|
[input] display 1280x800, VT=2
|
||||||
|
[input] CRTC pris, fond bleu marine peint
|
||||||
|
[input] InputBackend prêt, fd events=4
|
||||||
|
[input] #0001 Key 'a' scan=0x1e PRESS
|
||||||
|
[input] #0002 Key 'a' scan=0x1e RELEASE
|
||||||
|
[input] #0003 Key 'b' scan=0x30 PRESS
|
||||||
|
[input] #0004 Key 'b' scan=0x30 RELEASE
|
||||||
|
[input] #0005 Key 'c' scan=0x2e PRESS
|
||||||
|
[input] #0006 Key 'c' scan=0x2e RELEASE
|
||||||
|
[input] #0007 Key '·' scan=0x1c PRESS ← Enter
|
||||||
|
[input] #0008 Key '·' scan=0x1c RELEASE
|
||||||
|
[input] #0009 Key '·' scan=0x2a PRESS ← Shift down
|
||||||
|
[input] #0010 Key 'A' scan=0x1e PRESS ← keymap → 'A' (uppercase)
|
||||||
|
[input] #0011 Key 'A' scan=0x1e RELEASE
|
||||||
|
[input] #0012 Key '·' scan=0x2a RELEASE ← Shift up
|
||||||
|
[input] #0013 Key '·' scan=0x1d PRESS ← Ctrl down
|
||||||
|
[input] #0014 Key 'c' scan=0x2e PRESS
|
||||||
|
[input] #0015 Key 'c' scan=0x2e RELEASE
|
||||||
|
[input] #0016 Key '·' scan=0x1d RELEASE ← Ctrl up
|
||||||
|
[input] #0017 Key '·' scan=0x01 PRESS ← Esc
|
||||||
|
[input] #0018 Key '·' scan=0x01 RELEASE
|
||||||
|
[input] #0019 PointerButton L=true M=false R=false
|
||||||
|
[input] #0020 PointerButton L=false M=false R=false
|
||||||
|
```
|
||||||
|
|
||||||
|
Note : les caractères `'·'` représentent `'\0'` (touche non-imprimable
|
||||||
|
sans char associé : modificateurs, Enter, Esc...). Le scancode reste
|
||||||
|
disponible pour l'interpréter.
|
||||||
|
|
||||||
|
## Limitations connues
|
||||||
|
|
||||||
|
### Pas de `PointerMotion` testé
|
||||||
|
Le test n'a pas reçu d'event `PointerMotion` parce que la commande QEMU
|
||||||
|
utilisée n'incluait pas `-device usb-tablet` (qui fournit des coords
|
||||||
|
absolues). Avec `make qemu` standard, ce device est inclus. À retester
|
||||||
|
quand on aura un cas d'usage souris dans le compositor.
|
||||||
|
|
||||||
|
### Mouse relatif via PS/2 non testé
|
||||||
|
Pareil, dépend de `-device ps2` ou similaire.
|
||||||
|
|
||||||
|
### Doublement des lignes dans le log
|
||||||
|
Chaque ligne `[input]` apparaît deux fois dans le log capturé. Cause :
|
||||||
|
le `DebugSink` fait `println!` (→ stdout → fbcond → framebuffer →
|
||||||
|
captured-by-screendump-not-serial) ET `writeln!(/scheme/debug)`
|
||||||
|
(→ serial → captured-by-host-stdio). Les deux finissent par converger
|
||||||
|
dans le serial mux stdio du `make qemu`. Cosmétique, pas critique.
|
||||||
|
|
||||||
|
## Ce qui est prêt pour la suite
|
||||||
|
|
||||||
|
- Phase 6 (composition multi-surfaces shm) : peut consommer `InputEvent`
|
||||||
|
pour focus/clic-pour-amener-au-premier-plan
|
||||||
|
- Phase 7 (compositor utilisable) : peut wrapper `InputBackend` dans
|
||||||
|
une `EventQueue` Redox + traiter raccourcis globaux
|
||||||
|
|
||||||
|
## Code source
|
||||||
|
|
||||||
|
```
|
||||||
|
crates/redox-wl-input/ # lib (140 lignes)
|
||||||
|
├── Cargo.toml
|
||||||
|
└── src/lib.rs # InputEvent + InputBackend
|
||||||
|
|
||||||
|
crates/redox-wl-test-input/ # bin de test (130 lignes)
|
||||||
|
├── Cargo.toml
|
||||||
|
└── src/main.rs # display bleu + poll loop + log
|
||||||
|
|
||||||
|
crates/redox-wl-display/src/lib.rs # MODIFIÉ
|
||||||
|
# _consumer → consumer: Arc<ConsumerHandle>
|
||||||
|
# + pub fn consumer() -> Arc<ConsumerHandle>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Méthode de test reproductible
|
||||||
|
|
||||||
|
Image patchée : `/usr/lib/init.d/20_orbital` lance
|
||||||
|
`redox-wl-test-input` en VT=2, `30_console` vidé pour ne pas créer de
|
||||||
|
getty conflictuel.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qemu-system-x86_64 \
|
||||||
|
... \
|
||||||
|
-chardev stdio,id=debug,signal=off,mux=on \
|
||||||
|
-serial chardev:debug -mon chardev=debug \
|
||||||
|
-monitor unix:/tmp/qmp.sock,server,nowait \
|
||||||
|
-vga std -display none ... &
|
||||||
|
sleep 2 && printf "sendkey ret\n" | ncat -U /tmp/qmp.sock # bootloader
|
||||||
|
sleep 13 # boot + paint
|
||||||
|
printf "sendkey a\n" | ncat -U /tmp/qmp.sock # inject events
|
||||||
|
printf "mouse_button 1\n" | ncat -U /tmp/qmp.sock
|
||||||
|
printf "screendump /tmp/x.ppm\n" | ncat -U /tmp/qmp.sock # capture visuel
|
||||||
|
```
|
||||||
|
|
||||||
|
L'image a été restaurée à boot Orbital normal après la session.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Fin du document de phase 5.*
|
||||||
Loading…
Add table
Add a link
Reference in a new issue