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:
@@ -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`
|
||||
// =============================================================================
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user