From d0304415eb5e0f220f4f75ab536e8c22fb7d547e Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 22 Dec 2021 20:14:09 +0100 Subject: [PATCH] state: Add basic input handling --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/backend/x11.rs | 120 ++++++++---- src/input/mod.rs | 392 +++++++++++++++++++++++++++++++++++++++- src/main.rs | 1 + src/shell/workspaces.rs | 29 ++- 6 files changed, 503 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d28e09d8..bcdb5f91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -592,7 +592,7 @@ checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/Smithay/smithay.git?rev=7e2affd#7e2affdf3106b22cb17ef48c087d3f0229a0abdf" +source = "git+https://github.com/Smithay/smithay.git?rev=b683f7d#b683f7d4ddcf3dacd593ce5d46d0a6fffcbe2ac0" dependencies = [ "appendlist", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index 0a182b76..f8a188d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,6 @@ slog-stdlog = "4.1" [dependencies.smithay] version = "0.3" git = "https://github.com/Smithay/smithay.git" -rev = "7e2affd" +rev = "b683f7d" default-features = false features = ["backend_x11", "backend_egl", "desktop", "use_system_lib", "renderer_gl", "wayland_frontend"] \ No newline at end of file diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 24d00f37..1a1fbfc1 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ + input::{set_active_output, Devices}, state::{BackendData, State}, utils::GlobalDrop, }; @@ -10,10 +11,11 @@ use smithay::{ allocator::dmabuf::Dmabuf, drm::DrmNode, egl::{EGLContext, EGLDisplay}, + input::{Event, InputEvent}, renderer::{gles2::Gles2Renderer, Bind, ImportDma, ImportEgl, Unbind}, - x11::{Window, WindowBuilder, X11Backend, X11Event, X11Handle, X11Surface}, + x11::{Window, WindowBuilder, X11Backend, X11Event, X11Handle, X11Input, X11Surface}, }, - desktop::Space, + desktop::{layer_map_for_output, Space}, reexports::{ calloop::{ping, EventLoop, LoopHandle}, gbm::Device as GbmDevice, @@ -146,6 +148,7 @@ impl Surface { &self.output, age as usize, [0.153, 0.161, 0.165, 1.0], + &[], ) { Ok(true) => { slog_scope::trace!("Finished rendering"); @@ -156,6 +159,7 @@ impl Surface { } Ok(false) => { let _ = renderer.unbind(); + self.render.ping(); } Err(err) => { self.surface.reset_buffers(); @@ -207,56 +211,64 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res event_loop .handle() - .insert_source(backend, |event, window, state| match event { - X11Event::CloseRequested => { - window.unmap(); + .insert_source(backend, |event, _, state| match event { + X11Event::CloseRequested { window_id } => { // TODO: drain_filter - for output in state + for surface in state .backend .x11() .surfaces .iter() - .filter(|s| &s.window == window) - .map(|s| &s.output) + .filter(|s| s.window.id() == window_id) { - state.spaces.unmap_output(output); + surface.window.unmap(); + state.spaces.unmap_output(&surface.output); } - state.backend.x11().surfaces.retain(|s| &s.window != window); - } - X11Event::Resized(size) => { - let size = { (size.w as i32, size.h as i32).into() }; - let mode = Mode { - size, - refresh: 60_000, - }; - let surface = state - .backend - .x11() - .surfaces - .iter_mut() - .find(|s| &s.window == window) - .expect("Unmanaged X11 surface?"); - surface.render.ping(); - - let output = &surface.output; - output.delete_mode(output.current_mode().unwrap()); - output.change_current_state(Some(mode), None, None, None); - output.set_preferred(mode); - } - - X11Event::PresentCompleted | X11Event::Refresh => { state + .backend + .x11() + .surfaces + .retain(|s| s.window.id() != window_id); + } + X11Event::Resized { + new_size, + window_id, + } => { + let size = { (new_size.w as i32, new_size.h as i32).into() }; + let mode = Mode { + size, + refresh: 60_000, + }; + if let Some(surface) = state .backend .x11() .surfaces .iter_mut() - .find(|s| &s.window == window) - .expect("Unmanaged X11 surface?") - .render - .ping(); + .find(|s| s.window.id() == window_id) + { + surface.render.ping(); + + let output = &surface.output; + output.delete_mode(output.current_mode().unwrap()); + output.change_current_state(Some(mode), None, None, None); + output.set_preferred(mode); + layer_map_for_output(output).arrange(); + } } - X11Event::Input(event) => { /*TODO*/ } + X11Event::PresentCompleted { window_id } | X11Event::Refresh { window_id } => { + if let Some(surface) = state + .backend + .x11() + .surfaces + .iter_mut() + .find(|s| s.window.id() == window_id) + { + surface.render.ping(); + } + } + + X11Event::Input(event) => state.process_x11_event(event), }) .map_err(|_| anyhow::anyhow!("Failed to insert X11 Backend into event loop"))?; @@ -285,3 +297,35 @@ fn init_egl_client_side(display: &mut Display, renderer: Rc) { + // here we can handle special cases for x11 inputs, like mapping them to windows + match &event { + InputEvent::PointerMotionAbsolute { event } => { + if let Some(window) = event.window() { + let output = self + .backend + .x11() + .surfaces + .iter() + .find(|surface| &surface.window == window.as_ref()) + .map(|surface| surface.output.clone()) + .unwrap(); + + let device = event.device(); + for seat in self.seats.clone().iter() { + let devices = seat.user_data().get::().unwrap(); + if devices.has_device(&device) { + set_active_output(seat, &output); + break; + } + } + } + } + _ => {} + }; + + self.process_input_event(event); + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs index ed03f897..7e65db3c 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -3,19 +3,66 @@ use crate::state::State; use smithay::{ backend::input::{Device, DeviceCapability, InputBackend, InputEvent}, - reexports::wayland_server::Display, + desktop::{layer_map_for_output, Space}, + reexports::wayland_server::{protocol::wl_surface::WlSurface, Display}, + utils::{Logical, Point}, wayland::{ data_device::set_data_device_focus, output::Output, - seat::{CursorImageStatus, Keysym, Seat, XkbConfig}, + seat::{CursorImageStatus, FilterResult, Keysym, Seat, XkbConfig}, + shell::wlr_layer::Layer as WlrLayer, + SERIAL_COUNTER, }, }; use std::{cell::RefCell, collections::HashMap}; pub struct ActiveOutput(pub RefCell); +pub struct Devices(RefCell>>); + +impl Devices { + fn new() -> Devices { + Devices(RefCell::new(HashMap::new())) + } + + fn add_device(&self, device: &D) -> Vec { + let id = device.id(); + let mut map = self.0.borrow_mut(); + let caps = [DeviceCapability::Keyboard, DeviceCapability::Pointer] + .iter() + .cloned() + .filter(|c| device.has_capability(*c)) + .collect::>(); + let new_caps = caps + .iter() + .cloned() + .filter(|c| map.values().flatten().all(|has| *c != *has)) + .collect::>(); + map.insert(id, caps); + new_caps + } + + pub fn has_device(&self, device: &D) -> bool { + self.0.borrow().contains_key(&device.id()) + } + + fn remove_device(&self, device: &D) -> Vec { + let id = device.id(); + let mut map = self.0.borrow_mut(); + map.remove(&id) + .unwrap_or(Vec::new()) + .into_iter() + .filter(|c| map.values().flatten().all(|has| *c != *has)) + .collect() + } +} + pub fn add_seat(display: &mut Display, name: String) -> Seat { let (seat, _) = Seat::new(display, name, None); + let userdata = seat.user_data(); + userdata.insert_if_missing(|| Devices::new()); + userdata.insert_if_missing(|| RefCell::new(CursorImageStatus::Hidden)); + userdata.insert_if_missing(|| Vec::::new()); seat } @@ -32,3 +79,344 @@ pub fn active_output(seat: &Seat, state: &State) -> Output { .expect("Backend has no outputs?") }) } + +pub fn set_active_output(seat: &Seat, output: &Output) { + if !seat + .user_data() + .insert_if_missing(|| ActiveOutput(RefCell::new(output.clone()))) + { + *seat + .user_data() + .get::() + .unwrap() + .0 + .borrow_mut() = output.clone(); + } +} + +impl State { + pub fn process_input_event(&mut self, event: InputEvent) { + use smithay::backend::input::Event; + + match event { + InputEvent::DeviceAdded { device } => { + let seat = &mut self.last_active_seat; + let userdata = seat.user_data(); + let devices = userdata.get::().unwrap(); + for cap in devices.add_device(&device) { + match cap { + DeviceCapability::Keyboard => { + let _ = + seat.add_keyboard(XkbConfig::default(), 200, 25, |seat, focus| { + set_data_device_focus( + seat, + focus.and_then(|s| s.as_ref().client()), + ) + }); + } + DeviceCapability::Pointer => { + let output = self + .spaces + .outputs() + .next() + .expect("Backend initialized without output") + .clone(); + seat.user_data() + .insert_if_missing(|| ActiveOutput(RefCell::new(output))); + let owned_seat = seat.clone(); + seat.add_pointer(move |status| { + *owned_seat + .user_data() + .get::>() + .unwrap() + .borrow_mut() = status; + }); + } + _ => {} + } + } + } + InputEvent::DeviceRemoved { device } => { + for seat in &mut self.seats { + let userdata = seat.user_data(); + let devices = userdata.get::().unwrap(); + if devices.has_device(&device) { + for cap in devices.remove_device(&device) { + match cap { + DeviceCapability::Keyboard => { + seat.remove_keyboard(); + } + DeviceCapability::Pointer => { + seat.remove_pointer(); + } + _ => {} + } + } + break; + } + } + } + InputEvent::Keyboard { event, .. } => { + use smithay::backend::input::KeyboardKeyEvent; + + let device = event.device(); + for seat in self.seats.clone().iter() { + let userdata = seat.user_data(); + let devices = userdata.get::().unwrap(); + if devices.has_device(&device) { + let keycode = event.key_code(); + let state = event.state(); + slog_scope::trace!("key"; "keycode" => keycode, "state" => format!("{:?}", state)); + + let serial = SERIAL_COUNTER.next_serial(); + let time = Event::time(&event); + seat.get_keyboard().unwrap().input::<(), _>( + keycode, + state, + serial, + time, + |modifiers, handle| { + // here we can handle global shortcuts and the like + let _ = (modifiers, handle); + FilterResult::Forward + }, + ); + break; + } + } + } + InputEvent::PointerMotion { event, .. } => { + use smithay::backend::input::PointerMotionEvent; + + let device = event.device(); + for seat in self.seats.clone().iter() { + let userdata = seat.user_data(); + let devices = userdata.get::().unwrap(); + if devices.has_device(&device) { + let current_output = active_output(seat, &self); + + let mut position = seat.get_pointer().unwrap().current_location(); + position += event.delta(); + + let output = self + .spaces + .outputs() + .find(|output| { + self.spaces + .output_geometry(output) + .to_f64() + .contains(position) + }) + .unwrap_or(¤t_output); + if output != ¤t_output { + set_active_output(seat, output); + } + let output_geometry = self.spaces.output_geometry(output); + + position.x = 0.0f64 + .max(position.x) + .min((output_geometry.loc.x + output_geometry.size.w) as f64); + position.y = 0.0f64 + .max(position.y) + .min((output_geometry.loc.y + output_geometry.size.h) as f64); + + let serial = SERIAL_COUNTER.next_serial(); + let space = self.spaces.active_space(&output); + let under = State::surface_under(position, output, space); + seat.get_pointer() + .unwrap() + .motion(position, under, serial, event.time()); + break; + } + } + } + InputEvent::PointerMotionAbsolute { event, .. } => { + use smithay::backend::input::PointerMotionAbsoluteEvent; + + let device = event.device(); + for seat in self.seats.clone().iter() { + let userdata = seat.user_data(); + let devices = userdata.get::().unwrap(); + if devices.has_device(&device) { + let output = active_output(seat, &self); + let space = self.spaces.active_space(&output); + let geometry = self.spaces.output_geometry(&output); + let position = + geometry.loc.to_f64() + event.position_transformed(geometry.size); + let serial = SERIAL_COUNTER.next_serial(); + let under = State::surface_under(position, &output, space); + seat.get_pointer() + .unwrap() + .motion(position, under, serial, event.time()); + break; + } + } + } + InputEvent::PointerButton { event, .. } => { + use smithay::{ + backend::input::{ButtonState, PointerButtonEvent}, + reexports::wayland_server::protocol::wl_pointer, + }; + + let device = event.device(); + for seat in self.seats.clone().iter() { + let userdata = seat.user_data(); + let devices = userdata.get::().unwrap(); + if devices.has_device(&device) { + let serial = SERIAL_COUNTER.next_serial(); + let button = event.button_code(); + let state = match event.state() { + ButtonState::Pressed => { + // change the keyboard focus unless the pointer is grabbed + if !seat.get_pointer().unwrap().is_grabbed() { + let output = active_output(seat, &self); + let mut pos = seat.get_pointer().unwrap().current_location(); + let output_geo = self.spaces.output_geometry(&output); + let space = self.spaces.active_space_mut(&output); + let layers = layer_map_for_output(&output); + pos -= output_geo.loc.to_f64(); + let mut under = None; + + if let Some(layer) = layers + .layer_under(WlrLayer::Overlay, pos) + .or_else(|| layers.layer_under(WlrLayer::Top, pos)) + { + if layer.can_receive_keyboard_focus() { + let layer_loc = layers.layer_geometry(layer).loc; + under = layer + .surface_under(pos - layer_loc.to_f64()) + .map(|(s, _)| s); + } + } else if let Some(window) = space.window_under(pos).cloned() { + let window_loc = + space.window_geometry(&window).unwrap().loc; + under = window + .surface_under(pos - window_loc.to_f64()) + .map(|(s, _)| s); + space.raise_window(&window); + } else if let Some(layer) = layers + .layer_under(WlrLayer::Bottom, pos) + .or_else(|| layers.layer_under(WlrLayer::Background, pos)) + { + if layer.can_receive_keyboard_focus() { + let layer_loc = layers.layer_geometry(layer).loc; + under = layer + .surface_under(pos - layer_loc.to_f64()) + .map(|(s, _)| s); + } + }; + + if let Some(keyboard) = seat.get_keyboard() { + keyboard.set_focus(under.as_ref(), serial); + } + } + wl_pointer::ButtonState::Pressed + } + ButtonState::Released => wl_pointer::ButtonState::Released, + }; + seat.get_pointer() + .unwrap() + .button(button, state, serial, event.time()); + break; + } + } + } + InputEvent::PointerAxis { event, .. } => { + use smithay::{ + backend::input::{Axis, AxisSource, PointerAxisEvent}, + reexports::wayland_server::protocol::wl_pointer, + wayland::seat::AxisFrame, + }; + + let device = event.device(); + for seat in self.seats.clone().iter() { + let userdata = seat.user_data(); + let devices = userdata.get::().unwrap(); + if devices.has_device(&device) { + let source = match event.source() { + AxisSource::Continuous => wl_pointer::AxisSource::Continuous, + AxisSource::Finger => wl_pointer::AxisSource::Finger, + AxisSource::Wheel | AxisSource::WheelTilt => { + wl_pointer::AxisSource::Wheel + } + }; + let horizontal_amount = + event.amount(Axis::Horizontal).unwrap_or_else(|| { + event.amount_discrete(Axis::Horizontal).unwrap() * 3.0 + }); + let vertical_amount = event.amount(Axis::Vertical).unwrap_or_else(|| { + event.amount_discrete(Axis::Vertical).unwrap() * 3.0 + }); + let horizontal_amount_discrete = event.amount_discrete(Axis::Horizontal); + let vertical_amount_discrete = event.amount_discrete(Axis::Vertical); + + { + let mut frame = AxisFrame::new(event.time()).source(source); + if horizontal_amount != 0.0 { + frame = frame + .value(wl_pointer::Axis::HorizontalScroll, horizontal_amount); + if let Some(discrete) = horizontal_amount_discrete { + frame = frame.discrete( + wl_pointer::Axis::HorizontalScroll, + discrete as i32, + ); + } + } else if source == wl_pointer::AxisSource::Finger { + frame = frame.stop(wl_pointer::Axis::HorizontalScroll); + } + if vertical_amount != 0.0 { + frame = + frame.value(wl_pointer::Axis::VerticalScroll, vertical_amount); + if let Some(discrete) = vertical_amount_discrete { + frame = frame.discrete( + wl_pointer::Axis::VerticalScroll, + discrete as i32, + ); + } + } else if source == wl_pointer::AxisSource::Finger { + frame = frame.stop(wl_pointer::Axis::VerticalScroll); + } + seat.get_pointer().unwrap().axis(frame); + } + break; + } + } + } + _ => { /* TODO e.g. tablet or touch events */ } + } + } + + pub fn surface_under( + pos: Point, + output: &Output, + space: &Space, + ) -> Option<(WlSurface, Point)> { + let layers = layer_map_for_output(output); + let output_geo = space.output_geometry(output).unwrap(); + + if let Some(layer) = layers + .layer_under(WlrLayer::Overlay, pos) + .or_else(|| layers.layer_under(WlrLayer::Top, pos)) + { + let layer_loc = layers.layer_geometry(layer).loc; + layer + .surface_under(pos - output_geo.loc.to_f64() - layer_loc.to_f64()) + .map(|(s, loc)| (s, loc + layer_loc)) + } else if let Some(window) = space.window_under(pos) { + let window_loc = space.window_geometry(window).unwrap().loc; + window + .surface_under(pos - window_loc.to_f64()) + .map(|(s, loc)| (s, loc + window_loc)) + } else if let Some(layer) = layers + .layer_under(WlrLayer::Bottom, pos) + .or_else(|| layers.layer_under(WlrLayer::Background, pos)) + { + let layer_loc = layers.layer_geometry(layer).loc; + layer + .surface_under(pos - output_geo.loc.to_f64() - layer_loc.to_f64()) + .map(|(s, loc)| (s, loc + layer_loc)) + } else { + None + } + } +} diff --git a/src/main.rs b/src/main.rs index 9b0943e8..a939846c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use anyhow::{Context, Result}; use slog::Drain; pub mod backend; +pub mod input; pub mod shell; pub mod state; pub mod utils; diff --git a/src/shell/workspaces.rs b/src/shell/workspaces.rs index 5b0392c3..15adf74a 100644 --- a/src/shell/workspaces.rs +++ b/src/shell/workspaces.rs @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only pub use smithay::{ - desktop::Space, reexports::wayland_server::protocol::wl_surface::WlSurface, + desktop::Space, + reexports::wayland_server::protocol::wl_surface::WlSurface, + utils::{Logical, Rectangle, Size}, wayland::output::Output, }; use std::{cell::Cell, mem::MaybeUninit}; @@ -115,6 +117,31 @@ impl Workspaces { } } + pub fn output_size(&self, output: &Output) -> Size { + let space = self.active_space(output); + space + .output_geometry(&output) + .unwrap_or(Rectangle::from_loc_and_size((0, 0), (0, 0))) + .size + } + + pub fn output_geometry(&self, output: &Output) -> Rectangle { + // due to our different modes, we cannot just ask the space for the global output coordinates, + // because for `Mode::OutputBound` the origin will always be (0, 0) + + // TODO: Add a proper grid like structure, for now the outputs just extend to the right + let pos = + self.outputs + .iter() + .take_while(|o| o != &output) + .fold((0, 0), |(x, y), output| { + let size = self.output_size(output); + (x + size.w, y) + }); + + Rectangle::from_loc_and_size(pos, self.output_size(output)) + } + pub fn activate(&mut self, output: &Output, idx: usize) { match self.mode { Mode::OutputBound => {