mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Make default cursor respect output scale
First part of https://github.com/YaLTeR/niri/issues/16
This commit is contained in:
+103
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
@@ -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
@@ -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(|| {
|
||||||
|
|||||||
Reference in New Issue
Block a user