🎉 Phase 13.1.b — simple_window upstream validé runtime sur Redox
4 bugs identifiés et corrigés grâce au client tiers : - ESC mangée par le compositor au lieu d'être forwardée (raccourci compo déplacé sur Ctrl+Q + tracking ctrl_held) - Cursor accumulait les deltas PS/2 hors-écran (ajout screen_w/h + clamp_cursor + set_screen_size au boot) - Keycode envoyé scancode+8 au lieu d'evdev brut (bug racine — le +8 est entre evdev et X11, pas entre PS/2 et evdev) - Event loop client ne tolérait pas Interrupted/BrokenPipe transitoires → FAIL au lieu de PASS sur sortie normale (5e adaptation Redox côté client, upstream-compatible) Plus : tick log compo passé en debug! pour silencer la console série, run-qemu.sh durci (check /dev/fuse, IMAGE/REDOXFS overridables). 6/6 checkpoints 13.1 verts. Bugs secondaires (curseur stuck en bas-droite, ion page fault sur broken pipe d'un job background) listés dans le doc pour follow-up en 13.1.c. Leyoda 2026 – GPLv3
This commit is contained in:
parent
0d8032b5ff
commit
18391c85f7
5 changed files with 401 additions and 15 deletions
|
|
@ -138,6 +138,10 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
// Wayland frontend
|
||||
let socket_path = PathBuf::from(SOCKET_PATH);
|
||||
let mut frontend = WaylandFrontend::bind_absolute(&socket_path)?;
|
||||
// Phase 13.1.b : déclarer les bornes écran AVANT toute manipulation
|
||||
// de la position curseur, sinon les deltas relatifs (PS/2 sous QEMU)
|
||||
// s'accumulent hors-écran et hit_test ne trouve jamais les surfaces.
|
||||
frontend.set_screen_size(fb_w as i32, fb_h as i32);
|
||||
// Phase 7.3 : curseur visible dès le démarrage, placé au centre du fb.
|
||||
frontend.set_cursor_initial_position((fb_w as i32) / 2, (fb_h as i32) / 2);
|
||||
info!("Wayland socket : {SOCKET_PATH}");
|
||||
|
|
@ -154,6 +158,11 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let frame_period = Duration::from_millis(33); // ~30 fps
|
||||
let mut last_frame = Instant::now();
|
||||
let mut tick: u32 = 0;
|
||||
// Phase 13.1.b : Ctrl tenu, pour le raccourci compositor Ctrl+Q.
|
||||
// ESC n'est plus interceptée — elle est forwardée aux clients afin que
|
||||
// les apps tierces (cf. simple_window upstream) puissent décider elles-mêmes
|
||||
// de se terminer sur ESC sans race avec la fermeture du socket compositor.
|
||||
let mut ctrl_held = false;
|
||||
|
||||
while start.elapsed() < total {
|
||||
tick = tick.wrapping_add(1);
|
||||
|
|
@ -179,12 +188,23 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
tracing::debug!("{} input events from inputd", events.len());
|
||||
}
|
||||
for ev in events {
|
||||
// Track Ctrl (scancode 0x1D, set 1) avant le match : nécessaire
|
||||
// pour le raccourci Ctrl+Q et OK à dupliquer côté clients via
|
||||
// forward_input — wl_keyboard.key reste la source de vérité pour eux.
|
||||
if let InputEvent::Key {
|
||||
scancode: 0x1D,
|
||||
pressed,
|
||||
..
|
||||
} = ev
|
||||
{
|
||||
ctrl_held = pressed;
|
||||
}
|
||||
match &ev {
|
||||
InputEvent::Key {
|
||||
scancode, pressed, ..
|
||||
} if *pressed && *scancode == 0x01 => {
|
||||
// Esc → exit
|
||||
info!("Esc → exit");
|
||||
} if *pressed && *scancode == 0x10 && ctrl_held => {
|
||||
// Ctrl+Q → exit compositor (ESC est réservé aux clients)
|
||||
info!("Ctrl+Q → exit compositor");
|
||||
let _ = frontend.flush_clients();
|
||||
let _ = std::fs::remove_file(SOCKET_PATH);
|
||||
return Ok(());
|
||||
|
|
@ -229,9 +249,11 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
error!("flush err: {e}");
|
||||
}
|
||||
|
||||
// Log occasionnel
|
||||
if tick % 30 == 0 {
|
||||
info!(
|
||||
// Phase 13.1.b : tick log en debug! (saturait la console série).
|
||||
// Réactivable avec RUST_LOG=debug. Fréquence réduite à toutes les
|
||||
// 5s (~150 ticks @ 30fps) pour limiter encore le bruit.
|
||||
if tick % 150 == 0 {
|
||||
tracing::debug!(
|
||||
"tick={tick} surfaces={nb} elapsed={:.1}s",
|
||||
start.elapsed().as_secs_f32()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -70,6 +70,23 @@ fn dlog(s: &str) {
|
|||
SINK.get_or_init(DebugSink::new).writeln(s);
|
||||
}
|
||||
|
||||
fn is_interrupted_error(err: &(dyn std::error::Error + 'static)) -> bool {
|
||||
if let Some(ioe) = err.downcast_ref::<std::io::Error>() {
|
||||
return ioe.kind() == std::io::ErrorKind::Interrupted;
|
||||
}
|
||||
err.source().is_some_and(is_interrupted_error)
|
||||
}
|
||||
|
||||
fn is_connection_closed_error(err: &(dyn std::error::Error + 'static)) -> bool {
|
||||
if let Some(ioe) = err.downcast_ref::<std::io::Error>() {
|
||||
return matches!(
|
||||
ioe.kind(),
|
||||
std::io::ErrorKind::BrokenPipe | std::io::ErrorKind::ConnectionReset
|
||||
);
|
||||
}
|
||||
err.source().is_some_and(is_connection_closed_error)
|
||||
}
|
||||
|
||||
// ---------- Buffer SHM (équivalent de tempfile + draw upstream) ----------
|
||||
const INIT_W: u32 = 320;
|
||||
const INIT_H: u32 = 240;
|
||||
|
|
@ -350,7 +367,15 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
dlog("[real-client] entering event loop");
|
||||
while state.running {
|
||||
event_queue.blocking_dispatch(&mut state)?;
|
||||
match event_queue.blocking_dispatch(&mut state) {
|
||||
Ok(_) => {}
|
||||
Err(e) if is_interrupted_error(&e) => continue,
|
||||
Err(e) if is_connection_closed_error(&e) => {
|
||||
dlog("[real-client] compositor disconnected → exit cleanly");
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
dlog("[real-client] loop exited cleanly");
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -374,6 +374,11 @@ pub struct WaylandFrontend {
|
|||
/// Mise à jour par `forward_input(PointerMotion / PointerMotionRelative)`.
|
||||
cursor_x: i32,
|
||||
cursor_y: i32,
|
||||
/// Phase 13.1.b : bornes écran pour clamper le cursor. Si non
|
||||
/// défini par `set_screen_size`, valeurs i32::MAX → le clamp est un
|
||||
/// no-op (compat tests qui ne configurent pas le frontend).
|
||||
screen_w: i32,
|
||||
screen_h: i32,
|
||||
/// Counter monotone pour les serials seat events (différent de next_xdg_serial).
|
||||
next_input_serial: u32,
|
||||
/// Timestamp incrémental pour les events seat (ms-like).
|
||||
|
|
@ -445,6 +450,8 @@ impl WaylandFrontend {
|
|||
focused_surface: None,
|
||||
cursor_x: 0,
|
||||
cursor_y: 0,
|
||||
screen_w: i32::MAX,
|
||||
screen_h: i32::MAX,
|
||||
next_input_serial: 1,
|
||||
input_time_ms: 0,
|
||||
cursor_surface_id: None,
|
||||
|
|
@ -816,12 +823,15 @@ impl WaylandFrontend {
|
|||
};
|
||||
let time = self.alloc_input_time();
|
||||
let serial = self.alloc_input_serial();
|
||||
// Wayland keycodes = scancode evdev = scancode +8 on linux
|
||||
// (cf xkb_keycodes minimum=8). On garde le scancode brut
|
||||
// ici en attendant une keymap correcte (US Linux evdev offset
|
||||
// ne s'applique pas forcément à orbclient ; à durcir
|
||||
// quand on aura une vraie keymap XKB).
|
||||
let key = (*scancode as u32).saturating_add(8);
|
||||
// Phase 13.1.b : Wayland `wl_keyboard.key` attend le keycode
|
||||
// **evdev** brut, pas X11. L'offset +8 est entre evdev et
|
||||
// X11 (historique X11 où KeyCode minimum = 8), pas entre
|
||||
// PS/2 et evdev — pour les touches alphanumériques de base,
|
||||
// PS/2 Set 1 scancode == evdev keycode (ESC=1, Q=16, etc.).
|
||||
// Bug originel : on envoyait scancode+8 → simple_window
|
||||
// upstream qui teste `key == 1` pour ESC recevait 9 et
|
||||
// n'exitait jamais.
|
||||
let key = *scancode as u32;
|
||||
let state = if *pressed {
|
||||
wl_keyboard::KeyState::Pressed
|
||||
} else {
|
||||
|
|
@ -836,6 +846,7 @@ impl WaylandFrontend {
|
|||
RedoxInputEvent::PointerMotion { x, y } => {
|
||||
self.cursor_x = *x;
|
||||
self.cursor_y = *y;
|
||||
self.clamp_cursor();
|
||||
self.cursor_visible = true;
|
||||
// Phase 7.7 : si drag actif, déplacer la surface au lieu
|
||||
// d'envoyer motion au client.
|
||||
|
|
@ -859,6 +870,7 @@ impl WaylandFrontend {
|
|||
RedoxInputEvent::PointerMotionRelative { dx, dy } => {
|
||||
self.cursor_x = self.cursor_x.saturating_add(*dx);
|
||||
self.cursor_y = self.cursor_y.saturating_add(*dy);
|
||||
self.clamp_cursor();
|
||||
self.cursor_visible = true;
|
||||
// Phase 7.7 : si drag actif, déplacer la surface.
|
||||
if self.apply_interactive_drag() {
|
||||
|
|
@ -1089,6 +1101,26 @@ impl WaylandFrontend {
|
|||
self.cursor_x = x;
|
||||
self.cursor_y = y;
|
||||
self.cursor_visible = true;
|
||||
self.clamp_cursor();
|
||||
}
|
||||
|
||||
/// Phase 13.1.b : déclare la taille du framebuffer pour borner le
|
||||
/// curseur. Sans ça, les deltas `PointerMotionRelative` (PS/2 sous QEMU)
|
||||
/// s'accumulent et finissent par sortir l'écran → tous les hit_test
|
||||
/// retournent None → aucune fenêtre ne reçoit le focus → ESC tombe
|
||||
/// dans le vide. Doit être appelé après `bind_absolute` et idéalement
|
||||
/// re-appelé si la résolution change.
|
||||
pub fn set_screen_size(&mut self, w: i32, h: i32) {
|
||||
self.screen_w = w.max(1);
|
||||
self.screen_h = h.max(1);
|
||||
self.clamp_cursor();
|
||||
}
|
||||
|
||||
fn clamp_cursor(&mut self) {
|
||||
let max_x = (self.screen_w - 1).max(0);
|
||||
let max_y = (self.screen_h - 1).max(0);
|
||||
self.cursor_x = self.cursor_x.clamp(0, max_x);
|
||||
self.cursor_y = self.cursor_y.clamp(0, max_y);
|
||||
}
|
||||
|
||||
/// Force la position du curseur à tout moment. Utile pour tests
|
||||
|
|
@ -1101,6 +1133,7 @@ impl WaylandFrontend {
|
|||
pub fn set_cursor_position(&mut self, x: i32, y: i32) {
|
||||
self.cursor_x = x;
|
||||
self.cursor_y = y;
|
||||
self.clamp_cursor();
|
||||
self.cursor_visible = true;
|
||||
let _ = self.apply_interactive_drag();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue