feat(mirada): puntero/ratón en el backend DRM del compositor
El backend DRM del Cuerpo deja de ser sólo-teclado: `libinput` ahora mueve un cursor de software y reenvía clics y rueda a los clientes. - Enum `Frame` (vía `render_elements!`) que mezcla superficies de cliente y un `SolidColorRenderElement` para el cursor, marcado `Kind::Cursor` y compuesto encima de todo. - `handle_input` atiende `PointerMotion`/`PointerMotionAbsolute`/ `PointerButton`/`PointerAxis`; el puntero se acota a la salida. - Foco-sigue-ratón: `window_at` hace el test de impacto (flotantes sobre teseladas, contra el rectángulo real de la superficie) y, al cambiar de ventana, emite `BodyEvent::PointerEntered`. - `surface_px_size` en main.rs — tamaño presentado de una superficie, reusado por el test de impacto. Compila + clippy limpio; pendiente de verificar en hardware. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,10 @@ Corre directo sobre el hardware. Requiere una **TTY** (`Ctrl+Alt+F3`),
|
|||||||
una GPU con `/dev/dri`, y `seatd` o `logind` para la sesión. Toma la
|
una GPU con `/dev/dri`, y `seatd` o `logind` para la sesión. Toma la
|
||||||
pantalla completa; sal con `Super+Shift+e` o `Ctrl+C`.
|
pantalla completa; sal con `Super+Shift+e` o `Ctrl+C`.
|
||||||
|
|
||||||
|
Lleva teclado y ratón por `libinput`: el ratón mueve un cursor de
|
||||||
|
software, el foco sigue al puntero y los clics y la rueda llegan a la
|
||||||
|
ventana que tienes debajo.
|
||||||
|
|
||||||
- `MIRADA_STARTUP=<cmd>` — lanza una app al arrancar (`MIRADA_STARTUP=foot`).
|
- `MIRADA_STARTUP=<cmd>` — lanza una app al arrancar (`MIRADA_STARTUP=foot`).
|
||||||
- `MIRADA_DRM_TIMEOUT=<s>` — cierra el compositor solo tras N segundos
|
- `MIRADA_DRM_TIMEOUT=<s>` — cierra el compositor solo tras N segundos
|
||||||
(0 o sin definir = sin tope).
|
(0 o sin definir = sin tope).
|
||||||
@@ -112,9 +116,9 @@ En modo enlazado el socket de control lo abre el Cerebro (la app
|
|||||||
## Qué implementa
|
## Qué implementa
|
||||||
|
|
||||||
`wl_compositor`, `xdg_shell` (toplevels y popups), `wl_shm`, `wl_seat`
|
`wl_compositor`, `xdg_shell` (toplevels y popups), `wl_shm`, `wl_seat`
|
||||||
(teclado), `wl_output` y `wl_data_device` (selección). Composición con
|
(teclado, y ratón en el backend DRM), `wl_output` y `wl_data_device`
|
||||||
`GlesRenderer` — en `winit` sobre la ventana, en `drm` con un
|
(selección). Composición con `GlesRenderer` — en `winit` sobre la
|
||||||
`DrmCompositor` por salida.
|
ventana, en `drm` con un `DrmCompositor` por salida.
|
||||||
|
|
||||||
Reusa `mirada-body` para la contabilidad de salidas y superficies, y
|
Reusa `mirada-body` para la contabilidad de salidas y superficies, y
|
||||||
`mirada-link` para el cable hacia un Cerebro externo. Toda la lógica
|
`mirada-link` para el cable hacia un Cerebro externo. Toda la lógica
|
||||||
@@ -123,7 +127,7 @@ espacial es agnóstica de Wayland y vive en los crates de
|
|||||||
|
|
||||||
## Pendiente
|
## Pendiente
|
||||||
|
|
||||||
Del backend DRM: puntero/ratón (hoy sólo teclado), conmutación de VT,
|
Del backend DRM: conmutación de VT, hotplug de monitores, multi-GPU.
|
||||||
hotplug de monitores. Aislamiento de clientes. Ver el SDD.
|
Puntero en el backend `winit`. Aislamiento de clientes. Ver el SDD.
|
||||||
|
|
||||||
[`smithay`]: https://github.com/Smithay/smithay
|
[`smithay`]: https://github.com/Smithay/smithay
|
||||||
|
|||||||
@@ -25,18 +25,24 @@ use smithay::backend::drm::compositor::{DrmCompositor, FrameFlags};
|
|||||||
use smithay::backend::drm::exporter::gbm::GbmFramebufferExporter;
|
use smithay::backend::drm::exporter::gbm::GbmFramebufferExporter;
|
||||||
use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent};
|
use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent};
|
||||||
use smithay::backend::egl::{EGLContext, EGLDisplay};
|
use smithay::backend::egl::{EGLContext, EGLDisplay};
|
||||||
use smithay::backend::input::{InputEvent, KeyState, KeyboardKeyEvent};
|
use smithay::backend::input::{
|
||||||
|
AbsolutePositionEvent, Axis, AxisSource, InputEvent, KeyState, KeyboardKeyEvent,
|
||||||
|
PointerAxisEvent, PointerButtonEvent, PointerMotionEvent,
|
||||||
|
};
|
||||||
use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface};
|
use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface};
|
||||||
|
use smithay::backend::renderer::element::solid::SolidColorRenderElement;
|
||||||
use smithay::backend::renderer::element::surface::{
|
use smithay::backend::renderer::element::surface::{
|
||||||
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
|
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
|
||||||
};
|
};
|
||||||
use smithay::backend::renderer::element::Kind;
|
use smithay::backend::renderer::element::{render_elements, Id, Kind};
|
||||||
use smithay::backend::renderer::gles::GlesRenderer;
|
use smithay::backend::renderer::gles::GlesRenderer;
|
||||||
use smithay::backend::renderer::ImportDma;
|
use smithay::backend::renderer::utils::CommitCounter;
|
||||||
|
use smithay::backend::renderer::{ImportAll, ImportDma};
|
||||||
use smithay::backend::session::libseat::LibSeatSession;
|
use smithay::backend::session::libseat::LibSeatSession;
|
||||||
use smithay::backend::session::{Event as SessionEvent, Session};
|
use smithay::backend::session::{Event as SessionEvent, Session};
|
||||||
use smithay::backend::udev;
|
use smithay::backend::udev;
|
||||||
use smithay::input::keyboard::FilterResult;
|
use smithay::input::keyboard::FilterResult;
|
||||||
|
use smithay::input::pointer::{AxisFrame, ButtonEvent, MotionEvent};
|
||||||
use smithay::output::OutputModeSource;
|
use smithay::output::OutputModeSource;
|
||||||
use smithay::reexports::calloop::generic::Generic;
|
use smithay::reexports::calloop::generic::Generic;
|
||||||
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
|
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
|
||||||
@@ -46,7 +52,9 @@ use smithay::reexports::drm::control::{Device as ControlDevice, ModeTypeFlags};
|
|||||||
use smithay::reexports::input::Libinput;
|
use smithay::reexports::input::Libinput;
|
||||||
use smithay::reexports::rustix::fs::OFlags;
|
use smithay::reexports::rustix::fs::OFlags;
|
||||||
use smithay::reexports::wayland_server::{Display, ListeningSocket};
|
use smithay::reexports::wayland_server::{Display, ListeningSocket};
|
||||||
use smithay::utils::{DeviceFd, Scale, Size, Transform, SERIAL_COUNTER};
|
use smithay::utils::{
|
||||||
|
DeviceFd, Logical, Physical, Point, Rectangle, Scale, Size, Transform, SERIAL_COUNTER,
|
||||||
|
};
|
||||||
|
|
||||||
use mirada_brain::{CtlReply, Keymap};
|
use mirada_brain::{CtlReply, Keymap};
|
||||||
|
|
||||||
@@ -56,9 +64,23 @@ use crate::{combo_string, send_frames_surface_tree, App, Brain, ClientState, Set
|
|||||||
type Compositor =
|
type Compositor =
|
||||||
DrmCompositor<GbmAllocator<DrmDeviceFd>, GbmFramebufferExporter<DrmDeviceFd>, (), DrmDeviceFd>;
|
DrmCompositor<GbmAllocator<DrmDeviceFd>, GbmFramebufferExporter<DrmDeviceFd>, (), DrmDeviceFd>;
|
||||||
|
|
||||||
|
render_elements! {
|
||||||
|
/// Lo que el backend DRM compone en un cuadro: las superficies de los
|
||||||
|
/// clientes y, encima de todo, el cursor de software.
|
||||||
|
Frame<R> where R: ImportAll;
|
||||||
|
Window = WaylandSurfaceRenderElement<R>,
|
||||||
|
Cursor = SolidColorRenderElement,
|
||||||
|
}
|
||||||
|
|
||||||
/// Color de fondo del escritorio cuando no hay nada que lo tape.
|
/// Color de fondo del escritorio cuando no hay nada que lo tape.
|
||||||
const CLEAR_COLOR: [f32; 4] = [0.05, 0.05, 0.08, 1.0];
|
const CLEAR_COLOR: [f32; 4] = [0.05, 0.05, 0.08, 1.0];
|
||||||
|
|
||||||
|
/// Lado del cursor de software, en píxeles.
|
||||||
|
const CURSOR_SIZE: i32 = 12;
|
||||||
|
|
||||||
|
/// Color del cursor — un cuadrado casi blanco, opaco.
|
||||||
|
const CURSOR_COLOR: [f32; 4] = [0.95, 0.95, 0.97, 1.0];
|
||||||
|
|
||||||
/// El estado del bucle DRM — lo comparten todos los callbacks de `calloop`.
|
/// El estado del bucle DRM — lo comparten todos los callbacks de `calloop`.
|
||||||
struct DrmState {
|
struct DrmState {
|
||||||
app: App,
|
app: App,
|
||||||
@@ -74,31 +96,54 @@ struct DrmState {
|
|||||||
start: Instant,
|
start: Instant,
|
||||||
/// Nº de ventanas en el último `tick` — para registrar los cambios.
|
/// Nº de ventanas en el último `tick` — para registrar los cambios.
|
||||||
last_windows: usize,
|
last_windows: usize,
|
||||||
|
/// Identidad estable del cursor de software — el seguimiento de daño
|
||||||
|
/// la usa para no recomponer todo cuando el cursor sólo se mueve.
|
||||||
|
cursor_id: Id,
|
||||||
|
/// Ventana sobre la que estaba el puntero — para el foco-sigue-ratón.
|
||||||
|
last_pointer_window: Option<u64>,
|
||||||
|
/// Tamaño de la salida, en píxeles — los topes del puntero.
|
||||||
|
output_size: (f64, f64),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrmState {
|
impl DrmState {
|
||||||
/// Compone las ventanas y, si hubo cambios, encola el cuadro.
|
/// Compone el cursor y las ventanas y, si hubo cambios, encola el cuadro.
|
||||||
fn render(&mut self) {
|
fn render(&mut self) {
|
||||||
if self.pending_flip {
|
if self.pending_flip {
|
||||||
return; // aún esperamos el VBlank del cuadro anterior
|
return; // aún esperamos el VBlank del cuadro anterior
|
||||||
}
|
}
|
||||||
// Elementos a pintar: las flotantes primero (lista front-to-back).
|
// Elementos a pintar — lista front-to-back (índice 0 = encima):
|
||||||
let elements: Vec<WaylandSurfaceRenderElement<GlesRenderer>> = {
|
// primero el cursor, luego las flotantes, luego las teseladas.
|
||||||
|
let elements: Vec<Frame<GlesRenderer>> = {
|
||||||
|
let mut out: Vec<Frame<GlesRenderer>> = Vec::new();
|
||||||
|
|
||||||
|
let (cx, cy) = self.app.pointer_loc;
|
||||||
|
let cursor_rect = Rectangle::new(
|
||||||
|
Point::<i32, Physical>::from((cx.round() as i32, cy.round() as i32)),
|
||||||
|
Size::<i32, Physical>::from((CURSOR_SIZE, CURSOR_SIZE)),
|
||||||
|
);
|
||||||
|
out.push(Frame::Cursor(SolidColorRenderElement::new(
|
||||||
|
self.cursor_id.clone(),
|
||||||
|
cursor_rect,
|
||||||
|
CommitCounter::default(),
|
||||||
|
CURSOR_COLOR,
|
||||||
|
Kind::Cursor,
|
||||||
|
)));
|
||||||
|
|
||||||
let mut shown: Vec<_> = self.app.windows.iter().filter(|w| w.visible).collect();
|
let mut shown: Vec<_> = self.app.windows.iter().filter(|w| w.visible).collect();
|
||||||
shown.sort_by_key(|w| !w.floating);
|
shown.sort_by_key(|w| !w.floating);
|
||||||
shown
|
for w in &shown {
|
||||||
.iter()
|
for el in render_elements_from_surface_tree(
|
||||||
.flat_map(|w| {
|
&mut self.renderer,
|
||||||
render_elements_from_surface_tree(
|
&w.surface,
|
||||||
&mut self.renderer,
|
crate::render_loc(w),
|
||||||
&w.surface,
|
1.0,
|
||||||
crate::render_loc(w),
|
1.0,
|
||||||
1.0,
|
Kind::Unspecified,
|
||||||
1.0,
|
) {
|
||||||
Kind::Unspecified,
|
out.push(Frame::Window(el));
|
||||||
)
|
}
|
||||||
})
|
}
|
||||||
.collect()
|
out
|
||||||
};
|
};
|
||||||
match self.compositor.render_frame::<_, _>(
|
match self.compositor.render_frame::<_, _>(
|
||||||
&mut self.renderer,
|
&mut self.renderer,
|
||||||
@@ -168,42 +213,162 @@ impl DrmState {
|
|||||||
let _ = self.display.flush_clients();
|
let _ = self.display.flush_clients();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Procesa un evento de `libinput` — por ahora, sólo el teclado.
|
/// Procesa un evento de `libinput`: teclado y puntero.
|
||||||
fn handle_input(&mut self, event: InputEvent<LibinputInputBackend>) {
|
fn handle_input(&mut self, event: InputEvent<LibinputInputBackend>) {
|
||||||
let InputEvent::Keyboard { event } = event else {
|
|
||||||
return; // dispositivos, puntero, táctil: aún no
|
|
||||||
};
|
|
||||||
let Some(keyboard) = self.app.keyboard.clone() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let code = event.key_code();
|
|
||||||
let key_state = event.state();
|
|
||||||
let pressed = key_state == KeyState::Pressed;
|
|
||||||
let time = self.start.elapsed().as_millis() as u32;
|
let time = self.start.elapsed().as_millis() as u32;
|
||||||
keyboard.input::<(), _>(
|
match event {
|
||||||
&mut self.app,
|
// --- Teclado: intercepta los atajos del Cerebro --------------
|
||||||
code,
|
InputEvent::Keyboard { event } => {
|
||||||
key_state,
|
let Some(keyboard) = self.app.keyboard.clone() else {
|
||||||
SERIAL_COUNTER.next_serial(),
|
return;
|
||||||
time,
|
};
|
||||||
|st, mods, handle| {
|
let code = event.key_code();
|
||||||
if !pressed {
|
let key_state = event.state();
|
||||||
return FilterResult::Forward;
|
let pressed = key_state == KeyState::Pressed;
|
||||||
|
keyboard.input::<(), _>(
|
||||||
|
&mut self.app,
|
||||||
|
code,
|
||||||
|
key_state,
|
||||||
|
SERIAL_COUNTER.next_serial(),
|
||||||
|
time,
|
||||||
|
|st, mods, handle| {
|
||||||
|
if !pressed {
|
||||||
|
return FilterResult::Forward;
|
||||||
|
}
|
||||||
|
if let Some(combo) = combo_string(mods, handle.modified_sym()) {
|
||||||
|
if st.grabs.contains(&combo) {
|
||||||
|
st.pending_keybind = Some(combo);
|
||||||
|
return FilterResult::Intercept(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FilterResult::Forward
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if let Some(combo) = self.app.pending_keybind.take() {
|
||||||
|
let ev = self.app.body.keybind(combo);
|
||||||
|
self.app.brain_feed(ev);
|
||||||
}
|
}
|
||||||
if let Some(combo) = combo_string(mods, handle.modified_sym()) {
|
}
|
||||||
if st.grabs.contains(&combo) {
|
|
||||||
st.pending_keybind = Some(combo);
|
// --- Puntero: movimiento relativo (ratón, touchpad) ----------
|
||||||
return FilterResult::Intercept(());
|
InputEvent::PointerMotion { event } => {
|
||||||
|
let (mut x, mut y) = self.app.pointer_loc;
|
||||||
|
x = (x + event.delta_x()).clamp(0.0, self.output_size.0);
|
||||||
|
y = (y + event.delta_y()).clamp(0.0, self.output_size.1);
|
||||||
|
self.app.pointer_loc = (x, y);
|
||||||
|
self.pointer_motion(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Puntero: movimiento absoluto (táctil, tableta) ----------
|
||||||
|
InputEvent::PointerMotionAbsolute { event } => {
|
||||||
|
let space = Size::<i32, Logical>::from((
|
||||||
|
self.output_size.0 as i32,
|
||||||
|
self.output_size.1 as i32,
|
||||||
|
));
|
||||||
|
let pos = event.position_transformed(space);
|
||||||
|
self.app.pointer_loc = (
|
||||||
|
pos.x.clamp(0.0, self.output_size.0),
|
||||||
|
pos.y.clamp(0.0, self.output_size.1),
|
||||||
|
);
|
||||||
|
self.pointer_motion(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Puntero: botones — se reenvían a la ventana enfocada ----
|
||||||
|
InputEvent::PointerButton { event } => {
|
||||||
|
let Some(pointer) = self.app.pointer.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
pointer.button(
|
||||||
|
&mut self.app,
|
||||||
|
&ButtonEvent {
|
||||||
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
|
time,
|
||||||
|
button: event.button_code(),
|
||||||
|
state: event.state(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
pointer.frame(&mut self.app);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Puntero: rueda / desplazamiento -------------------------
|
||||||
|
InputEvent::PointerAxis { event } => {
|
||||||
|
let Some(pointer) = self.app.pointer.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let source = event.source();
|
||||||
|
let mut frame = AxisFrame::new(time).source(source);
|
||||||
|
for axis in [Axis::Horizontal, Axis::Vertical] {
|
||||||
|
match event.amount(axis) {
|
||||||
|
Some(v) if v != 0.0 => frame = frame.value(axis, v),
|
||||||
|
Some(_) if source == AxisSource::Finger => {
|
||||||
|
frame = frame.stop(axis);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if let Some(d) = event.amount_v120(axis) {
|
||||||
|
frame = frame.v120(axis, d as i32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FilterResult::Forward
|
pointer.axis(&mut self.app, frame);
|
||||||
|
pointer.frame(&mut self.app);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {} // otros dispositivos: aún no
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reenvía el puntero a la ventana que tiene debajo y, si esa ventana
|
||||||
|
/// cambió, aplica el foco-sigue-ratón avisando al Cerebro.
|
||||||
|
fn pointer_motion(&mut self, time: u32) {
|
||||||
|
let Some(pointer) = self.app.pointer.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (x, y) = self.app.pointer_loc;
|
||||||
|
let hit = self.window_at(x, y);
|
||||||
|
let focus = hit.map(|i| {
|
||||||
|
let w = &self.app.windows[i];
|
||||||
|
let (lx, ly) = crate::render_loc(w);
|
||||||
|
(
|
||||||
|
w.surface.clone(),
|
||||||
|
Point::<f64, Logical>::from((lx as f64, ly as f64)),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
pointer.motion(
|
||||||
|
&mut self.app,
|
||||||
|
focus,
|
||||||
|
&MotionEvent {
|
||||||
|
location: Point::from((x, y)),
|
||||||
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
|
time,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if let Some(combo) = self.app.pending_keybind.take() {
|
pointer.frame(&mut self.app);
|
||||||
let ev = self.app.body.keybind(combo);
|
|
||||||
self.app.brain_feed(ev);
|
// Foco-sigue-ratón: al pasar a otra ventana, que el Cerebro la enfoque.
|
||||||
|
let hovered = hit.map(|i| self.app.windows[i].id);
|
||||||
|
if hovered != self.last_pointer_window {
|
||||||
|
self.last_pointer_window = hovered;
|
||||||
|
if let Some(id) = hovered {
|
||||||
|
let ev = self.app.body.pointer_enter(id);
|
||||||
|
self.app.brain_feed(ev);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// El índice de la ventana visible bajo el punto `(x, y)`, si la hay —
|
||||||
|
/// en orden front-to-back (las flotantes ganan a las teseladas).
|
||||||
|
fn window_at(&self, x: f64, y: f64) -> Option<usize> {
|
||||||
|
let mut idx: Vec<usize> = (0..self.app.windows.len())
|
||||||
|
.filter(|&i| self.app.windows[i].visible)
|
||||||
|
.collect();
|
||||||
|
idx.sort_by_key(|&i| !self.app.windows[i].floating);
|
||||||
|
idx.into_iter().find(|&i| {
|
||||||
|
let w = &self.app.windows[i];
|
||||||
|
let (lx, ly) = crate::render_loc(w);
|
||||||
|
let (sw, sh) = crate::surface_px_size(w).unwrap_or(w.size);
|
||||||
|
x >= lx as f64 && y >= ly as f64 && x < (lx + sw) as f64 && y < (ly + sh) as f64
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Arranca el Cuerpo sobre DRM/KMS — fases 1, 2a y 2b.
|
/// Arranca el Cuerpo sobre DRM/KMS — fases 1, 2a y 2b.
|
||||||
@@ -339,6 +504,8 @@ pub fn run() -> Result<(), Box<dyn Error>> {
|
|||||||
// La salida del Cerebro = el modo del monitor.
|
// La salida del Cerebro = el modo del monitor.
|
||||||
let ev = app.body.add_output(0, mode_w as i32, mode_h as i32);
|
let ev = app.body.add_output(0, mode_w as i32, mode_h as i32);
|
||||||
app.brain_feed(ev);
|
app.brain_feed(ev);
|
||||||
|
// El puntero arranca en el centro de la pantalla.
|
||||||
|
app.pointer_loc = (mode_w as f64 / 2.0, mode_h as f64 / 2.0);
|
||||||
// Anuncia el monitor en el protocolo Wayland — los clientes lo exigen.
|
// Anuncia el monitor en el protocolo Wayland — los clientes lo exigen.
|
||||||
let _wl_output = crate::announce_output(
|
let _wl_output = crate::announce_output(
|
||||||
&display.handle(),
|
&display.handle(),
|
||||||
@@ -475,6 +642,9 @@ pub fn run() -> Result<(), Box<dyn Error>> {
|
|||||||
ctl,
|
ctl,
|
||||||
start: Instant::now(),
|
start: Instant::now(),
|
||||||
last_windows: 0,
|
last_windows: 0,
|
||||||
|
cursor_id: Id::new(),
|
||||||
|
last_pointer_window: None,
|
||||||
|
output_size: (mode_w as f64, mode_h as f64),
|
||||||
};
|
};
|
||||||
|
|
||||||
let signal = event_loop.get_signal();
|
let signal = event_loop.get_signal();
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ use smithay::backend::renderer::utils::{
|
|||||||
use smithay::backend::renderer::{Color32F, Frame, Renderer};
|
use smithay::backend::renderer::{Color32F, Frame, Renderer};
|
||||||
use smithay::backend::winit::{self, WinitEvent};
|
use smithay::backend::winit::{self, WinitEvent};
|
||||||
use smithay::input::keyboard::{xkb, FilterResult, KeyboardHandle, Keysym, ModifiersState};
|
use smithay::input::keyboard::{xkb, FilterResult, KeyboardHandle, Keysym, ModifiersState};
|
||||||
|
use smithay::input::pointer::PointerHandle;
|
||||||
use smithay::input::{Seat, SeatHandler, SeatState};
|
use smithay::input::{Seat, SeatHandler, SeatState};
|
||||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||||
use smithay::reexports::wayland_server::backend::{ClientData, ClientId, DisconnectReason};
|
use smithay::reexports::wayland_server::backend::{ClientData, ClientId, DisconnectReason};
|
||||||
@@ -107,6 +108,9 @@ struct App {
|
|||||||
data_device_state: DataDeviceState,
|
data_device_state: DataDeviceState,
|
||||||
seat: Seat<Self>,
|
seat: Seat<Self>,
|
||||||
keyboard: Option<KeyboardHandle<Self>>,
|
keyboard: Option<KeyboardHandle<Self>>,
|
||||||
|
pointer: Option<PointerHandle<Self>>,
|
||||||
|
/// Posición del puntero en coordenadas globales.
|
||||||
|
pointer_loc: (f64, f64),
|
||||||
|
|
||||||
/// Ventanas gestionadas, en orden de aparición.
|
/// Ventanas gestionadas, en orden de aparición.
|
||||||
windows: Vec<ManagedWindow>,
|
windows: Vec<ManagedWindow>,
|
||||||
@@ -534,6 +538,16 @@ fn render_loc(w: &ManagedWindow) -> (i32, i32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// El tamaño en píxeles de la superficie de una ventana, si el cliente
|
||||||
|
/// ya presentó un buffer. `None` mientras no haya dibujado nada — la usa
|
||||||
|
/// el backend DRM para acertar el rectángulo en el test de impacto del
|
||||||
|
/// puntero.
|
||||||
|
fn surface_px_size(w: &ManagedWindow) -> Option<(i32, i32)> {
|
||||||
|
with_renderer_surface_state(&w.surface, |s| s.surface_size())
|
||||||
|
.flatten()
|
||||||
|
.map(|s| (s.w, s.h))
|
||||||
|
}
|
||||||
|
|
||||||
/// Carga las reglas de ventana del usuario, o ninguna si no hay archivo.
|
/// Carga las reglas de ventana del usuario, o ninguna si no hay archivo.
|
||||||
fn load_user_rules() -> Rules {
|
fn load_user_rules() -> Rules {
|
||||||
match Rules::default_path() {
|
match Rules::default_path() {
|
||||||
@@ -627,6 +641,8 @@ fn build_app() -> Result<Setup, Box<dyn std::error::Error>> {
|
|||||||
data_device_state: DataDeviceState::new::<App>(&dh),
|
data_device_state: DataDeviceState::new::<App>(&dh),
|
||||||
seat,
|
seat,
|
||||||
keyboard: None,
|
keyboard: None,
|
||||||
|
pointer: None,
|
||||||
|
pointer_loc: (0.0, 0.0),
|
||||||
windows: Vec::new(),
|
windows: Vec::new(),
|
||||||
body: BodyState::new(),
|
body: BodyState::new(),
|
||||||
brain,
|
brain,
|
||||||
@@ -638,6 +654,7 @@ fn build_app() -> Result<Setup, Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
let keyboard = app.seat.add_keyboard(Default::default(), 200, 25)?;
|
let keyboard = app.seat.add_keyboard(Default::default(), 200, 25)?;
|
||||||
app.keyboard = Some(keyboard);
|
app.keyboard = Some(keyboard);
|
||||||
|
app.pointer = Some(app.seat.add_pointer());
|
||||||
|
|
||||||
// En modo embebido, el propio Desktop dicta los atajos a interceptar.
|
// En modo embebido, el propio Desktop dicta los atajos a interceptar.
|
||||||
if let Brain::Embedded(desktop) = &app.brain {
|
if let Brain::Embedded(desktop) = &app.brain {
|
||||||
|
|||||||
@@ -195,14 +195,18 @@ Cerebro: **autónomo** (`Desktop` embebido) o **enlazado** (`MIRADA_SOCKET`
|
|||||||
- **`winit`** — corre anidado, una ventana en la sesión gráfica actual.
|
- **`winit`** — corre anidado, una ventana en la sesión gráfica actual.
|
||||||
- **`drm`** (`drm_backend.rs`) — corre nativo sobre una TTY, sin sesión
|
- **`drm`** (`drm_backend.rs`) — corre nativo sobre una TTY, sin sesión
|
||||||
anfitriona: `libseat` (sesión), `udev` (GPU), `DrmDevice` + GBM + EGL +
|
anfitriona: `libseat` (sesión), `udev` (GPU), `DrmDevice` + GBM + EGL +
|
||||||
`DrmCompositor`, `libinput` (teclado), bucle `calloop`. Verificado en
|
`DrmCompositor`, `libinput` (teclado y ratón), bucle `calloop`.
|
||||||
hardware: sesión, render, teclado, atajos, clientes, salida limpia.
|
Verificado en hardware: sesión, render, teclado, atajos, clientes,
|
||||||
|
salida limpia. El ratón pinta un cursor de software (un
|
||||||
|
`SolidColorRenderElement` marcado `Kind::Cursor`, encima de todo en un
|
||||||
|
enum `Frame` de elementos de render); el foco sigue al puntero
|
||||||
|
(`BodyEvent::PointerEntered`) y clics y rueda van a la ventana debajo.
|
||||||
|
|
||||||
**Pendiente** — refinamientos del Cuerpo:
|
**Pendiente** — refinamientos del Cuerpo:
|
||||||
|
|
||||||
| capa pendiente | rol |
|
| capa pendiente | rol |
|
||||||
| ---------------- | ------------------------------------------------------------ |
|
| ---------------- | ------------------------------------------------------------ |
|
||||||
| puntero en `drm` | ratón/touchpad por `libinput` (hoy el backend DRM sólo teclado) |
|
| puntero en `winit` | ratón en el backend anidado (hoy sólo el backend DRM) |
|
||||||
| `mirada-input` | repetición de teclas, gestos; conmutación de VT, hotplug |
|
| `mirada-input` | repetición de teclas, gestos; conmutación de VT, hotplug |
|
||||||
| `mirada-sandbox` | aislamiento de clientes sobre `arje-incarnate` |
|
| `mirada-sandbox` | aislamiento de clientes sobre `arje-incarnate` |
|
||||||
|
|
||||||
|
|||||||
@@ -993,11 +993,15 @@
|
|||||||
|
|
||||||
|
|
||||||
El Cuerpo de verdad — mirada-compositor:
|
El Cuerpo de verdad — mirada-compositor:
|
||||||
Compositor Wayland teselante real sobre smithay, backend winit (corre anidado, una ventana dentro de tu sesión X11/Wayland).
|
Compositor Wayland teselante real sobre smithay, con dos backends gráficos (main() elige; --winit/--drm lo fuerzan):
|
||||||
|
· winit — corre anidado, una ventana dentro de tu sesión X11/Wayland (para desarrollar sin dejar el escritorio).
|
||||||
|
· drm — corre nativo sobre una TTY, sin sesión anfitriona: toma GPU (DRM/KMS/GBM/EGL), teclado y ratón (libinput).
|
||||||
cargo run -p mirada-compositor # autónomo: Cerebro Desktop embebido, un solo proceso
|
cargo run -p mirada-compositor # autónomo: Cerebro Desktop embebido, un solo proceso
|
||||||
WAYLAND_DISPLAY=wayland-1 foot # lanza clientes contra él (imprime su WAYLAND_DISPLAY al arrancar)
|
WAYLAND_DISPLAY=wayland-1 foot # lanza clientes contra él (imprime su WAYLAND_DISPLAY al arrancar)
|
||||||
MIRADA_SOCKET=/tmp/mirada.sock cargo run -p mirada-compositor # enlazado: la app mirada (Cerebro GPUI) decide la geometría
|
MIRADA_SOCKET=/tmp/mirada.sock cargo run -p mirada-compositor # enlazado: la app mirada (Cerebro GPUI) decide la geometría
|
||||||
|
cargo run -p mirada-compositor -- --drm # nativo sobre TTY (MIRADA_STARTUP=foot lanza un cliente al arrancar)
|
||||||
Habla wl_compositor/xdg_shell/wl_shm/wl_seat/wl_data_device; compone con GlesRenderer. Reusa mirada-body y mirada-link.
|
Habla wl_compositor/xdg_shell/wl_shm/wl_seat/wl_data_device; compone con GlesRenderer. Reusa mirada-body y mirada-link.
|
||||||
|
En --drm el ratón pinta un cursor de software, el foco sigue al puntero y clics/rueda van a la ventana debajo.
|
||||||
Ver crates/apps/mirada-compositor/README.md.
|
Ver crates/apps/mirada-compositor/README.md.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user