From 8fc1d99ddff618eaa0c2c29f4a51eaaed73443dd Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 22 May 2026 23:21:06 +0000 Subject: [PATCH] =?UTF-8?q?feat(renaser):=20Fase=2013=20=E2=80=94=20rat?= =?UTF-8?q?=C3=B3n,=20puntero=20y=20arrastre=20de=20flotantes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit renaser dialogaba sólo con el teclado; las ventanas flotantes nacían en cascada y allí se quedaban. La Fase 13 trae el ratón. - Driver `drivers/raton`: el ratón PS/2 cuelga del dispositivo auxiliar del 8042 + IRQ12. El driver despierta el aux, programa su IRQ, le ordena reportar, ensambla paquetes de 3 bytes con guarda del bit-3. Posición como atómicos, eventos como cola lock-free — el mismo guardarraíl que el teclado. - El puntero, capa de PRESENTACIÓN: `Pantalla::estampar_puntero` pinta un sprite de flecha 12×18 sobre el framebuffer después de copiar el lienzo. El lienzo nunca lo contiene — hace de save-under natural—. - Compositor: `atender_raton` drena eventos. Botón bajando es un clic-para-enfocar consistente con `mover_foco` (silencia bocina, alza si flota). Si la enfocada flota, arranca un arrastre con el desfase de agarre; el botón sostenido la sigue al puntero; al soltar, termina. - `refrescar_puntero` reestampa el framebuffer si el puntero se movió en una vuelta tranquila en que ninguna app pintó. Verificado en QEMU (mouse_move / mouse_button del monitor): el puntero aparece al arrancar, se mueve por la pantalla, un clic sobre pulso le da el foco, y un arrastre con el botón sostenido mueve la flotante de la cascada al centro-abajo. Co-Authored-By: Claude Opus 4.7 --- renaser/CHANGELOG.md | 40 +++++ renaser/CLAUDE.md | 7 +- renaser/DIARIO.md | 20 +++ renaser/ROADMAP.md | 17 ++ renaser/kernel/src/compositor.rs | 169 ++++++++++++++++++ renaser/kernel/src/consola.rs | 17 +- renaser/kernel/src/drivers/mod.rs | 3 + renaser/kernel/src/drivers/raton.rs | 261 ++++++++++++++++++++++++++++ renaser/kernel/src/grafico.rs | 90 ++++++++++ renaser/kernel/src/interrupts.rs | 13 ++ renaser/kernel/src/main.rs | 11 ++ 11 files changed, 644 insertions(+), 4 deletions(-) create mode 100644 renaser/kernel/src/drivers/raton.rs diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md index 6f603c1..9a5d1f8 100644 --- a/renaser/CHANGELOG.md +++ b/renaser/CHANGELOG.md @@ -906,3 +906,43 @@ capacidades. Hasta hoy renaser sólo sabía DIBUJAR para llamar la atención. pcspk-audiodev`), el PCM capturado —50 s— es una onda cuadrada oscilante de ±24 000 de amplitud, con una frecuencia media de ~375 Hz: la de la escala de Do mayor. + +## Fase 13 — Ratón, puntero y arrastre de flotantes — 2026-05-22 + +renaser tenía teclado y bocina como entrada/salida del usuario, pero no +puntero. Las ventanas flotantes de la Fase 9 nacían en cascada y se quedaban +ahí, clavadas. La Fase 13 trae el ratón: un puntero en pantalla, clic-para- +enfocar y arrastre del marco de las ventanas flotantes. + +### Añadido +- **Driver `drivers/raton`** — el ratón PS/2 cuelga del dispositivo auxiliar + del 8042 (la misma controladora que el teclado) y anuncia cada movimiento + por la IRQ12. El driver despierta el aux, enciende su IRQ, le ordena + reportar, y ensambla los paquetes de 3 bytes con la guarda del bit-3 que + detecta desincronización. Como el teclado, la IRQ12 sólo toca atómicos + —posición del puntero— y una cola lock-free de eventos. +- **El puntero, capa de presentación.** `Pantalla` gana `formato` y la + función `estampar_puntero`: un sprite de flecha de 12×18 que se pinta + DIRECTAMENTE sobre el framebuffer, justo después de copiar el lienzo. El + lienzo permanece libre de puntero —hace de save-under natural—; cada + `presentar` sella el puntero al final. +- **Capacidad del compositor**: `atender_raton` drena eventos del ratón cada + fotograma. El botón izquierdo bajando es un CLIC: enfoca la ventana viva + bajo el puntero (consistente con `mover_foco` — silencia la bocina, alza al + frente si flota). Si la enfocada es flotante, arranca un ARRASTRE con el + desfase de agarre; con el botón sostenido, la ventana sigue al puntero; + soltarlo lo termina. +- **`refrescar_puntero`**: en una vuelta tranquila en que ninguna app pinte, + reestampa el framebuffer si el puntero se ha movido — el centinela + empacado evita repintar dos veces el mismo instante. +- `Escritorio` gana `arrastre: Option` y `raton_izq: bool`. + `Arrastre` guarda el índice de la ventana asida y el desfase de agarre. + `cerrar` libera el arrastre si la ventana cerrada era la arrastrada. + +### Verificado +- QEMU (`mouse_move` + `mouse_button` del monitor). El puntero aparece en el + centro al arrancar; `mouse_move` lo lleva por la pantalla. Clic sobre la + ventana `pulso` (en la pila) la enfoca: el borde índigo deja la maestra y + envuelve a `pulso`. `Alt+F` la flota; el ratón la agarra y la arrastra de + la esquina superior izquierda al centro-abajo de la pantalla con el botón + sostenido. El kernel sigue estable a través de los gestos. diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md index 1031367..8ed7150 100644 --- a/renaser/CLAUDE.md +++ b/renaser/CLAUDE.md @@ -80,9 +80,10 @@ del teclado (8c), promoción y reordenación de ventanas (8d)—, la Fase 9 COMPLETA —orden-Z y ventanas flotantes: composición con solapamiento (`Alt+F`)— la Fase 10 COMPLETA —alta y baja de aplicaciones en vivo (`Alt+N` / `Alt+Q`)—, la Fase 11 COMPLETA —el reloj del sistema como capacidad de host -(`sys_tiempo_mono`) + la app `pulso`— y la Fase 12 COMPLETA —la bocina del PC -como capacidad de host (`sys_tono`) + la app `tonada`—. Todo verificado en -QEMU. Ver `ROADMAP.md`. +(`sys_tiempo_mono`) + la app `pulso`—, la Fase 12 COMPLETA —la bocina del PC +como capacidad de host (`sys_tono`) + la app `tonada`— y la Fase 13 COMPLETA +—ratón PS/2, puntero, clic-para-enfocar y arrastre de ventanas flotantes—. +Todo verificado en QEMU. Ver `ROADMAP.md`. ## Flujo de trabajo diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md index c20af8f..55661ef 100644 --- a/renaser/DIARIO.md +++ b/renaser/DIARIO.md @@ -476,6 +476,26 @@ escala, una y otra vez, y la dibuja como una escalera de luces que sube al compás de la música. Míralo cantar en silencio desde cualquier rincón; acércate —dale el foco— y lo oirás. +## El dedo — la casa aprende a señalar + +La casa sabía escuchar y hablar; sabía mirar y dibujar. Lo que le faltaba era +señalar. Sus habitantes vivían en sus cuartos sin que pudiéramos tocarlos —si +queríamos cambiar de cuarto, había que cantarle a la casa qué tecla tocar—. +Era una casa de palabras, sin gestos. + +Hoy le crece un dedo. Una flecha pequeña, blanca con su borde oscuro, que +aparece en mitad del salón cuando la casa abre los ojos. Se mueve por las +paredes y los cuartos siguiendo lo que la mano de afuera quiera. Y allí donde +señala, ahí está la atención: tocar un cuarto es elegirlo —el borde de la +mirada se traslada al que apuntas, sin más palabras—. + +Y los cuartos que flotaban, que hasta ayer nacían en su cascada y allí se +quedaban, hoy pueden moverse de sitio. Se les agarra por donde uno los toca, +con el dedo apretado, y se les lleva por la casa como si llevara uno una +maceta de un balcón al otro. La mano suelta, el cuarto se queda donde lo dejó. +Por fin la disposición no la dicta sólo la casa: también la conversación entre +quien vive dentro y quien habita fuera. + --- *El diario continúa. La próxima página la escribirá la próxima jornada.* diff --git a/renaser/ROADMAP.md b/renaser/ROADMAP.md index 6c86b82..f352c68 100644 --- a/renaser/ROADMAP.md +++ b/renaser/ROADMAP.md @@ -211,6 +211,23 @@ se suma a la matriz de capacidades. Verificada en QEMU. - Verificado por captura: la onda cuadrada de la bocina, enrutada a un WAV, late a la frecuencia media de la escala. +## Fase 13 — ratón, puntero y arrastre de flotantes (completada) + +Hasta la Fase 12 renaser dialogaba sólo con el teclado; las ventanas flotantes +nacían en cascada y allí se quedaban. La Fase 13 trae el ratón: un puntero, +clic-para-enfocar y arrastre del marco de las flotantes. Verificada en QEMU +(`mouse_move` / `mouse_button` del monitor). + +- Driver `drivers/raton`: el ratón PS/2 cuelga del dispositivo auxiliar del + 8042 + la IRQ12. El driver despierta el aux, programa su IRQ, le ordena + reportar, ensambla paquetes de 3 bytes con guarda del bit-3. +- El puntero como capa de presentación: `Pantalla::estampar_puntero` pinta un + sprite de flecha de 12×18 sobre el framebuffer, DESPUÉS de copiar el + lienzo. El lienzo nunca lo contiene — hace de save-under natural—. +- El compositor gana `atender_raton`: botón bajando → clic-para-enfocar (sobre + cualquier ventana viva); si la enfocada flota, arranca un ARRASTRE con el + desfase de agarre; el botón sostenido la sigue al puntero; al soltar, fin. + Líneas abiertas posteriores: reciclado de las ranuras de ventana cerradas; audio con varias voces (PCM) más allá del tono único de la bocina. diff --git a/renaser/kernel/src/compositor.rs b/renaser/kernel/src/compositor.rs index 03e542a..d8d4240 100644 --- a/renaser/kernel/src/compositor.rs +++ b/renaser/kernel/src/compositor.rs @@ -29,6 +29,14 @@ // ventanas solo crece —los indices son la IDENTIDAD, jamas se reciclan—; una // ventana cerrada queda como una ranura inerte, fuera del orden y del foco. // +// FASE 13 :: el raton entra en juego. Hay un PUNTERO en pantalla y el +// compositor gana dos gestos: clic-para-enfocar (sobre cualquier ventana viva) +// y ARRASTRAR una flotante con el boton izquierdo sostenido. Como el teclado +// y la bocina, los eventos del raton vienen del manejador de IRQ12 por una +// cola lock-free; `atender_raton` los drena cooperativamente y, al detectar +// un boton que baja o un arrastre en curso, mueve el foco o el marco. Los +// cuartos flotantes dejan, por fin, de estar clavados en su cascada. +// // EXCLUSION DE INTERRUPCIONES. El `ESCRITORIO` lo tocan SOLO tareas // cooperativas (el `tick` de una app, la tarea del compositor): el manejador // de IRQ1 jamas lo bloquea. La IRQ se comunica con el mundo cooperativo por @@ -96,6 +104,16 @@ pub enum Mando { Lanzar, } +/// Un arrastre EN CURSO (Fase 13): el indice de la ventana flotante asida con +/// el raton y el desfase con que se asio —para que la ventana no salte al +/// agarrarla, sino que siga al puntero como si lo llevara cogido por ahi—. +#[derive(Clone, Copy)] +struct Arrastre { + ventana: usize, + agarre_dx: usize, + agarre_dy: usize, +} + /// Una ventana del escritorio: una app, su geometria y su ultimo fotograma. struct Ventana { /// Tamaño natural del lienzo de la app — lo que sabe pintar, fijo. @@ -139,6 +157,11 @@ struct Escritorio { /// en `flotantes`, jamas en ambos ni en ninguno: juntos son una particion /// de `0..ventanas.len()`. flotantes: Vec, + /// ¿Estaba el boton izquierdo del raton pulsado en el evento anterior? + /// Para detectar las transiciones —el momento exacto del clic o de soltar—. + raton_izq: bool, + /// Arrastre en curso, si lo hay (Fase 13). + arrastre: Option, } /// El escritorio global. Se funda una sola vez, en el arranque. @@ -201,6 +224,8 @@ pub fn fundar(ancho: usize, alto: usize, naturales: &[(usize, usize)]) { ventanas, orden, flotantes: Vec::new(), + raton_izq: false, + arrastre: None, }; aplicar_teselado(&mut escritorio); @@ -589,6 +614,10 @@ fn cerrar() { // indices son la identidad, jamas se reciclan—, pero ya nadie la dibuja. escritorio.orden.retain(|&v| v != foco); escritorio.flotantes.retain(|&v| v != foco); + // Si la estabamos arrastrando con el raton (Fase 13), soltarla. + if escritorio.arrastre.map(|a| a.ventana) == Some(foco) { + escritorio.arrastre = None; + } // El foco salta a la primera ventana viva que quede; si no queda ninguna, // se queda donde estaba —inofensivo: no hay a quien enrutar el teclado—. let nuevo = escritorio @@ -661,6 +690,146 @@ pub fn partos_pendientes() -> usize { PARTOS.swap(0, Ordering::Relaxed) } +// ============================================================================= +// FASE 13 — raton, puntero y arrastre de ventanas flotantes +// ============================================================================= + +/// La ultima posicion del puntero que el compositor REFRESCO. Si la posicion +/// actual del raton coincide con esta, no hay nada nuevo que estampar; si +/// difiere, la consola debe volver a presentar. Empacada como `y * 65536 + x`, +/// con `usize::MAX` como centinela de «aun no he visto al raton». +static PUNTERO_REFRESCADO: AtomicUsize = AtomicUsize::new(usize::MAX); + +/// Drena los eventos del raton y los aplica: clic enfoca la ventana bajo el +/// puntero (y, si flota, inicia un arrastre); el boton sostenido la arrastra; +/// soltarlo termina el gesto. La invoca la tarea del compositor en cada +/// fotograma, desde el reactor cooperativo. +pub fn atender_raton() { + let Some(escritorio) = ESCRITORIO.get() else { + return; + }; + let mut escritorio = escritorio.lock(); + let mut cambio = false; + while let Some(evento) = crate::drivers::raton::siguiente_evento() { + let izq = evento.botones & 0b001 != 0; + let x = evento.x as usize; + let y = evento.y as usize; + let izq_antes = escritorio.raton_izq; + if izq && !izq_antes { + // Boton bajó: un CLIC sobre el punto (x, y). + if let Some(v) = ventana_en(&escritorio, x, y) { + let viva = { + let w = &escritorio.ventanas[v]; + w.baliza.is_none() && !w.cerrada + }; + if viva { + // Enfocar como hace `mover_foco`: foco + bocina muda + alza + // si flota. + FOCO.store(v, Ordering::Relaxed); + crate::drivers::altavoz::tono(0); + alzar_si_flota(&mut escritorio, v); + // Si la ventana flota, empezar a arrastrarla. + if escritorio.flotantes.contains(&v) { + let marco = escritorio.ventanas[v].marco; + escritorio.arrastre = Some(Arrastre { + ventana: v, + agarre_dx: x.saturating_sub(marco.x), + agarre_dy: y.saturating_sub(marco.y), + }); + } + cambio = true; + } + } + } else if izq && izq_antes { + // Boton sostenido: arrastrar la ventana asida, si la hay. + if let Some(arr) = escritorio.arrastre { + mover_arrastrada(&mut escritorio, arr, x, y); + cambio = true; + } + } else if !izq && izq_antes { + // Boton subió: fin del arrastre. + escritorio.arrastre = None; + } + escritorio.raton_izq = izq; + } + if cambio { + recomponer(&escritorio); + // El recomponer ya presento; sincronizar el centinela para no presentar + // dos veces en la misma vuelta. + PUNTERO_REFRESCADO.store(empacar_puntero(), Ordering::Relaxed); + } +} + +/// La ventana topmost que contiene el punto (x, y), si la hay. Recorre el +/// orden-Z de delante hacia atras: primero las flotantes (la ultima es la +/// frontal), despues las teseladas. +fn ventana_en(escritorio: &Escritorio, x: usize, y: usize) -> Option { + for &v in escritorio.flotantes.iter().rev() { + if contiene(escritorio.ventanas[v].marco, x, y) { + return Some(v); + } + } + for &v in escritorio.orden.iter().rev() { + if contiene(escritorio.ventanas[v].marco, x, y) { + return Some(v); + } + } + None +} + +/// ¿Contiene el marco al punto (x, y)? +fn contiene(marco: RegionPantalla, x: usize, y: usize) -> bool { + x >= marco.x && x < marco.x + marco.ancho && y >= marco.y && y < marco.y + marco.alto +} + +/// Mueve la ventana arrastrada de modo que el punto del puntero —la asa— siga +/// estando, dentro de la ventana, donde se asio. La ventana queda acotada al +/// area de apps. +fn mover_arrastrada(escritorio: &mut Escritorio, arr: Arrastre, x: usize, y: usize) { + let area = area_apps(escritorio.ancho, escritorio.alto); + let Some(ventana) = escritorio.ventanas.get_mut(arr.ventana) else { + return; + }; + let ancho = ventana.marco.ancho; + let alto = ventana.marco.alto; + let mut nx = x.saturating_sub(arr.agarre_dx); + let mut ny = y.saturating_sub(arr.agarre_dy); + // Acotar al area de apps: la ventana entera ha de caber dentro. + if nx + ancho > area.x + area.ancho { + nx = (area.x + area.ancho).saturating_sub(ancho); + } + if ny + alto > area.y + area.alto { + ny = (area.y + area.alto).saturating_sub(alto); + } + nx = nx.max(area.x); + ny = ny.max(area.y); + ventana.marco.x = nx; + ventana.marco.y = ny; +} + +/// Empaca la posicion actual del puntero en un solo `usize` —`y * 65536 + x`— +/// para compararla atomicamente con la ultima refrescada. `usize::MAX` indica +/// «el raton no esta vivo». +fn empacar_puntero() -> usize { + match crate::drivers::raton::posicion() { + Some((x, y)) => (y << 16) | (x & 0xFFFF), + None => usize::MAX, + } +} + +/// Si el puntero se ha movido desde la ultima presentacion del compositor, le +/// pide a la consola un volcado fresco —para reestampar el puntero en su +/// nuevo lugar—. La invoca la tarea del compositor cada fotograma. +pub fn refrescar_puntero() { + let actual = empacar_puntero(); + if actual == usize::MAX { + return; + } + if PUNTERO_REFRESCADO.swap(actual, Ordering::Relaxed) != actual { + crate::consola::refrescar(); + } +} + // ============================================================================= // Teselado — la geometria pura de `mirada-layout` // ============================================================================= diff --git a/renaser/kernel/src/consola.rs b/renaser/kernel/src/consola.rs index 6850ea1..8fa6800 100644 --- a/renaser/kernel/src/consola.rs +++ b/renaser/kernel/src/consola.rs @@ -291,9 +291,15 @@ impl Consola { ); } - /// Vuelca el lienzo sobre la pantalla fisica. + /// Vuelca el lienzo sobre la pantalla fisica y estampa el puntero del raton + /// como ultima capa, directamente sobre el framebuffer (Fase 13). El lienzo + /// permanece libre de puntero — es el «save-under» natural—; el framebuffer + /// lo recibe en cada volcado, asi que el puntero esta siempre encima. pub(crate) fn presentar(&mut self) { self.pantalla.presentar(&self.lienzo); + if let Some((x, y)) = crate::drivers::raton::posicion() { + self.pantalla.estampar_puntero(x, y); + } } } @@ -338,3 +344,12 @@ pub(crate) fn pintar_desalojo(marco: RegionPantalla, color: Color, enfocada: boo consola.lock().pintar_region(marco, color, enfocada); } } + +/// Vuelve a volcar el lienzo a pantalla y estampar el puntero (Fase 13). Sirve +/// para refrescar el puntero cuando el raton se mueve pero ninguna app pinta: +/// el lienzo es el mismo, pero el puntero esta en otro sitio. +pub(crate) fn refrescar() { + if let Some(consola) = CONSOLA.get() { + consola.lock().presentar(); + } +} diff --git a/renaser/kernel/src/drivers/mod.rs b/renaser/kernel/src/drivers/mod.rs index 80a0135..25a8258 100644 --- a/renaser/kernel/src/drivers/mod.rs +++ b/renaser/kernel/src/drivers/mod.rs @@ -11,8 +11,11 @@ // lectura, por sondeo, de su primer sector. // * `altavoz` — la bocina del PC: el canal 2 del PIT como generador de tono // (Fase 12). +// * `raton` — el raton PS/2: el dispositivo auxiliar del 8042 + IRQ12, +// paquetes de 3 bytes (Fase 13). // ============================================================================= pub mod altavoz; pub mod disco; pub mod pci; +pub mod raton; diff --git a/renaser/kernel/src/drivers/raton.rs b/renaser/kernel/src/drivers/raton.rs new file mode 100644 index 0000000..ceb65c7 --- /dev/null +++ b/renaser/kernel/src/drivers/raton.rs @@ -0,0 +1,261 @@ +// ============================================================================= +// renaser :: kernel/src/drivers/raton.rs — Fase 13 :: el raton PS/2 +// ----------------------------------------------------------------------------- +// El raton cuelga del controlador 8042 —el mismo que sirve el teclado—, por su +// puerto AUXILIAR, y anuncia cada movimiento por la IRQ12. Su lenguaje es un +// paquete de tres bytes: banderas (botones + signos), delta X y delta Y. +// +// Como el teclado en la Fase 8c, el raton respeta el GUARDARRAIL de +// interrupciones: el manejador de IRQ12 solo toca atomicos —la posicion del +// puntero— y una cola lock-free de eventos. Jamas un cerrojo cooperativo. El +// compositor drena esa cola desde el reactor, a su ritmo. +// ============================================================================= + +use core::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}; + +use crossbeam_queue::ArrayQueue; +use spin::Once; +use x86_64::instructions::port::Port; + +use crate::pic; + +/// Puerto de datos del controlador 8042 (compartido con el teclado). +const DATOS: u16 = 0x60; +/// Puerto de estado / comando del 8042. +const ESTADO: u16 = 0x64; + +/// Capacidad de la cola de eventos del raton — holgada: nadie agita tanto. +const CAPACIDAD: usize = 128; + +/// Un evento del raton, tal como lo entrega un paquete completo: la posicion +/// ya absoluta del puntero y el estado crudo de los botones (bit 0 izquierdo, +/// bit 1 derecho, bit 2 central). Lo produce la IRQ12; lo consume el compositor. +#[derive(Clone, Copy)] +pub struct EventoRaton { + pub x: u16, + pub y: u16, + pub botones: u8, +} + +/// Posicion del puntero, en pixeles. La escribe la IRQ12 a cada paquete; la lee +/// la consola para estampar el puntero, y el compositor para situar los clics. +static RATON_X: AtomicUsize = AtomicUsize::new(0); +static RATON_Y: AtomicUsize = AtomicUsize::new(0); + +/// Limites de la pantalla — el puntero jamas se sale de ellos. +static ANCHO: AtomicUsize = AtomicUsize::new(0); +static ALTO: AtomicUsize = AtomicUsize::new(0); + +/// ¿Esta el raton inicializado y vivo? Hasta entonces no hay puntero que pintar. +static ACTIVO: AtomicBool = AtomicBool::new(false); + +/// Estado del ensamblado del paquete de 3 bytes. Lo toca SOLO la IRQ12, que es +/// no-reentrante en un solo nucleo: una secuencia de atomicos basta, sin cerrojo. +static FASE: AtomicUsize = AtomicUsize::new(0); +static BYTE0: AtomicU8 = AtomicU8::new(0); +static BYTE1: AtomicU8 = AtomicU8::new(0); +/// Estado de los botones en el paquete anterior — para detectar transiciones. +static BOTONES_ANTES: AtomicU8 = AtomicU8::new(0); + +/// La cola de eventos: la IRQ12 deposita (lock-free, segura en interrupcion), +/// el compositor drena desde el reactor cooperativo. +static EVENTOS: Once> = Once::new(); + +// ============================================================================= +// Dialogo con el 8042 — esperas acotadas, sin colgar jamas el arranque +// ============================================================================= + +/// Espera, con un tope de intentos, a que el 8042 admita una escritura (su +/// bufer de entrada vacio). Devuelve `false` si se agota la paciencia. +fn esperar_envio() -> bool { + for _ in 0..100_000 { + // SEGURIDAD: 0x64 es el puerto de estado del 8042, fijo en el PC. + let estado = unsafe { Port::::new(ESTADO).read() }; + if estado & 0b10 == 0 { + return true; + } + } + false +} + +/// Espera, con un tope de intentos, a que el 8042 tenga un byte que entregar. +fn esperar_recepcion() -> bool { + for _ in 0..100_000 { + // SEGURIDAD: ver `esperar_envio`. + let estado = unsafe { Port::::new(ESTADO).read() }; + if estado & 0b01 != 0 { + return true; + } + } + false +} + +/// Escribe un byte en el puerto de comando del 8042. +fn comando_8042(byte: u8) { + esperar_envio(); + // SEGURIDAD: 0x64 es el puerto de comando del 8042 en la arquitectura PC. + unsafe { Port::::new(ESTADO).write(byte) }; +} + +/// Escribe un byte en el puerto de datos del 8042. +fn escribir_datos(byte: u8) { + esperar_envio(); + // SEGURIDAD: 0x60 es el puerto de datos del 8042 en la arquitectura PC. + unsafe { Port::::new(DATOS).write(byte) }; +} + +/// Lee un byte del puerto de datos del 8042. +fn leer_datos() -> u8 { + esperar_recepcion(); + // SEGURIDAD: ver `escribir_datos`. + unsafe { Port::::new(DATOS).read() } +} + +/// Envia un comando AL raton (no al 8042): el prefijo 0xD4 le dice al 8042 que +/// el proximo byte de datos va al dispositivo auxiliar. Consume el ACK (0xFA). +fn comando_raton(byte: u8) { + comando_8042(0xD4); + escribir_datos(byte); + let _ack = leer_datos(); +} + +/// Vacia el bufer de salida del 8042 — descarta bytes rezagados que pudieran +/// desincronizar el ensamblado del primer paquete. +fn vaciar() { + for _ in 0..16 { + // SEGURIDAD: ver `esperar_envio`. + let estado = unsafe { Port::::new(ESTADO).read() }; + if estado & 0b01 == 0 { + return; + } + let _ = unsafe { Port::::new(DATOS).read() }; + } +} + +// ============================================================================= +// Arranque +// ============================================================================= + +/// Funda el raton: despierta el dispositivo auxiliar del 8042, programa su IRQ, +/// le ordena reportar movimiento y deja el puntero en el centro de la pantalla. +/// Requiere el heap activo; debe invocarse una vez, antes de habilitar las +/// interrupciones. +pub fn init(ancho: usize, alto: usize) { + ANCHO.store(ancho, Ordering::Relaxed); + ALTO.store(alto, Ordering::Relaxed); + RATON_X.store(ancho / 2, Ordering::Relaxed); + RATON_Y.store(alto / 2, Ordering::Relaxed); + EVENTOS.call_once(|| ArrayQueue::new(CAPACIDAD)); + + vaciar(); + + // Despertar el dispositivo auxiliar (el raton) en el 8042. + comando_8042(0xA8); + + // Leer el byte de configuracion, encender la IRQ del auxiliar (bit 1) y + // asegurar que su reloj corre (bit 5 a cero), y reescribirlo. + comando_8042(0x20); + let mut config = leer_datos(); + config |= 0b0000_0010; + config &= !0b0010_0000; + comando_8042(0x60); + escribir_datos(config); + + // Al raton: valores por defecto y, despues, reportar movimiento. + comando_raton(0xF6); + comando_raton(0xF4); + + vaciar(); + ACTIVO.store(true, Ordering::Relaxed); + // Levantar la mascara de la IRQ12 — el raton vive en el PIC esclavo. + pic::desenmascarar(12); +} + +// ============================================================================= +// El paquete de 3 bytes — punto de entrada desde la IRQ12 +// ============================================================================= + +/// Punto de entrada DESDE el manejador de IRQ12. Ensambla el paquete de tres +/// bytes y, al completarlo, actualiza la posicion del puntero y encola un +/// evento. Deliberadamente breve y libre de panicos: corre en contexto de IRQ. +pub fn recibir_byte(byte: u8) { + match FASE.load(Ordering::Relaxed) { + 0 => { + // El primer byte SIEMPRE trae el bit 3 a 1. Si no, el flujo esta + // desincronizado: se descarta el byte y se sigue esperando uno bueno. + if byte & 0b0000_1000 == 0 { + return; + } + BYTE0.store(byte, Ordering::Relaxed); + FASE.store(1, Ordering::Relaxed); + } + 1 => { + BYTE1.store(byte, Ordering::Relaxed); + FASE.store(2, Ordering::Relaxed); + } + _ => { + FASE.store(0, Ordering::Relaxed); + procesar(BYTE0.load(Ordering::Relaxed), BYTE1.load(Ordering::Relaxed), byte); + } + } +} + +/// Procesa un paquete completo: traduce los deltas a una posicion absoluta del +/// puntero, acotada a la pantalla, y encola el evento si hay algo que el +/// compositor deba atender —un boton pulsado o un arrastre en curso—. +fn procesar(banderas: u8, dx_crudo: u8, dy_crudo: u8) { + // Un paquete con desbordamiento de delta trae un salto disparatado: se + // ignora su movimiento por completo. + if banderas & 0b1100_0000 != 0 { + return; + } + let dx = dx_crudo as i8 as i32; + // El raton PS/2 da Y positivo hacia ARRIBA; la pantalla, hacia ABAJO. + let dy = dy_crudo as i8 as i32; + + let ancho = ANCHO.load(Ordering::Relaxed) as i32; + let alto = ALTO.load(Ordering::Relaxed) as i32; + let x = (RATON_X.load(Ordering::Relaxed) as i32 + dx).clamp(0, (ancho - 1).max(0)) as usize; + let y = (RATON_Y.load(Ordering::Relaxed) as i32 - dy).clamp(0, (alto - 1).max(0)) as usize; + RATON_X.store(x, Ordering::Relaxed); + RATON_Y.store(y, Ordering::Relaxed); + + // Encolar un evento SOLO si importa: si los botones cambiaron, o si alguno + // sigue pulsado —un arrastre—. El movimiento ocioso no satura la cola; el + // puntero, aun asi, ya se movio (los atomicos de arriba). + let botones = banderas & 0b0000_0111; + let antes = BOTONES_ANTES.swap(botones, Ordering::Relaxed); + if botones != antes || botones != 0 { + if let Some(cola) = EVENTOS.get() { + // Si la cola se desborda, el evento se pierde en silencio: mas vale + // perder un gesto que arriesgar un panico dentro de una IRQ. + let _ = cola.push(EventoRaton { + x: x as u16, + y: y as u16, + botones, + }); + } + } +} + +// ============================================================================= +// Consulta — para la consola (puntero) y el compositor (eventos) +// ============================================================================= + +/// La posicion actual del puntero, o `None` si el raton aun no esta vivo. La +/// consulta la consola para estampar el puntero en cada volcado de pantalla. +pub fn posicion() -> Option<(usize, usize)> { + if !ACTIVO.load(Ordering::Relaxed) { + return None; + } + Some(( + RATON_X.load(Ordering::Relaxed), + RATON_Y.load(Ordering::Relaxed), + )) +} + +/// Extrae el siguiente evento del raton, o `None` si no hay ninguno pendiente. +/// La drena el compositor, evento a evento, desde el reactor cooperativo. +pub fn siguiente_evento() -> Option { + EVENTOS.get().and_then(ArrayQueue::pop) +} diff --git a/renaser/kernel/src/grafico.rs b/renaser/kernel/src/grafico.rs index e04e07c..4417f46 100644 --- a/renaser/kernel/src/grafico.rs +++ b/renaser/kernel/src/grafico.rs @@ -305,6 +305,9 @@ pub(crate) struct Pantalla { pub(crate) alto: usize, pub(crate) paso_bytes: usize, pub(crate) bytes_por_pixel: usize, + /// Formato de pixel — necesario para estampar capas que se pintan + /// DIRECTAMENTE sobre el framebuffer, no sobre el lienzo (Fase 13). + pub(crate) formato: PixelFormat, } impl Pantalla { @@ -318,6 +321,7 @@ impl Pantalla { alto: info.height, paso_bytes: info.stride * info.bytes_per_pixel, bytes_por_pixel: info.bytes_per_pixel, + formato: info.pixel_format, } } @@ -341,3 +345,89 @@ impl Pantalla { } } } + +// ============================================================================= +// EL PUNTERO DEL RATON — un sprite estampado sobre el framebuffer (Fase 13) +// ----------------------------------------------------------------------------- +// El puntero NO vive en el lienzo: el lienzo es el escritorio limpio, y se +// recompone con frecuencia. El puntero es una capa de PRESENTACION que cada +// volcado vuelve a sellar sobre el framebuffer, despues de copiar el lienzo. +// Asi no hay save-under que mantener: el lienzo HACE de save-under, y el +// framebuffer recibe el puntero como ultimo gesto. +// ============================================================================= + +/// Ancho del sprite del puntero, en pixeles. +const PUNTERO_ANCHO: usize = 12; +/// El sprite del puntero — una flecha noroeste. `#` es el borde oscuro, `*` el +/// relleno claro, `.` transparente. +const PUNTERO: [&[u8; PUNTERO_ANCHO]; 18] = [ + b"#...........", + b"##..........", + b"#*#.........", + b"#**#........", + b"#***#.......", + b"#****#......", + b"#*****#.....", + b"#******#....", + b"#*******#...", + b"#********#..", + b"#*********#.", + b"#*****#####.", + b"#**#**#.....", + b"#*#.#**#....", + b"##..#**#....", + b"#....#**#...", + b".....#**#...", + b"......###...", +]; + +impl Pantalla { + /// Estampa el sprite del puntero del raton sobre el framebuffer, con su + /// vertice en (x, y). El sprite se recorta con firmeza a los limites de la + /// pantalla. NO altera el lienzo: la proxima recomposicion lo deja intacto; + /// el siguiente volcado lo vuelve a estampar (Fase 13). + pub(crate) fn estampar_puntero(&mut self, x: usize, y: usize) { + let borde = codificar( + self.formato, + Color { + r: 0x10, + g: 0x12, + b: 0x18, + }, + ); + let relleno = codificar( + self.formato, + Color { + r: 0xF0, + g: 0xF2, + b: 0xF8, + }, + ); + for (fila, linea) in PUNTERO.iter().enumerate() { + let py = y + fila; + if py >= self.alto { + break; + } + for (col, &celda) in linea.iter().enumerate() { + let px = x + col; + if px >= self.ancho { + continue; + } + let valor = match celda { + b'#' => borde, + b'*' => relleno, + _ => continue, + }; + // SEGURIDAD: (px, py) acotado a las dimensiones reales del + // framebuffer; el desplazamiento cae dentro de la memoria de + // video que el firmware nos entrego. + unsafe { + let destino = self + .base + .add(py * self.paso_bytes + px * self.bytes_por_pixel); + escribir_pixel_volatil(destino, valor, self.bytes_por_pixel); + } + } + } + } +} diff --git a/renaser/kernel/src/interrupts.rs b/renaser/kernel/src/interrupts.rs index 9d5e46a..47f436b 100644 --- a/renaser/kernel/src/interrupts.rs +++ b/renaser/kernel/src/interrupts.rs @@ -54,6 +54,7 @@ pub fn init() { // --- Interrupciones de hardware, ya remapeadas por el PIC --- idt[pic::VECTOR_TEMPORIZADOR].set_handler_fn(irq_temporizador); idt[pic::VECTOR_TECLADO].set_handler_fn(irq_teclado); + idt[pic::vector_irq(12)].set_handler_fn(irq_raton); let idt_estatica: &'static InterruptDescriptorTable = idt; idt_estatica.load(); @@ -136,6 +137,18 @@ extern "x86-interrupt" fn irq_teclado(_marco: InterruptStackFrame) { pic::fin_de_interrupcion(pic::VECTOR_TECLADO); } +/// IRQ12 — Raton PS/2 (Fase 13). Lee un byte del puerto de datos del 8042 —el +/// mismo que sirve al teclado—; el ensamblador de paquetes del raton se ocupa +/// de juntar tres bytes y entregar el evento al compositor. +extern "x86-interrupt" fn irq_raton(_marco: InterruptStackFrame) { + // SEGURIDAD: 0x60 es el puerto de datos del 8042 en la arquitectura PC. + // Que la IRQ12 ha disparado garantiza que el byte es del raton, no del + // teclado: el controlador alza una linea distinta para cada dispositivo. + let byte: u8 = unsafe { x86_64::instructions::port::Port::new(0x60).read() }; + crate::drivers::raton::recibir_byte(byte); + pic::fin_de_interrupcion(pic::vector_irq(12)); +} + /// IRQ del disco — virtio-blk (Fase 6.2). El disco ha terminado una /// transferencia: `atender_irq` reconoce la interrupcion en el dispositivo /// —lo que libera su linea— y despierta a la tarea que aguardaba el bloque. diff --git a/renaser/kernel/src/main.rs b/renaser/kernel/src/main.rs index 90c0336..eb85942 100644 --- a/renaser/kernel/src/main.rs +++ b/renaser/kernel/src/main.rs @@ -143,6 +143,11 @@ async fn tarea_compositor() { loop { async_system::reloj::EsperaFrame::nueva().await; compositor::atender_mandos(); + // FASE 13 :: atender los eventos del raton (clic-para-enfocar y + // arrastre de flotantes), y refrescar el puntero si se movio en una + // vuelta tranquila en que ninguna app pinto. + compositor::atender_raton(); + compositor::refrescar_puntero(); // FASE 10 :: atender las altas en vivo. Por cada `Alt+N` pendiente, // dar a luz una aplicacion nueva — el compositor solo conto la // peticion; instanciar el WASM es trabajo del orquestador. @@ -455,6 +460,12 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { } } + // --- 6.6. FASE 13 :: despertar el raton PS/2. El 8042 enciende su + // dispositivo auxiliar, el raton empieza a reportar, y el PIC + // desenmascara su IRQ12. Desde aqui hay un puntero en pantalla, + // y los clics pueden alcanzar al compositor. + drivers::raton::init(ancho_lienzo, alto_lienzo); + // --- 7. FASE 7 :: levantar el reactor y poblar el userspace DESDE EL // GRAFO. El kernel ya no empotra los modulos WASM: lee el // Manifiesto de Genesis que `boot` sembro en la imagen de disco e