diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/grabs/mod.rs | 5 | ||||
| -rw-r--r-- | src/grabs/move_grab.rs | 75 | ||||
| -rw-r--r-- | src/grabs/resize_grab.rs | 278 | ||||
| -rw-r--r-- | src/handlers/compositor.rs | 57 | ||||
| -rw-r--r-- | src/handlers/mod.rs | 54 | ||||
| -rw-r--r-- | src/handlers/xdg_shell.rs | 163 | ||||
| -rw-r--r-- | src/input.rs | 131 | ||||
| -rw-r--r-- | src/main.rs | 52 | ||||
| -rw-r--r-- | src/state.rs | 164 | ||||
| -rw-r--r-- | src/winit.rs | 139 |
10 files changed, 1118 insertions, 0 deletions
diff --git a/src/grabs/mod.rs b/src/grabs/mod.rs new file mode 100644 index 00000000..5756e1be --- /dev/null +++ b/src/grabs/mod.rs @@ -0,0 +1,5 @@ +pub mod move_grab; +pub use move_grab::MoveSurfaceGrab; + +pub mod resize_grab; +pub use resize_grab::ResizeSurfaceGrab; diff --git a/src/grabs/move_grab.rs b/src/grabs/move_grab.rs new file mode 100644 index 00000000..c9aacbec --- /dev/null +++ b/src/grabs/move_grab.rs @@ -0,0 +1,75 @@ +use crate::Smallvil; +use smithay::{ + desktop::Window, + input::pointer::{ + AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, + PointerInnerHandle, RelativeMotionEvent, + }, + reexports::wayland_server::protocol::wl_surface::WlSurface, + utils::{Logical, Point}, +}; + +pub struct MoveSurfaceGrab { + pub start_data: PointerGrabStartData<Smallvil>, + pub window: Window, + pub initial_window_location: Point<i32, Logical>, +} + +impl PointerGrab<Smallvil> for MoveSurfaceGrab { + fn motion( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + _focus: Option<(WlSurface, Point<i32, Logical>)>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus + handle.motion(data, None, event); + + let delta = event.location - self.start_data.location; + let new_location = self.initial_window_location.to_f64() + delta; + data.space + .map_element(self.window.clone(), new_location.to_i32_round(), true); + } + + fn relative_motion( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + focus: Option<(WlSurface, Point<i32, Logical>)>, + event: &RelativeMotionEvent, + ) { + handle.relative_motion(data, focus, event); + } + + fn button( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + event: &ButtonEvent, + ) { + handle.button(data, event); + + // The button is a button code as defined in the + // Linux kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT. + const BTN_LEFT: u32 = 0x110; + + if !handle.current_pressed().contains(&BTN_LEFT) { + // No more buttons are pressed, release the grab. + handle.unset_grab(data, event.serial, event.time); + } + } + + fn axis( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + details: AxisFrame, + ) { + handle.axis(data, details) + } + + fn start_data(&self) -> &PointerGrabStartData<Smallvil> { + &self.start_data + } +} diff --git a/src/grabs/resize_grab.rs b/src/grabs/resize_grab.rs new file mode 100644 index 00000000..6a39cfcd --- /dev/null +++ b/src/grabs/resize_grab.rs @@ -0,0 +1,278 @@ +use crate::Smallvil; +use smithay::{ + desktop::{Space, Window}, + input::pointer::{ + AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, + PointerInnerHandle, RelativeMotionEvent, + }, + reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel, wayland_server::protocol::wl_surface::WlSurface, + }, + utils::{Logical, Point, Rectangle, Size}, + wayland::{compositor, shell::xdg::SurfaceCachedState}, +}; +use std::cell::RefCell; + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct ResizeEdge: u32 { + const TOP = 0b0001; + const BOTTOM = 0b0010; + const LEFT = 0b0100; + const RIGHT = 0b1000; + + const TOP_LEFT = Self::TOP.bits() | Self::LEFT.bits(); + const BOTTOM_LEFT = Self::BOTTOM.bits() | Self::LEFT.bits(); + + const TOP_RIGHT = Self::TOP.bits() | Self::RIGHT.bits(); + const BOTTOM_RIGHT = Self::BOTTOM.bits() | Self::RIGHT.bits(); + } +} + +impl From<xdg_toplevel::ResizeEdge> for ResizeEdge { + #[inline] + fn from(x: xdg_toplevel::ResizeEdge) -> Self { + Self::from_bits(x as u32).unwrap() + } +} + +pub struct ResizeSurfaceGrab { + start_data: PointerGrabStartData<Smallvil>, + window: Window, + + edges: ResizeEdge, + + initial_rect: Rectangle<i32, Logical>, + last_window_size: Size<i32, Logical>, +} + +impl ResizeSurfaceGrab { + pub fn start( + start_data: PointerGrabStartData<Smallvil>, + window: Window, + edges: ResizeEdge, + initial_window_rect: Rectangle<i32, Logical>, + ) -> Self { + let initial_rect = initial_window_rect; + + ResizeSurfaceState::with(window.toplevel().wl_surface(), |state| { + *state = ResizeSurfaceState::Resizing { edges, initial_rect }; + }); + + Self { + start_data, + window, + edges, + initial_rect, + last_window_size: initial_rect.size, + } + } +} + +impl PointerGrab<Smallvil> for ResizeSurfaceGrab { + fn motion( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + _focus: Option<(WlSurface, Point<i32, Logical>)>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus + handle.motion(data, None, event); + + let mut delta = event.location - self.start_data.location; + + let mut new_window_width = self.initial_rect.size.w; + let mut new_window_height = self.initial_rect.size.h; + + if self.edges.intersects(ResizeEdge::LEFT | ResizeEdge::RIGHT) { + if self.edges.intersects(ResizeEdge::LEFT) { + delta.x = -delta.x; + } + + new_window_width = (self.initial_rect.size.w as f64 + delta.x) as i32; + } + + if self.edges.intersects(ResizeEdge::TOP | ResizeEdge::BOTTOM) { + if self.edges.intersects(ResizeEdge::TOP) { + delta.y = -delta.y; + } + + new_window_height = (self.initial_rect.size.h as f64 + delta.y) as i32; + } + + let (min_size, max_size) = compositor::with_states(self.window.toplevel().wl_surface(), |states| { + let data = states.cached_state.current::<SurfaceCachedState>(); + (data.min_size, data.max_size) + }); + + let min_width = min_size.w.max(1); + let min_height = min_size.h.max(1); + + let max_width = (max_size.w == 0).then(i32::max_value).unwrap_or(max_size.w); + let max_height = (max_size.h == 0).then(i32::max_value).unwrap_or(max_size.h); + + self.last_window_size = Size::from(( + new_window_width.max(min_width).min(max_width), + new_window_height.max(min_height).min(max_height), + )); + + let xdg = self.window.toplevel(); + xdg.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Resizing); + state.size = Some(self.last_window_size); + }); + + xdg.send_pending_configure(); + } + + fn relative_motion( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + focus: Option<(WlSurface, Point<i32, Logical>)>, + event: &RelativeMotionEvent, + ) { + handle.relative_motion(data, focus, event); + } + + fn button( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + event: &ButtonEvent, + ) { + handle.button(data, event); + + // The button is a button code as defined in the + // Linux kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT. + const BTN_LEFT: u32 = 0x110; + + if !handle.current_pressed().contains(&BTN_LEFT) { + // No more buttons are pressed, release the grab. + handle.unset_grab(data, event.serial, event.time); + + let xdg = self.window.toplevel(); + xdg.with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Resizing); + state.size = Some(self.last_window_size); + }); + + xdg.send_pending_configure(); + + ResizeSurfaceState::with(xdg.wl_surface(), |state| { + *state = ResizeSurfaceState::WaitingForLastCommit { + edges: self.edges, + initial_rect: self.initial_rect, + }; + }); + } + } + + fn axis( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + details: AxisFrame, + ) { + handle.axis(data, details) + } + + fn start_data(&self) -> &PointerGrabStartData<Smallvil> { + &self.start_data + } +} + +/// State of the resize operation. +/// +/// It is stored inside of WlSurface, +/// and can be accessed using [`ResizeSurfaceState::with`] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] +enum ResizeSurfaceState { + #[default] + Idle, + Resizing { + edges: ResizeEdge, + /// The initial window size and location. + initial_rect: Rectangle<i32, Logical>, + }, + /// Resize is done, we are now waiting for last commit, to do the final move + WaitingForLastCommit { + edges: ResizeEdge, + /// The initial window size and location. + initial_rect: Rectangle<i32, Logical>, + }, +} + +impl ResizeSurfaceState { + fn with<F, T>(surface: &WlSurface, cb: F) -> T + where + F: FnOnce(&mut Self) -> T, + { + compositor::with_states(surface, |states| { + states.data_map.insert_if_missing(RefCell::<Self>::default); + let state = states.data_map.get::<RefCell<Self>>().unwrap(); + + cb(&mut state.borrow_mut()) + }) + } + + fn commit(&mut self) -> Option<(ResizeEdge, Rectangle<i32, Logical>)> { + match *self { + Self::Resizing { edges, initial_rect } => Some((edges, initial_rect)), + Self::WaitingForLastCommit { edges, initial_rect } => { + // The resize is done, let's go back to idle + *self = Self::Idle; + + Some((edges, initial_rect)) + } + Self::Idle => None, + } + } +} + +/// Should be called on `WlSurface::commit` +pub fn handle_commit(space: &mut Space<Window>, surface: &WlSurface) -> Option<()> { + let window = space + .elements() + .find(|w| w.toplevel().wl_surface() == surface) + .cloned()?; + + let mut window_loc = space.element_location(&window)?; + let geometry = window.geometry(); + + let new_loc: Point<Option<i32>, Logical> = ResizeSurfaceState::with(surface, |state| { + state + .commit() + .and_then(|(edges, initial_rect)| { + // If the window is being resized by top or left, its location must be adjusted + // accordingly. + edges.intersects(ResizeEdge::TOP_LEFT).then(|| { + let new_x = edges + .intersects(ResizeEdge::LEFT) + .then_some(initial_rect.loc.x + (initial_rect.size.w - geometry.size.w)); + + let new_y = edges + .intersects(ResizeEdge::TOP) + .then_some(initial_rect.loc.y + (initial_rect.size.h - geometry.size.h)); + + (new_x, new_y).into() + }) + }) + .unwrap_or_default() + }); + + if let Some(new_x) = new_loc.x { + window_loc.x = new_x; + } + if let Some(new_y) = new_loc.y { + window_loc.y = new_y; + } + + if new_loc.x.is_some() || new_loc.y.is_some() { + // If TOP or LEFT side of the window got resized, we have to move it + space.map_element(window, window_loc, false); + } + + Some(()) +} diff --git a/src/handlers/compositor.rs b/src/handlers/compositor.rs new file mode 100644 index 00000000..fcb0dc02 --- /dev/null +++ b/src/handlers/compositor.rs @@ -0,0 +1,57 @@ +use crate::{grabs::resize_grab, state::ClientState, Smallvil}; +use smithay::{ + backend::renderer::utils::on_commit_buffer_handler, + delegate_compositor, delegate_shm, + reexports::wayland_server::{ + protocol::{wl_buffer, wl_surface::WlSurface}, + Client, + }, + wayland::{ + buffer::BufferHandler, + compositor::{ + get_parent, is_sync_subsurface, CompositorClientState, CompositorHandler, CompositorState, + }, + shm::{ShmHandler, ShmState}, + }, +}; + +use super::xdg_shell; + +impl CompositorHandler for Smallvil { + fn compositor_state(&mut self) -> &mut CompositorState { + &mut self.compositor_state + } + + fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { + &client.get_data::<ClientState>().unwrap().compositor_state + } + + fn commit(&mut self, surface: &WlSurface) { + on_commit_buffer_handler::<Self>(surface); + if !is_sync_subsurface(surface) { + let mut root = surface.clone(); + while let Some(parent) = get_parent(&root) { + root = parent; + } + if let Some(window) = self.space.elements().find(|w| w.toplevel().wl_surface() == &root) { + window.on_commit(); + } + }; + + xdg_shell::handle_commit(&self.space, surface); + resize_grab::handle_commit(&mut self.space, surface); + } +} + +impl BufferHandler for Smallvil { + fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {} +} + +impl ShmHandler for Smallvil { + fn shm_state(&self) -> &ShmState { + &self.shm_state + } +} + +delegate_compositor!(Smallvil); +delegate_shm!(Smallvil); diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 00000000..bc2cd23f --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,54 @@ +mod compositor; +mod xdg_shell; + +use crate::Smallvil; + +// +// Wl Seat +// + +use smithay::input::{SeatHandler, SeatState}; +use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; +use smithay::wayland::data_device::{ClientDndGrabHandler, DataDeviceHandler, ServerDndGrabHandler}; +use smithay::{delegate_data_device, delegate_output, delegate_seat}; + +impl SeatHandler for Smallvil { + type KeyboardFocus = WlSurface; + type PointerFocus = WlSurface; + + fn seat_state(&mut self) -> &mut SeatState<Smallvil> { + &mut self.seat_state + } + + fn cursor_image( + &mut self, + _seat: &smithay::input::Seat<Self>, + _image: smithay::input::pointer::CursorImageStatus, + ) { + } + fn focus_changed(&mut self, _seat: &smithay::input::Seat<Self>, _focused: Option<&WlSurface>) {} +} + +delegate_seat!(Smallvil); + +// +// Wl Data Device +// + +impl DataDeviceHandler for Smallvil { + type SelectionUserData = (); + fn data_device_state(&self) -> &smithay::wayland::data_device::DataDeviceState { + &self.data_device_state + } +} + +impl ClientDndGrabHandler for Smallvil {} +impl ServerDndGrabHandler for Smallvil {} + +delegate_data_device!(Smallvil); + +// +// Wl Output & Xdg Output +// + +delegate_output!(Smallvil); diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs new file mode 100644 index 00000000..7351d3bf --- /dev/null +++ b/src/handlers/xdg_shell.rs @@ -0,0 +1,163 @@ +use smithay::{ + delegate_xdg_shell, + desktop::{Space, Window}, + input::{ + pointer::{Focus, GrabStartData as PointerGrabStartData}, + Seat, + }, + reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel, + wayland_server::{ + protocol::{wl_seat, wl_surface::WlSurface}, + Resource, + }, + }, + utils::{Rectangle, Serial}, + wayland::{ + compositor::with_states, + shell::xdg::{ + PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, + XdgToplevelSurfaceData, + }, + }, +}; + +use crate::{ + grabs::{MoveSurfaceGrab, ResizeSurfaceGrab}, + Smallvil, +}; + +impl XdgShellHandler for Smallvil { + fn xdg_shell_state(&mut self) -> &mut XdgShellState { + &mut self.xdg_shell_state + } + + fn new_toplevel(&mut self, surface: ToplevelSurface) { + let window = Window::new(surface); + self.space.map_element(window, (0, 0), false); + } + + fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) { + // TODO: Popup handling using PopupManager + } + + fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) { + let seat = Seat::from_resource(&seat).unwrap(); + + let wl_surface = surface.wl_surface(); + + if let Some(start_data) = check_grab(&seat, wl_surface, serial) { + let pointer = seat.get_pointer().unwrap(); + + let window = self + .space + .elements() + .find(|w| w.toplevel().wl_surface() == wl_surface) + .unwrap() + .clone(); + let initial_window_location = self.space.element_location(&window).unwrap(); + + let grab = MoveSurfaceGrab { + start_data, + window, + initial_window_location, + }; + + pointer.set_grab(self, grab, serial, Focus::Clear); + } + } + + fn resize_request( + &mut self, + surface: ToplevelSurface, + seat: wl_seat::WlSeat, + serial: Serial, + edges: xdg_toplevel::ResizeEdge, + ) { + let seat = Seat::from_resource(&seat).unwrap(); + + let wl_surface = surface.wl_surface(); + + if let Some(start_data) = check_grab(&seat, wl_surface, serial) { + let pointer = seat.get_pointer().unwrap(); + + let window = self + .space + .elements() + .find(|w| w.toplevel().wl_surface() == wl_surface) + .unwrap() + .clone(); + let initial_window_location = self.space.element_location(&window).unwrap(); + let initial_window_size = window.geometry().size; + + surface.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Resizing); + }); + + surface.send_pending_configure(); + + let grab = ResizeSurfaceGrab::start( + start_data, + window, + edges.into(), + Rectangle::from_loc_and_size(initial_window_location, initial_window_size), + ); + + pointer.set_grab(self, grab, serial, Focus::Clear); + } + } + + fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) { + // TODO popup grabs + } +} + +// Xdg Shell +delegate_xdg_shell!(Smallvil); + +fn check_grab( + seat: &Seat<Smallvil>, + surface: &WlSurface, + serial: Serial, +) -> Option<PointerGrabStartData<Smallvil>> { + let pointer = seat.get_pointer()?; + + // Check that this surface has a click grab. + if !pointer.has_grab(serial) { + return None; + } + + let start_data = pointer.grab_start_data()?; + + let (focus, _) = start_data.focus.as_ref()?; + // If the focus was for a different surface, ignore the request. + if !focus.id().same_client_as(&surface.id()) { + return None; + } + + Some(start_data) +} + +/// Should be called on `WlSurface::commit` +pub fn handle_commit(space: &Space<Window>, surface: &WlSurface) -> Option<()> { + let window = space + .elements() + .find(|w| w.toplevel().wl_surface() == surface) + .cloned()?; + + let initial_configure_sent = with_states(surface, |states| { + states + .data_map + .get::<XdgToplevelSurfaceData>() + .unwrap() + .lock() + .unwrap() + .initial_configure_sent + }); + + if !initial_configure_sent { + window.toplevel().send_configure(); + } + + Some(()) +} diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 00000000..0531c29b --- /dev/null +++ b/src/input.rs @@ -0,0 +1,131 @@ +use smithay::{ + backend::input::{ + AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent, + KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, + }, + input::{ + keyboard::FilterResult, + pointer::{AxisFrame, ButtonEvent, MotionEvent}, + }, + reexports::wayland_server::protocol::wl_surface::WlSurface, + utils::SERIAL_COUNTER, +}; + +use crate::state::Smallvil; + +impl Smallvil { + pub fn process_input_event<I: InputBackend>(&mut self, event: InputEvent<I>) { + match event { + InputEvent::Keyboard { event, .. } => { + let serial = SERIAL_COUNTER.next_serial(); + let time = Event::time_msec(&event); + + self.seat.get_keyboard().unwrap().input::<(), _>( + self, + event.key_code(), + event.state(), + serial, + time, + |_, _, _| FilterResult::Forward, + ); + } + InputEvent::PointerMotion { .. } => {} + InputEvent::PointerMotionAbsolute { event, .. } => { + let output = self.space.outputs().next().unwrap(); + + let output_geo = self.space.output_geometry(output).unwrap(); + + let pos = event.position_transformed(output_geo.size) + output_geo.loc.to_f64(); + + let serial = SERIAL_COUNTER.next_serial(); + + let pointer = self.seat.get_pointer().unwrap(); + + let under = self.surface_under_pointer(&pointer); + + pointer.motion( + self, + under, + &MotionEvent { + location: pos, + serial, + time: event.time_msec(), + }, + ); + } + InputEvent::PointerButton { event, .. } => { + let pointer = self.seat.get_pointer().unwrap(); + let keyboard = self.seat.get_keyboard().unwrap(); + + let serial = SERIAL_COUNTER.next_serial(); + + let button = event.button_code(); + + let button_state = event.state(); + + if ButtonState::Pressed == button_state && !pointer.is_grabbed() { + if let Some((window, _loc)) = self + .space + .element_under(pointer.current_location()) + .map(|(w, l)| (w.clone(), l)) + { + self.space.raise_element(&window, true); + keyboard.set_focus(self, Some(window.toplevel().wl_surface().clone()), serial); + self.space.elements().for_each(|window| { + window.toplevel().send_pending_configure(); + }); + } else { + self.space.elements().for_each(|window| { + window.set_activated(false); + window.toplevel().send_pending_configure(); + }); + keyboard.set_focus(self, Option::<WlSurface>::None, serial); + } + }; + + pointer.button( + self, + &ButtonEvent { + button, + state: button_state, + serial, + time: event.time_msec(), + }, + ); + } + InputEvent::PointerAxis { event, .. } => { + let source = event.source(); + + 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_msec()).source(source); + if horizontal_amount != 0.0 { + frame = frame.value(Axis::Horizontal, horizontal_amount); + if let Some(discrete) = horizontal_amount_discrete { + frame = frame.discrete(Axis::Horizontal, discrete as i32); + } + } else if source == AxisSource::Finger { + frame = frame.stop(Axis::Horizontal); + } + if vertical_amount != 0.0 { + frame = frame.value(Axis::Vertical, vertical_amount); + if let Some(discrete) = vertical_amount_discrete { + frame = frame.discrete(Axis::Vertical, discrete as i32); + } + } else if source == AxisSource::Finger { + frame = frame.stop(Axis::Vertical); + } + + self.seat.get_pointer().unwrap().axis(self, frame); + } + _ => {} + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..9d472a7d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,52 @@ +#![allow(irrefutable_let_patterns)] + +mod handlers; + +mod grabs; +mod input; +mod state; +mod winit; + +use smithay::reexports::{calloop::EventLoop, wayland_server::Display}; +pub use state::Smallvil; + +pub struct CalloopData { + state: Smallvil, + display: Display<Smallvil>, +} + +fn main() -> Result<(), Box<dyn std::error::Error>> { + if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() { + tracing_subscriber::fmt().with_env_filter(env_filter).init(); + } else { + tracing_subscriber::fmt().init(); + } + + let mut event_loop: EventLoop<CalloopData> = EventLoop::try_new()?; + + let mut display: Display<Smallvil> = Display::new()?; + let state = Smallvil::new(&mut event_loop, &mut display); + + let mut data = CalloopData { state, display }; + + crate::winit::init_winit(&mut event_loop, &mut data)?; + + let mut args = std::env::args().skip(1); + let flag = args.next(); + let arg = args.next(); + + match (flag.as_deref(), arg) { + (Some("-c") | Some("--command"), Some(command)) => { + std::process::Command::new(command).spawn().ok(); + } + _ => { + std::process::Command::new("weston-terminal").spawn().ok(); + } + } + + event_loop.run(None, &mut data, move |_| { + // Smallvil is running + })?; + + Ok(()) +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 00000000..a0405b77 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,164 @@ +use std::{ffi::OsString, os::unix::io::AsRawFd, sync::Arc}; + +use smithay::{ + desktop::{Space, Window, WindowSurfaceType}, + input::{pointer::PointerHandle, Seat, SeatState}, + reexports::{ + calloop::{generic::Generic, EventLoop, Interest, LoopSignal, Mode, PostAction}, + wayland_server::{ + backend::{ClientData, ClientId, DisconnectReason}, + protocol::wl_surface::WlSurface, + Display, + }, + }, + utils::{Logical, Point}, + wayland::{ + compositor::{CompositorClientState, CompositorState}, + data_device::DataDeviceState, + output::OutputManagerState, + shell::xdg::XdgShellState, + shm::ShmState, + socket::ListeningSocketSource, + }, +}; + +use crate::CalloopData; + +pub struct Smallvil { + pub start_time: std::time::Instant, + pub socket_name: OsString, + + pub space: Space<Window>, + pub loop_signal: LoopSignal, + + // Smithay State + pub compositor_state: CompositorState, + pub xdg_shell_state: XdgShellState, + pub shm_state: ShmState, + pub output_manager_state: OutputManagerState, + pub seat_state: SeatState<Smallvil>, + pub data_device_state: DataDeviceState, + + pub seat: Seat<Self>, +} + +impl Smallvil { + pub fn new(event_loop: &mut EventLoop<CalloopData>, display: &mut Display<Self>) -> Self { + let start_time = std::time::Instant::now(); + + let dh = display.handle(); + + let compositor_state = CompositorState::new::<Self>(&dh); + let xdg_shell_state = XdgShellState::new::<Self>(&dh); + let shm_state = ShmState::new::<Self>(&dh, vec![]); + let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&dh); + let mut seat_state = SeatState::new(); + let data_device_state = DataDeviceState::new::<Self>(&dh); + + // A seat is a group of keyboards, pointer and touch devices. + // A seat typically has a pointer and maintains a keyboard focus and a pointer focus. + let mut seat: Seat<Self> = seat_state.new_wl_seat(&dh, "winit"); + + // Notify clients that we have a keyboard, for the sake of the example we assume that keyboard is always present. + // You may want to track keyboard hot-plug in real compositor. + seat.add_keyboard(Default::default(), 200, 200).unwrap(); + + // Notify clients that we have a pointer (mouse) + // Here we assume that there is always pointer plugged in + seat.add_pointer(); + + // A space represents a two-dimensional plane. Windows and Outputs can be mapped onto it. + // + // Windows get a position and stacking order through mapping. + // Outputs become views of a part of the Space and can be rendered via Space::render_output. + let space = Space::default(); + + let socket_name = Self::init_wayland_listener(display, event_loop); + + // Get the loop signal, used to stop the event loop + let loop_signal = event_loop.get_signal(); + + Self { + start_time, + + space, + loop_signal, + socket_name, + + compositor_state, + xdg_shell_state, + shm_state, + output_manager_state, + seat_state, + data_device_state, + seat, + } + } + + fn init_wayland_listener( + display: &mut Display<Smallvil>, + event_loop: &mut EventLoop<CalloopData>, + ) -> OsString { + // Creates a new listening socket, automatically choosing the next available `wayland` socket name. + let listening_socket = ListeningSocketSource::new_auto().unwrap(); + + // Get the name of the listening socket. + // Clients will connect to this socket. + let socket_name = listening_socket.socket_name().to_os_string(); + + let handle = event_loop.handle(); + + event_loop + .handle() + .insert_source(listening_socket, move |client_stream, _, state| { + // Inside the callback, you should insert the client into the display. + // + // You may also associate some data with the client when inserting the client. + state + .display + .handle() + .insert_client(client_stream, Arc::new(ClientState::default())) + .unwrap(); + }) + .expect("Failed to init the wayland event source."); + + // You also need to add the display itself to the event loop, so that client events will be processed by wayland-server. + handle + .insert_source( + Generic::new( + display.backend().poll_fd().as_raw_fd(), + Interest::READ, + Mode::Level, + ), + |_, _, state| { + state.display.dispatch_clients(&mut state.state).unwrap(); + Ok(PostAction::Continue) + }, + ) + .unwrap(); + + socket_name + } + + pub fn surface_under_pointer( + &self, + pointer: &PointerHandle<Self>, + ) -> Option<(WlSurface, Point<i32, Logical>)> { + let pos = pointer.current_location(); + self.space.element_under(pos).and_then(|(window, location)| { + window + .surface_under(pos - location.to_f64(), WindowSurfaceType::ALL) + .map(|(s, p)| (s, p + location)) + }) + } +} + +#[derive(Default)] +pub struct ClientState { + pub compositor_state: CompositorClientState, |
