anvil/udev: Try to load XCursor theme for default cursor
This commit is contained in:
parent
56f5557f8c
commit
b67688e1c8
|
@ -9,6 +9,8 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1.2.1"
|
bitflags = "1.2.1"
|
||||||
input = { version = "0.5.0", features = ["udev"], optional = true }
|
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 }
|
image = { version = "0.23.14", default-features = false, optional = true }
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
slog = { version = "2.1.1" }
|
slog = { version = "2.1.1" }
|
||||||
|
@ -36,7 +38,7 @@ gl_generator = "0.14"
|
||||||
default = [ "winit", "udev", "logind", "egl", "xwayland" ]
|
default = [ "winit", "udev", "logind", "egl", "xwayland" ]
|
||||||
egl = [ "smithay/use_system_lib", "smithay/backend_egl" ]
|
egl = [ "smithay/use_system_lib", "smithay/backend_egl" ]
|
||||||
winit = [ "smithay/backend_winit" ]
|
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" ]
|
logind = [ "smithay/backend_session_logind" ]
|
||||||
elogind = ["logind", "smithay/backend_session_elogind" ]
|
elogind = ["logind", "smithay/backend_session_elogind" ]
|
||||||
libseat = ["smithay/backend_session_libseat" ]
|
libseat = ["smithay/backend_session_libseat" ]
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -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)
|
||||||
|
}
|
|
@ -14,6 +14,8 @@ use std::{cell::RefCell, rc::Rc};
|
||||||
use slog::Drain;
|
use slog::Drain;
|
||||||
use smithay::reexports::{calloop::EventLoop, wayland_server::Display};
|
use smithay::reexports::{calloop::EventLoop, wayland_server::Display};
|
||||||
|
|
||||||
|
#[cfg(feature = "udev")]
|
||||||
|
mod cursor;
|
||||||
mod drawing;
|
mod drawing;
|
||||||
mod input_handler;
|
mod input_handler;
|
||||||
mod shell;
|
mod shell;
|
||||||
|
|
|
@ -90,7 +90,7 @@ pub struct UdevData {
|
||||||
primary_gpu: Option<PathBuf>,
|
primary_gpu: Option<PathBuf>,
|
||||||
backends: HashMap<dev_t, BackendData>,
|
backends: HashMap<dev_t, BackendData>,
|
||||||
signaler: Signaler<SessionSignal>,
|
signaler: Signaler<SessionSignal>,
|
||||||
pointer_image: ImageBuffer<Rgba<u8>, Vec<u8>>,
|
pointer_image: crate::cursor::Cursor,
|
||||||
render_timer: TimerHandle<(u64, crtc::Handle)>,
|
render_timer: TimerHandle<(u64, crtc::Handle)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +122,6 @@ pub fn run_udev(
|
||||||
/*
|
/*
|
||||||
* Initialize the compositor
|
* Initialize the compositor
|
||||||
*/
|
*/
|
||||||
let pointer_bytes = include_bytes!("../resources/cursor2.rgba");
|
|
||||||
#[cfg(feature = "egl")]
|
#[cfg(feature = "egl")]
|
||||||
let primary_gpu = primary_gpu(&session.seat()).unwrap_or_default();
|
let primary_gpu = primary_gpu(&session.seat()).unwrap_or_default();
|
||||||
|
|
||||||
|
@ -135,7 +134,7 @@ pub fn run_udev(
|
||||||
primary_gpu,
|
primary_gpu,
|
||||||
backends: HashMap::new(),
|
backends: HashMap::new(),
|
||||||
signaler: session_signal.clone(),
|
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(),
|
render_timer: timer.handle(),
|
||||||
};
|
};
|
||||||
let mut state = AnvilState::init(display.clone(), event_loop.handle(), data, log.clone());
|
let mut state = AnvilState::init(display.clone(), event_loop.handle(), data, log.clone());
|
||||||
|
@ -258,7 +257,7 @@ pub type RenderSurface = GbmBufferedSurface<SessionFd>;
|
||||||
struct BackendData {
|
struct BackendData {
|
||||||
_restart_token: SignalToken,
|
_restart_token: SignalToken,
|
||||||
surfaces: Rc<RefCell<HashMap<crtc::Handle, Rc<RefCell<RenderSurface>>>>>,
|
surfaces: Rc<RefCell<HashMap<crtc::Handle, Rc<RefCell<RenderSurface>>>>>,
|
||||||
pointer_image: Gles2Texture,
|
pointer_images: Vec<(xcursor::parser::Image, Gles2Texture)>,
|
||||||
renderer: Rc<RefCell<Gles2Renderer>>,
|
renderer: Rc<RefCell<Gles2Renderer>>,
|
||||||
gbm: GbmDevice<SessionFd>,
|
gbm: GbmDevice<SessionFd>,
|
||||||
registration_token: RegistrationToken,
|
registration_token: RegistrationToken,
|
||||||
|
@ -469,9 +468,6 @@ impl AnvilState<UdevData> {
|
||||||
&self.log,
|
&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 dev_id = device.device_id();
|
||||||
let handle = self.handle.clone();
|
let handle = self.handle.clone();
|
||||||
let restart_token = self.backend_data.signaler.register(move |signal| match signal {
|
let restart_token = self.backend_data.signaler.register(move |signal| match signal {
|
||||||
|
@ -509,7 +505,7 @@ impl AnvilState<UdevData> {
|
||||||
surfaces: backends,
|
surfaces: backends,
|
||||||
renderer,
|
renderer,
|
||||||
gbm,
|
gbm,
|
||||||
pointer_image,
|
pointer_images: Vec::new(),
|
||||||
dev_id,
|
dev_id,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -608,15 +604,34 @@ impl AnvilState<UdevData> {
|
||||||
};
|
};
|
||||||
|
|
||||||
for (&crtc, surface) in to_render_iter {
|
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(
|
let result = render_surface(
|
||||||
&mut *surface.borrow_mut(),
|
&mut *surface.borrow_mut(),
|
||||||
&mut *device_backend.renderer.borrow_mut(),
|
renderer,
|
||||||
device_backend.dev_id,
|
device_backend.dev_id,
|
||||||
crtc,
|
crtc,
|
||||||
&mut *self.window_map.borrow_mut(),
|
&mut *self.window_map.borrow_mut(),
|
||||||
&*self.output_map.borrow(),
|
&*self.output_map.borrow(),
|
||||||
self.pointer_location,
|
self.pointer_location,
|
||||||
&device_backend.pointer_image,
|
&pointer_image,
|
||||||
&*self.dnd_icon.lock().unwrap(),
|
&*self.dnd_icon.lock().unwrap(),
|
||||||
&mut *self.cursor_status.lock().unwrap(),
|
&mut *self.cursor_status.lock().unwrap(),
|
||||||
&self.log,
|
&self.log,
|
||||||
|
@ -802,7 +817,7 @@ fn initial_render(surface: &mut RenderSurface, renderer: &mut Gles2Renderer) ->
|
||||||
|
|
||||||
fn import_bitmap<C: std::ops::Deref<Target = [u8]>>(
|
fn import_bitmap<C: std::ops::Deref<Target = [u8]>>(
|
||||||
renderer: &mut Gles2Renderer,
|
renderer: &mut Gles2Renderer,
|
||||||
image: &image::ImageBuffer<image::Rgba<u8>, C>,
|
image: &ImageBuffer<Rgba<u8>, C>,
|
||||||
) -> Result<Gles2Texture, Gles2Error> {
|
) -> Result<Gles2Texture, Gles2Error> {
|
||||||
use smithay::backend::renderer::gles2::ffi;
|
use smithay::backend::renderer::gles2::ffi;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue