Impl proper xcursor support

This commit is contained in:
Ottatop 2024-06-20 21:02:25 -05:00
parent 71e4c1f159
commit 00cf02158a
11 changed files with 420 additions and 339 deletions

View file

@ -66,6 +66,9 @@ features = [
[workspace.lints.clippy]
too_many_arguments = "allow"
new_without_default = "allow"
type_complexity = "allow"
let_and_return = "allow"
########################################################################yo😎###########

View file

@ -32,11 +32,9 @@ use smithay::{
libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{
self, damage,
element::{
self, surface::render_elements_from_surface_tree, texture::TextureBuffer, Element,
},
element::{self, surface::render_elements_from_surface_tree, Element},
gles::{GlesRenderbuffer, GlesRenderer},
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer, MultiTexture},
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer},
sync::SyncPoint,
utils::{CommitCounter, DamageSet},
Bind, Blit, BufferType, ExportMem, ImportDma, ImportEgl, ImportMemWl, Offscreen,
@ -57,8 +55,8 @@ use smithay::{
reexports::{
ash::vk::ExtPhysicalDeviceDrmFn,
calloop::{
self, generic::Generic, Dispatcher, Idle, Interest, LoopHandle, PostAction,
RegistrationToken,
self, generic::Generic, timer::Timer, Dispatcher, Idle, Interest, LoopHandle,
PostAction, RegistrationToken,
},
drm::control::{connector, crtc, ModeTypeFlags},
input::Libinput,
@ -86,8 +84,8 @@ use crate::{
config::ConnectorSavedState,
output::{BlankingState, OutputMode, OutputName},
render::{
pointer::PointerElement, pointer_render_elements, take_presentation_feedback,
OutputRenderElement, CLEAR_COLOR, CLEAR_COLOR_LOCKED,
pointer::pointer_render_elements, take_presentation_feedback, OutputRenderElement,
CLEAR_COLOR, CLEAR_COLOR_LOCKED,
},
state::{Pinnacle, State, SurfaceDmabufFeedback, WithState},
};
@ -134,9 +132,6 @@ pub struct Udev {
allocator: Option<Box<dyn Allocator<Buffer = Dmabuf, Error = AnyError>>>,
pub(super) gpu_manager: GpuManager<GbmGlesBackend<GlesRenderer, DrmDeviceFd>>,
backends: HashMap<DrmNode, UdevBackendData>,
pointer_images: Vec<(xcursor::parser::Image, TextureBuffer<MultiTexture>)>,
pointer_element: PointerElement<MultiTexture>,
pointer_image: crate::cursor::Cursor,
pub(super) upscale_filter: TextureFilter,
pub(super) downscale_filter: TextureFilter,
@ -225,9 +220,6 @@ impl Udev {
gpu_manager,
allocator: None,
backends: HashMap::new(),
pointer_image: crate::cursor::Cursor::load(),
pointer_images: Vec::new(),
pointer_element: PointerElement::default(),
upscale_filter: TextureFilter::Linear,
downscale_filter: TextureFilter::Linear,
@ -531,8 +523,10 @@ impl Udev {
match &surface.render_state {
RenderState::Idle => {
tracing::info!("inserting idle render");
let output = output.clone();
let token = loop_handle.insert_idle(move |state| {
tracing::info!("actually rendering");
state
.backend
.udev_mut()
@ -543,6 +537,7 @@ impl Udev {
}
RenderState::Scheduled(_) => (),
RenderState::WaitingForVblank { dirty: _ } => {
tracing::info!("making dirty");
surface.render_state = RenderState::WaitingForVblank { dirty: true }
}
}
@ -933,7 +928,7 @@ impl Udev {
state
.backend
.udev_mut()
.on_vblank(&state.pinnacle, node, crtc, metadata);
.on_vblank(&mut state.pinnacle, node, crtc, metadata);
}
DrmEvent::Error(error) => {
error!("{:?}", error);
@ -1271,7 +1266,7 @@ impl Udev {
/// Mark [`OutputPresentationFeedback`]s as presented and schedule a new render on idle.
fn on_vblank(
&mut self,
pinnacle: &Pinnacle,
pinnacle: &mut Pinnacle,
dev_id: DrmNode,
crtc: crtc::Handle,
metadata: &mut Option<DrmEventMetadata>,
@ -1369,6 +1364,20 @@ impl Udev {
);
}
}
// Schedule a render when the next frame of an animated cursor should be drawn.
if let Some(until) = pinnacle
.cursor_state
.time_until_next_animated_cursor_frame()
{
let _ = pinnacle.loop_handle.insert_source(
Timer::from_duration(until),
move |_, _, state| {
state.schedule_render(&output);
calloop::timer::TimeoutAction::Drop
},
);
}
}
/// Render to the [`RenderSurface`] associated with the given `output`.
@ -1392,13 +1401,6 @@ impl Udev {
assert!(matches!(surface.render_state, RenderState::Scheduled(_)));
// TODO get scale from the rendersurface when supporting HiDPI
let frame = self.pointer_image.get_image(
1,
// output.current_scale().integer_scale() as u32,
pinnacle.clock.now().into(),
);
let render_node = surface.render_node;
let primary_gpu = self.primary_gpu;
let mut renderer = if primary_gpu == render_node {
@ -1413,32 +1415,19 @@ impl Udev {
let _ = renderer.upscale_filter(self.upscale_filter);
let _ = renderer.downscale_filter(self.downscale_filter);
let pointer_images = &mut self.pointer_images;
let (pointer_image, hotspot) = pointer_images
.iter()
.find_map(|(image, texture)| {
if image == &frame {
Some((texture.clone(), (frame.xhot as i32, frame.yhot as i32)))
} else {
None
}
})
.unwrap_or_else(|| {
let texture = TextureBuffer::from_memory(
&mut renderer,
&frame.pixels_rgba,
Fourcc::Abgr8888,
(frame.width as i32, frame.height as i32),
false,
1,
Transform::Normal,
None,
)
.expect("Failed to import cursor bitmap");
let hotspot = (frame.xhot as i32, frame.yhot as i32);
pointer_images.push((frame, texture.clone()));
(texture, hotspot)
});
///////////////////////////////////////////////////////////////////////////////////////////
// draw the cursor as relevant and
// reset the cursor if the surface is no longer alive
if let CursorImageStatus::Surface(surface) = &pinnacle.cursor_state.cursor_image() {
if !surface.alive() {
pinnacle
.cursor_state
.set_cursor_image(CursorImageStatus::default_named());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
let pointer_location = pinnacle
.seat
@ -1446,71 +1435,26 @@ impl Udev {
.map(|ptr| ptr.current_location())
.unwrap_or((0.0, 0.0).into());
// set cursor
self.pointer_element.set_texture(pointer_image.clone());
// draw the cursor as relevant and
// reset the cursor if the surface is no longer alive
if let CursorImageStatus::Surface(surface) = &pinnacle.cursor_status {
if !surface.alive() {
pinnacle.cursor_status = CursorImageStatus::default_named();
}
}
self.pointer_element
.set_status(pinnacle.cursor_status.clone());
let pending_screencopy_with_cursor =
output.with_state(|state| state.screencopy.as_ref().map(|sc| sc.overlay_cursor()));
let mut output_render_elements = Vec::new();
let should_blank = pinnacle.lock_state.is_locking()
|| (pinnacle.lock_state.is_locked()
&& output.with_state(|state| state.lock_surface.is_none()));
// If there isn't a pending screencopy that doesn't want to overlay the cursor,
// render it.
match pending_screencopy_with_cursor {
Some(include_cursor) if pinnacle.lock_state.is_unlocked() => {
if include_cursor {
// HACK: Doing `RenderFrameResult::blit_frame_result` with something on the
// | cursor plane causes the cursor to overwrite the pixels underneath it,
// | leading to a transparent hole under the cursor.
// | To circumvent that, we set the cursor to render on the primary plane instead.
// | Unfortunately that means I can't composite the cursor separately from
// | the screencopy, meaning if you have an active screencopy recording
// | without cursor overlay then the cursor will dim/flicker out/disappear.
self.pointer_element
.set_element_kind(element::Kind::Unspecified);
let pointer_render_elements = pointer_render_elements(
output,
&mut renderer,
&pinnacle.space,
pointer_location,
&mut pinnacle.cursor_status,
pinnacle.dnd_icon.as_ref(),
hotspot.into(),
&self.pointer_element,
);
self.pointer_element.set_element_kind(element::Kind::Cursor);
output_render_elements.extend(pointer_render_elements);
}
}
_ => {
let pointer_render_elements = pointer_render_elements(
output,
&mut renderer,
&pinnacle.space,
pointer_location,
&mut pinnacle.cursor_status,
pinnacle.dnd_icon.as_ref(),
hotspot.into(),
&self.pointer_element,
);
output_render_elements.extend(pointer_render_elements);
}
}
let pointer_render_elements = pointer_render_elements(
output,
&mut renderer,
&mut pinnacle.cursor_state,
&pinnacle.space,
pointer_location,
pinnacle.dnd_icon.as_ref(),
&pinnacle.clock,
);
output_render_elements.extend(
pointer_render_elements
.into_iter()
.map(OutputRenderElement::from),
);
if should_blank {
output.with_state_mut(|state| {
@ -1601,7 +1545,7 @@ impl Udev {
scanout_feedback: &feedback.scanout_feedback,
}),
Duration::from(pinnacle.clock.now()),
&pinnacle.cursor_status,
pinnacle.cursor_state.cursor_image(),
);
let rendered = !render_frame_result.is_empty;

View file

@ -10,7 +10,7 @@ use smithay::{
self, buffer_type,
damage::{self, OutputDamageTracker, RenderOutputResult},
element::{self, surface::render_elements_from_surface_tree},
gles::{GlesRenderbuffer, GlesRenderer, GlesTexture},
gles::{GlesRenderbuffer, GlesRenderer},
Bind, Blit, BufferType, ExportMem, ImportDma, ImportEgl, ImportMemWl, Offscreen,
TextureFilter,
},
@ -38,8 +38,8 @@ use tracing::{debug, error, trace, warn};
use crate::{
output::{BlankingState, OutputMode},
render::{
pointer::PointerElement, pointer_render_elements, take_presentation_feedback, CLEAR_COLOR,
CLEAR_COLOR_LOCKED,
pointer::pointer_render_elements, take_presentation_feedback, OutputRenderElement,
CLEAR_COLOR, CLEAR_COLOR_LOCKED,
},
state::{Pinnacle, State, WithState},
};
@ -263,17 +263,21 @@ impl Winit {
let full_redraw = &mut self.full_redraw;
*full_redraw = full_redraw.saturating_sub(1);
if let CursorImageStatus::Surface(surface) = &pinnacle.cursor_status {
if let CursorImageStatus::Surface(surface) = pinnacle.cursor_state.cursor_image() {
if !surface.alive() {
pinnacle.cursor_status = CursorImageStatus::default_named();
pinnacle
.cursor_state
.set_cursor_image(CursorImageStatus::default_named());
}
}
let cursor_visible = !matches!(pinnacle.cursor_status, CursorImageStatus::Surface(_));
let cursor_visible = !matches!(
pinnacle.cursor_state.cursor_image(),
CursorImageStatus::Surface(_)
);
let mut pointer_element = PointerElement::<GlesTexture>::new();
pointer_element.set_status(pinnacle.cursor_status.clone());
// TODO: make winit cursor disappear if not surface
// TODO: set winit cursor to named cursor when possible
// The z-index of these is determined by `state.fixup_z_layering()`, which is called at the end
// of every event loop cycle
@ -300,14 +304,17 @@ impl Winit {
let pointer_render_elements = pointer_render_elements(
&self.output,
self.backend.renderer(),
&mut pinnacle.cursor_state,
&pinnacle.space,
pointer_location,
&mut pinnacle.cursor_status,
pinnacle.dnd_icon.as_ref(),
(0, 0).into(), // Nonsurface cursors are hidden
&pointer_element,
&pinnacle.clock,
);
output_render_elements.extend(
pointer_render_elements
.into_iter()
.map(OutputRenderElement::from),
);
output_render_elements.extend(pointer_render_elements);
}
let should_blank = pinnacle.lock_state.is_locking()
@ -421,7 +428,7 @@ impl Winit {
&pinnacle.space,
None,
time.into(),
&pinnacle.cursor_status,
pinnacle.cursor_state.cursor_image(),
);
if has_rendered {

View file

@ -1,91 +1,227 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use std::{io::Read, time::Duration};
use std::time::{Duration, Instant};
use std::{collections::HashMap, rc::Rc};
use xcursor::{parser::Image, CursorTheme};
use anyhow::Context;
use smithay::backend::allocator::Fourcc;
use smithay::{
backend::renderer::element::memory::MemoryRenderBuffer,
input::pointer::{CursorIcon, CursorImageStatus},
utils::Transform,
};
use xcursor::{
parser::{parse_xcursor, Image},
CursorTheme,
};
use crate::render::pointer::PointerElement;
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba");
pub struct Cursor {
icons: Vec<Image>,
pub struct CursorState {
start_time: Instant,
current_cursor_image: CursorImageStatus,
theme: CursorTheme,
size: u32,
// memory buffer cache
mem_buffer_cache: Vec<(Image, MemoryRenderBuffer)>,
// map of cursor icons to loaded images
loaded_images: HashMap<CursorIcon, Option<Rc<XCursor>>>,
}
impl Cursor {
pub fn load() -> Self {
let name = std::env::var("XCURSOR_THEME")
.ok()
.unwrap_or_else(|| "default".into());
let size = std::env::var("XCURSOR_SIZE")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(24);
impl CursorState {
pub fn new() -> Self {
let (theme, size) = load_xcursor_theme();
let theme = CursorTheme::load(&name);
let icons = load_icon(&theme)
.map_err(|err| tracing::warn!("Unable to load xcursor: {}, using fallback cursor", err))
.unwrap_or_else(|_| {
vec![Image {
size: 32,
width: 64,
height: 64,
xhot: 1,
yhot: 1,
delay: 1,
pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA),
pixels_argb: vec![], //unused
}]
});
Cursor { icons, size }
Self {
start_time: Instant::now(),
current_cursor_image: CursorImageStatus::default_named(),
theme,
size,
mem_buffer_cache: Default::default(),
loaded_images: Default::default(),
}
}
pub fn get_image(&self, scale: u32, time: Duration) -> Image {
let size = self.size * scale;
frame(time.as_millis() as u32, size, &self.icons)
pub fn set_theme_and_size(&mut self, theme: CursorTheme, size: u32) {
self.theme = theme;
self.size = size;
self.mem_buffer_cache.clear();
self.loaded_images.clear();
}
pub fn cursor_size(&self) -> u32 {
self.size
}
pub fn set_cursor_image(&mut self, image: CursorImageStatus) {
self.current_cursor_image = image;
}
pub fn cursor_image(&self) -> &CursorImageStatus {
&self.current_cursor_image
}
pub fn get_xcursor_images(&mut self, icon: CursorIcon) -> Option<Rc<XCursor>> {
self.loaded_images
.entry(icon)
.or_insert_with_key(|icon| {
let mut images = load_xcursor_images(&self.theme, *icon);
if *icon == CursorIcon::Default && images.is_err() {
images = Ok(fallback_cursor());
}
images.ok().map(Rc::new)
})
.clone()
}
pub fn buffer_for_image(&mut self, image: Image) -> MemoryRenderBuffer {
self.mem_buffer_cache
.iter()
.find_map(|(img, buf)| (*img == image).then(|| buf.clone()))
.unwrap_or_else(|| {
// TODO: scale
let buffer = MemoryRenderBuffer::from_slice(
&image.pixels_rgba,
Fourcc::Abgr8888,
(image.width as i32, image.height as i32),
1,
Transform::Normal,
None,
);
self.mem_buffer_cache.push((image, buffer.clone()));
buffer
})
}
pub fn pointer_element(&mut self) -> PointerElement {
match &self.current_cursor_image {
CursorImageStatus::Hidden => PointerElement::Hidden,
CursorImageStatus::Named(icon) => {
let cursor = self
.get_xcursor_images(*icon)
.or_else(|| self.get_xcursor_images(CursorIcon::Default))
.unwrap();
PointerElement::Named {
cursor,
size: self.size,
}
}
CursorImageStatus::Surface(surface) => PointerElement::Surface {
surface: surface.clone(),
},
}
}
/// If the current cursor is named and animated, get the time to the next frame, in milliseconds.
pub fn time_until_next_animated_cursor_frame(&mut self) -> Option<Duration> {
match &self.current_cursor_image {
CursorImageStatus::Hidden => None,
CursorImageStatus::Named(icon) => {
let cursor = self
.get_xcursor_images(*icon)
.or_else(|| self.get_xcursor_images(CursorIcon::Default))
.unwrap();
if cursor.images.len() <= 1 {
return None;
}
// FIXME: copied from below, unify
let mut millis = self.start_time.duration_since(Instant::now()).as_millis() as u32;
let animation_length_ms = nearest_size_images(self.size, &cursor.images)
.fold(0, |acc, image| acc + image.delay);
millis %= animation_length_ms;
for img in nearest_size_images(self.size, &cursor.images) {
if millis < img.delay {
return Some(Duration::from_millis((img.delay - millis).into()));
}
millis -= img.delay;
}
unreachable!()
}
CursorImageStatus::Surface(_) => None,
}
}
}
fn nearest_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
pub struct XCursor {
images: Vec<Image>,
}
impl XCursor {
pub fn image(&self, time: Duration, size: u32) -> Image {
let mut millis = time.as_millis() as u32;
let animation_length_ms =
nearest_size_images(size, &self.images).fold(0, |acc, image| acc + image.delay);
millis %= animation_length_ms;
for img in nearest_size_images(size, &self.images) {
if millis < img.delay {
return img.clone();
}
millis -= img.delay;
}
unreachable!()
}
}
fn nearest_size_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
// Follow the nominal size of the cursor to choose the nearest
let nearest_image = images
.iter()
.min_by_key(|image| (size as i32 - image.size as i32).abs())
.expect("no nearest image");
.unwrap();
images.iter().filter(move |image| {
image.width == nearest_image.width && image.height == nearest_image.height
})
}
fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image {
let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay);
millis %= total;
/// Load a theme and size from $XCURSOR_THEME and $XCURSOR_SIZE
fn load_xcursor_theme() -> (CursorTheme, u32) {
let theme = std::env::var("XCURSOR_THEME").unwrap_or_else(|_| "default".into());
let size = std::env::var("XCURSOR_SIZE")
.ok()
.and_then(|size| size.parse::<u32>().ok())
.unwrap_or(24);
for img in nearest_images(size, images) {
if millis < img.delay {
return img.clone();
}
millis -= img.delay;
(CursorTheme::load(&theme), size)
}
/// Load xcursor images for the given theme and icon.
///
/// Looks through legacy names as fallback.
fn load_xcursor_images(theme: &CursorTheme, icon: CursorIcon) -> anyhow::Result<XCursor> {
let icon_path = std::iter::once(&icon.name())
.chain(icon.alt_names())
.find_map(|name| theme.load_icon(name))
.context("no images for icon")?;
let cursor_bytes = std::fs::read(icon_path).context("failed to read xcursor file")?;
parse_xcursor(&cursor_bytes)
.map(|images| XCursor { images })
.context("failed to parse xcursor bytes")
}
fn fallback_cursor() -> XCursor {
XCursor {
images: vec![Image {
size: 32,
width: 64,
height: 64,
xhot: 1,
yhot: 1,
delay: 1,
pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA),
pixels_argb: vec![], // unused
}],
}
unreachable!()
}
#[derive(thiserror::Error, Debug)]
enum Error {
#[error("Theme has no default cursor")]
NoDefaultCursor,
#[error("Error opening xcursor file: {0}")]
File(#[from] std::io::Error),
#[error("Failed to parse XCursor file")]
Parse,
}
fn load_icon(theme: &CursorTheme) -> Result<Vec<Image>, Error> {
let icon_path = theme.load_icon("default").ok_or(Error::NoDefaultCursor)?;
let mut cursor_file = std::fs::File::open(icon_path)?;
let mut cursor_data = Vec::new();
cursor_file.read_to_end(&mut cursor_data)?;
xcursor::parser::parse_xcursor(&cursor_data).ok_or(Error::Parse)
}

View file

@ -10,11 +10,15 @@ mod xwayland;
use std::{collections::HashMap, mem, os::fd::OwnedFd, sync::Arc};
use smithay::{
backend::renderer::utils::{self, with_renderer_surface_state},
delegate_compositor, delegate_data_control, delegate_data_device, delegate_fractional_scale,
delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_presentation,
delegate_primary_selection, delegate_relative_pointer, delegate_seat,
delegate_security_context, delegate_shm, delegate_viewporter, delegate_xwayland_shell,
backend::{
input::TabletToolDescriptor,
renderer::utils::{self, with_renderer_surface_state},
},
delegate_compositor, delegate_cursor_shape, delegate_data_control, delegate_data_device,
delegate_fractional_scale, delegate_layer_shell, delegate_output, delegate_pointer_constraints,
delegate_presentation, delegate_primary_selection, delegate_relative_pointer, delegate_seat,
delegate_security_context, delegate_shm, delegate_tablet_manager, delegate_viewporter,
delegate_xwayland_shell,
desktop::{
self, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, PopupKind,
PopupManager, WindowSurfaceType,
@ -62,13 +66,11 @@ use smithay::{
SelectionHandler, SelectionSource, SelectionTarget,
},
shell::{
wlr_layer::{
self, Layer, LayerSurfaceCachedState, LayerSurfaceData, WlrLayerShellHandler,
WlrLayerShellState,
},
wlr_layer::{self, Layer, LayerSurfaceData, WlrLayerShellHandler, WlrLayerShellState},
xdg::{PopupSurface, XdgPopupSurfaceData, XdgToplevelSurfaceData},
},
shm::{ShmHandler, ShmState},
tablet_manager::TabletSeatHandler,
xwayland_shell::{XWaylandShellHandler, XWaylandShellState},
},
xwayland::XWaylandClientData,
@ -541,7 +543,7 @@ impl SeatHandler for State {
}
fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
self.pinnacle.cursor_status = image;
self.pinnacle.cursor_state.set_cursor_image(image);
}
fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&Self::KeyboardFocus>) {
@ -898,6 +900,17 @@ impl OutputPowerManagementHandler for State {
}
delegate_output_power_management!(State);
impl TabletSeatHandler for State {
fn tablet_tool_image(&mut self, tool: &TabletToolDescriptor, image: CursorImageStatus) {
// TODO:
let _ = tool;
let _ = image;
}
}
delegate_tablet_manager!(State);
delegate_cursor_shape!(State);
impl Pinnacle {
fn position_popup(&self, popup: &PopupSurface) {
trace!("State::position_popup");

View file

@ -4,6 +4,7 @@ use std::{process::Stdio, time::Duration};
use smithay::{
desktop::Window,
input::pointer::CursorIcon,
utils::{Logical, Point, Rectangle, Size, SERIAL_COUNTER},
wayland::selection::{
data_device::{
@ -24,7 +25,6 @@ use smithay::{
use tracing::{debug, error, trace, warn};
use crate::{
cursor::Cursor,
focus::keyboard::KeyboardFocusTarget,
state::{Pinnacle, State, WithState},
window::{window_state::FloatingOrTiled, WindowElement},
@ -505,8 +505,13 @@ impl Pinnacle {
)
.expect("Failed to attach x11wm");
let cursor = Cursor::load();
let image = cursor.get_image(1, Duration::ZERO);
let cursor = state
.pinnacle
.cursor_state
.get_xcursor_images(CursorIcon::Default)
.unwrap();
let image =
cursor.image(Duration::ZERO, state.pinnacle.cursor_state.cursor_size());
wm.set_cursor(
&image.pixels_rgba,
Size::from((image.width as u16, image.height as u16)),

View file

@ -438,7 +438,6 @@ impl State {
if self.pinnacle.lock_state.is_unlocked() {
// Focus the topmost exclusive layer, if any
let mut exclusive_layers_exist = false;
for layer in self.pinnacle.layer_shell_state.layer_surfaces().rev() {
let data = compositor::with_states(layer.wl_surface(), |states| {
*states.cached_state.current::<LayerSurfaceCachedState>()
@ -456,7 +455,6 @@ impl State {
});
if let Some(layer_surface) = layer_surface {
exclusive_layers_exist = true;
keyboard.set_focus(
self,
Some(KeyboardFocusTarget::LayerSurface(layer_surface)),

View file

@ -223,6 +223,7 @@ async fn main() -> anyhow::Result<()> {
}
event_loop.run(Duration::from_secs(1), &mut state, |state| {
tracing::info!("after idles");
state.on_event_loop_cycle_completion();
})?;

View file

@ -5,7 +5,7 @@ pub mod render_elements;
pub mod texture;
pub mod util;
use std::{ops::Deref, sync::Mutex};
use std::ops::Deref;
use smithay::{
backend::renderer::{
@ -22,11 +22,9 @@ use smithay::{
},
PopupManager, Space, WindowSurface,
},
input::pointer::{CursorImageAttributes, CursorImageStatus},
output::Output,
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::{Logical, Point, Scale},
wayland::{compositor, shell::wlr_layer},
wayland::shell::wlr_layer,
};
use crate::{
@ -38,8 +36,7 @@ use crate::{
};
use self::{
pointer::{PointerElement, PointerRenderElement},
texture::CommonTextureRenderElement,
pointer::PointerRenderElement, texture::CommonTextureRenderElement,
util::surface::texture_render_elements_from_surface_tree,
};
@ -269,62 +266,6 @@ fn window_render_elements<R: PRenderer>(
)
}
pub fn pointer_render_elements<R: PRenderer>(
output: &Output,
renderer: &mut R,
space: &Space<WindowElement>,
pointer_location: Point<f64, Logical>,
cursor_status: &mut CursorImageStatus,
dnd_icon: Option<&WlSurface>,
fallback_hotspot: Point<i32, Logical>,
pointer_element: &PointerElement<<R as Renderer>::TextureId>,
) -> Vec<OutputRenderElement<R>> {
let mut output_render_elements = Vec::new();
let Some(output_geometry) = space.output_geometry(output) else {
return output_render_elements;
};
let scale = Scale::from(output.current_scale().fractional_scale());
if output_geometry.to_f64().contains(pointer_location) {
let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = cursor_status {
compositor::with_states(surface, |states| {
states
.data_map
.get::<Mutex<CursorImageAttributes>>()
.expect("surface data map had no CursorImageAttributes")
.lock()
.expect("failed to lock mutex")
.hotspot
})
} else {
fallback_hotspot
};
let cursor_pos = pointer_location - output_geometry.loc.to_f64() - cursor_hotspot.to_f64();
let cursor_pos_scaled = cursor_pos.to_physical_precise_round(scale);
output_render_elements.extend(pointer_element.render_elements(
renderer,
cursor_pos_scaled,
scale,
1.0,
));
if let Some(dnd_icon) = dnd_icon {
output_render_elements.extend(AsRenderElements::render_elements(
&smithay::desktop::space::SurfaceTree::from_surface(dnd_icon),
renderer,
cursor_pos_scaled,
scale,
1.0,
));
}
}
output_render_elements
}
/// Render elements for any pending layout transaction.
///
/// Returns fullscreen_and_up elements then under_fullscreen elements.

View file

@ -1,99 +1,123 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use std::{rc::Rc, sync::Mutex};
use smithay::{
backend::renderer::{
element::{
self,
surface::{self, WaylandSurfaceRenderElement},
texture::{TextureBuffer, TextureRenderElement},
memory::MemoryRenderBufferRenderElement,
surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement},
AsRenderElements,
},
ImportAll, Renderer, Texture,
ImportAll, ImportMem,
},
input::pointer::CursorImageStatus,
desktop::Space,
input::pointer::CursorImageAttributes,
output::Output,
reexports::wayland_server::protocol::wl_surface::WlSurface,
render_elements,
utils::{Physical, Point, Scale},
utils::{Clock, Logical, Monotonic, Point, Scale},
wayland::compositor,
};
use crate::{
cursor::{CursorState, XCursor},
window::WindowElement,
};
use super::PRenderer;
pub struct PointerElement<T: Texture> {
texture: Option<TextureBuffer<T>>,
status: CursorImageStatus,
kind: element::Kind,
}
impl<T: Texture> Default for PointerElement<T> {
fn default() -> Self {
Self {
texture: Default::default(),
status: CursorImageStatus::default_named(),
kind: element::Kind::Cursor,
}
}
}
impl<T: Texture> PointerElement<T> {
pub fn new() -> Self {
Default::default()
}
pub fn set_status(&mut self, status: CursorImageStatus) {
self.status = status;
}
pub fn set_texture(&mut self, texture: TextureBuffer<T>) {
self.texture = Some(texture);
}
pub fn set_element_kind(&mut self, kind: element::Kind) {
self.kind = kind;
}
pub enum PointerElement {
Hidden,
Named { cursor: Rc<XCursor>, size: u32 },
Surface { surface: WlSurface },
}
render_elements! {
#[derive(Debug)]
pub PointerRenderElement<R> where R: ImportAll;
Surface=WaylandSurfaceRenderElement<R>,
Texture=TextureRenderElement<<R as Renderer>::TextureId>,
pub PointerRenderElement<R> where R: ImportAll + ImportMem;
Surface = WaylandSurfaceRenderElement<R>,
Memory = MemoryRenderBufferRenderElement<R>,
}
impl<R: PRenderer> AsRenderElements<R> for PointerElement<R::TextureId> {
type RenderElement = PointerRenderElement<R>;
pub fn pointer_render_elements<R: PRenderer>(
output: &Output,
renderer: &mut R,
cursor_state: &mut CursorState,
space: &Space<WindowElement>,
pointer_location: Point<f64, Logical>,
dnd_icon: Option<&WlSurface>,
clock: &Clock<Monotonic>,
) -> Vec<PointerRenderElement<R>> {
let mut pointer_render_elements = Vec::new();
fn render_elements<C: From<Self::RenderElement>>(
&self,
renderer: &mut R,
location: Point<i32, Physical>,
scale: Scale<f64>,
alpha: f32,
) -> Vec<C> {
match &self.status {
CursorImageStatus::Hidden => vec![],
CursorImageStatus::Named(_) => {
if let Some(texture) = self.texture.as_ref() {
vec![PointerRenderElement::<R>::from(
TextureRenderElement::from_texture_buffer(
location.to_f64(),
texture,
None,
None,
None,
self.kind,
),
)
.into()]
} else {
vec![]
}
let Some(output_geometry) = space.output_geometry(output) else {
return pointer_render_elements;
};
let scale = Scale::from(output.current_scale().fractional_scale());
let pointer_elem = cursor_state.pointer_element();
if output_geometry.to_f64().contains(pointer_location) {
let cursor_pos = pointer_location - output_geometry.loc.to_f64();
let mut elements = match &pointer_elem {
PointerElement::Hidden => vec![],
PointerElement::Named { cursor, size } => {
let image = cursor.image(clock.now().into(), *size);
let hotspot = (image.xhot as i32, image.yhot as i32);
let buffer = cursor_state.buffer_for_image(image);
let elem = MemoryRenderBufferRenderElement::from_buffer(
renderer,
(cursor_pos - Point::from(hotspot).to_f64()).to_physical_precise_round(scale),
&buffer,
None,
None,
None,
element::Kind::Cursor,
);
elem.map(|elem| vec![PointerRenderElement::Memory(elem)])
.unwrap_or_default()
}
CursorImageStatus::Surface(surface) => {
let elements: Vec<PointerRenderElement<R>> =
surface::render_elements_from_surface_tree(
renderer, surface, location, scale, alpha, self.kind,
);
elements.into_iter().map(C::from).collect()
PointerElement::Surface { surface } => {
let hotspot = compositor::with_states(surface, |states| {
states
.data_map
.get::<Mutex<CursorImageAttributes>>()
.expect("surface data map had no CursorImageAttributes")
.lock()
.expect("failed to lock mutex")
.hotspot
});
let elems = render_elements_from_surface_tree(
renderer,
surface,
(cursor_pos - hotspot.to_f64()).to_physical_precise_round(scale),
scale,
1.0,
element::Kind::Cursor,
);
elems
}
};
if let Some(dnd_icon) = dnd_icon {
elements.extend(AsRenderElements::render_elements(
&smithay::desktop::space::SurfaceTree::from_surface(dnd_icon),
renderer,
cursor_pos.to_physical_precise_round(scale),
scale,
1.0,
));
}
pointer_render_elements = elements;
}
pointer_render_elements
}

View file

@ -5,6 +5,7 @@ use crate::{
backend::{self, udev::Udev, winit::Winit, Backend},
cli::{self, Cli},
config::Config,
cursor::CursorState,
focus::OutputFocusStack,
grab::resize_grab::ResizeSurfaceState,
handlers::session_lock::LockState,
@ -23,7 +24,7 @@ use indexmap::IndexMap;
use pinnacle_api_defs::pinnacle::v0alpha1::ShutdownWatchResponse;
use smithay::{
desktop::{PopupManager, Space},
input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState},
input::{keyboard::XkbConfig, Seat, SeatState},
output::Output,
reexports::{
calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction},
@ -36,6 +37,7 @@ use smithay::{
utils::{Clock, Monotonic},
wayland::{
compositor::{self, CompositorClientState, CompositorState},
cursor_shape::CursorShapeManagerState,
dmabuf::DmabufFeedback,
fractional_scale::FractionalScaleManagerState,
idle_notify::IdleNotifierState,
@ -51,6 +53,7 @@ use smithay::{
shell::{wlr_layer::WlrLayerShellState, xdg::XdgShellState},
shm::ShmState,
socket::ListeningSocketSource,
tablet_manager::TabletManagerState,
viewporter::ViewporterState,
xwayland_shell::XWaylandShellState,
},
@ -112,6 +115,7 @@ pub struct Pinnacle {
pub idle_notifier_state: IdleNotifierState<State>,
pub output_management_manager_state: OutputManagementManagerState,
pub output_power_management_state: OutputPowerManagementState,
pub tablet_manager_state: TabletManagerState,
pub lock_state: LockState,
@ -123,7 +127,6 @@ pub struct Pinnacle {
pub popup_manager: PopupManager,
pub cursor_status: CursorImageStatus,
pub dnd_icon: Option<WlSurface>,
/// The main window vec
@ -160,6 +163,9 @@ pub struct Pinnacle {
pub snowcap_shutdown_ping: Option<smithay::reexports::calloop::ping::Ping>,
#[cfg(feature = "snowcap")]
pub snowcap_join_handle: Option<tokio::task::JoinHandle<()>>,
pub cursor_shape_manager_state: CursorShapeManagerState,
pub cursor_state: CursorState,
}
impl State {
@ -286,7 +292,6 @@ impl Pinnacle {
seat_state,
shm_state: ShmState::new::<State>(&display_handle, vec![]),
space: Space::<WindowElement>::default(),
cursor_status: CursorImageStatus::default_named(),
output_manager_state: OutputManagerState::new_with_xdg_output::<State>(&display_handle),
xdg_shell_state: XdgShellState::new::<State>(&display_handle),
viewporter_state: ViewporterState::new::<State>(&display_handle),
@ -333,6 +338,7 @@ impl Pinnacle {
&display_handle,
filter_restricted_client,
),
tablet_manager_state: TabletManagerState::new::<State>(&display_handle),
lock_state: LockState::default(),
@ -378,6 +384,9 @@ impl Pinnacle {
snowcap_shutdown_ping: None,
#[cfg(feature = "snowcap")]
snowcap_join_handle: None,
cursor_shape_manager_state: CursorShapeManagerState::new::<State>(&display_handle),
cursor_state: CursorState::new(),
};
Ok(pinnacle)