diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..82f0bcf --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "pinnacle" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tracing = "0.1.37" +smithay = { git = "https://github.com/Smithay/smithay" } diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..dc85c99 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +error_on_line_overflow = true diff --git a/src/grab.rs b/src/grab.rs new file mode 100644 index 0000000..47dd219 --- /dev/null +++ b/src/grab.rs @@ -0,0 +1,2 @@ +pub mod move_grab; +pub mod resize_grab; diff --git a/src/grab/move_grab.rs b/src/grab/move_grab.rs new file mode 100644 index 0000000..4adc717 --- /dev/null +++ b/src/grab/move_grab.rs @@ -0,0 +1,82 @@ +use smithay::{ + desktop::Window, + // NOTE: maybe alias this to PointerGrabStartData because there's another GrabStartData in + // | input::keyboard + input::{ + pointer::PointerGrab, + pointer::{ + AxisFrame, ButtonEvent, GrabStartData, MotionEvent, PointerInnerHandle, + RelativeMotionEvent, + }, + SeatHandler, + }, + utils::{IsAlive, Logical, Point}, +}; + +use crate::State; + +pub struct MoveSurfaceGrab { + pub start_data: GrabStartData, + pub window: Window, + pub initial_window_loc: Point, +} + +impl PointerGrab for MoveSurfaceGrab { + fn motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(::PointerFocus, Point)>, + event: &MotionEvent, + ) { + handle.motion(data, None, event); + + if !self.window.alive() { + handle.unset_grab(data, event.serial, event.time); + return; + } + + let delta = event.location - self.start_data.location; + let new_loc = self.initial_window_loc.to_f64() + delta; + data.space + .map_element(self.window.clone(), new_loc.to_i32_round(), true); + } + + fn relative_motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + focus: Option<(::PointerFocus, Point)>, + event: &RelativeMotionEvent, + ) { + handle.relative_motion(data, focus, event); + } + + fn button( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &ButtonEvent, + ) { + handle.button(data, event); + + const BUTTON_LEFT: u32 = 0x110; + + if !handle.current_pressed().contains(&BUTTON_LEFT) { + handle.unset_grab(data, event.serial, event.time); + } + } + + fn axis( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + handle.axis(data, details); + } + + fn start_data(&self) -> &GrabStartData { + &self.start_data + } +} diff --git a/src/grab/resize_grab.rs b/src/grab/resize_grab.rs new file mode 100644 index 0000000..0dc33d4 --- /dev/null +++ b/src/grab/resize_grab.rs @@ -0,0 +1,256 @@ +use smithay::{ + desktop::{Space, Window}, + input::{ + pointer::{AxisFrame, ButtonEvent, GrabStartData, PointerGrab, PointerInnerHandle}, + SeatHandler, + }, + reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge}, + wayland_server::protocol::wl_surface::WlSurface, + }, + utils::{IsAlive, Logical, Point, Rectangle, Size}, + wayland::shell::xdg::SurfaceCachedState, +}; + +use crate::{window::SurfaceState, State}; + +pub struct ResizeSurfaceGrab { + start_data: GrabStartData, + window: Window, + + edges: ResizeEdge, + + initial_window_rect: Rectangle, + last_window_size: Size, + + button_used: u32, +} + +impl ResizeSurfaceGrab { + pub fn start( + start_data: GrabStartData, + window: Window, + edges: ResizeEdge, + initial_window_rect: Rectangle, + button_used: u32, + ) -> Self { + ResizeSurfaceState::with_state(window.toplevel().wl_surface(), |state| { + *state = ResizeSurfaceState::Resizing { + edges, + initial_window_rect, + }; + }); + + Self { + start_data, + window, + edges, + initial_window_rect, + last_window_size: initial_window_rect.size, + button_used, + } + } +} + +impl PointerGrab for ResizeSurfaceGrab { + fn motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(::PointerFocus, Point)>, + event: &smithay::input::pointer::MotionEvent, + ) { + handle.motion(data, None, event); + + if !self.window.alive() { + handle.unset_grab(data, event.serial, event.time); + return; + } + + let delta = (event.location - self.start_data.location).to_i32_round::(); + + let mut new_window_width = self.initial_window_rect.size.w; + let mut new_window_height = self.initial_window_rect.size.h; + + if let ResizeEdge::Left | ResizeEdge::TopLeft | ResizeEdge::BottomLeft = self.edges { + new_window_width = self.initial_window_rect.size.w - delta.x; + } + if let ResizeEdge::Right | ResizeEdge::TopRight | ResizeEdge::BottomRight = self.edges { + new_window_width = self.initial_window_rect.size.w + delta.x; + } + if let ResizeEdge::Top | ResizeEdge::TopRight | ResizeEdge::TopLeft = self.edges { + new_window_height = self.initial_window_rect.size.h - delta.y; + } + if let ResizeEdge::Bottom | ResizeEdge::BottomRight | ResizeEdge::BottomLeft = self.edges { + new_window_height = self.initial_window_rect.size.h + delta.y; + } + + let (min_size, max_size) = smithay::wayland::compositor::with_states( + self.window.toplevel().wl_surface(), + |states| { + let data = states.cached_state.current::(); + (data.min_size, data.max_size) + }, + ); + + let min_width = i32::min(1, min_size.w); + let min_height = i32::min(1, min_size.h); + + let max_width = if max_size.w != 0 { + max_size.w + } else { + i32::MAX + }; + let max_height = if max_size.h != 0 { + max_size.h + } else { + i32::MAX + }; + + self.last_window_size = Size::from(( + new_window_width.clamp(min_width, max_width), + new_window_height.clamp(min_height, max_height), + )); + + let toplevel_surface = self.window.toplevel(); + + toplevel_surface.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Resizing); + state.size = Some(self.last_window_size); + }); + + toplevel_surface.send_pending_configure(); + } + + fn relative_motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + focus: Option<(::PointerFocus, Point)>, + event: &smithay::input::pointer::RelativeMotionEvent, + ) { + handle.relative_motion(data, focus, event); + } + + fn button( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &ButtonEvent, + ) { + handle.button(data, event); + + if !handle.current_pressed().contains(&self.button_used) { + handle.unset_grab(data, event.serial, event.time); + + let toplevel_surface = self.window.toplevel(); + toplevel_surface.with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Resizing); + state.size = Some(self.last_window_size); + }); + + toplevel_surface.send_pending_configure(); + + ResizeSurfaceState::with_state(toplevel_surface.wl_surface(), |state| { + *state = ResizeSurfaceState::WaitingForLastCommit { + edges: self.edges, + initial_window_rect: self.initial_window_rect, + }; + }); + } + } + + fn axis( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + handle.axis(data, details); + } + + fn start_data(&self) -> &GrabStartData { + &self.start_data + } +} + +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] +enum ResizeSurfaceState { + #[default] + Idle, + Resizing { + edges: ResizeEdge, + initial_window_rect: Rectangle, + }, + WaitingForLastCommit { + edges: ResizeEdge, + initial_window_rect: Rectangle, + }, +} + +impl ResizeSurfaceState { + fn commit(&mut self) -> Option<(ResizeEdge, Rectangle)> { + match *self { + Self::Idle => None, + Self::Resizing { + edges, + initial_window_rect, + } => Some((edges, initial_window_rect)), + Self::WaitingForLastCommit { + edges, + initial_window_rect, + } => { + *self = Self::Idle; + Some((edges, initial_window_rect)) + } + } + } +} + +impl SurfaceState for ResizeSurfaceState {} + +pub fn handle_commit(space: &mut Space, 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, Logical> = ResizeSurfaceState::with_state(surface, |state| { + state + .commit() + .map(|(edges, initial_window_rect)| { + let mut new_x: Option = None; + let mut new_y: Option = None; + if let ResizeEdge::Left | ResizeEdge::TopLeft | ResizeEdge::BottomLeft = edges { + new_x = Some( + initial_window_rect.loc.x + (initial_window_rect.size.w - geometry.size.w), + ); + } + if let ResizeEdge::Top | ResizeEdge::TopLeft | ResizeEdge::TopRight = edges { + new_y = Some( + initial_window_rect.loc.y + (initial_window_rect.size.h - geometry.size.h), + ); + } + + (new_x, new_y) + }) + .unwrap_or_default() + .into() + }); + + 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() { + space.map_element(window, window_loc, false); + } + + Some(()) +} diff --git a/src/handlers.rs b/src/handlers.rs new file mode 100644 index 0000000..b2dfe04 --- /dev/null +++ b/src/handlers.rs @@ -0,0 +1,169 @@ +use smithay::{ + backend::renderer::utils, + delegate_compositor, delegate_data_device, delegate_output, delegate_seat, delegate_shm, + delegate_xdg_shell, + desktop::Window, + input::{pointer::CursorImageStatus, Seat, SeatHandler, SeatState}, + reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, + wayland_server::{ + protocol::{wl_buffer::WlBuffer, wl_seat::WlSeat, wl_surface::WlSurface}, + Client, + }, + }, + wayland::{ + buffer::BufferHandler, + compositor::{self, CompositorClientState, CompositorHandler, CompositorState}, + data_device::{ + ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler, + }, + shell::xdg::{ + PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, + XdgToplevelSurfaceData, + }, + shm::{ShmHandler, ShmState}, + }, +}; + +use crate::{ClientState, State}; + +impl BufferHandler for State { + fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {} +} + +impl CompositorHandler for State { + fn compositor_state(&mut self) -> &mut CompositorState { + &mut self.compositor_state + } + + fn commit(&mut self, surface: &WlSurface) { + utils::on_commit_buffer_handler::(surface); + + if let Some(window) = self + .space + .elements() + .find(|w| w.toplevel().wl_surface() == surface) + .cloned() + { + // TODO: from smallvil: check if subsurfaces are synced then do on_commit or something + window.on_commit(); + + let initial_configure_sent = compositor::with_states(surface, |states| { + states + .data_map + .get::() + .unwrap() + .lock() + .unwrap() + .initial_configure_sent + }); + + if !initial_configure_sent { + window.toplevel().send_configure(); + } + } + + crate::grab::resize_grab::handle_commit(&mut self.space, surface); + } + + fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { + &client.get_data::().unwrap().compositor_state + } +} +delegate_compositor!(State); + +impl ClientDndGrabHandler for State {} +impl ServerDndGrabHandler for State {} + +impl DataDeviceHandler for State { + fn data_device_state(&self) -> &DataDeviceState { + &self.data_device_state + } +} +delegate_data_device!(State); + +impl SeatHandler for State { + type KeyboardFocus = WlSurface; + type PointerFocus = WlSurface; + + fn seat_state(&mut self) -> &mut SeatState { + &mut self.seat_state + } + + fn cursor_image(&mut self, _seat: &smithay::input::Seat, _image: CursorImageStatus) { + self.cursor_status = _image; + } + + fn focus_changed( + &mut self, + _seat: &smithay::input::Seat, + _focused: Option<&Self::KeyboardFocus>, + ) { + } +} +delegate_seat!(State); + +impl ShmHandler for State { + fn shm_state(&self) -> &ShmState { + &self.shm_state + } +} +delegate_shm!(State); + +impl XdgShellHandler for State { + 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, (50, 50), true); + + // TODO: refresh all window geometries + } + + fn toplevel_destroyed(&mut self, surface: ToplevelSurface) { + // TODO: refresh geometries + } + + fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {} + + fn move_request( + &mut self, + surface: ToplevelSurface, + seat: WlSeat, + serial: smithay::utils::Serial, + ) { + crate::xdg::request::move_request( + self, + &surface, + &Seat::from_resource(&seat).unwrap(), + serial, + ); + } + + fn resize_request( + &mut self, + surface: ToplevelSurface, + seat: WlSeat, + serial: smithay::utils::Serial, + edges: ResizeEdge, + ) { + const BUTTON_LEFT: u32 = 0x110; + crate::xdg::request::resize_request( + self, + &surface, + &Seat::from_resource(&seat).unwrap(), + serial, + edges, + BUTTON_LEFT, + ); + } + + fn grab(&mut self, surface: PopupSurface, seat: WlSeat, serial: smithay::utils::Serial) {} + + // TODO: impl the rest of the fns in XdgShellHandler +} +delegate_xdg_shell!(State); + +delegate_output!(State); diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..bd34fdf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,492 @@ +mod grab; +mod handlers; +mod pointer; +mod window; +mod xdg; + +use std::{error::Error, os::fd::AsRawFd, sync::Arc, time::Duration}; + +use smithay::{ + backend::{ + input::{ + AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputEvent, KeyState, + KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, + }, + renderer::{ + damage::OutputDamageTracker, element::surface::WaylandSurfaceRenderElement, + gles::GlesRenderer, + }, + winit::{WinitError, WinitEvent}, + }, + desktop::{space, Space, Window, WindowSurfaceType}, + input::{ + keyboard::{keysyms, FilterResult}, + pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent}, + SeatState, + }, + output::{Output, Subpixel}, + reexports::{ + calloop::{ + generic::Generic, + timer::{TimeoutAction, Timer}, + EventLoop, Interest, LoopHandle, LoopSignal, Mode, PostAction, + }, + wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, + wayland_server::{ + backend::{ClientData, ClientId, DisconnectReason}, + Display, + }, + }, + utils::{Clock, Logical, Monotonic, Physical, Point, Scale, Transform, SERIAL_COUNTER}, + wayland::{ + compositor::{CompositorClientState, CompositorState}, + data_device::DataDeviceState, + output::OutputManagerState, + shell::xdg::XdgShellState, + shm::ShmState, + socket::ListeningSocketSource, + }, +}; + +fn main() -> Result<(), Box> { + let mut event_loop: EventLoop = EventLoop::try_new()?; + + let mut display: Display = Display::new()?; + + let socket = ListeningSocketSource::new_auto()?; + let socket_name = socket.socket_name().to_os_string(); + + let evt_loop_handle = event_loop.handle(); + + evt_loop_handle.insert_source(socket, |stream, _metadata, data| { + data.display + .handle() + .insert_client(stream, Arc::new(ClientState::default())) + .unwrap(); + })?; + + evt_loop_handle.insert_source( + Generic::new( + display.backend().poll_fd().as_raw_fd(), + Interest::READ, + Mode::Level, + ), + |_readiness, _metadata, data| { + data.display.dispatch_clients(&mut data.state)?; + Ok(PostAction::Continue) + }, + )?; + + let display_handle = display.handle(); + + let mut seat_state = SeatState::::new(); + let mut seat = seat_state.new_wl_seat(&display_handle, "seat1"); + + seat.add_keyboard(Default::default(), 500, 50)?; + seat.add_pointer(); + + let state = State { + loop_signal: event_loop.get_signal(), + loop_handle: event_loop.handle(), + clock: Clock::::new()?, + compositor_state: CompositorState::new::(&display_handle), + data_device_state: DataDeviceState::new::(&display_handle), + seat_state, + shm_state: ShmState::new::(&display_handle, Vec::new()), + space: Space::::default(), + cursor_status: CursorImageStatus::Default, + pointer_location: (0.0, 0.0).into(), + output_manager_state: OutputManagerState::new_with_xdg_output::(&display_handle), + xdg_shell_state: XdgShellState::new::(&display_handle), + + move_mode: false, + }; + + let mut data = Data { display, state }; + + let (mut winit_backend, mut winit_evt_loop) = smithay::backend::winit::init::()?; + + let mode = smithay::output::Mode { + size: winit_backend.window_size().physical_size, + refresh: 60_000, + }; + + let physical_properties = smithay::output::PhysicalProperties { + size: (0, 0).into(), + subpixel: Subpixel::Unknown, + make: "Comp make".to_string(), + model: "Comp model".to_string(), + }; + + let output = Output::new("27GL83A".to_string(), physical_properties); + + output.create_global::(&display_handle); + + output.change_current_state( + Some(mode), + Some(Transform::Flipped180), + None, + Some((0, 0).into()), + ); + + output.set_preferred(mode); + + data.state.space.map_output(&output, (0, 0)); + + std::env::set_var("WAYLAND_DISPLAY", socket_name); + + let start_time = std::time::Instant::now(); + let timer = Timer::immediate(); + + let mut damage_tracker = OutputDamageTracker::from_output(&output); + + // TODO: pointer + evt_loop_handle.insert_source(timer, move |_instant, _metadata, data| { + let display = &mut data.display; + let state = &mut data.state; + + let result = winit_evt_loop.dispatch_new_events(|event| { + match event { + WinitEvent::Resized { + size, + scale_factor: _, + } => { + output.change_current_state( + Some(smithay::output::Mode { + size, + refresh: 60_000, + }), + None, + None, + None, + ); + } + WinitEvent::Focus(_) => {} + WinitEvent::Input(input_evt) => match input_evt { + // TODO: extract input events + // | into separate function + + // InputEvent::DeviceAdded { device } => todo!(), + // InputEvent::DeviceRemoved { device } => todo!(), + InputEvent::Keyboard { event } => { + let serial = SERIAL_COUNTER.next_serial(); + let time = event.time_msec(); + let press_state = event.state(); + let mut move_mode = false; + let action = seat.get_keyboard().unwrap().input( + state, + event.key_code(), + press_state, + serial, + time, + |_a, _modifiers, keysym| { + if press_state == KeyState::Pressed + && keysym.modified_sym() == keysyms::KEY_L + { + FilterResult::Intercept(1) + } else if keysym.modified_sym() == keysyms::KEY_Control_L { + match press_state { + KeyState::Pressed => { + move_mode = true; + } + KeyState::Released => { + move_mode = false; + } + } + FilterResult::Forward + } else { + FilterResult::Forward + } + }, + ); + + state.move_mode = move_mode; + + if action == Some(1) { + std::process::Command::new("alacritty").spawn().unwrap(); + } + } + InputEvent::PointerMotion { event } => {} + InputEvent::PointerMotionAbsolute { event } => { + let output = state.space.outputs().next().unwrap(); + let output_geo = state.space.output_geometry(output).unwrap(); + let pointer_loc = + event.position_transformed(output_geo.size) + output_geo.loc.to_f64(); + let serial = SERIAL_COUNTER.next_serial(); + let pointer = seat.get_pointer().unwrap(); + + let surface_under_pointer = state + .space + .element_under(pointer_loc) + .and_then(|(window, location)| { + window + .surface_under( + pointer_loc - location.to_f64(), + WindowSurfaceType::ALL, + ) + .map(|(s, p)| (s, p + location)) + }); + + pointer.motion( + state, + surface_under_pointer, + &MotionEvent { + location: pointer_loc, + serial, + time: event.time_msec(), + }, + ); + } + InputEvent::PointerButton { event } => { + let pointer = seat.get_pointer().unwrap(); + let keyboard = seat.get_keyboard().unwrap(); + + // A serial is a number sent with a event that is sent back to the + // server by the clients in further requests. This allows the server to + // keep track of which event caused which requests. It is an AtomicU32 + // that increments when next_serial is called. + let serial = SERIAL_COUNTER.next_serial(); + + // Returns which button on the pointer was used. + let button = event.button_code(); + + // The state, either released or pressed. + let button_state = event.state(); + + let pointer_loc = pointer.current_location(); + + // If the button was clicked, focus on the window below if exists, else + // unfocus on windows. + if ButtonState::Pressed == button_state { + if let Some((window, window_loc)) = state + .space + .element_under(pointer_loc) + .map(|(w, l)| (w.clone(), l)) + { + const BUTTON_LEFT: u32 = 0x110; + const BUTTON_RIGHT: u32 = 0x111; + if state.move_mode { + if event.button_code() == BUTTON_LEFT { + // BTN_RIGHT + crate::xdg::request::move_request_force( + state, + window.toplevel(), + &seat, + serial, + ); + return; // TODO: kinda ugly return here + } else if event.button_code() == BUTTON_RIGHT { + let window_geometry = window.geometry(); + let window_x = window_loc.x as f64; + let window_y = window_loc.y as f64; + let window_width = window_geometry.size.w as f64; + let window_height = window_geometry.size.h as f64; + let half_width = window_x + window_width / 2.0; + let half_height = window_y + window_height / 2.0; + let full_width = window_x + window_width; + let full_height = window_y + window_height; + + println!( + "window loc: {}, {} | window size: {}, {}", + window_x, window_y, window_width, window_height + ); + + let edges = match pointer_loc { + Point { x, y, .. } + if (window_x..=half_width).contains(&x) + && (window_y..=half_height).contains(&y) => + { + ResizeEdge::TopLeft + } + Point { x, y, .. } + if (half_width..=full_width).contains(&x) + && (window_y..=half_height).contains(&y) => + { + ResizeEdge::TopRight + } + Point { x, y, .. } + if (window_x..=half_width).contains(&x) + && (half_height..=full_height).contains(&y) => + { + ResizeEdge::BottomLeft + } + Point { x, y, .. } + if (half_width..=full_width).contains(&x) + && (half_height..=full_height).contains(&y) => + { + ResizeEdge::BottomRight + } + _ => ResizeEdge::None, + }; + + crate::xdg::request::resize_request_force( + state, + window.toplevel(), + &seat, + serial, + edges, + BUTTON_RIGHT, + ); + } + } else { + // Move window to top of stack. + state.space.raise_element(&window, true); + + // Focus on window. + keyboard.set_focus( + state, + Some(window.toplevel().wl_surface().clone()), + serial, + ); + state.space.elements().for_each(|window| { + window.toplevel().send_configure(); + }); + } + } else { + state.space.elements().for_each(|window| { + window.set_activated(false); + window.toplevel().send_configure(); + }); + keyboard.set_focus(state, None, serial); + } + }; + + // Send the button event to the client. + pointer.button( + state, + &ButtonEvent { + button, + state: button_state, + serial, + time: event.time_msec(), + }, + ); + } + InputEvent::PointerAxis { event } => { + let pointer = seat.get_pointer().unwrap(); + + 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); + } + + println!("axisframe: {:?}", frame); + pointer.axis(state, frame); + } + // TODO: rest of the InputEvents + _ => (), + }, + WinitEvent::Refresh => {} + } + }); + + match result { + Ok(_) => {} + Err(WinitError::WindowClosed) => { + state.loop_signal.stop(); + } + }; + + winit_backend.bind().unwrap(); + + let scale = Scale::from(output.current_scale().fractional_scale()); + let cursor_pos = state.pointer_location; + let _cursor_pos_scaled: Point = cursor_pos.to_physical(scale).to_i32_round(); + + space::render_output::<_, WaylandSurfaceRenderElement, _, _>( + &output, + winit_backend.renderer(), + 1.0, + 0, + [&state.space], + &[], + &mut damage_tracker, + [0.1, 0.1, 0.1, 1.0], + ) + .unwrap(); + + winit_backend.submit(None).unwrap(); + + state.space.elements().for_each(|window| { + window.send_frame( + &output, + start_time.elapsed(), + Some(Duration::ZERO), + |_, _| Some(output.clone()), + ) + }); + + state.space.refresh(); + + display.flush_clients().unwrap(); + + TimeoutAction::ToDuration(Duration::from_millis(16)) + })?; + + event_loop.run(None, &mut data, |_data| {})?; + + Ok(()) +} + +pub struct State { + pub loop_signal: LoopSignal, + pub loop_handle: LoopHandle<'static, Data>, + pub clock: Clock, + pub compositor_state: CompositorState, + pub data_device_state: DataDeviceState, + pub seat_state: SeatState, + pub shm_state: ShmState, + pub space: Space, + pub cursor_status: CursorImageStatus, + pub pointer_location: Point, + pub output_manager_state: OutputManagerState, + pub xdg_shell_state: XdgShellState, + + pub move_mode: bool, +} + +pub struct Data { + pub display: Display, + pub state: State, +} + +#[derive(Default)] +struct ClientState { + pub compositor_state: CompositorClientState, +} +impl ClientData for ClientState { + fn initialized(&self, _client_id: ClientId) {} + + fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {} + + // fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {} +} diff --git a/src/pointer.rs b/src/pointer.rs new file mode 100644 index 0000000..5afa7df --- /dev/null +++ b/src/pointer.rs @@ -0,0 +1,35 @@ +use smithay::{ + input::{ + pointer::{GrabStartData, PointerHandle}, + SeatHandler, + }, + reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource}, + utils::Serial, +}; + +/// Returns the [GrabStartData] from a pointer grab, if any. +pub fn pointer_grab_start_data( + pointer: &PointerHandle, + surface: &WlSurface, + serial: Serial, +) -> Option> +where + S: SeatHandler + 'static, +{ + println!("start of pointer_grab_start_data"); + if !pointer.has_grab(serial) { + println!("pointer doesn't have grab"); + return None; + } + + let start_data = pointer.grab_start_data()?; + + let (focus_surface, _point) = start_data.focus.as_ref()?; + + if !focus_surface.id().same_client_as(&surface.id()) { + println!("surface isn't the same"); + return None; + } + + Some(start_data) +} diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 0000000..d9217ac --- /dev/null +++ b/src/window.rs @@ -0,0 +1,58 @@ +use std::cell::RefCell; + +use smithay::{reexports::wayland_server::protocol::wl_surface::WlSurface, wayland::compositor}; + +use crate::State; + +use self::window_state::{Float, WindowState}; + +pub mod window_state; + +pub trait SurfaceState: Default + 'static { + fn with_state(wl_surface: &WlSurface, function: F) -> T + where + F: FnOnce(&mut Self) -> T, + { + compositor::with_states(wl_surface, |states| { + states.data_map.insert_if_missing(RefCell::::default); + let state = states.data_map.get::>().unwrap(); + + function(&mut state.borrow_mut()) + }) + } +} + +pub fn toggle_floating(state: &mut State, wl_surface: &WlSurface) { + WindowState::with_state(wl_surface, |window_state| { + let window = state + .space + .elements() + .find(|w| w.toplevel().wl_surface() == wl_surface) + .unwrap() + .clone(); + match window_state.floating { + Float::NotFloating(prev_loc_and_size) => { + if let Some((prev_loc, prev_size)) = prev_loc_and_size { + window.toplevel().with_pending_state(|state| { + state.size = Some(prev_size); + }); + + window.toplevel().send_pending_configure(); + + state.space.map_element(window, prev_loc, false); // TODO: should it activate? + } + + window_state.floating = Float::Floating + } + Float::Floating => { + // TODO: recompute all non-floating window positions + + window_state.floating = Float::NotFloating(Some(( + state.space.element_location(&window).unwrap(), // We get the location this way + // because window.geometry().loc doesn't seem to be the actual location + window.geometry().size, + ))); + } + } + }) +} diff --git a/src/window/window_state.rs b/src/window/window_state.rs new file mode 100644 index 0000000..8d46dd3 --- /dev/null +++ b/src/window/window_state.rs @@ -0,0 +1,30 @@ +use smithay::utils::{Logical, Point, Size}; + +use super::SurfaceState; + +pub struct WindowState { + pub floating: Float, +} + +pub enum Float { + NotFloating(Option<(Point, Size)>), + /// An [Option] of a tuple of the previous location and previous size of the window + Floating, +} + +impl Default for WindowState { + fn default() -> Self { + Self::new() // TODO: maybe actual defaults + } +} + +impl WindowState { + pub fn new() -> Self { + Self { + floating: Float::NotFloating(None), // TODO: get this from a config file instead of + // | hardcoding + } + } +} + +impl SurfaceState for WindowState {} diff --git a/src/xdg.rs b/src/xdg.rs new file mode 100644 index 0000000..be9378d --- /dev/null +++ b/src/xdg.rs @@ -0,0 +1 @@ +pub mod request; diff --git a/src/xdg/request.rs b/src/xdg/request.rs new file mode 100644 index 0000000..9d9ea2e --- /dev/null +++ b/src/xdg/request.rs @@ -0,0 +1,174 @@ +use smithay::{ + input::{pointer::Focus, Seat}, + reexports::wayland_protocols::xdg::shell::server::xdg_toplevel, + utils::Rectangle, + wayland::shell::xdg::ToplevelSurface, +}; + +use crate::{ + grab::{move_grab::MoveSurfaceGrab, resize_grab::ResizeSurfaceGrab}, + State, +}; + +pub fn move_request( + state: &mut State, + surface: &ToplevelSurface, + seat: &Seat, + serial: smithay::utils::Serial, +) { + println!("move_request started"); + + let wl_surface = surface.wl_surface(); + + let pointer = seat.get_pointer().unwrap(); + if let Some(start_data) = crate::pointer::pointer_grab_start_data(&pointer, wl_surface, serial) + { + let window = state + .space + .elements() + .find(|w| w.toplevel().wl_surface() == wl_surface) + .unwrap() + .clone(); + + let initial_window_loc = state.space.element_location(&window).unwrap(); + + let grab = MoveSurfaceGrab { + start_data, + window, + initial_window_loc, + }; + + pointer.set_grab(state, grab, serial, Focus::Clear); + } else { + println!("no grab start data"); + } +} + +// TODO: see how this interacts with drag and drop and other grabs +pub fn move_request_force( + state: &mut State, + surface: &ToplevelSurface, + seat: &Seat, + serial: smithay::utils::Serial, +) { + println!("move_request_force started"); + + let wl_surface = surface.wl_surface(); + + let pointer = seat.get_pointer().unwrap(); + let window = state + .space + .elements() + .find(|w| w.toplevel().wl_surface() == wl_surface) + .unwrap() + .clone(); + + let initial_window_loc = state.space.element_location(&window).unwrap(); + + let start_data = smithay::input::pointer::GrabStartData { + focus: pointer + .current_focus() + .map(|focus| (focus, initial_window_loc)), + button: 0x110, + location: pointer.current_location(), + }; + + let grab = MoveSurfaceGrab { + start_data, + window, + initial_window_loc, + }; + + pointer.set_grab(state, grab, serial, Focus::Clear); +} + +pub fn resize_request( + state: &mut State, + surface: &ToplevelSurface, + seat: &Seat, + serial: smithay::utils::Serial, + edges: xdg_toplevel::ResizeEdge, + button_used: u32, +) { + let wl_surface = surface.wl_surface(); + + let pointer = seat.get_pointer().unwrap(); + + if let Some(start_data) = crate::pointer::pointer_grab_start_data(&pointer, wl_surface, serial) + { + let window = state + .space + .elements() + .find(|w| w.toplevel().wl_surface() == wl_surface) + .unwrap() + .clone(); // TODO: move this search into its own function + + let initial_window_loc = state.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, + Rectangle::from_loc_and_size(initial_window_loc, initial_window_size), + button_used, + ); + + pointer.set_grab(state, grab, serial, Focus::Clear); + } +} + +pub fn resize_request_force( + state: &mut State, + surface: &ToplevelSurface, + seat: &Seat, + serial: smithay::utils::Serial, + edges: xdg_toplevel::ResizeEdge, + button_used: u32, +) { + println!("resize_request_force started with edges {:?}", edges); + let wl_surface = surface.wl_surface(); + + let pointer = seat.get_pointer().unwrap(); + + let window = state + .space + .elements() + .find(|w| w.toplevel().wl_surface() == wl_surface) + .unwrap() + .clone(); // TODO: move this search into its own function + + let initial_window_loc = state.space.element_location(&window).unwrap(); + let initial_window_size = window.geometry().size; + + surface.with_pending_state(|state| { + println!("setting xdg state to Resizing"); + state.states.set(xdg_toplevel::State::Resizing); + }); + + surface.send_pending_configure(); + + let start_data = smithay::input::pointer::GrabStartData { + focus: pointer + .current_focus() + .map(|focus| (focus, initial_window_loc)), + button: button_used, + location: pointer.current_location(), + }; + + let grab = ResizeSurfaceGrab::start( + start_data, + window, + edges, + Rectangle::from_loc_and_size(initial_window_loc, initial_window_size), + button_used, + ); + + pointer.set_grab(state, grab, serial, Focus::Clear); +}