diff --git a/Cargo.lock b/Cargo.lock index 82bf0027..ac5246e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,9 +46,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.52" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" dependencies = [ "backtrace", ] @@ -99,9 +99,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" dependencies = [ "addr2line", "cc", @@ -309,6 +309,8 @@ dependencies = [ "slog-term", "smithay", "smithay-egui", + "thiserror", + "xcursor", ] [[package]] @@ -500,9 +502,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] @@ -660,9 +662,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" dependencies = [ "wasm-bindgen", ] @@ -681,9 +683,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.115" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a8d982fa7a96a000f6ec4cfe966de9703eccde29750df2bb8949da91b0e818d" +checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" [[package]] name = "libdbus-sys" @@ -716,9 +718,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -851,9 +853,9 @@ checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" [[package]] name = "nix" -version = "0.22.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" dependencies = [ "bitflags", "cc", @@ -1025,9 +1027,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2", ] @@ -1285,9 +1287,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2", "quote", @@ -1347,9 +1349,9 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ "once_cell", ] @@ -1410,9 +1412,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -1420,9 +1422,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" dependencies = [ "bumpalo", "lazy_static", @@ -1435,9 +1437,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1445,9 +1447,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2", "quote", @@ -1458,9 +1460,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" [[package]] name = "wayland-client" @@ -1568,9 +1570,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index c3aa353f..feecfff7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ slog-scope = "4.4" slog-stdlog = "4.1" egui = { version = "0.16", optional = true } edid-rs = { version = "0.1" } +thiserror = "1.0.26" +xcursor = "0.3.3" [dependencies.smithay] version = "0.3" diff --git a/resources/cursor.rgba b/resources/cursor.rgba new file mode 100644 index 00000000..729c1cc4 Binary files /dev/null and b/resources/cursor.rgba differ diff --git a/src/backend/cursor.rs b/src/backend/cursor.rs new file mode 100644 index 00000000..e3c25910 --- /dev/null +++ b/src/backend/cursor.rs @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::{ + cell::RefCell, + io::Read, + rc::Rc, + sync::Mutex, +}; +use smithay::{ + backend::{ + renderer::{Frame, ImportAll, Renderer, Texture, gles2}, + SwapBuffersError, + }, + desktop::space::{RenderElement, SpaceOutputTuple, SurfaceTree, DynamicRenderElements}, + reexports::wayland_server::protocol::wl_surface, + utils::{Logical, Buffer, Point, Rectangle, Size, Transform}, + wayland::{ + compositor::{get_role, with_states}, + seat::{Seat, CursorImageAttributes, CursorImageStatus}, + }, +}; +use xcursor::{ + parser::{parse_xcursor, Image}, + CursorTheme, +}; +use crate::state::get_dnd_icon; + +static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../resources/cursor.rgba"); + +#[derive(Debug, Clone)] +pub struct Cursor { + icons: Vec, + size: u32, +} + +impl Cursor { + pub fn load() -> Cursor { + let name = std::env::var("XCURSOR_THEME") + .ok() + .unwrap_or_else(|| "default".into()); + let size = std::env::var("XCURSOR_SIZE") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(24); + + let theme = CursorTheme::load(&name); + let icons = load_icon(&theme) + .map_err(|err| slog_scope::warn!("Unable to load xcursor: {}, using fallback cursor", err)) + .unwrap_or_else(|_| { + vec![Image { + size: 32, + width: 64, + height: 64, + xhot: 1, + yhot: 1, + delay: 1, + pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA), + pixels_argb: vec![], //unused + }] + }); + + Cursor { icons, size } + } + + pub fn get_image(&self, scale: u32, millis: u32) -> Image { + let size = self.size * scale; + frame(millis, size, &self.icons) + } +} + +impl Default for Cursor { + fn default() -> Cursor { Cursor::load() } +} + +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()) + .unwrap(); + + images + .iter() + .filter(move |image| image.width == nearest_image.width && image.height == nearest_image.height) +} + +fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image { + let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay); + millis %= total; + + for img in nearest_images(size, images) { + if millis < img.delay { + return img.clone(); + } + millis -= img.delay; + } + + unreachable!() +} + +#[derive(thiserror::Error, Debug)] +enum Error { + #[error("Theme has no default cursor")] + NoDefaultCursor, + #[error("Error opening xcursor file: {0}")] + File(#[from] std::io::Error), + #[error("Failed to parse XCursor file")] + Parse, +} + +fn load_icon(theme: &CursorTheme) -> Result, Error> { + let icon_path = theme.load_icon("default").ok_or(Error::NoDefaultCursor)?; + let mut cursor_file = std::fs::File::open(&icon_path)?; + let mut cursor_data = Vec::new(); + cursor_file.read_to_end(&mut cursor_data)?; + parse_xcursor(&cursor_data).ok_or(Error::Parse) +} + +pub fn draw_surface_cursor( + surface: wl_surface::WlSurface, + location: impl Into>, +) -> impl RenderElement +where + R: Renderer + ImportAll + 'static, + F: Frame + 'static, + E: std::error::Error + Into + 'static, + T: Texture + 'static, +{ + let mut position = location.into(); + let ret = with_states(&surface, |states| { + Some( + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .hotspot, + ) + }) + .unwrap_or(None); + position -= match ret { + Some(h) => h, + None => { + slog_scope::warn!("Trying to display as a cursor a surface that does not have the CursorImage role."); + (0, 0).into() + } + }; + SurfaceTree { surface, position } +} + +pub fn draw_dnd_icon( + surface: wl_surface::WlSurface, + location: impl Into>, +) -> impl RenderElement +where + R: Renderer + ImportAll + 'static, + F: Frame + 'static, + E: std::error::Error + Into + 'static, + T: Texture + 'static, +{ + if get_role(&surface) != Some("dnd_icon") { + slog_scope::warn!("Trying to display as a dnd icon a surface that does not have the DndIcon role."); + } + SurfaceTree { + surface, + position: location.into(), + } +} + +pub struct PointerElement { + texture: T, + position: Point, + size: Size, + new_frame: bool, +} + +impl PointerElement { + pub fn new(texture: T, relative_pointer_pos: Point, new_frame: bool) -> PointerElement { + let size = texture.size().to_logical(1, Transform::Normal); + PointerElement { + texture, + position: relative_pointer_pos, + size, + new_frame, + } + } +} + +impl RenderElement for PointerElement +where + R: Renderer + ImportAll + 'static, + F: Frame + 'static, + E: std::error::Error + Into + 'static, + T: Texture + 'static, +{ + fn id(&self) -> usize { + 0 + } + + fn geometry(&self) -> Rectangle { + Rectangle::from_loc_and_size(self.position, self.size) + } + + fn accumulated_damage(&self, _: Option>) -> Vec> { + if self.new_frame { vec![Rectangle::from_loc_and_size((0, 0), self.size)] } else { vec![] } + } + + fn draw( + &self, + _renderer: &mut R, + frame: &mut F, + scale: f64, + position: Point, + damage: &[Rectangle], + _log: &slog::Logger, + ) -> Result<(), R::Error> { + frame.render_texture_at( + &self.texture, + position.to_f64().to_physical(scale as f64).to_i32_round(), + 1, + scale as f64, + Transform::Normal, + &*damage + .iter() + .map(|rect| rect.to_buffer(1, Transform::Normal, &self.size)) + .collect::>(), + 1.0, + )?; + Ok(()) + } +} + +#[derive(Debug, Default)] +struct CursorState { + cursor: Cursor, + current_image: RefCell>, +} + +pub type Textures = Vec<(Image, gles2::Gles2Texture)>; + +pub fn draw_cursor( + renderer: &mut gles2::Gles2Renderer, + seat: &Seat, + start_time: &std::time::Instant, + draw_default: bool, +) -> Option> +{ + let pointer = match seat.get_pointer() { + Some(ptr) => ptr, + None => return None, + }; + let location = pointer.current_location(); + + // draw the dnd icon if applicable + { + if let Some(wl_surface) = get_dnd_icon(seat) { + if wl_surface.as_ref().is_alive() { + return Some(Box::new(draw_dnd_icon( + wl_surface, + location.to_i32_round(), + ))); + } + } + } + + // draw the cursor as relevant + { + // reset the cursor if the surface is no longer alive + let cursor_status = seat + .user_data() + .get::>() + .map(|cell| { + let mut cursor_status = cell.borrow_mut(); + if let CursorImageStatus::Image(ref surface) = *cursor_status { + if !surface.as_ref().is_alive() { + *cursor_status = CursorImageStatus::Default; + } + } + cursor_status.clone() + }) + .unwrap_or(CursorImageStatus::Default); + + if let CursorImageStatus::Image(wl_surface) = cursor_status { + Some(Box::new(draw_surface_cursor( + wl_surface.clone(), + location.to_i32_round(), + ))) + } else if draw_default { + let seat_userdata = seat.user_data(); + seat_userdata.insert_if_missing(CursorState::default); + let state = seat_userdata.get::().unwrap(); + let frame = state.cursor.get_image(1, start_time.elapsed().as_millis() as u32); + let new_frame = state.current_image.borrow().as_ref() != Some(&frame); + + let egl_userdata = renderer.egl_context().user_data(); + egl_userdata.insert_if_missing(|| Rc::new(RefCell::new(Textures::new()))); + let pointer_images = egl_userdata.get::>>().unwrap().clone(); + let pointer_images_ref = &mut *pointer_images.borrow_mut(); + let pointer_image = pointer_images_ref + .iter() + .find_map(|(image, texture)| if image == &frame { Some(texture) } else { None }) + .cloned() + .unwrap_or_else(|| { + let texture = import_bitmap(renderer, &frame.pixels_rgba, gles2::ffi::RGBA, (frame.width as i32, frame.height as i32)) + .expect("Failed to import cursor bitmap"); + pointer_images_ref.push((frame.clone(), texture.clone())); + texture + }); + let hotspot = Point::::from((frame.xhot as i32, frame.yhot as i32)); + *state.current_image.borrow_mut() = Some(frame); + + Some(Box::new(PointerElement::new( + pointer_image.clone(), + location.to_i32_round() - hotspot, + new_frame, + ))) + } else { + None + } + } +} + +pub fn import_bitmap( + renderer: &mut gles2::Gles2Renderer, + image: impl std::convert::AsRef<[u8]>, + format: gles2::ffi::types::GLenum, + size: impl Into>, +) -> Result { + use smithay::backend::renderer::gles2::ffi; + + let size = size.into(); + renderer.with_context(|renderer, gl| unsafe { + let mut tex = 0; + gl.GenTextures(1, &mut tex); + gl.BindTexture(ffi::TEXTURE_2D, tex); + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32); + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32); + gl.TexImage2D( + ffi::TEXTURE_2D, + 0, + format as i32, + size.w, + size.h, + 0, + format, + ffi::UNSIGNED_BYTE as u32, + image.as_ref().as_ptr() as *const _, + ); + gl.BindTexture(ffi::TEXTURE_2D, 0); + + gles2::Gles2Texture::from_raw( + renderer, + tex, + size, + ) + }) +} \ No newline at end of file diff --git a/src/backend/kms/crtc_mapping.rs b/src/backend/kms/drm_helpers.rs similarity index 100% rename from src/backend/kms/crtc_mapping.rs rename to src/backend/kms/drm_helpers.rs diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index a8199de7..27eb9d64 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ + backend::cursor, state::{BackendData, Common, State}, utils::GlobalDrop, }; @@ -45,7 +46,7 @@ use std::{ time::{Duration, Instant, SystemTime}, }; -mod crtc_mapping; +mod drm_helpers; mod session_fd; use session_fd::*; @@ -260,7 +261,7 @@ impl Device { let drm = &mut *self.drm.as_source_mut(); // enumerate our outputs - let config = crtc_mapping::display_configuration(drm, self.supports_atomic)?; + let config = drm_helpers::display_configuration(drm, self.supports_atomic)?; let surfaces = self.surfaces.iter() .map(|(c, s)| (*c, s.surface.current_connectors().into_iter().next().unwrap())) @@ -293,9 +294,9 @@ impl Device { let drm = &mut *self.drm.as_source_mut(); let crtc_info = drm.get_crtc(crtc)?; let conn_info = drm.get_connector(conn)?; - let vrr = crtc_mapping::set_vrr(drm, crtc, conn, true)?; - let interface = crtc_mapping::interface_name(drm, conn)?; - let edid_info = crtc_mapping::edid_info(drm, conn)?; + let vrr = drm_helpers::set_vrr(drm, crtc, conn, true)?; + let interface = drm_helpers::interface_name(drm, conn)?; + let edid_info = drm_helpers::edid_info(drm, conn)?; let mode = crtc_info.mode().unwrap_or(conn_info.modes()[0]); let mut surface = drm.create_surface(crtc, mode, &[conn])?; surface.link(signaler); @@ -351,7 +352,7 @@ impl Device { _global: output_global.into(), surface: target, vrr, - refresh_rate: crtc_mapping::calculate_refresh_rate(mode), + refresh_rate: drm_helpers::calculate_refresh_rate(mode), last_submit: None, pending: true, render_timer: timer_handle, @@ -369,7 +370,30 @@ impl Surface { renderer: &mut Gles2Renderer, state: &mut Common, ) -> Result<()> { - let custom_elements = Vec::new(); + #[allow(unused_mut)] + let mut custom_elements = Vec::new(); + + #[cfg(feature = "debug")] + { + let space = state.spaces.active_space(&self.output); + let size = space.output_geometry(&self.output).unwrap(); + let scale = space.output_scale(&self.output).unwrap(); + let frame = debug_ui(state, &self.fps, size, scale, true); + custom_elements.push( + Box::new(frame) as smithay::desktop::space::DynamicRenderElements + ); + } + + for seat in &state.seats { + if let Some(cursor) = cursor::draw_cursor( + renderer, + seat, + &state.start_time, + true, + ) { + custom_elements.push(cursor) + } + } let space = state.spaces.active_space_mut(&self.output); let (buffer, age) = self @@ -390,6 +414,10 @@ impl Surface { self.surface .queue_buffer() .with_context(|| "Failed to submit buffer for display")?; + #[cfg(feature = "debug")] + { + self.fps.tick(); + } } Err(err) => { self.surface.reset_buffers(); diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 3bd89fff..96dc9af1 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -4,6 +4,7 @@ use crate::state::State; use anyhow::Result; use smithay::reexports::calloop::EventLoop; +pub mod cursor; pub mod kms; pub mod winit; pub mod x11; diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 38a5666e..3529d5cc 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::state::Common; use crate::{ + backend::cursor, input::{set_active_output, Devices}, - state::{BackendData, State}, + state::{BackendData, State, Common}, }; use anyhow::{Context, Result}; use smithay::{ @@ -58,6 +58,17 @@ impl WinitState { let space = state.spaces.active_space_mut(&self.output); let mut backend = self.backend.borrow_mut(); + + for seat in &state.seats { + if let Some(cursor) = cursor::draw_cursor( + backend.renderer(), + seat, + &state.start_time, + false, + ) { + custom_elements.push(cursor) + } + } backend.bind().with_context(|| "Failed to bind buffer")?; let age = backend.buffer_age().unwrap_or(0); diff --git a/src/backend/x11.rs b/src/backend/x11.rs index fba76855..6852995f 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::state::Common; use crate::{ + backend::cursor, input::{set_active_output, Devices}, - state::{BackendData, State}, + state::{BackendData, State, Common}, utils::GlobalDrop, }; use anyhow::{Context, Result}; @@ -166,6 +166,17 @@ impl Surface { ); } + for seat in &state.seats { + if let Some(cursor) = cursor::draw_cursor( + renderer, + seat, + &state.start_time, + false, + ) { + custom_elements.push(cursor) + } + } + let space = state.spaces.active_space_mut(&self.output); let (buffer, age) = self .surface diff --git a/src/state.rs b/src/state.rs index 3e61435d..3b5b4aa8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,10 +7,13 @@ use crate::{ use smithay::{ reexports::{ calloop::LoopHandle, - wayland_server::Display, + wayland_server::{ + Display, + protocol::wl_surface::WlSurface, + }, }, wayland::{ - data_device::{default_action_chooser, init_data_device}, + data_device::{default_action_chooser, init_data_device, DataDeviceEvent}, output::{xdg::init_xdg_output_manager, Output}, seat::Seat, shell::xdg::ToplevelSurface, @@ -110,6 +113,15 @@ impl BackendData { } } +struct DnDIcon { + surface: RefCell>, +} + +pub fn get_dnd_icon(seat: &Seat) -> Option { + let userdata = seat.user_data(); + userdata.get::().and_then(|x| x.surface.borrow().clone()) +} + impl State { pub fn new(mut display: Display, handle: LoopHandle<'static, State>) -> State { init_shm_global(&mut display, vec![], None); @@ -118,7 +130,17 @@ impl State { let initial_seat = crate::input::add_seat(&mut display, "seat-0".into()); init_data_device( &mut display, - |_dnd_event| { /* TODO */ }, + |dnd_event| match dnd_event { + DataDeviceEvent::DnDStarted { icon, seat, .. } => { + let user_data = seat.user_data(); + user_data.insert_if_missing(|| DnDIcon { surface: RefCell::new(None) }); + *user_data.get::().unwrap().surface.borrow_mut() = icon; + }, + DataDeviceEvent::DnDDropped { seat } => { + seat.user_data().get::().unwrap().surface.borrow_mut().take(); + }, + _ => {}, + }, default_action_chooser, None, );