Capture preuve : docs/phase7-2-input-routing.png — fenêtre client
xdg_toplevel 480x320 (damier turquoise) à (60,60), compositor stable
pendant que les keyboard events transitent en parallèle.
Validation runtime exhaustive : tous les events injectés via QEMU
sendkey/mouse_button arrivent au client via wl_keyboard.key /
wl_pointer.button :
[client-input] wl_keyboard.key key=54 Pressed ← 'c'
[client-input] wl_keyboard.key key=50 Pressed ← shift
[client-input] wl_keyboard.key key=38 Pressed ← 'a' avec shift
[client-input] wl_keyboard.key key=37 Pressed ← ctrl
...
Modifications redox-wl-wayland-frontend :
- + dep redox-wl-input (pour InputEvent type)
- wl_seat global v7 avec capabilities = Pointer | Keyboard
- wl_seat.name = "redox-wl-seat0" (v2+)
- Dispatch wl_seat : GetPointer, GetKeyboard, GetTouch (no-op),
Release ; au get_keyboard envoie keymap NoKeymap + repeat_info
- Dispatch wl_pointer / wl_keyboard / wl_touch : Release retire la
resource de state.{pointers,keyboards}
- forward_input(InputEvent) public method qui broadcast
wl_keyboard.key, wl_pointer.motion/button/axis/frame aux clients
- set_focus(surface) public method qui envoie keyboard/pointer
enter/leave events sur changement de focus
- Tracking : focused_surface, cursor_x/y, next_input_serial,
input_time_ms, pointers/keyboards Vec<Resource>
Modif wl_surface.commit : appelle set_focus(Some(_resource)) pour que
la dernière surface commitée reçoive l'enter automatiquement
(politique simple 7.2, à raffiner en 7.4).
Modif compositor binaire (redox-wl-compositor) :
- Forward chaque InputEvent au frontend.forward_input(&ev)
- Esc reste géré côté compositor pour exit propre
Bin redox-wl-test-client-input ajouté (~280 lignes) :
- Bind wl_compositor + wl_shm + xdg_wm_base + wl_seat
- get_keyboard + get_pointer après reception caps
- Crée xdg_toplevel + buffer ARGB damier turquoise
- Log chaque wl_keyboard.{enter,leave,key,modifiers,repeat_info}
et wl_pointer.{enter,leave,motion,button,axis}
- Boucle event_queue : flush + prepare_read.read + dispatch_pending
(CORRECT pattern pour wayland-rs ; le bug initial était d'utiliser
juste dispatch_pending qui ne lit pas le socket)
Critère de fin 7.2 validé : un client qui bind wl_seat reçoit
keyboard events via wl_keyboard.key sans panic serveur.
Limitations connues (sous-tickets ultérieurs) :
- Keymap NoKeymap (pas de XKB layout) — 7.2 utilise scancodes raw
- Broadcast à tous les keyboards/pointers (pas de filtrage par
client focus) — multi-client viendra en 7.6
- Pas de pointer.motion testé (besoin -device usb-tablet QEMU)
- Pas de validation modifier state (juste enter envoie 0,0,0,0)
Image Redox restaurée à boot Orbital normal.
Phrase reprise 7.3 :
> Reprendre au commit XXX : Phase 7.3 curseur software. Dessiner un
> sprite curseur 16x16 par-dessus la composition, position basée sur
> InputBackend cursor_x/y. Hot-spot configurable via wl_pointer.set_cursor
> (déjà no-op à 7.2). Tester avec usb-tablet QEMU pour avoir motion absolu.
Leyoda 2026 – GPLv3
198 lines
6.2 KiB
Rust
198 lines
6.2 KiB
Rust
//! Phase 6.4 — Compositor binaire complet.
|
|
//!
|
|
//! Boucle main d'un mini compositor Wayland :
|
|
//! 1. Ouvre RedoxOutput (display) et take CRTC
|
|
//! 2. Ouvre InputBackend partageant le ConsumerHandle
|
|
//! 3. Bind un ListeningSocket Wayland sur `/tmp/redox-wl-comp.sock`
|
|
//! 4. Loop :
|
|
//! - accept_pending_clients()
|
|
//! - dispatch_clients() (lit les requests, appelle nos Dispatch impls)
|
|
//! - poll() input → log + raise on click éventuel
|
|
//! - clear bg + compose_into(output) + present
|
|
//! - notify_frame_done() pour les wl_callback en attente
|
|
//! - flush_clients()
|
|
//! - sleep ~16ms
|
|
//!
|
|
//! Tourne 60 secondes max, exit propre.
|
|
|
|
use std::env;
|
|
use std::fs::OpenOptions;
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
use std::process::{Command, ExitCode};
|
|
use std::sync::{Mutex, OnceLock};
|
|
use std::thread;
|
|
use std::time::{Duration, Instant};
|
|
|
|
use redox_wl_compositor_core::Framebuffer;
|
|
use redox_wl_display::RedoxOutput;
|
|
use redox_wl_input::{InputBackend, InputEvent};
|
|
use redox_wl_wayland_frontend::WaylandFrontend;
|
|
|
|
const SOCKET_PATH: &str = "/tmp/redox-wl-comp.sock";
|
|
const BG_COLOR: u32 = 0xFF101820;
|
|
|
|
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("[comp] Phase 6.4 — compositor Wayland démarrage");
|
|
|
|
// Display
|
|
let mut output = RedoxOutput::open()?;
|
|
let our_vt = output.vt();
|
|
let fb_w = output.width();
|
|
let fb_h = output.height();
|
|
dlog(&format!("[comp] display {fb_w}x{fb_h}, VT={our_vt}"));
|
|
|
|
let _ = Command::new("inputd")
|
|
.arg("-A")
|
|
.arg(our_vt.to_string())
|
|
.status();
|
|
thread::sleep(Duration::from_millis(300));
|
|
output.take_crtc()?;
|
|
dlog("[comp] CRTC pris");
|
|
|
|
// Clear initial → fond bleu nuit pour signaler "compositor up"
|
|
{
|
|
let pixels = <RedoxOutput as Framebuffer>::pixels_mut(&mut output);
|
|
for p in pixels.iter_mut() {
|
|
*p = BG_COLOR;
|
|
}
|
|
}
|
|
output.present_with_takeover()?;
|
|
|
|
// Input
|
|
let input = InputBackend::new(output.consumer());
|
|
|
|
// Wayland frontend
|
|
let socket_path = PathBuf::from(SOCKET_PATH);
|
|
let mut frontend = WaylandFrontend::bind_absolute(&socket_path)?;
|
|
dlog(&format!("[comp] Wayland socket : {SOCKET_PATH}"));
|
|
|
|
// Exporter WAYLAND_DISPLAY pour les clients lancés par l'OS qui regarderaient
|
|
// l'env. (Notre client de test va connecter explicitement au path.)
|
|
unsafe {
|
|
env::set_var("WAYLAND_DISPLAY", SOCKET_PATH);
|
|
}
|
|
|
|
// Boucle principale
|
|
let start = Instant::now();
|
|
let total = Duration::from_secs(60);
|
|
let frame_period = Duration::from_millis(33); // ~30 fps
|
|
let mut last_frame = Instant::now();
|
|
let mut tick: u32 = 0;
|
|
|
|
while start.elapsed() < total {
|
|
tick = tick.wrapping_add(1);
|
|
|
|
// 1. Accepter nouveaux clients Wayland
|
|
if let Err(e) = frontend.accept_pending_clients() {
|
|
dlog(&format!("[comp] accept err: {e}"));
|
|
}
|
|
|
|
// 2. Dispatch des requêtes Wayland en attente
|
|
if let Err(e) = frontend.dispatch_clients() {
|
|
dlog(&format!("[comp] dispatch err: {e}"));
|
|
}
|
|
|
|
// 3. Input
|
|
if let Ok(events) = input.poll() {
|
|
if !events.is_empty() {
|
|
dlog(&format!("[comp] {} input events from inputd", events.len()));
|
|
}
|
|
for ev in events {
|
|
match &ev {
|
|
InputEvent::Key {
|
|
scancode, pressed, ..
|
|
} if *pressed && *scancode == 0x01 => {
|
|
// Esc → exit
|
|
dlog("[comp] Esc → exit");
|
|
let _ = frontend.flush_clients();
|
|
let _ = std::fs::remove_file(SOCKET_PATH);
|
|
return Ok(());
|
|
}
|
|
InputEvent::Quit => {
|
|
dlog("[comp] Quit reçu");
|
|
let _ = frontend.flush_clients();
|
|
let _ = std::fs::remove_file(SOCKET_PATH);
|
|
return Ok(());
|
|
}
|
|
_ => {}
|
|
}
|
|
// Phase 7.2 : forward tous les events vers les clients
|
|
// Wayland (keyboard.key, pointer.button, etc.)
|
|
frontend.forward_input(&ev);
|
|
}
|
|
}
|
|
|
|
// 4. Render
|
|
let nb = frontend.registry.len();
|
|
// Recompose tout à chaque frame pour 6.4 (pas de damage tracking)
|
|
{
|
|
let pixels = <RedoxOutput as Framebuffer>::pixels_mut(&mut output);
|
|
for p in pixels.iter_mut() {
|
|
*p = BG_COLOR;
|
|
}
|
|
}
|
|
frontend.registry.compose_into(&mut output);
|
|
if let Err(e) = output.present_with_takeover() {
|
|
dlog(&format!("[comp] present err: {e}"));
|
|
}
|
|
|
|
// 5. Frame callbacks done après le present
|
|
let elapsed_ms = last_frame.elapsed().as_millis() as u32;
|
|
last_frame = Instant::now();
|
|
frontend.notify_frame_done(elapsed_ms);
|
|
|
|
// 6. Flush vers les clients
|
|
if let Err(e) = frontend.flush_clients() {
|
|
dlog(&format!("[comp] flush err: {e}"));
|
|
}
|
|
|
|
// Log occasionnel
|
|
if tick % 30 == 0 {
|
|
dlog(&format!(
|
|
"[comp] tick={tick} surfaces={nb} elapsed={:.1}s",
|
|
start.elapsed().as_secs_f32()
|
|
));
|
|
}
|
|
|
|
thread::sleep(frame_period);
|
|
}
|
|
|
|
dlog("[comp] timeout 60s atteint, exit");
|
|
let _ = std::fs::remove_file(SOCKET_PATH);
|
|
Ok(())
|
|
}
|
|
|
|
fn main() -> ExitCode {
|
|
match run() {
|
|
Ok(()) => {
|
|
dlog("[comp] PASS");
|
|
ExitCode::SUCCESS
|
|
}
|
|
Err(e) => {
|
|
dlog(&format!("[comp] FAIL: {e}"));
|
|
ExitCode::FAILURE
|
|
}
|
|
}
|
|
}
|