🎉 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
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue