Make default cursor respect output scale

First part of https://github.com/YaLTeR/niri/issues/16
This commit is contained in:
Ivan Molodetskikh
2023-10-01 07:59:28 +04:00
parent d39da3f461
commit 8d443c2e84
4 changed files with 118 additions and 80 deletions
+103
View File
@@ -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)
}
+1
View File
@@ -4,6 +4,7 @@ extern crate tracing;
mod animation; mod animation;
mod backend; mod backend;
mod config; mod config;
mod cursor;
mod dbus; mod dbus;
mod frame_clock; mod frame_clock;
mod handlers; mod handlers;
+11 -9
View File
@@ -15,7 +15,7 @@ use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::surface::{ use smithay::backend::renderer::element::surface::{
render_elements_from_surface_tree, WaylandSurfaceRenderElement, 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::utils::{Relocate, RelocateRenderElement};
use smithay::backend::renderer::element::{ use smithay::backend::renderer::element::{
render_elements, AsRenderElements, Kind, RenderElement, RenderElementStates, render_elements, AsRenderElements, Kind, RenderElement, RenderElementStates,
@@ -71,6 +71,7 @@ use zbus::fdo::RequestNameFlags;
use crate::backend::{Backend, Tty, Winit}; use crate::backend::{Backend, Tty, Winit};
use crate::config::Config; use crate::config::Config;
use crate::cursor::Cursor;
use crate::dbus::gnome_shell_screenshot::{self, NiriToScreenshot, ScreenshotToNiri}; use crate::dbus::gnome_shell_screenshot::{self, NiriToScreenshot, ScreenshotToNiri};
use crate::dbus::mutter_display_config::DisplayConfig; use crate::dbus::mutter_display_config::DisplayConfig;
#[cfg(feature = "xdp-gnome-screencast")] #[cfg(feature = "xdp-gnome-screencast")]
@@ -79,7 +80,7 @@ use crate::dbus::mutter_service_channel::ServiceChannel;
use crate::frame_clock::FrameClock; use crate::frame_clock::FrameClock;
use crate::layout::{output_size, MonitorRenderElement, MonitorSet}; use crate::layout::{output_size, MonitorRenderElement, MonitorSet};
use crate::pw_utils::{Cast, PipeWire}; 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 struct Niri {
pub config: Rc<RefCell<Config>>, pub config: Rc<RefCell<Config>>,
@@ -122,7 +123,7 @@ pub struct Niri {
pub seat: Seat<State>, pub seat: Seat<State>,
pub pointer_buffer: Option<(TextureBuffer<GlesTexture>, Point<i32, Physical>)>, pub default_cursor: Cursor,
pub cursor_image: CursorImageStatus, pub cursor_image: CursorImageStatus,
pub dnd_icon: Option<WlSurface>, pub dnd_icon: Option<WlSurface>,
@@ -328,6 +329,8 @@ impl Niri {
.unwrap(); .unwrap();
seat.add_pointer(); seat.add_pointer();
let default_cursor = Cursor::load();
let socket_source = ListeningSocketSource::new_auto().unwrap(); let socket_source = ListeningSocketSource::new_auto().unwrap();
let socket_name = socket_source.socket_name().to_os_string(); let socket_name = socket_source.socket_name().to_os_string();
event_loop event_loop
@@ -675,7 +678,7 @@ impl Niri {
presentation_state, presentation_state,
seat, seat,
pointer_buffer: None, default_cursor,
cursor_image: CursorImageStatus::Default, cursor_image: CursorImageStatus::Default,
dnd_icon: None, dnd_icon: None,
@@ -985,10 +988,9 @@ impl Niri {
let output_pos = self.global_space.output_geometry(output).unwrap().loc; 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 pointer_pos = self.seat.get_pointer().unwrap().current_location() - output_pos.to_f64();
let (default_buffer, default_hotspot) = self let output_scale_int = output.current_scale().integer_scale();
.pointer_buffer let (default_buffer, default_hotspot) = self.default_cursor.get(renderer, output_scale_int);
.get_or_insert_with(|| load_default_cursor(renderer)); let default_hotspot = default_hotspot.to_logical(output_scale_int);
let default_hotspot = default_hotspot.to_logical(1);
let hotspot = if let CursorImageStatus::Surface(surface) = &mut self.cursor_image { let hotspot = if let CursorImageStatus::Surface(surface) = &mut self.cursor_image {
if surface.alive() { if surface.alive() {
@@ -1015,7 +1017,7 @@ impl Niri {
CursorImageStatus::Default => vec![OutputRenderElements::DefaultPointer( CursorImageStatus::Default => vec![OutputRenderElements::DefaultPointer(
TextureRenderElement::from_texture_buffer( TextureRenderElement::from_texture_buffer(
pointer_pos.to_f64(), pointer_pos.to_f64(),
default_buffer, &default_buffer,
None, None,
None, None,
None, None,
+3 -71
View File
@@ -1,24 +1,15 @@
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs::File; use std::io;
use std::io::{self, Read};
use std::os::unix::process::CommandExt; use std::os::unix::process::CommandExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::time::Duration; use std::time::Duration;
use anyhow::{anyhow, Context}; use anyhow::Context;
use directories::UserDirs; use directories::UserDirs;
use nix::time::{clock_gettime, ClockId}; use nix::time::{clock_gettime, ClockId};
use smithay::backend::allocator::Fourcc; use smithay::utils::{Logical, Point, Rectangle};
use smithay::backend::renderer::element::texture::TextureBuffer;
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
use smithay::utils::{Logical, Physical, Point, Rectangle, Transform};
use time::OffsetDateTime; 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 { pub fn get_monotonic_time() -> Duration {
Duration::from(clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap()) 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() 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> { pub fn make_screenshot_path() -> anyhow::Result<PathBuf> {
let dirs = UserDirs::new().context("error retrieving home directory")?; let dirs = UserDirs::new().context("error retrieving home directory")?;
let mut path = dirs.picture_dir().map(|p| p.to_owned()).unwrap_or_else(|| { let mut path = dirs.picture_dir().map(|p| p.to_owned()).unwrap_or_else(|| {