mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Add cursor-shape protocol
This commit is contained in:
+275
-81
@@ -3,116 +3,310 @@ use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
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 smithay::input::pointer::{CursorIcon, CursorImageAttributes, CursorImageStatus};
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{IsAlive, Logical, Physical, Point, Transform};
|
||||
use smithay::wayland::compositor::with_states;
|
||||
use xcursor::parser::{parse_xcursor, Image};
|
||||
use xcursor::CursorTheme;
|
||||
|
||||
/// Some default looking `left_ptr` icon.
|
||||
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba");
|
||||
|
||||
type CursorCache = HashMap<i32, (TextureBuffer<GlesTexture>, Point<i32, Physical>)>;
|
||||
type XCursorCache = HashMap<(CursorIcon, i32), Option<Rc<XCursor>>>;
|
||||
|
||||
pub struct Cursor {
|
||||
images: Vec<Image>,
|
||||
size: i32,
|
||||
cache: RefCell<CursorCache>,
|
||||
pub struct CursorManager {
|
||||
theme: CursorTheme,
|
||||
size: u8,
|
||||
current_cursor: CursorImageStatus,
|
||||
named_cursor_cache: RefCell<XCursorCache>,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
/// Load the said theme as well as set the `XCURSOR_THEME` and `XCURSOR_SIZE`
|
||||
/// env variables.
|
||||
pub fn load(theme: &str, size: u8) -> Self {
|
||||
env::set_var("XCURSOR_THEME", theme);
|
||||
env::set_var("XCURSOR_SIZE", size.to_string());
|
||||
impl CursorManager {
|
||||
pub fn new(theme: &str, size: u8) -> Self {
|
||||
Self::ensure_env(theme, size);
|
||||
|
||||
let images = match load_xcursor(theme) {
|
||||
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![],
|
||||
}]
|
||||
}
|
||||
};
|
||||
let theme = CursorTheme::load(theme);
|
||||
|
||||
Self {
|
||||
images,
|
||||
size: size as i32,
|
||||
cache: Default::default(),
|
||||
theme,
|
||||
size,
|
||||
current_cursor: CursorImageStatus::default_named(),
|
||||
named_cursor_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reload the cursor theme.
|
||||
pub fn reload(&mut self, theme: &str, size: u8) {
|
||||
Self::ensure_env(theme, size);
|
||||
self.theme = CursorTheme::load(theme);
|
||||
self.size = size;
|
||||
self.named_cursor_cache.get_mut().clear();
|
||||
}
|
||||
|
||||
/// Checks if the cursor WlSurface is alive, and if not, cleans it up.
|
||||
pub fn check_cursor_image_surface_alive(&mut self) {
|
||||
if let CursorImageStatus::Surface(surface) = &self.current_cursor {
|
||||
if !surface.alive() {
|
||||
self.current_cursor = CursorImageStatus::default_named();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current rendering cursor.
|
||||
pub fn get_render_cursor(&self, scale: i32) -> RenderCursor {
|
||||
match self.current_cursor.clone() {
|
||||
CursorImageStatus::Hidden => RenderCursor::Hidden,
|
||||
CursorImageStatus::Surface(surface) => {
|
||||
let hotspot = with_states(&surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<Mutex<CursorImageAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.hotspot
|
||||
});
|
||||
|
||||
RenderCursor::Surface { hotspot, surface }
|
||||
}
|
||||
CursorImageStatus::Named(icon) => self
|
||||
.get_cursor_with_name(icon, scale)
|
||||
.map(|cursor| RenderCursor::Named {
|
||||
icon,
|
||||
scale,
|
||||
cursor,
|
||||
})
|
||||
.unwrap_or_else(|| RenderCursor::Named {
|
||||
icon: Default::default(),
|
||||
scale,
|
||||
cursor: self.get_default_cursor(scale),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_current_cursor_animated(&self, scale: i32) -> bool {
|
||||
match &self.current_cursor {
|
||||
CursorImageStatus::Hidden => false,
|
||||
CursorImageStatus::Surface(_) => false,
|
||||
CursorImageStatus::Named(icon) => self
|
||||
.get_cursor_with_name(*icon, scale)
|
||||
.unwrap_or_else(|| self.get_default_cursor(scale))
|
||||
.is_animated_cursor(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get named cursor for the given `icon` and `scale`.
|
||||
pub fn get_cursor_with_name(&self, icon: CursorIcon, scale: i32) -> Option<Rc<XCursor>> {
|
||||
self.named_cursor_cache
|
||||
.borrow_mut()
|
||||
.entry((icon, scale))
|
||||
.or_insert_with_key(|(icon, scale)| {
|
||||
let size = self.size as i32 * scale;
|
||||
let mut cursor = Self::load_xcursor(&self.theme, icon.name(), size);
|
||||
if let Err(err) = &cursor {
|
||||
warn!("error loading xcursor {}@{size}: {err:?}", icon.name());
|
||||
}
|
||||
|
||||
// The default cursor must always have a fallback.
|
||||
if *icon == CursorIcon::Default && cursor.is_err() {
|
||||
cursor = Ok(Self::fallback_cursor());
|
||||
}
|
||||
|
||||
cursor.ok().map(Rc::new)
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Get default cursor.
|
||||
pub fn get_default_cursor(&self, scale: i32) -> Rc<XCursor> {
|
||||
// The default cursor always has a fallback.
|
||||
self.get_cursor_with_name(CursorIcon::Default, scale)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Currenly used cursor_image as a cursor provider.
|
||||
pub fn cursor_image(&self) -> &CursorImageStatus {
|
||||
&self.current_cursor
|
||||
}
|
||||
|
||||
/// Set new cursor image provider.
|
||||
pub fn set_cursor_image(&mut self, cursor: CursorImageStatus) {
|
||||
self.current_cursor = cursor;
|
||||
}
|
||||
|
||||
/// Load the cursor with the given `name` from the file system picking the closest
|
||||
/// one to the given `size`.
|
||||
fn load_xcursor(theme: &CursorTheme, name: &str, size: i32) -> anyhow::Result<XCursor> {
|
||||
let _span = tracy_client::span!("load_xcursor");
|
||||
|
||||
let path = theme
|
||||
.load_icon(name)
|
||||
.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 mut images = parse_xcursor(&buf).context("error parsing cursor icon file")?;
|
||||
|
||||
let (width, height) = images
|
||||
.iter()
|
||||
.min_by_key(|image| (size - image.size as i32).abs())
|
||||
.map(|image| (image.width, image.height))
|
||||
.unwrap();
|
||||
|
||||
images.retain(move |image| image.width == width && image.height == height);
|
||||
|
||||
let animation_duration = images.iter().fold(0, |acc, image| acc + image.delay);
|
||||
|
||||
Ok(XCursor {
|
||||
images,
|
||||
animation_duration,
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the common XCURSOR env variables.
|
||||
fn ensure_env(theme: &str, size: u8) {
|
||||
env::set_var("XCURSOR_THEME", theme);
|
||||
env::set_var("XCURSOR_SIZE", size.to_string());
|
||||
}
|
||||
|
||||
fn fallback_cursor() -> XCursor {
|
||||
let images = vec![Image {
|
||||
size: 32,
|
||||
width: 64,
|
||||
height: 64,
|
||||
xhot: 1,
|
||||
yhot: 1,
|
||||
delay: 0,
|
||||
pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA),
|
||||
pixels_argb: vec![],
|
||||
}];
|
||||
|
||||
XCursor {
|
||||
images,
|
||||
animation_duration: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The cursor prepared for renderer.
|
||||
pub enum RenderCursor {
|
||||
Hidden,
|
||||
Surface {
|
||||
hotspot: Point<i32, Logical>,
|
||||
surface: WlSurface,
|
||||
},
|
||||
Named {
|
||||
icon: CursorIcon,
|
||||
scale: i32,
|
||||
cursor: Rc<XCursor>,
|
||||
},
|
||||
}
|
||||
|
||||
type TextureCache = HashMap<(CursorIcon, i32), Vec<TextureBuffer<GlesTexture>>>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CursorTextureCache {
|
||||
cache: RefCell<TextureCache>,
|
||||
}
|
||||
|
||||
impl CursorTextureCache {
|
||||
pub fn clear(&mut self) {
|
||||
self.cache.get_mut().clear();
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
&self,
|
||||
renderer: &mut GlesRenderer,
|
||||
icon: CursorIcon,
|
||||
scale: i32,
|
||||
) -> (TextureBuffer<GlesTexture>, Point<i32, Physical>) {
|
||||
cursor: &XCursor,
|
||||
idx: usize,
|
||||
) -> TextureBuffer<GlesTexture> {
|
||||
self.cache
|
||||
.borrow_mut()
|
||||
.entry(scale)
|
||||
.or_insert_with_key(|scale| {
|
||||
let _span = tracy_client::span!("create cursor texture");
|
||||
|
||||
let size = self.size * scale;
|
||||
|
||||
let nearest_image = self
|
||||
.images
|
||||
.entry((icon, scale))
|
||||
.or_insert_with(|| {
|
||||
cursor
|
||||
.frames()
|
||||
.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
|
||||
.map(|frame| {
|
||||
let _span = tracy_client::span!("create TextureBuffer");
|
||||
|
||||
TextureBuffer::from_memory(
|
||||
renderer,
|
||||
&frame.pixels_rgba,
|
||||
Fourcc::Abgr8888,
|
||||
(frame.width as i32, frame.height as i32),
|
||||
false,
|
||||
scale,
|
||||
Transform::Normal,
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.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())
|
||||
})
|
||||
.collect()
|
||||
})[idx]
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cached_hotspot(&self, scale: i32) -> Option<Point<i32, Physical>> {
|
||||
self.cache.borrow().get(&scale).map(|(_, hotspot)| *hotspot)
|
||||
// The XCursorBuffer implementation is inspired by `wayland-rs`, thus provided under MIT license.
|
||||
|
||||
/// The state of the `NamedCursor`.
|
||||
pub struct XCursor {
|
||||
/// The image for the underlying named cursor.
|
||||
images: Vec<Image>,
|
||||
/// The total duration of the animation.
|
||||
animation_duration: u32,
|
||||
}
|
||||
|
||||
impl XCursor {
|
||||
/// Given a time, calculate which frame to show, and how much time remains until the next frame.
|
||||
///
|
||||
/// Time will wrap, so if for instance the cursor has an animation lasting 100ms,
|
||||
/// then calling this function with 5ms and 105ms as input gives the same output.
|
||||
pub fn frame(&self, mut millis: u32) -> (usize, &Image) {
|
||||
if self.animation_duration == 0 {
|
||||
return (0, &self.images[0]);
|
||||
}
|
||||
|
||||
millis %= self.animation_duration;
|
||||
|
||||
let mut res = 0;
|
||||
for (i, img) in self.images.iter().enumerate() {
|
||||
if millis < img.delay {
|
||||
res = i;
|
||||
break;
|
||||
}
|
||||
millis -= img.delay;
|
||||
}
|
||||
|
||||
(res, &self.images[res])
|
||||
}
|
||||
|
||||
/// Get the frames for the given `XCursor`.
|
||||
pub fn frames(&self) -> &[Image] {
|
||||
&self.images
|
||||
}
|
||||
|
||||
/// Check whether the cursor is animated.
|
||||
pub fn is_animated_cursor(&self) -> bool {
|
||||
self.images.len() > 1
|
||||
}
|
||||
|
||||
/// Get hotspot for the given `image`.
|
||||
pub fn hotspot(image: &Image) -> Point<i32, Physical> {
|
||||
(image.xhot as i32, image.yhot as i32).into()
|
||||
}
|
||||
}
|
||||
|
||||
fn load_xcursor(theme: &str) -> anyhow::Result<Vec<Image>> {
|
||||
let _span = tracy_client::span!();
|
||||
|
||||
let theme = CursorTheme::load(theme);
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -161,7 +161,8 @@ impl CompositorHandler for State {
|
||||
self.layer_shell_handle_commit(surface);
|
||||
|
||||
// This might be a cursor surface.
|
||||
if matches!(&self.niri.cursor_image, CursorImageStatus::Surface(s) if s == surface) {
|
||||
if matches!(&self.niri.cursor_manager.cursor_image(), CursorImageStatus::Surface(s) if s == surface)
|
||||
{
|
||||
// FIXME: granular redraws for cursors.
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
|
||||
+6
-5
@@ -35,10 +35,10 @@ use smithay::wayland::session_lock::{
|
||||
LockSurface, SessionLockHandler, SessionLockManagerState, SessionLocker,
|
||||
};
|
||||
use smithay::{
|
||||
delegate_data_control, delegate_data_device, delegate_dmabuf, delegate_input_method_manager,
|
||||
delegate_output, delegate_pointer_gestures, delegate_presentation, delegate_primary_selection,
|
||||
delegate_seat, delegate_session_lock, delegate_tablet_manager, delegate_text_input_manager,
|
||||
delegate_virtual_keyboard_manager,
|
||||
delegate_cursor_shape, delegate_data_control, delegate_data_device, delegate_dmabuf,
|
||||
delegate_input_method_manager, delegate_output, delegate_pointer_gestures,
|
||||
delegate_presentation, delegate_primary_selection, delegate_seat, delegate_session_lock,
|
||||
delegate_tablet_manager, delegate_text_input_manager, delegate_virtual_keyboard_manager,
|
||||
};
|
||||
|
||||
use crate::layout::output_size;
|
||||
@@ -53,7 +53,7 @@ impl SeatHandler for State {
|
||||
}
|
||||
|
||||
fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
|
||||
self.niri.cursor_image = image;
|
||||
self.niri.cursor_manager.set_cursor_image(image);
|
||||
// FIXME: more granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
@@ -66,6 +66,7 @@ impl SeatHandler for State {
|
||||
}
|
||||
}
|
||||
delegate_seat!(State);
|
||||
delegate_cursor_shape!(State);
|
||||
delegate_tablet_manager!(State);
|
||||
delegate_pointer_gestures!(State);
|
||||
delegate_text_input_manager!(State);
|
||||
|
||||
+129
-105
@@ -4,7 +4,7 @@ use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{env, mem, thread};
|
||||
|
||||
use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as KdeDecorationsMode;
|
||||
@@ -45,13 +45,14 @@ use smithay::reexports::wayland_server::backend::{
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::reexports::wayland_server::{Display, DisplayHandle};
|
||||
use smithay::utils::{
|
||||
ClockSource, IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size, Transform,
|
||||
ClockSource, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size, Transform,
|
||||
SERIAL_COUNTER,
|
||||
};
|
||||
use smithay::wayland::compositor::{
|
||||
send_surface_state, with_states, with_surface_tree_downward, CompositorClientState,
|
||||
CompositorState, SurfaceData, TraversalAction,
|
||||
};
|
||||
use smithay::wayland::cursor_shape::CursorShapeManagerState;
|
||||
use smithay::wayland::dmabuf::DmabufFeedback;
|
||||
use smithay::wayland::input_method::InputMethodManagerState;
|
||||
use smithay::wayland::output::OutputManagerState;
|
||||
@@ -75,7 +76,7 @@ use smithay::wayland::virtual_keyboard::VirtualKeyboardManagerState;
|
||||
|
||||
use crate::backend::{Backend, RenderResult, Tty, Winit};
|
||||
use crate::config::Config;
|
||||
use crate::cursor::Cursor;
|
||||
use crate::cursor::{CursorManager, CursorTextureCache, RenderCursor, XCursor};
|
||||
#[cfg(feature = "dbus")]
|
||||
use crate::dbus::gnome_shell_screenshot::{NiriToScreenshot, ScreenshotToNiri};
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
@@ -97,6 +98,8 @@ pub struct Niri {
|
||||
pub display_handle: DisplayHandle,
|
||||
pub socket_name: OsString,
|
||||
|
||||
pub start_time: Instant,
|
||||
|
||||
// Each workspace corresponds to a Space. Each workspace generally has one Output mapped to it,
|
||||
// however it may have none (when there are no outputs connected) or mutiple (when mirroring).
|
||||
pub layout: Layout<Window>,
|
||||
@@ -139,8 +142,9 @@ pub struct Niri {
|
||||
/// Scancodes of the keys to suppress.
|
||||
pub suppressed_keys: HashSet<u32>,
|
||||
|
||||
pub default_cursor: Cursor,
|
||||
pub cursor_image: CursorImageStatus,
|
||||
pub cursor_manager: CursorManager,
|
||||
pub cursor_texture_cache: CursorTextureCache,
|
||||
pub cursor_shape_manager_state: CursorShapeManagerState,
|
||||
pub dnd_icon: Option<WlSurface>,
|
||||
pub pointer_focus: Option<PointerFocus>,
|
||||
|
||||
@@ -268,7 +272,7 @@ impl State {
|
||||
|
||||
// These should be called periodically, before flushing the clients.
|
||||
self.niri.layout.refresh();
|
||||
self.niri.check_cursor_image_surface_alive();
|
||||
self.niri.cursor_manager.check_cursor_image_surface_alive();
|
||||
self.niri.refresh_pointer_outputs();
|
||||
self.niri.popups.cleanup();
|
||||
self.update_focus();
|
||||
@@ -393,8 +397,10 @@ impl State {
|
||||
let mut old_config = self.niri.config.borrow_mut();
|
||||
|
||||
if config.cursor != old_config.cursor {
|
||||
self.niri.default_cursor =
|
||||
Cursor::load(&config.cursor.xcursor_theme, config.cursor.xcursor_size);
|
||||
self.niri
|
||||
.cursor_manager
|
||||
.reload(&config.cursor.xcursor_theme, config.cursor.xcursor_size);
|
||||
self.niri.cursor_texture_cache.clear();
|
||||
}
|
||||
|
||||
*old_config = config;
|
||||
@@ -568,8 +574,9 @@ impl Niri {
|
||||
.unwrap();
|
||||
seat.add_pointer();
|
||||
|
||||
let default_cursor =
|
||||
Cursor::load(&config_.cursor.xcursor_theme, config_.cursor.xcursor_size);
|
||||
let cursor_shape_manager_state = CursorShapeManagerState::new::<State>(&display_handle);
|
||||
let cursor_manager =
|
||||
CursorManager::new(&config_.cursor.xcursor_theme, config_.cursor.xcursor_size);
|
||||
|
||||
let socket_source = ListeningSocketSource::new_auto().unwrap();
|
||||
let socket_name = socket_source.socket_name().to_os_string();
|
||||
@@ -607,8 +614,9 @@ impl Niri {
|
||||
|
||||
event_loop,
|
||||
stop_signal,
|
||||
display_handle,
|
||||
socket_name,
|
||||
display_handle,
|
||||
start_time: Instant::now(),
|
||||
|
||||
layout,
|
||||
global_space: Space::default(),
|
||||
@@ -639,8 +647,9 @@ impl Niri {
|
||||
presentation_state,
|
||||
|
||||
seat,
|
||||
default_cursor,
|
||||
cursor_image: CursorImageStatus::default_named(),
|
||||
cursor_manager,
|
||||
cursor_texture_cache: Default::default(),
|
||||
cursor_shape_manager_state,
|
||||
dnd_icon: None,
|
||||
pointer_focus: None,
|
||||
|
||||
@@ -1085,51 +1094,60 @@ impl Niri {
|
||||
output: &Output,
|
||||
) -> Vec<OutputRenderElements<GlesRenderer>> {
|
||||
let _span = tracy_client::span!("Niri::pointer_element");
|
||||
|
||||
let output_scale = Scale::from(output.current_scale().fractional_scale());
|
||||
let output_scale = output.current_scale();
|
||||
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 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);
|
||||
// Get the render cursor to draw.
|
||||
let cursor_scale = output_scale.integer_scale();
|
||||
let render_cursor = self.cursor_manager.get_render_cursor(cursor_scale);
|
||||
|
||||
let hotspot = if let CursorImageStatus::Surface(surface) = &self.cursor_image {
|
||||
with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<Mutex<CursorImageAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.hotspot
|
||||
})
|
||||
} else {
|
||||
default_hotspot
|
||||
};
|
||||
let pointer_pos = (pointer_pos - hotspot.to_f64()).to_physical_precise_round(output_scale);
|
||||
let output_scale = Scale::from(output.current_scale().fractional_scale());
|
||||
|
||||
let mut pointer_elements = match &self.cursor_image {
|
||||
CursorImageStatus::Hidden => vec![],
|
||||
CursorImageStatus::Surface(surface) => render_elements_from_surface_tree(
|
||||
renderer,
|
||||
surface,
|
||||
pointer_pos,
|
||||
output_scale,
|
||||
1.,
|
||||
Kind::Cursor,
|
||||
),
|
||||
// Default shape catch-all
|
||||
_ => vec![OutputRenderElements::DefaultPointer(
|
||||
TextureRenderElement::from_texture_buffer(
|
||||
pointer_pos.to_f64(),
|
||||
&default_buffer,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
let (mut pointer_elements, pointer_pos) = match render_cursor {
|
||||
RenderCursor::Hidden => (vec![], pointer_pos.to_physical_precise_round(output_scale)),
|
||||
RenderCursor::Surface { surface, hotspot } => {
|
||||
let pointer_pos =
|
||||
(pointer_pos - hotspot.to_f64()).to_physical_precise_round(output_scale);
|
||||
|
||||
let pointer_elements = render_elements_from_surface_tree(
|
||||
renderer,
|
||||
&surface,
|
||||
pointer_pos,
|
||||
output_scale,
|
||||
1.,
|
||||
Kind::Cursor,
|
||||
),
|
||||
)],
|
||||
);
|
||||
|
||||
(pointer_elements, pointer_pos)
|
||||
}
|
||||
RenderCursor::Named {
|
||||
icon,
|
||||
scale,
|
||||
cursor,
|
||||
} => {
|
||||
let (idx, frame) = cursor.frame(self.start_time.elapsed().as_millis() as u32);
|
||||
let hotspot = XCursor::hotspot(frame).to_logical(scale);
|
||||
let pointer_pos =
|
||||
(pointer_pos - hotspot.to_f64()).to_physical_precise_round(output_scale);
|
||||
|
||||
let texture = self
|
||||
.cursor_texture_cache
|
||||
.get(renderer, icon, scale, &cursor, idx);
|
||||
|
||||
let pointer_elements = vec![OutputRenderElements::NamedPointer(
|
||||
TextureRenderElement::from_texture_buffer(
|
||||
pointer_pos.to_f64(),
|
||||
&texture,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Kind::Cursor,
|
||||
),
|
||||
)];
|
||||
|
||||
(pointer_elements, pointer_pos)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(dnd_icon) = &self.dnd_icon {
|
||||
@@ -1146,56 +1164,11 @@ impl Niri {
|
||||
pointer_elements
|
||||
}
|
||||
|
||||
pub fn check_cursor_image_surface_alive(&mut self) {
|
||||
if let CursorImageStatus::Surface(surface) = &self.cursor_image {
|
||||
if !surface.alive() {
|
||||
self.cursor_image = CursorImageStatus::default_named();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_pointer_outputs(&self) {
|
||||
pub fn refresh_pointer_outputs(&mut self) {
|
||||
let _span = tracy_client::span!("Niri::refresh_pointer_outputs");
|
||||
|
||||
match &self.cursor_image {
|
||||
CursorImageStatus::Hidden | CursorImageStatus::Named(_) => {
|
||||
// There's no cursor surface, but there might be a DnD icon.
|
||||
let Some(surface) = &self.dnd_icon else {
|
||||
return;
|
||||
};
|
||||
|
||||
let pointer_pos = self.seat.get_pointer().unwrap().current_location();
|
||||
|
||||
let mut dnd_scale = 1;
|
||||
for output in self.global_space.outputs() {
|
||||
let geo = self.global_space.output_geometry(output).unwrap();
|
||||
|
||||
// The default cursor is rendered at the right scale for each output, which
|
||||
// means that it may have a different hotspot for each output.
|
||||
let output_scale = output.current_scale().integer_scale();
|
||||
let Some(hotspot) = self.default_cursor.get_cached_hotspot(output_scale) else {
|
||||
// Oh well; it'll get cached next time we render.
|
||||
continue;
|
||||
};
|
||||
let hotspot = hotspot.to_logical(output_scale);
|
||||
|
||||
let surface_pos = pointer_pos.to_i32_round() - hotspot;
|
||||
let bbox = bbox_from_surface_tree(surface, surface_pos);
|
||||
|
||||
if let Some(mut overlap) = geo.intersection(bbox) {
|
||||
overlap.loc -= surface_pos;
|
||||
dnd_scale = dnd_scale.max(output.current_scale().integer_scale());
|
||||
output_update(output, Some(overlap), surface);
|
||||
} else {
|
||||
output_update(output, None, surface);
|
||||
}
|
||||
|
||||
with_states(surface, |data| {
|
||||
send_surface_state(surface, data, dnd_scale, Transform::Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
CursorImageStatus::Surface(surface) => {
|
||||
match self.cursor_manager.cursor_image().clone() {
|
||||
CursorImageStatus::Surface(ref surface) => {
|
||||
let hotspot = with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
@@ -1252,6 +1225,52 @@ impl Niri {
|
||||
});
|
||||
}
|
||||
}
|
||||
cursor_image => {
|
||||
// There's no cursor surface, but there might be a DnD icon.
|
||||
let Some(surface) = &self.dnd_icon else {
|
||||
return;
|
||||
};
|
||||
|
||||
let icon = if let CursorImageStatus::Named(icon) = cursor_image {
|
||||
icon
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let pointer_pos = self.seat.get_pointer().unwrap().current_location();
|
||||
|
||||
let mut dnd_scale = 1;
|
||||
for output in self.global_space.outputs() {
|
||||
let geo = self.global_space.output_geometry(output).unwrap();
|
||||
|
||||
// The default cursor is rendered at the right scale for each output, which
|
||||
// means that it may have a different hotspot for each output.
|
||||
let output_scale = output.current_scale().integer_scale();
|
||||
let cursor = self
|
||||
.cursor_manager
|
||||
.get_cursor_with_name(icon, output_scale)
|
||||
.unwrap_or_else(|| self.cursor_manager.get_default_cursor(output_scale));
|
||||
|
||||
// For simplicity, we always use frame 0 for this computation. Let's hope the
|
||||
// hotspot doesn't change between frames.
|
||||
let hotspot = XCursor::hotspot(&cursor.frames()[0]).to_logical(output_scale);
|
||||
|
||||
let surface_pos = pointer_pos.to_i32_round() - hotspot;
|
||||
let bbox = bbox_from_surface_tree(surface, surface_pos);
|
||||
|
||||
if let Some(mut overlap) = geo.intersection(bbox) {
|
||||
overlap.loc -= surface_pos;
|
||||
dnd_scale = dnd_scale.max(output.current_scale().integer_scale());
|
||||
output_update(output, Some(overlap), surface);
|
||||
} else {
|
||||
output_update(output, None, surface);
|
||||
}
|
||||
|
||||
with_states(surface, |data| {
|
||||
send_surface_state(surface, data, dnd_scale, Transform::Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1414,6 +1433,11 @@ impl Niri {
|
||||
.unwrap()
|
||||
.are_animations_ongoing();
|
||||
|
||||
// Also keep redrawing if the current cursor is animated.
|
||||
state.unfinished_animations_remain |= self
|
||||
.cursor_manager
|
||||
.is_current_cursor_animated(output.current_scale().integer_scale());
|
||||
|
||||
// Render the elements.
|
||||
let elements = self.render(renderer, output, true);
|
||||
|
||||
@@ -1495,7 +1519,7 @@ impl Niri {
|
||||
//
|
||||
// While we only have cursors and DnD icons crossing output boundaries though, it doesn't
|
||||
// matter all that much.
|
||||
if let CursorImageStatus::Surface(surface) = &self.cursor_image {
|
||||
if let CursorImageStatus::Surface(surface) = &self.cursor_manager.cursor_image() {
|
||||
with_surface_tree_downward(
|
||||
surface,
|
||||
(),
|
||||
@@ -1614,7 +1638,7 @@ impl Niri {
|
||||
);
|
||||
}
|
||||
|
||||
if let CursorImageStatus::Surface(surface) = &self.cursor_image {
|
||||
if let CursorImageStatus::Surface(surface) = &self.cursor_manager.cursor_image() {
|
||||
send_dmabuf_feedback_surface_tree(
|
||||
surface,
|
||||
output,
|
||||
@@ -1690,7 +1714,7 @@ impl Niri {
|
||||
send_frames_surface_tree(surface, output, frame_callback_time, None, should_send);
|
||||
}
|
||||
|
||||
if let CursorImageStatus::Surface(surface) = &self.cursor_image {
|
||||
if let CursorImageStatus::Surface(surface) = self.cursor_manager.cursor_image() {
|
||||
send_frames_surface_tree(surface, output, frame_callback_time, None, should_send);
|
||||
}
|
||||
}
|
||||
@@ -1702,7 +1726,7 @@ impl Niri {
|
||||
) -> OutputPresentationFeedback {
|
||||
let mut feedback = OutputPresentationFeedback::new(output);
|
||||
|
||||
if let CursorImageStatus::Surface(surface) = &self.cursor_image {
|
||||
if let CursorImageStatus::Surface(surface) = &self.cursor_manager.cursor_image() {
|
||||
take_presentation_feedback_surface_tree(
|
||||
surface,
|
||||
&mut feedback,
|
||||
@@ -2037,7 +2061,7 @@ render_elements! {
|
||||
pub OutputRenderElements<R> where R: ImportAll;
|
||||
Monitor = MonitorRenderElement<R>,
|
||||
Wayland = WaylandSurfaceRenderElement<R>,
|
||||
DefaultPointer = TextureRenderElement<<R as Renderer>::TextureId>,
|
||||
NamedPointer = TextureRenderElement<<R as Renderer>::TextureId>,
|
||||
SolidColor = SolidColorRenderElement,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user