use log::error; use smithay_client_toolkit::{ reexports::{ client::{ protocol::{wl_pointer, wl_seat::WlSeat}, DispatchData, }, protocols::xdg_shell::client::xdg_toplevel::ResizeEdge, }, seat::pointer::ThemedPointer, window::FrameRequest, }; use crate::{ buttons::{ButtonKind, Buttons}, parts::DecorationPartKind, precise_location, theme::{BORDER_SIZE, HEADER_SIZE}, Inner, Location, }; pub(crate) struct PointerUserData { pub location: Location, current_surface: DecorationPartKind, position: (f64, f64), pub seat: WlSeat, last_click: Option, lpm_grab: Option, } impl PointerUserData { pub fn new(seat: WlSeat) -> Self { Self { location: Location::None, current_surface: DecorationPartKind::None, position: (0.0, 0.0), seat, last_click: None, lpm_grab: None, } } pub fn event( &mut self, event: wl_pointer::Event, inner: &mut Inner, buttons: &Buttons, pointer: &ThemedPointer, ddata: DispatchData<'_>, ) { use wl_pointer::Event; match event { Event::Enter { serial, surface, surface_x, surface_y, } => { self.location = precise_location( buttons, inner.parts.find_surface(&surface), inner.size.0, surface_x, surface_y, ); self.current_surface = inner.parts.find_decoration_part(&surface); self.position = (surface_x, surface_y); change_pointer(pointer, inner, self.location, Some(serial)) } Event::Leave { serial, .. } => { self.current_surface = DecorationPartKind::None; self.location = Location::None; change_pointer(pointer, inner, self.location, Some(serial)); (inner.implem)(FrameRequest::Refresh, 0, ddata); } Event::Motion { surface_x, surface_y, .. } => { self.position = (surface_x, surface_y); let newpos = precise_location(buttons, self.location, inner.size.0, surface_x, surface_y); if newpos != self.location { match (newpos, self.location) { (Location::Button(_), _) | (_, Location::Button(_)) => { // pointer movement involves a button, request refresh (inner.implem)(FrameRequest::Refresh, 0, ddata); } _ => (), } // we changed of part of the decoration, pointer image // may need to be changed self.location = newpos; change_pointer(pointer, inner, self.location, None) } } Event::Button { serial, button, state, .. } => { let request = if state == wl_pointer::ButtonState::Pressed { match button { // Left mouse button. 0x110 => lmb_press(self, inner.maximized, inner.resizable), // Right mouse button. 0x111 => rmb_press(self), _ => None, } } else { // Left mouse button. if button == 0x110 { lmb_release(self, inner.maximized) } else { None } }; if let Some(request) = request { (inner.implem)(request, serial, ddata); } } _ => {} } } } fn lmb_press( pointer_data: &mut PointerUserData, maximized: bool, resizable: bool, ) -> Option { match pointer_data.location { Location::Top if resizable => Some(FrameRequest::Resize( pointer_data.seat.clone(), ResizeEdge::Top, )), Location::TopLeft if resizable => Some(FrameRequest::Resize( pointer_data.seat.clone(), ResizeEdge::TopLeft, )), Location::Left if resizable => Some(FrameRequest::Resize( pointer_data.seat.clone(), ResizeEdge::Left, )), Location::BottomLeft if resizable => Some(FrameRequest::Resize( pointer_data.seat.clone(), ResizeEdge::BottomLeft, )), Location::Bottom if resizable => Some(FrameRequest::Resize( pointer_data.seat.clone(), ResizeEdge::Bottom, )), Location::BottomRight if resizable => Some(FrameRequest::Resize( pointer_data.seat.clone(), ResizeEdge::BottomRight, )), Location::Right if resizable => Some(FrameRequest::Resize( pointer_data.seat.clone(), ResizeEdge::Right, )), Location::TopRight if resizable => Some(FrameRequest::Resize( pointer_data.seat.clone(), ResizeEdge::TopRight, )), Location::Head => { let last_click = pointer_data.last_click.replace(std::time::Instant::now()); if let Some(last) = last_click { if last.elapsed() < std::time::Duration::from_millis(400) { pointer_data.last_click = None; if maximized { Some(FrameRequest::UnMaximize) } else { Some(FrameRequest::Maximize) } } else { Some(FrameRequest::Move(pointer_data.seat.clone())) } } else { Some(FrameRequest::Move(pointer_data.seat.clone())) } } Location::Button(btn) => { pointer_data.lpm_grab = Some(btn); None } _ => None, } } fn lmb_release(pointer_data: &mut PointerUserData, maximized: bool) -> Option { let lpm_grab = pointer_data.lpm_grab.take(); match pointer_data.location { Location::Button(btn) => { if lpm_grab == Some(btn) { let req = match btn { ButtonKind::Close => FrameRequest::Close, ButtonKind::Maximize => { if maximized { FrameRequest::UnMaximize } else { FrameRequest::Maximize } } ButtonKind::Minimize => FrameRequest::Minimize, }; Some(req) } else { None } } _ => None, } } fn rmb_press(pointer_data: &PointerUserData) -> Option { match pointer_data.location { Location::Head | Location::Button(_) => Some(FrameRequest::ShowMenu( pointer_data.seat.clone(), pointer_data.position.0 as i32 - BORDER_SIZE as i32, // We must offset it by header size for precise position. pointer_data.position.1 as i32 - (HEADER_SIZE as i32 + BORDER_SIZE as i32), )), _ => None, } } fn change_pointer(pointer: &ThemedPointer, inner: &Inner, location: Location, serial: Option) { // Prevent theming of the surface if it was requested. if !inner.theme_over_surface && location == Location::None { return; } let name = match location { // If we can't resize a frame we shouldn't show resize cursors. _ if !inner.resizable => "left_ptr", Location::Top => "top_side", Location::TopRight => "top_right_corner", Location::Right => "right_side", Location::BottomRight => "bottom_right_corner", Location::Bottom => "bottom_side", Location::BottomLeft => "bottom_left_corner", Location::Left => "left_side", Location::TopLeft => "top_left_corner", _ => "left_ptr", }; if pointer.set_cursor(name, serial).is_err() { error!("Failed to set cursor"); } }