anvil/udev: Try to load XCursor theme for default cursor

This commit is contained in:
Victor Brekenfeld 2021-07-10 22:16:59 +02:00
parent 56f5557f8c
commit b67688e1c8
6 changed files with 123 additions and 12 deletions

View File

@ -9,6 +9,8 @@ edition = "2018"
[dependencies]
bitflags = "1.2.1"
input = { version = "0.5.0", features = ["udev"], optional = true }
thiserror = "1"
xcursor = { version = "0.3.3", optional = true }
image = { version = "0.23.14", default-features = false, optional = true }
rand = "0.7"
slog = { version = "2.1.1" }
@ -36,7 +38,7 @@ gl_generator = "0.14"
default = [ "winit", "udev", "logind", "egl", "xwayland" ]
egl = [ "smithay/use_system_lib", "smithay/backend_egl" ]
winit = [ "smithay/backend_winit" ]
udev = [ "smithay/backend_libinput", "smithay/backend_udev", "smithay/backend_drm", "smithay/backend_gbm", "smithay/backend_egl", "smithay/backend_session", "input", "image" ]
udev = [ "smithay/backend_libinput", "smithay/backend_udev", "smithay/backend_drm", "smithay/backend_gbm", "smithay/backend_egl", "smithay/backend_session", "input", "image", "xcursor" ]
logind = [ "smithay/backend_session_logind" ]
elogind = ["logind", "smithay/backend_session_elogind" ]
libseat = ["smithay/backend_session_libseat" ]

Binary file not shown.

Binary file not shown.

92
anvil/src/cursor.rs Normal file
View File

@ -0,0 +1,92 @@
use std::io::Read;
use xcursor::{
parser::{parse_xcursor, Image},
CursorTheme,
};
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba");
pub struct Cursor {
icons: Vec<Image>,
size: u32,
}
impl Cursor {
pub fn load(log: &::slog::Logger) -> Cursor {
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);
let theme = CursorTheme::load(&name);
let icons = load_icon(&theme)
.map_err(|err| slog::warn!(log, "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 }
}
pub fn get_image(&self, scale: u32, millis: u32) -> Image {
let size = self.size * scale;
frame(millis, size, &self.icons)
}
}
fn nearest_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())
.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;
for img in nearest_images(size, images) {
if millis < img.delay {
return img.clone();
}
millis -= img.delay;
}
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)?;
parse_xcursor(&cursor_data).ok_or(Error::Parse)
}

View File

@ -14,6 +14,8 @@ use std::{cell::RefCell, rc::Rc};
use slog::Drain;
use smithay::reexports::{calloop::EventLoop, wayland_server::Display};
#[cfg(feature = "udev")]
mod cursor;
mod drawing;
mod input_handler;
mod shell;

View File

@ -90,7 +90,7 @@ pub struct UdevData {
primary_gpu: Option<PathBuf>,
backends: HashMap<dev_t, BackendData>,
signaler: Signaler<SessionSignal>,
pointer_image: ImageBuffer<Rgba<u8>, Vec<u8>>,
pointer_image: crate::cursor::Cursor,
render_timer: TimerHandle<(u64, crtc::Handle)>,
}
@ -122,7 +122,6 @@ pub fn run_udev(
/*
* Initialize the compositor
*/
let pointer_bytes = include_bytes!("../resources/cursor2.rgba");
#[cfg(feature = "egl")]
let primary_gpu = primary_gpu(&session.seat()).unwrap_or_default();
@ -135,7 +134,7 @@ pub fn run_udev(
primary_gpu,
backends: HashMap::new(),
signaler: session_signal.clone(),
pointer_image: ImageBuffer::from_raw(64, 64, pointer_bytes.to_vec()).unwrap(),
pointer_image: crate::cursor::Cursor::load(&log),
render_timer: timer.handle(),
};
let mut state = AnvilState::init(display.clone(), event_loop.handle(), data, log.clone());
@ -258,7 +257,7 @@ pub type RenderSurface = GbmBufferedSurface<SessionFd>;
struct BackendData {
_restart_token: SignalToken,
surfaces: Rc<RefCell<HashMap<crtc::Handle, Rc<RefCell<RenderSurface>>>>>,
pointer_image: Gles2Texture,
pointer_images: Vec<(xcursor::parser::Image, Gles2Texture)>,
renderer: Rc<RefCell<Gles2Renderer>>,
gbm: GbmDevice<SessionFd>,
registration_token: RegistrationToken,
@ -469,9 +468,6 @@ impl AnvilState<UdevData> {
&self.log,
)));
let pointer_image = import_bitmap(&mut *renderer.borrow_mut(), &self.backend_data.pointer_image)
.expect("Failed to load pointer");
let dev_id = device.device_id();
let handle = self.handle.clone();
let restart_token = self.backend_data.signaler.register(move |signal| match signal {
@ -509,7 +505,7 @@ impl AnvilState<UdevData> {
surfaces: backends,
renderer,
gbm,
pointer_image,
pointer_images: Vec::new(),
dev_id,
},
);
@ -608,15 +604,34 @@ impl AnvilState<UdevData> {
};
for (&crtc, surface) in to_render_iter {
// TODO get scale from the rendersurface when supporting HiDPI
let frame = self
.backend_data
.pointer_image
.get_image(1 /*scale*/, self.start_time.elapsed().as_millis() as u32);
let renderer = &mut *device_backend.renderer.borrow_mut();
let pointer_images = &mut device_backend.pointer_images;
let pointer_image = pointer_images
.iter()
.find_map(|(image, texture)| if image == &frame { Some(texture) } else { None })
.cloned()
.unwrap_or_else(|| {
let image =
ImageBuffer::from_raw(frame.width, frame.height, &*frame.pixels_rgba).unwrap();
let texture = import_bitmap(renderer, &image).expect("Failed to import cursor bitmap");
pointer_images.push((frame, texture.clone()));
texture
});
let result = render_surface(
&mut *surface.borrow_mut(),
&mut *device_backend.renderer.borrow_mut(),
renderer,
device_backend.dev_id,
crtc,
&mut *self.window_map.borrow_mut(),
&*self.output_map.borrow(),
self.pointer_location,
&device_backend.pointer_image,
&pointer_image,
&*self.dnd_icon.lock().unwrap(),
&mut *self.cursor_status.lock().unwrap(),
&self.log,
@ -802,7 +817,7 @@ fn initial_render(surface: &mut RenderSurface, renderer: &mut Gles2Renderer) ->
fn import_bitmap<C: std::ops::Deref<Target = [u8]>>(
renderer: &mut Gles2Renderer,
image: &image::ImageBuffer<image::Rgba<u8>, C>,
image: &ImageBuffer<Rgba<u8>, C>,
) -> Result<Gles2Texture, Gles2Error> {
use smithay::backend::renderer::gles2::ffi;