diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2023-10-01 07:59:28 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2023-10-01 08:03:12 +0400 |
| commit | 8d443c2e841505dd9a09d50dbb24a2a5956ecbd7 (patch) | |
| tree | 09d98ab7566dc72a34842c5f1083355e62e2eca3 | |
| parent | d39da3f46112395e5e359efd756e9c510c03e243 (diff) | |
| download | niri-8d443c2e841505dd9a09d50dbb24a2a5956ecbd7.tar.gz niri-8d443c2e841505dd9a09d50dbb24a2a5956ecbd7.tar.bz2 niri-8d443c2e841505dd9a09d50dbb24a2a5956ecbd7.zip | |
Make default cursor respect output scale
First part of https://github.com/YaLTeR/niri/issues/16
| -rw-r--r-- | src/cursor.rs | 103 | ||||
| -rw-r--r-- | src/main.rs | 1 | ||||
| -rw-r--r-- | src/niri.rs | 20 | ||||
| -rw-r--r-- | src/utils.rs | 74 |
4 files changed, 118 insertions, 80 deletions
diff --git a/src/cursor.rs b/src/cursor.rs new file mode 100644 index 00000000..454cd99b --- /dev/null +++ b/src/cursor.rs @@ -0,0 +1,103 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; + +use anyhow::{anyhow, Context}; +use smithay::backend::allocator::Fourcc; +use smithay::backend::renderer::element::texture::TextureBuffer; +use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture}; +use smithay::utils::{Physical, Point, Transform}; +use xcursor::parser::{parse_xcursor, Image}; +use xcursor::CursorTheme; + +const CURSOR_SIZE: i32 = 24; +static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba"); + +pub struct Cursor { + images: Vec<Image>, + cache: HashMap<i32, (TextureBuffer<GlesTexture>, Point<i32, Physical>)>, +} + +impl Cursor { + pub fn load() -> Self { + let images = match load_xcursor() { + Ok(images) => images, + Err(err) => { + warn!("error loading xcursor default cursor: {err:?}"); + + vec![Image { + size: 32, + width: 64, + height: 64, + xhot: 1, + yhot: 1, + delay: 1, + pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA), + pixels_argb: vec![], + }] + } + }; + + Self { + images, + cache: HashMap::new(), + } + } + + pub fn get( + &mut self, + renderer: &mut GlesRenderer, + scale: i32, + ) -> (TextureBuffer<GlesTexture>, Point<i32, Physical>) { + self.cache + .entry(scale) + .or_insert_with_key(|scale| { + let _span = tracy_client::span!("create cursor texture"); + + let size = CURSOR_SIZE * scale; + + let nearest_image = self + .images + .iter() + .min_by_key(|image| (size - image.size as i32).abs()) + .unwrap(); + let frame = self + .images + .iter() + .find(move |image| { + image.width == nearest_image.width && image.height == nearest_image.height + }) + .unwrap(); + + let texture = TextureBuffer::from_memory( + renderer, + &frame.pixels_rgba, + Fourcc::Abgr8888, + (frame.width as i32, frame.height as i32), + false, + *scale, + Transform::Normal, + None, + ) + .unwrap(); + (texture, (frame.xhot as i32, frame.yhot as i32).into()) + }) + .clone() + } +} + +fn load_xcursor() -> anyhow::Result<Vec<Image>> { + let _span = tracy_client::span!(); + + let theme = CursorTheme::load("default"); + let path = theme + .load_icon("default") + .ok_or_else(|| anyhow!("no default icon"))?; + let mut file = File::open(path).context("error opening cursor icon file")?; + let mut buf = vec![]; + file.read_to_end(&mut buf) + .context("error reading cursor icon file")?; + let images = parse_xcursor(&buf).context("error parsing cursor icon file")?; + + Ok(images) +} diff --git a/src/main.rs b/src/main.rs index 0aa05921..4e994d9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ extern crate tracing; mod animation; mod backend; mod config; +mod cursor; mod dbus; mod frame_clock; mod handlers; diff --git a/src/niri.rs b/src/niri.rs index eea0a2c0..dda9cd29 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -15,7 +15,7 @@ use smithay::backend::allocator::Fourcc; use smithay::backend::renderer::element::surface::{ render_elements_from_surface_tree, WaylandSurfaceRenderElement, }; -use smithay::backend::renderer::element::texture::{TextureBuffer, TextureRenderElement}; +use smithay::backend::renderer::element::texture::TextureRenderElement; use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement}; use smithay::backend::renderer::element::{ render_elements, AsRenderElements, Kind, RenderElement, RenderElementStates, @@ -71,6 +71,7 @@ use zbus::fdo::RequestNameFlags; use crate::backend::{Backend, Tty, Winit}; use crate::config::Config; +use crate::cursor::Cursor; use crate::dbus::gnome_shell_screenshot::{self, NiriToScreenshot, ScreenshotToNiri}; use crate::dbus::mutter_display_config::DisplayConfig; #[cfg(feature = "xdp-gnome-screencast")] @@ -79,7 +80,7 @@ use crate::dbus::mutter_service_channel::ServiceChannel; use crate::frame_clock::FrameClock; use crate::layout::{output_size, MonitorRenderElement, MonitorSet}; use crate::pw_utils::{Cast, PipeWire}; -use crate::utils::{center, get_monotonic_time, load_default_cursor, make_screenshot_path}; +use crate::utils::{center, get_monotonic_time, make_screenshot_path}; pub struct Niri { pub config: Rc<RefCell<Config>>, @@ -122,7 +123,7 @@ pub struct Niri { pub seat: Seat<State>, - pub pointer_buffer: Option<(TextureBuffer<GlesTexture>, Point<i32, Physical>)>, + pub default_cursor: Cursor, pub cursor_image: CursorImageStatus, pub dnd_icon: Option<WlSurface>, @@ -328,6 +329,8 @@ impl Niri { .unwrap(); seat.add_pointer(); + let default_cursor = Cursor::load(); + let socket_source = ListeningSocketSource::new_auto().unwrap(); let socket_name = socket_source.socket_name().to_os_string(); event_loop @@ -675,7 +678,7 @@ impl Niri { presentation_state, seat, - pointer_buffer: None, + default_cursor, cursor_image: CursorImageStatus::Default, dnd_icon: None, @@ -985,10 +988,9 @@ impl Niri { let output_pos = self.global_space.output_geometry(output).unwrap().loc; let pointer_pos = self.seat.get_pointer().unwrap().current_location() - output_pos.to_f64(); - let (default_buffer, default_hotspot) = self - .pointer_buffer - .get_or_insert_with(|| load_default_cursor(renderer)); - let default_hotspot = default_hotspot.to_logical(1); + let output_scale_int = output.current_scale().integer_scale(); + let (default_buffer, default_hotspot) = self.default_cursor.get(renderer, output_scale_int); + let default_hotspot = default_hotspot.to_logical(output_scale_int); let hotspot = if let CursorImageStatus::Surface(surface) = &mut self.cursor_image { if surface.alive() { @@ -1015,7 +1017,7 @@ impl Niri { CursorImageStatus::Default => vec![OutputRenderElements::DefaultPointer( TextureRenderElement::from_texture_buffer( pointer_pos.to_f64(), - default_buffer, + &default_buffer, None, None, None, diff --git a/src/utils.rs b/src/utils.rs index f1aca8b2..7113bc92 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,24 +1,15 @@ use std::ffi::OsStr; -use std::fs::File; -use std::io::{self, Read}; +use std::io; use std::os::unix::process::CommandExt; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::time::Duration; -use anyhow::{anyhow, Context}; +use anyhow::Context; use directories::UserDirs; use nix::time::{clock_gettime, ClockId}; -use smithay::backend::allocator::Fourcc; -use smithay::backend::renderer::element::texture::TextureBuffer; -use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture}; -use smithay::utils::{Logical, Physical, Point, Rectangle, Transform}; +use smithay::utils::{Logical, Point, Rectangle}; use time::OffsetDateTime; -use xcursor::parser::parse_xcursor; -use xcursor::CursorTheme; - -const CURSOR_SIZE: u32 = 24; -static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba"); pub fn get_monotonic_time() -> Duration { Duration::from(clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap()) @@ -28,65 +19,6 @@ pub fn center(rect: Rectangle<i32, Logical>) -> Point<i32, Logical> { rect.loc + rect.size.downscale(2).to_point() } -fn load_xcursor() -> anyhow::Result<xcursor::parser::Image> { - let theme = CursorTheme::load("default"); - let path = theme - .load_icon("default") - .ok_or_else(|| anyhow!("no default icon"))?; - let mut file = File::open(path).context("error opening cursor icon file")?; - let mut buf = vec![]; - file.read_to_end(&mut buf) - .context("error reading cursor icon file")?; - let images = parse_xcursor(&buf).context("error parsing cursor icon file")?; - - let nearest_image = images - .iter() - .min_by_key(|image| (CURSOR_SIZE as i32 - image.size as i32).abs()) - .unwrap(); - let frame = images - .iter() - .find(move |image| { - image.width == nearest_image.width && image.height == nearest_image.height - }) - .unwrap(); - Ok(frame.clone()) -} - -pub fn load_default_cursor( - renderer: &mut GlesRenderer, -) -> (TextureBuffer<GlesTexture>, Point<i32, Physical>) { - let frame = match load_xcursor() { - Ok(frame) => frame, - Err(err) => { - warn!("error loading xcursor default cursor: {err:?}"); - - xcursor::parser::Image { - size: 32, - width: 64, - height: 64, - xhot: 1, - yhot: 1, - delay: 1, - pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA), - pixels_argb: vec![], - } - } - }; - - let texture = TextureBuffer::from_memory( - renderer, - &frame.pixels_rgba, - Fourcc::Abgr8888, - (frame.width as i32, frame.height as i32), - false, - 1, - Transform::Normal, - None, - ) - .unwrap(); - (texture, (frame.xhot as i32, frame.yhot as i32).into()) -} - pub fn make_screenshot_path() -> anyhow::Result<PathBuf> { let dirs = UserDirs::new().context("error retrieving home directory")?; let mut path = dirs.picture_dir().map(|p| p.to_owned()).unwrap_or_else(|| { |
