diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index b1a23f96..dc98ae4a 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -14,7 +14,7 @@ use smithay::{ }, }, input::{ - pointer::{CursorImageAttributes, CursorImageStatus}, + pointer::{CursorIcon, CursorImageAttributes, CursorImageStatus}, Seat, }, reexports::wayland_server::protocol::wl_surface, @@ -24,7 +24,7 @@ use smithay::{ }, wayland::compositor::{get_role, with_states}, }; -use std::{collections::HashMap, io::Read, sync::Mutex, time::Duration}; +use std::{collections::HashMap, io::Read, sync::Mutex}; use tracing::warn; use xcursor::{ parser::{parse_xcursor, Image}, @@ -33,42 +33,6 @@ use xcursor::{ static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../../resources/cursor.rgba"); -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum CursorShape { - Default, - ColResize, - RowResize, - Grab, - EastResize, - WestResize, - NorthResize, - SouthResize, - NorthEastResize, - NorthWestResize, - SouthEastResize, - SouthWestResize, -} - -impl ToString for CursorShape { - fn to_string(&self) -> String { - match self { - CursorShape::Default => "default", - CursorShape::ColResize => "col-resize", - CursorShape::RowResize => "row-resize", - CursorShape::Grab => "grabbing", - CursorShape::EastResize => "e-resize", - CursorShape::WestResize => "w-resize", - CursorShape::NorthResize => "n-resize", - CursorShape::SouthResize => "s-resize", - CursorShape::NorthEastResize => "ne-resize", - CursorShape::NorthWestResize => "nw-resize", - CursorShape::SouthEastResize => "se-resize", - CursorShape::SouthWestResize => "sw-resize", - } - .to_string() - } -} - #[derive(Debug, Clone)] pub struct Cursor { icons: Vec, @@ -76,9 +40,10 @@ pub struct Cursor { } impl Cursor { - pub fn load(theme: &CursorTheme, shape: CursorShape, size: u32) -> Cursor { - let icons = load_icon(&theme, shape) + pub fn load(theme: &CursorTheme, shape: CursorIcon, size: u32) -> Cursor { + let icons = load_icon(theme, shape) .map_err(|err| warn!(?err, "Unable to load xcursor, using fallback cursor")) + .or_else(|_| load_icon(theme, CursorIcon::Default)) .unwrap_or_else(|_| { vec![Image { size: 32, @@ -105,7 +70,7 @@ fn nearest_images(size: u32, images: &[Image]) -> impl Iterator { // Follow the nominal size of the cursor to choose the nearest let nearest_image = images .iter() - .min_by_key(|image| (size as i32 - image.size as i32).abs()) + .min_by_key(|image| u32::abs_diff(size, image.size)) .unwrap(); images.iter().filter(move |image| { @@ -142,7 +107,7 @@ enum Error { Parse, } -fn load_icon(theme: &CursorTheme, shape: CursorShape) -> Result, Error> { +fn load_icon(theme: &CursorTheme, shape: CursorIcon) -> Result, Error> { let icon_path = theme .load_icon(&shape.to_string()) .ok_or(Error::NoDefaultCursor)?; @@ -228,15 +193,29 @@ where pub type CursorState = Mutex; pub struct CursorStateInner { - current_cursor: CursorShape, - pub cursors: HashMap, + current_cursor: Option, + + cursor_theme: CursorTheme, + cursor_size: u32, + + cursors: HashMap, current_image: Option, image_cache: Vec<(Image, MemoryRenderBuffer)>, } impl CursorStateInner { - pub fn set_shape(&mut self, shape: CursorShape) { - self.current_cursor = shape; + pub fn set_shape(&mut self, shape: CursorIcon) { + self.current_cursor = Some(shape); + } + + pub fn unset_shape(&mut self) { + self.current_cursor = None; + } + + pub fn get_named_cursor(&mut self, shape: CursorIcon) -> &Cursor { + self.cursors + .entry(shape) + .or_insert_with(|| Cursor::load(&self.cursor_theme, shape, self.cursor_size)) } } @@ -255,59 +234,12 @@ impl Default for CursorStateInner { fn default() -> CursorStateInner { let (theme, size) = load_cursor_theme(); CursorStateInner { - current_cursor: CursorShape::Default, - cursors: { - let mut map = HashMap::new(); - map.insert( - CursorShape::Default, - Cursor::load(&theme, CursorShape::Default, size), - ); - map.insert( - CursorShape::ColResize, - Cursor::load(&theme, CursorShape::ColResize, size), - ); - map.insert( - CursorShape::RowResize, - Cursor::load(&theme, CursorShape::RowResize, size), - ); - map.insert( - CursorShape::Grab, - Cursor::load(&theme, CursorShape::Grab, size), - ); - map.insert( - CursorShape::NorthResize, - Cursor::load(&theme, CursorShape::NorthResize, size), - ); - map.insert( - CursorShape::SouthResize, - Cursor::load(&theme, CursorShape::SouthResize, size), - ); - map.insert( - CursorShape::EastResize, - Cursor::load(&theme, CursorShape::EastResize, size), - ); - map.insert( - CursorShape::WestResize, - Cursor::load(&theme, CursorShape::WestResize, size), - ); - map.insert( - CursorShape::NorthEastResize, - Cursor::load(&theme, CursorShape::NorthEastResize, size), - ); - map.insert( - CursorShape::SouthEastResize, - Cursor::load(&theme, CursorShape::SouthEastResize, size), - ); - map.insert( - CursorShape::NorthWestResize, - Cursor::load(&theme, CursorShape::NorthWestResize, size), - ); - map.insert( - CursorShape::SouthWestResize, - Cursor::load(&theme, CursorShape::SouthWestResize, size), - ); - map - }, + current_cursor: None, + + cursor_size: size, + cursor_theme: theme, + + cursors: HashMap::new(), current_image: None, image_cache: Vec::new(), } @@ -343,19 +275,23 @@ where }) .unwrap_or(CursorImageStatus::default_named()); - if let CursorImageStatus::Surface(ref wl_surface) = cursor_status { - return draw_surface_cursor(renderer, wl_surface, location.to_i32_round(), scale); - // TODO: Handle other named cursors - } else if draw_default && CursorImageStatus::default_named() == cursor_status { - let integer_scale = scale.x.max(scale.y).ceil() as u32; + let seat_userdata = seat.user_data(); + let mut state_ref = seat_userdata.get::().unwrap().lock().unwrap(); + let state = &mut *state_ref; - let seat_userdata = seat.user_data(); - let mut state_ref = seat_userdata.get::().unwrap().lock().unwrap(); - let state = &mut *state_ref; - let frame = state.cursors.get(&state.current_cursor).unwrap().get_image( - integer_scale, - Into::::into(time).as_millis() as u32, - ); + let named_cursor = state.current_cursor.or(match cursor_status { + CursorImageStatus::Named(named_cursor) => Some(named_cursor), + _ => None, + }); + if let Some(current_cursor) = named_cursor { + if !draw_default && current_cursor == CursorIcon::Default { + return Vec::new(); + } + + let integer_scale = scale.x.max(scale.y).ceil() as u32; + let frame = state + .get_named_cursor(current_cursor) + .get_image(integer_scale, time.as_millis()); let pointer_images = &mut state.image_cache; let maybe_image = @@ -396,6 +332,8 @@ where ), hotspot, )]; + } else if let CursorImageStatus::Surface(ref wl_surface) = cursor_status { + return draw_surface_cursor(renderer, wl_surface, location.to_i32_round(), scale); } else { Vec::new() } diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index e0c9158a..2a9167a4 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -1,9 +1,6 @@ use super::{surface::RESIZE_BORDER, window::Focus, CosmicSurface}; use crate::{ - backend::render::{ - cursor::{CursorShape, CursorState}, - SplitRenderElements, - }, + backend::render::{cursor::CursorState, SplitRenderElements}, shell::{ focus::target::PointerFocusTarget, grabs::{ReleaseMode, ResizeEdge}, @@ -1331,7 +1328,7 @@ impl PointerTarget for CosmicStack { .unwrap() .lock() .unwrap(); - cursor_state.set_shape(CursorShape::Default); + cursor_state.unset_shape(); let _previous = p.swap_focus(None); }); diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 24c151eb..fdda4033 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -1,8 +1,5 @@ use crate::{ - backend::render::{ - cursor::{CursorShape, CursorState}, - SplitRenderElements, - }, + backend::render::{cursor::CursorState, SplitRenderElements}, shell::{ focus::target::PointerFocusTarget, grabs::{ReleaseMode, ResizeEdge}, @@ -35,10 +32,10 @@ use smithay::{ input::{ keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, pointer::{ - AxisFrame, ButtonEvent, CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent, - GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, - GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent, - PointerTarget, RelativeMotionEvent, + AxisFrame, ButtonEvent, CursorIcon, CursorImageStatus, GestureHoldBeginEvent, + GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent, + GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent, + GestureSwipeUpdateEvent, MotionEvent, PointerTarget, RelativeMotionEvent, }, touch::{ DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget, @@ -146,17 +143,17 @@ impl Focus { } } - pub fn cursor_shape(&self) -> CursorShape { + pub fn cursor_shape(&self) -> CursorIcon { match self { - Focus::ResizeTopLeft => CursorShape::NorthWestResize, - Focus::ResizeTopRight => CursorShape::NorthEastResize, - Focus::ResizeTop => CursorShape::NorthResize, - Focus::ResizeBottomLeft => CursorShape::SouthWestResize, - Focus::ResizeBottomRight => CursorShape::SouthEastResize, - Focus::ResizeBottom => CursorShape::SouthResize, - Focus::ResizeLeft => CursorShape::WestResize, - Focus::ResizeRight => CursorShape::EastResize, - Focus::Header => CursorShape::Default, + Focus::ResizeTopLeft => CursorIcon::NwResize, + Focus::ResizeTopRight => CursorIcon::NeResize, + Focus::ResizeTop => CursorIcon::NResize, + Focus::ResizeBottomLeft => CursorIcon::SwResize, + Focus::ResizeBottomRight => CursorIcon::SeResize, + Focus::ResizeBottom => CursorIcon::SResize, + Focus::ResizeLeft => CursorIcon::WResize, + Focus::ResizeRight => CursorIcon::EResize, + Focus::Header => CursorIcon::Default, } } @@ -754,7 +751,7 @@ impl PointerTarget for CosmicWindow { fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { self.0.with_program(|p| { let cursor_state = seat.user_data().get::().unwrap(); - cursor_state.lock().unwrap().set_shape(CursorShape::Default); + cursor_state.lock().unwrap().unset_shape(); let _previous = p.swap_focus(None); }); PointerTarget::leave(&self.0, seat, data, serial, time) diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 785a0c77..6c5e93fb 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -2,9 +2,8 @@ use crate::{ backend::render::{ - cursor::{CursorShape, CursorState}, - element::AsGlowRenderer, - BackdropShader, IndicatorShader, Key, SplitRenderElements, Usage, + cursor::CursorState, element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, + SplitRenderElements, Usage, }, shell::{ element::{ @@ -32,7 +31,7 @@ use smithay::{ desktop::{layer_map_for_output, space::SpaceElement}, input::{ pointer::{ - AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, + AxisFrame, ButtonEvent, CursorIcon, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, @@ -710,7 +709,7 @@ impl MoveGrab { { let cursor_state = seat.user_data().get::().unwrap(); - cursor_state.lock().unwrap().set_shape(CursorShape::Grab); + cursor_state.lock().unwrap().set_shape(CursorIcon::Grab); } MoveGrab { @@ -850,7 +849,7 @@ impl Drop for MoveGrab { { let cursor_state = seat.user_data().get::().unwrap(); - cursor_state.lock().unwrap().set_shape(CursorShape::Default); + cursor_state.lock().unwrap().unset_shape(); } if let Some((mapped, position)) = position { diff --git a/src/shell/layout/tiling/grabs/resize.rs b/src/shell/layout/tiling/grabs/resize.rs index 2f908941..03a3cf62 100644 --- a/src/shell/layout/tiling/grabs/resize.rs +++ b/src/shell/layout/tiling/grabs/resize.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - backend::render::cursor::{CursorShape, CursorState}, + backend::render::cursor::CursorState, shell::{ focus::target::PointerFocusTarget, grabs::{GrabStartData, ReleaseMode}, @@ -14,7 +14,7 @@ use smithay::{ backend::input::ButtonState, input::{ pointer::{ - AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent, + AxisFrame, ButtonEvent, CursorIcon, Focus, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, @@ -54,8 +54,8 @@ impl PointerTarget for ResizeForkTarget { .lock() .unwrap() .set_shape(match self.orientation { - Orientation::Horizontal => CursorShape::RowResize, - Orientation::Vertical => CursorShape::ColResize, + Orientation::Horizontal => CursorIcon::RowResize, + Orientation::Vertical => CursorIcon::ColResize, }); } @@ -68,7 +68,7 @@ impl PointerTarget for ResizeForkTarget { ) { let user_data = seat.user_data(); let cursor_state = user_data.get::().unwrap(); - cursor_state.lock().unwrap().set_shape(CursorShape::Default) + cursor_state.lock().unwrap().unset_shape(); } fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { diff --git a/src/shell/seats.rs b/src/shell/seats.rs index 1073b70f..8fa33069 100644 --- a/src/shell/seats.rs +++ b/src/shell/seats.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{any::Any, cell::RefCell, collections::HashMap, sync::Mutex, time::Duration}; +use std::{any::Any, cell::RefCell, collections::HashMap, sync::Mutex}; use crate::{ - backend::render::cursor::{CursorShape, CursorState}, + backend::render::cursor::CursorState, config::{xkb_config_to_wl, Config}, input::{ModifiersShortcutQueue, SupressedButtons, SupressedKeys}, state::State, @@ -13,7 +13,7 @@ use smithay::{ desktop::utils::bbox_from_surface_tree, input::{ keyboard::{LedState, XkbConfig}, - pointer::{CursorIcon, CursorImageAttributes, CursorImageStatus}, + pointer::{CursorImageAttributes, CursorImageStatus}, Seat, SeatState, }, output::Output, @@ -347,17 +347,15 @@ impl SeatExt for Seat { ); Some((buffer_geo, (hotspot.x, hotspot.y).into())) } - CursorImageStatus::Named(CursorIcon::Default) => { + CursorImageStatus::Named(cursor_icon) => { let seat_userdata = self.user_data(); seat_userdata.insert_if_missing_threadsafe(CursorState::default); let state = seat_userdata.get::().unwrap(); let frame = state .lock() .unwrap() - .cursors - .get(&CursorShape::Default) - .unwrap() - .get_image(1, Into::::into(time).as_millis() as u32); + .get_named_cursor(cursor_icon) + .get_image(1, time.as_millis()); Some(( Rectangle::from_loc_and_size( @@ -367,10 +365,6 @@ impl SeatExt for Seat { (frame.xhot as i32, frame.yhot as i32).into(), )) } - CursorImageStatus::Named(_) => { - // TODO: Handle for `cursor_shape_v1` protocol - None - } CursorImageStatus::Hidden => None, } } diff --git a/src/state.rs b/src/state.rs index 11142a2d..9eba149e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -66,6 +66,7 @@ use smithay::{ wayland::{ alpha_modifier::AlphaModifierState, compositor::{CompositorClientState, CompositorState, SurfaceData}, + cursor_shape::CursorShapeManagerState, dmabuf::{DmabufFeedback, DmabufGlobal, DmabufState}, foreign_toplevel_list::ForeignToplevelListState, fractional_scale::{with_fractional_scale, FractionalScaleManagerState}, @@ -210,6 +211,7 @@ pub struct Common { pub idle_inhibit_manager_state: IdleInhibitManagerState, pub idle_inhibiting_surfaces: HashSet, pub shm_state: ShmState, + pub cursor_shape_manager_state: CursorShapeManagerState, pub wl_drm_state: WlDrmState>, pub viewporter_state: ViewporterState, pub kde_decoration_state: KdeDecorationState, @@ -506,6 +508,7 @@ impl State { let screencopy_state = ScreencopyState::new::(dh, client_is_privileged); let shm_state = ShmState::new::(dh, vec![wl_shm::Format::Xbgr8888, wl_shm::Format::Abgr8888]); + let cursor_shape_manager_state = CursorShapeManagerState::new::(dh); let seat_state = SeatState::::new(); let viewporter_state = ViewporterState::new::(dh); let wl_drm_state = WlDrmState::>::default(); @@ -600,6 +603,7 @@ impl State { image_source_state, screencopy_state, shm_state, + cursor_shape_manager_state, seat_state, session_lock_manager_state, keyboard_shortcuts_inhibit_state, diff --git a/src/wayland/handlers/seat.rs b/src/wayland/handlers/seat.rs index 6ded40be..60511b08 100644 --- a/src/wayland/handlers/seat.rs +++ b/src/wayland/handlers/seat.rs @@ -6,7 +6,7 @@ use crate::{ state::State, }; use smithay::{ - delegate_seat, + delegate_cursor_shape, delegate_seat, input::{keyboard::LedState, pointer::CursorImageStatus, SeatHandler, SeatState}, }; use std::sync::Mutex; @@ -48,3 +48,4 @@ impl SeatHandler for State { } delegate_seat!(State); +delegate_cursor_shape!(State); diff --git a/src/xwayland.rs b/src/xwayland.rs index 6875c2a2..c734ed7d 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -1,7 +1,7 @@ use std::{ffi::OsString, os::unix::io::OwnedFd, process::Stdio}; use crate::{ - backend::render::cursor::{load_cursor_theme, Cursor, CursorShape}, + backend::render::cursor::{load_cursor_theme, Cursor}, shell::{ element::surface::SSD_HEIGHT, focus::target::KeyboardFocusTarget, grabs::ReleaseMode, CosmicSurface, Shell, @@ -15,6 +15,7 @@ use crate::{ use smithay::{ backend::drm::DrmNode, desktop::space::SpaceElement, + input::pointer::CursorIcon, reexports::{wayland_server::Client, x11rb::protocol::xproto::Window as X11Window}, utils::{Logical, Point, Rectangle, Size, SERIAL_COUNTER}, wayland::{ @@ -99,7 +100,7 @@ impl State { }; let (theme, size) = load_cursor_theme(); - let cursor = Cursor::load(&theme, CursorShape::Default, size); + let cursor = Cursor::load(&theme, CursorIcon::Default, size); let image = cursor.get_image(1, 0); if let Err(err) = wm.set_cursor( &image.pixels_rgba,