feat(renaser): Fase 13 — ratón, puntero y arrastre de flotantes

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 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-22 23:21:06 +00:00
parent d1b700eb2b
commit 8fc1d99ddf
11 changed files with 644 additions and 4 deletions
+40
View File
@@ -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<Arrastre>` 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.
+4 -3
View File
@@ -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
+20
View File
@@ -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.*
+17
View File
@@ -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.
+169
View File
@@ -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<usize>,
/// ¿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<Arrastre>,
}
/// 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<usize> {
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`
// =============================================================================
+16 -1
View File
@@ -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();
}
}
+3
View File
@@ -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;
+261
View File
@@ -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<ArrayQueue<EventoRaton>> = 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::<u8>::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::<u8>::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::<u8>::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::<u8>::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::<u8>::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::<u8>::new(ESTADO).read() };
if estado & 0b01 == 0 {
return;
}
let _ = unsafe { Port::<u8>::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<EventoRaton> {
EVENTOS.get().and_then(ArrayQueue::pop)
}
+90
View File
@@ -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);
}
}
}
}
}
+13
View File
@@ -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.
+11
View File
@@ -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