diff --git a/Cargo.lock b/Cargo.lock index cbb1707a..1fecb9e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -268,6 +268,7 @@ dependencies = [ "backtrace", "bitflags", "bstr", + "byteorder", "env_logger", "futures", "isnt", diff --git a/Cargo.toml b/Cargo.toml index 20170dcd..32d70b4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ rand = "0.8.4" renderdoc = "0.10.1" smallvec = { version = "1.8.0", features = ["const_generics", "const_new", "union"] } backtrace = "0.3.64" +byteorder = "1.4.3" [build-dependencies] repc = "0.1.1" diff --git a/src/backends/xorg/mod.rs b/src/backends/xorg/mod.rs index 76a303aa..2bd9094a 100644 --- a/src/backends/xorg/mod.rs +++ b/src/backends/xorg/mod.rs @@ -303,7 +303,7 @@ impl XorgBackend { slf.query_devices(ffi::XCB_INPUT_DEVICE_ALL_MASTER as _)?; slf.handle_events()?; - state.render_ctx.set(Some(ctx.clone())); + state.set_render_ctx(&ctx); Ok(slf) } diff --git a/src/cursor.rs b/src/cursor.rs new file mode 100644 index 00000000..3d289a58 --- /dev/null +++ b/src/cursor.rs @@ -0,0 +1,489 @@ +use crate::rect::Rect; +use crate::render::{RenderContext, Renderer, Texture}; +use crate::{ErrorFmt, NumCell, RenderError}; +use ahash::AHashSet; +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use byteorder::{LittleEndian, ReadBytesExt}; +use isnt::std_1::primitive::IsntSliceExt; +use std::cell::Cell; +use std::convert::TryInto; +use std::fmt::{Debug, Formatter}; +use std::fs::File; +use std::io::{BufRead, BufReader, Seek, SeekFrom}; +use std::rc::Rc; +use std::{env, io, slice, str}; +use std::mem::MaybeUninit; +use thiserror::Error; +use uapi::c; +use crate::format::ARGB8888; + +const XCURSOR_MAGIC: u32 = 0x72756358; +const XCURSOR_IMAGE_TYPE: u32 = 0xfffd0002; +const XCURSOR_PATH_DEFAULT: &[u8] = + b"~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons"; +const XCURSOR_PATH: &str = "XCURSOR_PATH"; +const HOME: &str = "HOME"; + +const HEADER_SIZE: u32 = 16; + +pub trait Cursor { + fn set_position(&self, x: i32, y: i32); + fn render(&self, renderer: &mut Renderer, x: i32, y: i32); + fn extents(&self) -> Rect; + fn handle_unset(&self) { } + fn tick(&self) { } +} + +pub struct ServerCursors { + pub default: ServerCursorTemplate, + pub resize_right: ServerCursorTemplate, + pub resize_left: ServerCursorTemplate, + pub resize_top: ServerCursorTemplate, + pub resize_bottom: ServerCursorTemplate, + pub resize_top_bottom: ServerCursorTemplate, + pub resize_left_right: ServerCursorTemplate, + pub resize_top_left: ServerCursorTemplate, + pub resize_top_right: ServerCursorTemplate, + pub resize_bottom_left: ServerCursorTemplate, + pub resize_bottom_right: ServerCursorTemplate, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum KnownCursor { + Default, +} + +impl ServerCursors { + pub fn load(ctx: &Rc) -> Result { + let paths = find_cursor_paths(); + log::debug!("Trying to load cursors from paths {:?}", paths); + let load = |name: &str| { + ServerCursorTemplate::load(name, None, 16, &paths, ctx) + }; + Ok(Self { + // default: load("left_ptr")?, + default: load("left_ptr_watch")?, + resize_right: load("right_side")?, + resize_left: load("left_side")?, + resize_top: load("top_side")?, + resize_bottom: load("bottom_side")?, + resize_top_bottom: load("v_double_arrow")?, + resize_left_right: load("h_double_arrow")?, + resize_top_left: load("top_left_corner")?, + resize_top_right: load("top_right_corner")?, + resize_bottom_left: load("top_left_corner")?, + resize_bottom_right: load("bottom_right_corner")?, + }) + } +} + +pub struct ServerCursorTemplate { + var: ServerCursorTemplateVariant, +} + +enum ServerCursorTemplateVariant { + Static(Rc), + Animated(Rc>), +} + +impl ServerCursorTemplate { + fn load( + name: &str, + theme: Option<&BStr>, + size: u32, + paths: &[BString], + ctx: &Rc, + ) -> Result { + match open_cursor(name, theme, size, paths) { + Ok(c) => { + if c.len() == 1 { + let c = &c[0]; + let cursor = CursorImage::from_bytes(ctx, &c.pixels, 0, c.width, c.height, c.xhot, c.yhot)?; + Ok(ServerCursorTemplate { + var: ServerCursorTemplateVariant::Static(Rc::new(cursor)), + }) + } else { + let mut images = vec!(); + for c in c { + let img = CursorImage::from_bytes(ctx, &c.pixels, c.delay as _, c.width, c.height, c.xhot, c.yhot)?; + images.push(img); + } + Ok(ServerCursorTemplate { + var: ServerCursorTemplateVariant::Animated(Rc::new(images)), + }) + } + } + Err(e) => { + log::warn!("Could not load cursor {}: {}", name, ErrorFmt(e)); + let empty: [Cell; 4] = unsafe { MaybeUninit::zeroed().assume_init() }; + let cursor = CursorImage::from_bytes(ctx, &empty, 0, 1, 1, 0, 0)?; + Ok(ServerCursorTemplate { + var: ServerCursorTemplateVariant::Static(Rc::new(cursor)), + }) + } + } + } + + pub fn instantiate(&self) -> Rc { + match &self.var { + ServerCursorTemplateVariant::Static(s) => { + Rc::new(StaticCursor { + x: Cell::new(0), + y: Cell::new(0), + extents: Cell::new(s.extents), + image: s.clone(), + }) + }, + ServerCursorTemplateVariant::Animated(a) => { + let mut start = c::timespec { + tv_sec: 0, + tv_nsec: 0, + }; + uapi::clock_gettime(c::CLOCK_MONOTONIC, &mut start).unwrap(); + Rc::new(AnimatedCursor { + start, + next: NumCell::new(a[0].delay_ns), + idx: Cell::new(0), + images: a.clone(), + x: Cell::new(0), + y: Cell::new(0), + extents: Cell::new(a[0].extents), + }) + } + } + } +} + +struct CursorImage { + extents: Rect, + xhot: i32, + yhot: i32, + delay_ns: u64, + tex: Rc, +} + +impl CursorImage { + fn from_bytes(ctx: &Rc, data: &[Cell], delay_ms: u64, width: i32, height: i32, xhot: i32, yhot: i32) -> Result { + Ok(Self { + extents: Rect::new_sized(-xhot, -yhot, width, height).unwrap(), + xhot, + yhot, + delay_ns: delay_ms * 1_000_000, + tex: ctx.shmem_texture(data, ARGB8888, width, height, width * 4)?, + }) + } +} + +struct StaticCursor { + x: Cell, + y: Cell, + extents: Cell, + image: Rc, +} + +impl Cursor for StaticCursor { + fn set_position(&self, x: i32, y: i32) { + let dx = x - self.x.replace(x); + let dy = y - self.y.replace(y); + self.extents.set(self.extents.get().move_(dx, dy)); + } + + fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { + renderer.render_texture(&self.image.tex, x, y, ARGB8888); + } + + fn extents(&self) -> Rect { + self.extents.get() + } +} + +struct AnimatedCursor { + start: c::timespec, + next: NumCell, + idx: Cell, + images: Rc>, + x: Cell, + y: Cell, + extents: Cell, +} + +impl Cursor for AnimatedCursor { + fn set_position(&self, x: i32, y: i32) { + let dx = x - self.x.replace(x); + let dy = y - self.y.replace(y); + self.extents.set(self.extents.get().move_(dx, dy)); + } + + fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { + let img = &self.images[self.idx.get()]; + renderer.render_texture(&img.tex, x, y, ARGB8888); + } + + fn extents(&self) -> Rect { + self.extents.get() + } + + fn tick(&self) { + let mut now = c::timespec { + tv_sec: 0, + tv_nsec: 0, + }; + uapi::clock_gettime(c::CLOCK_MONOTONIC, &mut now).unwrap(); + let dist = (now.tv_sec.wrapping_sub(self.start.tv_sec)) as i64 * 1_000_000_000 + + now.tv_nsec.wrapping_sub(self.start.tv_nsec) as i64; + if (dist as u64) < self.next.get() { + return; + } + let idx = (self.idx.get() + 1) % self.images.len(); + self.idx.set(idx); + let image = &self.images[idx]; + self.extents.set( + Rect::new_sized( + self.x.get() - image.xhot, + self.y.get() - image.yhot, + image.extents.width(), + image.extents.height(), + ) + .unwrap(), + ); + self.next.fetch_add(image.delay_ns); + } +} + +fn open_cursor( + name: &str, + theme: Option<&BStr>, + size: u32, + paths: &[BString], +) -> Result, CursorError> { + let name = name.as_bytes().as_bstr(); + let mut file = None; + let mut themes_tested = AHashSet::new(); + if let Some(theme) = theme { + file = open_cursor_file(&mut themes_tested, paths, theme, name); + } + if file.is_none() { + file = open_cursor_file(&mut themes_tested, paths, b"default".as_bstr(), name); + } + let file = match file { + Some(f) => f, + _ => return Err(CursorError::NotFound), + }; + let mut file = BufReader::new(file); + let images = parser_cursor_file(&mut file, size)?; + if images.is_empty() { + return Err(CursorError::EmptyXcursorFile); + } + Ok(images) +} + +fn open_cursor_file( + themes_tested: &mut AHashSet, + paths: &[BString], + theme: &BStr, + name: &BStr, +) -> Option { + if !themes_tested.insert(theme.to_owned()) { + return None; + } + if paths.is_empty() { + return None; + } + let mut parents = None; + for cursor_path in paths { + let mut theme_dir = cursor_path.to_vec(); + theme_dir.push(b'/'); + theme_dir.extend_from_slice(theme.as_bytes()); + let mut cursor_file = theme_dir.clone(); + cursor_file.extend_from_slice(b"/cursors/"); + cursor_file.extend_from_slice(name.as_bytes()); + if let Ok(f) = File::open(cursor_file.to_os_str().unwrap()) { + return Some(f); + } + if parents.is_none() { + let mut index_file = theme_dir.clone(); + index_file.extend_from_slice(b"/index.theme"); + parents = find_parent_themes(&index_file); + } + } + if let Some(parents) = parents { + for parent in parents { + if let Some(file) = open_cursor_file(themes_tested, paths, parent.as_bstr(), name) { + return Some(file); + } + } + } + None +} + +fn find_cursor_paths() -> Vec { + let home = env::var_os(HOME).map(|h| Vec::from_os_string(h).unwrap()); + let cursor_paths = env::var_os(XCURSOR_PATH); + let cursor_paths = cursor_paths + .as_ref() + .map(|c| <[u8]>::from_os_str(c).unwrap()) + .unwrap_or(XCURSOR_PATH_DEFAULT); + let mut paths = vec![]; + for path in <[u8]>::split(cursor_paths, |b| *b == b':') { + if path.first() == Some(&b'~') { + if let Some(home) = home.as_ref() { + let mut full_path = home.clone(); + full_path.extend_from_slice(&path[1..]); + paths.push(full_path.into()); + } else { + log::warn!( + "`HOME` is not set. Cannot expand {}. Ignoring.", + path.as_bstr() + ); + } + } else { + paths.push(path.as_bstr().to_owned()); + } + } + paths +} + +fn find_parent_themes(path: &[u8]) -> Option> { + // NOTE: The files we're reading here are really INI files with a hierarchy. This + // algorithm treats it as a flat list and is inherited from libxcursor. + let file = match File::open(path.to_os_str().unwrap()) { + Ok(f) => f, + _ => return None, + }; + let mut buf_reader = BufReader::new(file); + let mut buf = vec![]; + loop { + buf.clear(); + match buf_reader.read_until(b'\n', &mut buf) { + Ok(n) if n > 0 => {} + _ => return None, + } + let mut suffix = match buf.strip_prefix(b"Inherits") { + Some(s) => s, + _ => continue, + }; + while suffix.first() == Some(&b' ') { + suffix = &suffix[1..]; + } + if suffix.first() != Some(&b'=') { + continue; + } + suffix = &suffix[1..]; + let parents = suffix + .split(|b| matches!(*b, b' ' | b'\t' | b'\n' | b';' | b',')) + .filter(|v| v.is_not_empty()) + .map(|v| v.as_bstr().to_owned()) + .collect(); + return Some(parents); + } +} + +#[derive(Debug, Error)] +pub enum CursorError { + #[error("An IO error occurred: {0}")] + Io(#[from] io::Error), + #[error("The file is not an Xcursor file")] + NotAnXcursorFile, + #[error("The Xcursor file contains more than 0x10000 images")] + OversizedXcursorFile, + #[error("The Xcursor file is empty")] + EmptyXcursorFile, + #[error("The Xcursor file is corrupt")] + CorruptXcursorFile, + #[error("The requested cursor could not be found")] + NotFound, + #[error("Could not import the cursor as a texture")] + ImportError(#[from] RenderError), +} + +#[derive(Default, Clone)] +struct XCursorImage { + width: i32, + height: i32, + xhot: i32, + yhot: i32, + delay: u32, + pixels: Vec>, +} + +impl Debug for XCursorImage { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("XcbCursorImage") + .field("width", &self.width) + .field("height", &self.height) + .field("xhot", &self.xhot) + .field("yhot", &self.yhot) + .field("delay", &self.delay) + .finish_non_exhaustive() + } +} + +fn parser_cursor_file( + r: &mut R, + target: u32, +) -> Result, CursorError> { + let [magic, header] = read_u32_n(r)?; + if magic != XCURSOR_MAGIC || header < HEADER_SIZE { + return Err(CursorError::NotAnXcursorFile); + } + let [_version, ntoc] = read_u32_n(r)?; + r.seek(SeekFrom::Current((HEADER_SIZE - header) as i64))?; + if ntoc > 0x10000 { + return Err(CursorError::OversizedXcursorFile); + } + let mut images_positions = vec![]; + let mut best_fit = i64::MAX; + for _ in 0..ntoc { + let [type_, size, position] = read_u32_n(r)?; + if type_ != XCURSOR_IMAGE_TYPE { + continue; + } + let fit = (size as i64 - target as i64).abs(); + if fit < best_fit { + best_fit = fit; + images_positions.clear(); + } + if fit == best_fit { + images_positions.push(position); + } + } + let mut images = Vec::with_capacity(images_positions.len()); + for position in images_positions { + r.seek(SeekFrom::Start(position as u64))?; + let [_chunk_header, _type_, _size, _version, width, height, xhot, yhot, delay] = + read_u32_n(r)?; + let [width, height, xhot, yhot] = u32_to_i32([width, height, xhot, yhot])?; + let mut image = XCursorImage { + width, + height, + xhot, + yhot, + delay, + pixels: vec![], + }; + let num_bytes = width as usize * height as usize * 4; + unsafe { + image.pixels.reserve_exact(num_bytes as usize); + image.pixels.set_len(num_bytes as usize); + r.read_exact(slice::from_raw_parts_mut(image.pixels.as_mut_ptr() as _, num_bytes))?; + } + images.push(image); + } + Ok(images) +} + +fn read_u32_n(r: &mut R) -> Result<[u32; N], io::Error> { + let mut res = [0; N]; + r.read_u32_into::(&mut res)?; + Ok(res) +} + +fn u32_to_i32(n: [u32; N]) -> Result<[i32; N], CursorError> { + let mut res = [0; N]; + for i in 0..N { + res[i] = n[i] + .try_into() + .map_err(|_| CursorError::CorruptXcursorFile)?; + } + Ok(res) +} diff --git a/src/ifs/wl_seat/handling.rs b/src/ifs/wl_seat/handling.rs index 629e0a35..9566b2ed 100644 --- a/src/ifs/wl_seat/handling.rs +++ b/src/ifs/wl_seat/handling.rs @@ -350,6 +350,9 @@ impl WlSeatGlobal { .enter(self, x.apply_fract(new.x), y.apply_fract(new.y)); stack.push(new.node); } + if let Some(node) = stack.last() { + node.pointer_target(self); + } } found_tree.clear(); } @@ -397,7 +400,7 @@ impl WlSeatGlobal { self.focus_toplevel(n); } - pub fn enter_popup(self: &Rc, n: &Rc) { + pub fn enter_popup(self: &Rc, _n: &Rc) { // self.focus_xdg_surface(&n.xdg); } diff --git a/src/ifs/wl_seat/mod.rs b/src/ifs/wl_seat/mod.rs index b317b33d..9ef7b52f 100644 --- a/src/ifs/wl_seat/mod.rs +++ b/src/ifs/wl_seat/mod.rs @@ -12,7 +12,6 @@ use crate::ifs::wl_data_device::{WlDataDevice, WlDataDeviceId}; use crate::ifs::wl_seat::wl_keyboard::{WlKeyboard, WlKeyboardId, REPEAT_INFO_SINCE}; use crate::ifs::wl_seat::wl_pointer::{WlPointer, WlPointerId}; use crate::ifs::wl_seat::wl_touch::WlTouch; -use crate::ifs::wl_surface::cursor::CursorSurface; use crate::ifs::wl_surface::xdg_surface::xdg_toplevel::XdgToplevel; use crate::object::{Interface, Object, ObjectId}; use crate::tree::{FloatNode, FoundNode, Node}; @@ -31,6 +30,7 @@ use std::io::Write; use std::rc::Rc; pub use types::*; use uapi::{c, OwnedFd}; +use crate::cursor::{Cursor, KnownCursor}; id!(WlSeatId); @@ -74,7 +74,7 @@ pub struct WlSeatGlobal { kb_state: RefCell, layout: Rc, layout_size: u32, - cursor: CloneCell>>, + cursor: CloneCell>>, serial: NumCell, } @@ -121,7 +121,21 @@ impl WlSeatGlobal { } } - pub fn set_cursor(&self, cursor: Option>) { + pub fn set_known_cursor(&self, cursor: KnownCursor) { + let cursors = match self.state.cursors.get() { + Some(c) => c, + None => { + self.set_cursor(None); + return; + } + }; + let tpl = match cursor { + KnownCursor::Default => &cursors.default, + }; + self.set_cursor(Some(tpl.instantiate())); + } + + pub fn set_cursor(&self, cursor: Option>) { if let Some(old) = self.cursor.get() { if let Some(new) = cursor.as_ref() { if Rc::ptr_eq(&old, new) { @@ -130,10 +144,15 @@ impl WlSeatGlobal { } old.handle_unset(); } + if let Some(cursor) = cursor.as_ref() { + log::info!("setting new cursor"); + let (x, y) = self.pos.get(); + cursor.set_position(x.round_down(), y.round_down()); + } self.cursor.set(cursor); } - pub fn get_cursor(&self) -> Option> { + pub fn get_cursor(&self) -> Option> { self.cursor.get() } diff --git a/src/ifs/wl_seat/wl_pointer/mod.rs b/src/ifs/wl_seat/wl_pointer/mod.rs index 63cb8fe3..0ee94eff 100644 --- a/src/ifs/wl_seat/wl_pointer/mod.rs +++ b/src/ifs/wl_seat/wl_pointer/mod.rs @@ -8,6 +8,7 @@ use crate::object::{Interface, Object, ObjectId}; use crate::utils::buffd::MsgParser; use std::rc::Rc; pub use types::*; +use crate::cursor::Cursor; const SET_CURSOR: u32 = 0; const RELEASE: u32 = 1; @@ -153,7 +154,7 @@ impl WlPointer { let surface = self.seat.client.get_surface(req.surface)?; let cursor = surface.get_cursor(&self.seat.global)?; cursor.set_hotspot(req.hotspot_x, req.hotspot_y); - cursor_opt = Some(cursor); + cursor_opt = Some(cursor as Rc); } let pointer_node = match self.seat.global.pointer_stack.borrow().last().cloned() { Some(n) => n, diff --git a/src/ifs/wl_surface/cursor.rs b/src/ifs/wl_surface/cursor.rs index 6ee611d2..9ebddd7b 100644 --- a/src/ifs/wl_surface/cursor.rs +++ b/src/ifs/wl_surface/cursor.rs @@ -1,8 +1,10 @@ use crate::ifs::wl_seat::WlSeatGlobal; use crate::ifs::wl_surface::WlSurface; use crate::rect::Rect; +use crate::render::Renderer; use std::cell::Cell; use std::rc::Rc; +use crate::cursor::Cursor; pub struct CursorSurface { seat: Rc, @@ -38,15 +40,6 @@ impl CursorSurface { ); } - pub fn set_position(&self, x: i32, y: i32) { - self.pos.set((x, y)); - self.update_extents(); - } - - pub fn handle_unset(&self) { - self.surface.cursors.remove(&self.seat.id()); - } - pub fn handle_surface_destroy(&self) { self.seat.set_cursor(None); } @@ -71,12 +64,23 @@ impl CursorSurface { self.hotspot.set((hot_x - hotspot_dx, hot_y - hotspot_dy)); self.update_extents(); } +} - pub fn surface(&self) -> &Rc { - &self.surface +impl Cursor for CursorSurface { + fn set_position(&self, x: i32, y: i32) { + self.pos.set((x, y)); + self.update_extents(); } - pub fn extents(&self) -> Rect { + fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { + renderer.render_surface(&self.surface, x, y); + } + + fn extents(&self) -> Rect { self.extents.get() } + + fn handle_unset(&self) { + self.surface.cursors.remove(&self.seat.id()); + } } diff --git a/src/ifs/wl_surface/mod.rs b/src/ifs/wl_surface/mod.rs index 92344d43..8e330fab 100644 --- a/src/ifs/wl_surface/mod.rs +++ b/src/ifs/wl_surface/mod.rs @@ -206,6 +206,7 @@ impl WlSurface { } self.set_role(SurfaceRole::Cursor)?; let cursor = Rc::new(CursorSurface::new(seat, self)); + cursor.handle_buffer_change(); self.cursors.insert(seat.id(), cursor.clone()); Ok(cursor) } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_popup/mod.rs b/src/ifs/wl_surface/xdg_surface/xdg_popup/mod.rs index ade8c4e7..90a9d010 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_popup/mod.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_popup/mod.rs @@ -4,7 +4,7 @@ use crate::client::{ClientId, DynEventFormatter}; use crate::fixed::Fixed; use crate::ifs::wl_seat::{NodeSeatState, WlSeatGlobal}; use crate::ifs::wl_surface::xdg_surface::{XdgSurface, XdgSurfaceError, XdgSurfaceExt}; -use crate::ifs::xdg_positioner::{XdgPositioned, XdgPositioner}; +use crate::ifs::xdg_positioner::{XdgPositioned, XdgPositioner, CA}; use crate::object::{Interface, Object, ObjectId}; use crate::rect::Rect; use crate::render::Renderer; @@ -15,6 +15,7 @@ use crate::utils::linkedlist::LinkedNode; use std::cell::{Cell, RefCell}; use std::rc::Rc; pub use types::*; +use crate::cursor::KnownCursor; const DESTROY: u32 = 0; const GRAB: u32 = 1; @@ -85,13 +86,94 @@ impl XdgPopup { Box::new(PopupDone { obj: self.clone() }) } - fn update_relative_position(&self, parent: &XdgSurface) -> Result<(), XdgPopupError> { - let parent = parent.extents.get(); - let positioner = self.pos.borrow(); - if !parent.contains_rect(&positioner.ar) { - // return Err(XdgPopupError::AnchorRectOutside); + fn update_position(&self, parent: &XdgSurface) -> Result<(), XdgPopupError> { + // let parent = parent.extents.get(); + let positioner = self.pos.borrow_mut(); + // if !parent.contains_rect(&positioner.ar) { + // return Err(XdgPopupError::AnchorRectOutside); + // } + let parent_abs = parent.absolute_desired_extents.get(); + let mut rel_pos = positioner.get_position(false, false); + let mut abs_pos = rel_pos.move_(parent_abs.x1(), parent_abs.y1()); + if let Some(ws) = parent.workspace.get() { + let output_pos = ws.output.get().position.get(); + let mut overflow = output_pos.get_overflow(&abs_pos); + if !overflow.is_contained() { + let mut flip_x = positioner.ca.contains(CA::FLIP_X) && overflow.x_overflow(); + let mut flip_y = positioner.ca.contains(CA::FLIP_Y) && overflow.y_overflow(); + if flip_x || flip_y { + let mut adj_rel = positioner.get_position(flip_x, flip_y); + let mut adj_abs = adj_rel.move_(parent_abs.x1(), parent_abs.y1()); + let mut adj_overflow = output_pos.get_overflow(&adj_abs); + let mut recalculate = false; + if flip_x && adj_overflow.x_overflow() { + flip_x = false; + recalculate = true; + } + if flip_y && adj_overflow.y_overflow() { + flip_y = false; + recalculate = true; + } + if flip_x || flip_y { + if recalculate { + adj_rel = positioner.get_position(flip_x, flip_y); + adj_abs = adj_rel.move_(parent_abs.x1(), parent_abs.y1()); + adj_overflow = output_pos.get_overflow(&adj_abs); + } + rel_pos = adj_rel; + abs_pos = adj_abs; + overflow = adj_overflow; + } + } + let (mut dx, mut dy) = (0, 0); + if positioner.ca.contains(CA::SLIDE_X) && overflow.x_overflow() { + dx = if overflow.left > 0 || overflow.left + overflow.right > 0 { + parent_abs.x1() - abs_pos.x1() + } else { + parent_abs.x2() - abs_pos.x2() + }; + } + if positioner.ca.contains(CA::SLIDE_Y) && overflow.y_overflow() { + dy = if overflow.top > 0 || overflow.top + overflow.bottom > 0 { + parent_abs.y1() - abs_pos.y1() + } else { + parent_abs.y2() - abs_pos.y2() + }; + } + if dx != 0 || dy != 0 { + rel_pos = rel_pos.move_(dx, dy); + abs_pos = rel_pos.move_(parent_abs.x1(), parent_abs.y1()); + overflow = output_pos.get_overflow(&abs_pos); + } + let (mut dx1, mut dx2, mut dy1, mut dy2) = (0, 0, 0, 0); + if positioner.ca.contains(CA::RESIZE_X) { + dx1 = overflow.left.max(0); + dx2 = -overflow.right.max(0); + } + if positioner.ca.contains(CA::RESIZE_Y) { + dy1 = overflow.top.max(0); + dy2 = -overflow.bottom.max(0); + } + if dx1 > 0 || dx2 < 0 || dy1 > 0 || dy2 < 0 { + abs_pos = Rect::new( + abs_pos.x1() + dx1, + abs_pos.y1() + dy1, + abs_pos.x2() + dx2, + abs_pos.y2() + dy2, + ) + .unwrap(); + rel_pos = Rect::new_sized( + abs_pos.x1() - parent_abs.x1(), + abs_pos.y1() - parent_abs.y1(), + abs_pos.width(), + abs_pos.height(), + ) + .unwrap(); + } + } } - self.relative_position.set(positioner.get_position()); + self.relative_position.set(rel_pos); + self.xdg.absolute_desired_extents.set(abs_pos); Ok(()) } @@ -134,7 +216,7 @@ impl XdgPopup { .get_xdg_positioner(req.positioner)? .value(); if let Some(parent) = self.parent.get() { - self.update_relative_position(&parent)?; + self.update_position(&parent)?; let rel = self.relative_position.get(); self.xdg.surface.client.event(self.repositioned(req.token)); self.xdg.surface.client.event(self.configure( @@ -144,24 +226,6 @@ impl XdgPopup { rel.height(), )); self.xdg.send_configure(); - let parent = parent.absolute_desired_extents.get(); - self.xdg - .absolute_desired_extents - .set(rel.move_(parent.x1(), parent.y1())); - } - Ok(()) - } - - fn handle_request_( - self: &Rc, - request: u32, - parser: MsgParser<'_, '_>, - ) -> Result<(), XdgPopupError> { - match request { - DESTROY => self.destroy(parser)?, - GRAB => self.grab(parser)?, - REPOSITION => self.reposition(parser)?, - _ => unreachable!(), } Ok(()) } @@ -171,7 +235,13 @@ impl XdgPopup { } } -handle_request!(XdgPopup); +handle_request! { + XdgPopup, XdgPopupError; + + DESTROY => destroy, + GRAB => grab, + REPOSITION => reposition, +} impl Object for XdgPopup { fn id(&self) -> ObjectId { @@ -183,7 +253,11 @@ impl Object for XdgPopup { } fn num_requests(&self) -> u32 { - REPOSITION + 1 + let last_req = match self.xdg.base.version { + 0..=2 => GRAB, + _ => REPOSITION, + }; + last_req + 1 } fn break_loops(&self) { @@ -228,6 +302,10 @@ impl Node for XdgPopup { seat.enter_popup(&self); } + fn pointer_target(&self, seat: &Rc) { + seat.set_known_cursor(KnownCursor::Default); + } + fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { renderer.render_xdg_surface(&self.xdg, x, y) } @@ -244,7 +322,7 @@ impl Node for XdgPopup { impl XdgSurfaceExt for XdgPopup { fn initial_configure(self: Rc) -> Result<(), XdgSurfaceError> { if let Some(parent) = self.parent.get() { - self.update_relative_position(&parent)?; + self.update_position(&parent)?; let rel = self.relative_position.get(); self.xdg.surface.client.event(self.configure( rel.x1(), @@ -252,10 +330,6 @@ impl XdgSurfaceExt for XdgPopup { rel.width(), rel.height(), )); - let parent = parent.absolute_desired_extents.get(); - self.xdg - .absolute_desired_extents - .set(rel.move_(parent.x1(), parent.y1())); } Ok(()) } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel/mod.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel/mod.rs index 40266caa..89da70e4 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel/mod.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel/mod.rs @@ -21,6 +21,7 @@ use std::cell::{Cell, RefCell}; use std::mem; use std::rc::Rc; pub use types::*; +use crate::cursor::KnownCursor; const DESTROY: u32 = 0; const SET_PARENT: u32 = 1; @@ -395,6 +396,10 @@ impl Node for XdgToplevel { seat.enter_toplevel(&self); } + fn pointer_target(&self, seat: &Rc) { + seat.set_known_cursor(KnownCursor::Default); + } + fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { renderer.render_xdg_surface(&self.xdg, x, y) } diff --git a/src/ifs/xdg_positioner/mod.rs b/src/ifs/xdg_positioner/mod.rs index f7b3df42..d52ffb58 100644 --- a/src/ifs/xdg_positioner/mod.rs +++ b/src/ifs/xdg_positioner/mod.rs @@ -35,7 +35,7 @@ const BOTTOM_RIGHT: u32 = 8; bitflags::bitflags! { #[derive(Default)] - pub struct Square: u32 { + pub struct Edge: u32 { const TOP = 1 << 0; const BOTTOM = 1 << 1; const LEFT = 1 << 2; @@ -43,18 +43,18 @@ bitflags::bitflags! { } } -impl Square { +impl Edge { fn from_enum(e: u32) -> Option { let s = match e { - NONE => Square::empty(), - TOP => Square::TOP, - BOTTOM => Square::BOTTOM, - LEFT => Square::LEFT, - RIGHT => Square::RIGHT, - TOP_LEFT => Square::TOP | Square::LEFT, - BOTTOM_LEFT => Square::BOTTOM | Square::LEFT, - TOP_RIGHT => Square::TOP | Square::RIGHT, - BOTTOM_RIGHT => Square::BOTTOM | Square::RIGHT, + NONE => Edge::empty(), + TOP => Edge::TOP, + BOTTOM => Edge::BOTTOM, + LEFT => Edge::LEFT, + RIGHT => Edge::RIGHT, + TOP_LEFT => Edge::TOP | Edge::LEFT, + BOTTOM_LEFT => Edge::BOTTOM | Edge::LEFT, + TOP_RIGHT => Edge::TOP | Edge::RIGHT, + BOTTOM_RIGHT => Edge::BOTTOM | Edge::RIGHT, _ => return None, }; Some(s) @@ -88,8 +88,8 @@ pub struct XdgPositioned { pub size_width: i32, pub size_height: i32, pub ar: Rect, - pub anchor: Square, - pub gravity: Square, + pub anchor: Edge, + pub gravity: Edge, pub ca: CA, pub off_x: i32, pub off_y: i32, @@ -104,35 +104,46 @@ impl XdgPositioned { self.size_height != 0 && self.size_width != 0 } - pub fn get_position(&self) -> Rect { + pub fn get_position(&self, flip_x: bool, flip_y: bool) -> Rect { + let mut anchor = self.anchor; + let mut gravity = self.gravity; + if flip_x { + anchor ^= Edge::LEFT | Edge::RIGHT; + gravity ^= Edge::LEFT | Edge::RIGHT; + } + if flip_y { + anchor ^= Edge::TOP | Edge::BOTTOM; + gravity ^= Edge::TOP | Edge::BOTTOM; + } + let mut x1 = self.off_x; let mut y1 = self.off_x; - if self.anchor.contains(Square::LEFT) { + if anchor.contains(Edge::LEFT) { x1 += self.ar.x1(); - } else if self.anchor.contains(Square::RIGHT) { + } else if anchor.contains(Edge::RIGHT) { x1 += self.ar.x2(); } else { x1 += self.ar.x1() + self.ar.width() / 2; } - if self.anchor.contains(Square::TOP) { + if anchor.contains(Edge::TOP) { y1 += self.ar.y1(); - } else if self.anchor.contains(Square::BOTTOM) { + } else if anchor.contains(Edge::BOTTOM) { y1 += self.ar.y2(); } else { y1 += self.ar.y1() + self.ar.height() / 2; } - if self.gravity.contains(Square::LEFT) { + if gravity.contains(Edge::LEFT) { x1 -= self.size_width; - } else if !self.gravity.contains(Square::RIGHT) { + } else if !gravity.contains(Edge::RIGHT) { x1 -= self.size_width / 2; } - if self.gravity.contains(Square::TOP) { + if gravity.contains(Edge::TOP) { y1 -= self.size_height; - } else if !self.gravity.contains(Square::BOTTOM) { + } else if !gravity.contains(Edge::BOTTOM) { y1 -= self.size_height / 2; } @@ -193,7 +204,7 @@ impl XdgPositioner { fn set_anchor(&self, parser: MsgParser<'_, '_>) -> Result<(), SetAnchorError> { let req: SetAnchor = self.client.parse(self, parser)?; - let anchor = match Square::from_enum(req.anchor) { + let anchor = match Edge::from_enum(req.anchor) { Some(a) => a, _ => return Err(SetAnchorError::UnknownAnchor(req.anchor)), }; @@ -203,7 +214,7 @@ impl XdgPositioner { fn set_gravity(&self, parser: MsgParser<'_, '_>) -> Result<(), SetGravityError> { let req: SetGravity = self.client.parse(self, parser)?; - let gravity = match Square::from_enum(req.gravity) { + let gravity = match Edge::from_enum(req.gravity) { Some(a) => a, _ => return Err(SetGravityError::UnknownGravity(req.gravity)), }; diff --git a/src/macros.rs b/src/macros.rs index de639e76..83ec8843 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -24,6 +24,31 @@ macro_rules! handle_request { } } }; + ($oname:ty, $ename:ty; $($code:ident => $f:ident,)*) => { + impl crate::object::ObjectHandleRequest for $oname { + fn handle_request( + self: std::rc::Rc, + request: u32, + parser: crate::utils::buffd::MsgParser<'_, '_>, + ) -> Result<(), crate::client::ClientError> { + fn handle_request( + slf: std::rc::Rc<$oname>, + request: u32, + parser: crate::utils::buffd::MsgParser<'_, '_>, + ) -> Result<(), $ename> { + match request { + $( + $code => slf.$f(parser)?, + )* + _ => unreachable!(), + } + Ok(()) + } + handle_request(self, request, parser)?; + Ok(()) + } + } + }; } macro_rules! bind { diff --git a/src/main.rs b/src/main.rs index 6c2cf001..06f496e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,6 +53,7 @@ mod backend; mod backends; mod client; mod clientmem; +mod cursor; mod drm; mod event_loop; mod fixed; @@ -116,6 +117,7 @@ fn main_() -> Result<(), MainError> { eng: engine.clone(), el: el.clone(), render_ctx: Default::default(), + cursors: Default::default(), wheel, clients: Clients::new(), next_name: NumCell::new(1), diff --git a/src/rect.rs b/src/rect.rs index f1226540..fb721f54 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -1,4 +1,6 @@ -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +use std::fmt::{Debug, Formatter}; + +#[derive(Copy, Clone, Eq, PartialEq, Default)] pub struct Rect { x1: i32, y1: i32, @@ -6,6 +8,41 @@ pub struct Rect { y2: i32, } +impl Debug for Rect { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Rect") + .field("x1", &self.x1) + .field("y1", &self.y1) + .field("x2", &self.x2) + .field("y2", &self.y2) + .field("width", &(self.x2 - self.x1)) + .field("height", &(self.y2 - self.y1)) + .finish() + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +pub struct RectOverflow { + pub left: i32, + pub right: i32, + pub top: i32, + pub bottom: i32, +} + +impl RectOverflow { + pub fn is_contained(&self) -> bool { + self.left <= 0 && self.right <= 0 && self.top <= 0 && self.bottom <= 0 + } + + pub fn x_overflow(&self) -> bool { + self.left > 0 || self.right > 0 + } + + pub fn y_overflow(&self) -> bool { + self.top > 0 || self.bottom > 0 + } +} + impl Rect { pub fn new_empty(x: i32, y: i32) -> Self { Self { @@ -64,6 +101,15 @@ impl Rect { self.x1 <= rect.x1 && self.y1 <= rect.x1 && rect.x2 <= self.x2 && rect.y2 <= self.y2 } + pub fn get_overflow(&self, child: &Self) -> RectOverflow { + RectOverflow { + left: self.x1 - child.x1, + right: child.x2 - self.x2, + top: self.y1 - child.y1, + bottom: child.y2 - self.y2, + } + } + pub fn is_empty(&self) -> bool { self.x1 == self.x2 || self.y1 == self.y2 } diff --git a/src/render/renderer/framebuffer.rs b/src/render/renderer/framebuffer.rs index 1822d846..464af530 100644 --- a/src/render/renderer/framebuffer.rs +++ b/src/render/renderer/framebuffer.rs @@ -39,10 +39,11 @@ impl Framebuffer { let seats = state.globals.lock_seats(); for seat in seats.values() { if let Some(cursor) = seat.get_cursor() { + cursor.tick(); let extents = cursor.extents(); if extents.intersects(&rect) { let (x, y) = rect.translate(extents.x1(), extents.y1()); - renderer.render_surface(cursor.surface(), x, y); + cursor.render(&mut renderer, x, y); } } } diff --git a/src/render/renderer/renderer.rs b/src/render/renderer/renderer.rs index 7c9b3c43..2adabed8 100644 --- a/src/render/renderer/renderer.rs +++ b/src/render/renderer/renderer.rs @@ -18,6 +18,8 @@ use crate::tree::{ use std::ops::Deref; use std::rc::Rc; use std::slice; +use crate::format::Format; +use crate::render::Texture; const NON_COLOR: (f32, f32, f32) = (0.2, 0.2, 0.2); const CHILD_COLOR: (f32, f32, f32) = (0.8, 0.8, 0.8); @@ -218,10 +220,12 @@ impl Renderer<'_> { } pub fn render_buffer(&mut self, buffer: &WlBuffer, x: i32, y: i32) { - let texture = match buffer.texture.get() { - Some(t) => t, - _ => return, - }; + if let Some(tex) = buffer.texture.get() { + self.render_texture(&tex, x, y, buffer.format); + } + } + + pub fn render_texture(&mut self, texture: &Texture, x: i32, y: i32, format: &Format) { assert!(Rc::ptr_eq(&self.ctx.ctx, &texture.ctx.ctx)); unsafe { glActiveTexture(GL_TEXTURE0); @@ -229,7 +233,7 @@ impl Renderer<'_> { glBindTexture(GL_TEXTURE_2D, texture.gl.tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - let prog = match buffer.format.has_alpha { + let prog = match format.has_alpha { true => { glEnable(GL_BLEND); &self.ctx.tex_alpha_prog diff --git a/src/state.rs b/src/state.rs index 3d3dc631..27f41aeb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,15 +14,17 @@ use crate::utils::copyhashmap::CopyHashMap; use crate::utils::linkedlist::LinkedList; use crate::utils::numcell::NumCell; use crate::utils::queue::AsyncQueue; -use crate::Wheel; +use crate::{ErrorFmt, Wheel}; use ahash::AHashMap; use std::cell::{Cell, RefCell}; use std::rc::Rc; +use crate::cursor::ServerCursors; pub struct State { pub eng: Rc, pub el: Rc, pub render_ctx: CloneCell>>, + pub cursors: CloneCell>>, pub wheel: Rc, pub clients: Clients, pub next_name: NumCell, @@ -47,6 +49,18 @@ pub struct SeatData { } impl State { + pub fn set_render_ctx(&self, ctx: &Rc) { + let cursors = match ServerCursors::load(ctx) { + Ok(c) => Some(Rc::new(c)), + Err(e) => { + log::error!("Could not load the cursors: {}", ErrorFmt(e)); + None + } + }; + self.cursors.set(cursors); + self.render_ctx.set(Some(ctx.clone())); + } + pub fn add_global(&self, global: &Rc) where Globals: AddGlobal, diff --git a/src/tree/container.rs b/src/tree/container.rs index 6d041a96..472120b4 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -1,4 +1,4 @@ -use crate::ifs::wl_seat::NodeSeatState; +use crate::ifs::wl_seat::{NodeSeatState, WlSeatGlobal}; use crate::rect::Rect; use crate::render::Renderer; use crate::tree::{FindTreeResult, FoundNode, Node, NodeId, WorkspaceNode}; @@ -8,6 +8,7 @@ use crate::{NumCell, State}; use ahash::AHashMap; use std::cell::{Cell, RefCell}; use std::rc::Rc; +use crate::cursor::KnownCursor; #[allow(dead_code)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -354,6 +355,10 @@ impl Node for ContainerNode { } } + fn pointer_target(&self, seat: &Rc) { + seat.set_known_cursor(KnownCursor::Default); + } + fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { renderer.render_container(self, x, y); } diff --git a/src/tree/mod.rs b/src/tree/mod.rs index ec3032ff..83268375 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -16,6 +16,7 @@ use std::fmt::Display; use std::ops::Deref; use std::rc::Rc; pub use workspace::*; +use crate::cursor::KnownCursor; mod container; mod workspace; @@ -126,6 +127,10 @@ pub trait Node { let _ = y; } + fn pointer_target(&self, seat: &Rc) { + let _ = seat; + } + fn motion(&self, seat: &WlSeatGlobal, x: Fixed, y: Fixed) { let _ = seat; let _ = x; @@ -249,6 +254,10 @@ impl Node for DisplayNode { } FindTreeResult::AcceptsInput } + + fn pointer_target(&self, seat: &Rc) { + seat.set_known_cursor(KnownCursor::Default); + } } tree_id!(OutputNodeId); @@ -298,6 +307,10 @@ impl Node for OutputNode { self.workspace.set(None); } + fn pointer_target(&self, seat: &Rc) { + seat.set_known_cursor(KnownCursor::Default); + } + fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { renderer.render_output(self, x, y); } @@ -376,6 +389,10 @@ impl Node for FloatNode { .set(Rect::new_sized(pos.x1(), pos.x2(), width, height).unwrap()); } + fn pointer_target(&self, seat: &Rc) { + seat.set_known_cursor(KnownCursor::Default); + } + fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { renderer.render_floating(self, x, y) } diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index 05cc32dc..94840a02 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -1,4 +1,4 @@ -use crate::ifs::wl_seat::NodeSeatState; +use crate::ifs::wl_seat::{NodeSeatState, WlSeatGlobal}; use crate::rect::Rect; use crate::render::Renderer; use crate::tree::container::ContainerNode; @@ -6,6 +6,7 @@ use crate::tree::{AbsoluteNode, FindTreeResult, FoundNode, Node, NodeId, OutputN use crate::utils::clonecell::CloneCell; use crate::utils::linkedlist::LinkedList; use std::rc::Rc; +use crate::cursor::KnownCursor; tree_id!(WorkspaceNodeId); @@ -61,6 +62,10 @@ impl Node for WorkspaceNode { self.container.set(None); } + fn pointer_target(&self, seat: &Rc) { + seat.set_known_cursor(KnownCursor::Default); + } + fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { renderer.render_workspace(self, x, y); }