1
0
Fork 0
forked from wry/wry

all: implement hardware cursors

This commit is contained in:
Julian Orth 2022-06-01 21:46:31 +02:00
parent 6cc97ee56e
commit 3b8935cf55
23 changed files with 614 additions and 91 deletions

View file

@ -363,6 +363,13 @@ impl Client {
self.send(&ClientMessage::SetCursorSize { seat, size })
}
pub fn set_use_hardware_cursor(&self, seat: Seat, use_hardware_cursor: bool) {
self.send(&ClientMessage::SetUseHardwareCursor {
seat,
use_hardware_cursor,
})
}
pub fn set_size(&self, sized: Resizable, size: i32) {
self.send(&ClientMessage::SetSize { sized, size })
}

View file

@ -300,6 +300,10 @@ pub enum ClientMessage<'a> {
device: InputDevice,
enabled: bool,
},
SetUseHardwareCursor {
seat: Seat,
use_hardware_cursor: bool,
},
}
#[derive(Encode, Decode, Debug)]

View file

@ -128,6 +128,16 @@ impl Seat {
Self(raw)
}
/// Sets whether this seat's cursor uses the hardware cursor if available.
///
/// Only one seat at a time can use the hardware cursor. Setting this to `true` for a
/// seat automatically unsets it for all other seats.
///
/// By default, the first created seat uses the hardware cursor.
pub fn use_hardware_cursor(self, use_hardware_cursor: bool) {
get!().set_use_hardware_cursor(self, use_hardware_cursor);
}
/// Sets the size of the cursor theme.
///
/// Default: 16.

View file

@ -3,6 +3,7 @@ use {
async_engine::SpawnedFuture,
fixed::Fixed,
ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL},
render::Framebuffer,
video::drm::ConnectorType,
},
std::{
@ -85,11 +86,21 @@ pub trait Connector {
#[derive(Debug)]
pub enum ConnectorEvent {
Connected(MonitorInfo),
HardwareCursor(Option<Rc<dyn HardwareCursor>>),
Disconnected,
Removed,
ModeChanged(Mode),
}
pub trait HardwareCursor: Debug {
fn set_enabled(&self, enabled: bool);
fn get_buffer(&self) -> Rc<Framebuffer>;
fn set_position(&self, x: i32, y: i32);
fn swap_buffer(&self);
fn commit(&self);
fn max_size(&self) -> (i32, i32);
}
pub type TransformMatrix = [[f64; 2]; 2];
pub trait InputDevice {

View file

@ -3,11 +3,11 @@ use {
async_engine::{Phase, SpawnedFuture},
backend::{
BackendDrmDevice, BackendEvent, Connector, ConnectorEvent, ConnectorId,
ConnectorKernelId, DrmDeviceId, MonitorInfo,
ConnectorKernelId, DrmDeviceId, HardwareCursor, MonitorInfo,
},
backends::metal::{MetalBackend, MetalError},
edid::Descriptor,
format::{Format, XRGB8888},
format::{Format, ARGB8888, XRGB8888},
ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC},
render::{Framebuffer, RenderContext, RenderResult, ResetStatus, Texture},
state::State,
@ -66,6 +66,8 @@ pub struct MetalDrmDevice {
pub max_width: u32,
pub min_height: u32,
pub max_height: u32,
pub cursor_width: u64,
pub cursor_height: u64,
pub gbm: GbmDevice,
pub handle_events: HandleEvents,
}
@ -153,12 +155,14 @@ pub struct MetalConnector {
pub can_present: Cell<bool>,
pub has_damage: Cell<bool>,
pub cursor_changed: Cell<bool>,
pub display: RefCell<ConnectorDisplayData>,
pub connect_sent: Cell<bool>,
pub primary_plane: CloneCell<Option<Rc<MetalPlane>>>,
pub cursor_plane: CloneCell<Option<Rc<MetalPlane>>>,
pub crtc: CloneCell<Option<Rc<MetalCrtc>>>,
@ -167,6 +171,77 @@ pub struct MetalConnector {
pub present_trigger: AsyncEvent,
pub render_result: RefCell<RenderResult>,
pub cursor_generation: NumCell<u64>,
pub cursor_x: Cell<i32>,
pub cursor_y: Cell<i32>,
pub cursor_enabled: Cell<bool>,
pub cursor_buffers: CloneCell<Option<Rc<[RenderBuffer; 2]>>>,
pub cursor_front_buffer: NumCell<usize>,
}
#[derive(Debug)]
pub struct MetalHardwareCursor {
pub generation: u64,
pub connector: Rc<MetalConnector>,
pub cursor_swap_buffer: Cell<bool>,
pub cursor_enabled_pending: Cell<bool>,
pub cursor_x_pending: Cell<i32>,
pub cursor_y_pending: Cell<i32>,
pub cursor_buffers: Rc<[RenderBuffer; 2]>,
pub have_changes: Cell<bool>,
}
impl HardwareCursor for MetalHardwareCursor {
fn set_enabled(&self, enabled: bool) {
if self.cursor_enabled_pending.replace(enabled) != enabled {
self.have_changes.set(true);
}
}
fn get_buffer(&self) -> Rc<Framebuffer> {
let buffer = (self.connector.cursor_front_buffer.get() + 1) % 2;
self.cursor_buffers[buffer].fb.clone()
}
fn set_position(&self, x: i32, y: i32) {
self.cursor_x_pending.set(x);
self.cursor_y_pending.set(y);
self.have_changes.set(true);
}
fn swap_buffer(&self) {
self.cursor_swap_buffer.set(true);
self.have_changes.set(true);
}
fn commit(&self) {
if self.generation != self.connector.cursor_generation.get() {
return;
}
if !self.have_changes.take() {
return;
}
self.connector
.cursor_enabled
.set(self.cursor_enabled_pending.get());
self.connector.cursor_x.set(self.cursor_x_pending.get());
self.connector.cursor_y.set(self.cursor_y_pending.get());
if self.cursor_swap_buffer.take() {
self.connector.cursor_front_buffer.fetch_add(1);
}
self.connector.cursor_changed.set(true);
if self.connector.can_present.get() {
self.connector.schedule_present();
}
}
fn max_size(&self) -> (i32, i32) {
(
self.connector.dev.cursor_width as _,
self.connector.dev.cursor_height as _,
)
}
}
pub struct ConnectorFutures {
@ -201,6 +276,27 @@ impl MetalConnector {
}
}
fn send_hardware_cursor(self: &Rc<Self>) {
if !self.connect_sent.get() {
return;
}
let generation = self.cursor_generation.fetch_add(1) + 1;
let hc = match self.cursor_buffers.get() {
Some(cp) => Some(Rc::new(MetalHardwareCursor {
generation,
connector: self.clone(),
cursor_swap_buffer: Cell::new(false),
cursor_enabled_pending: Cell::new(self.cursor_enabled.get()),
cursor_x_pending: Cell::new(self.cursor_x.get()),
cursor_y_pending: Cell::new(self.cursor_y.get()),
cursor_buffers: cp.clone(),
have_changes: Cell::new(false),
}) as _),
_ => None,
};
self.send_event(ConnectorEvent::HardwareCursor(hc));
}
fn connected(&self) -> bool {
let dd = self.display.borrow_mut();
dd.connection == ConnectorStatus::Connected && self.primary_plane.get().is_some()
@ -218,14 +314,11 @@ impl MetalConnector {
}
pub fn present(&self) {
if !self.backend.check_render_context() {
return;
}
let crtc = match self.crtc.get() {
Some(crtc) => crtc,
_ => return,
};
if !self.has_damage.get() || !self.can_present.get() {
if (!self.has_damage.get() && !self.cursor_changed.get()) || !self.can_present.get() {
return;
}
if !crtc.active.value.get() {
@ -239,27 +332,58 @@ impl MetalConnector {
Some(b) => b,
_ => return,
};
let buffer = &buffers[self.next_buffer.fetch_add(1) % buffers.len()];
if let Some(node) = self.state.root.outputs.get(&self.connector_id) {
let mut rr = self.render_result.borrow_mut();
buffer.fb.render(
&*node,
&self.state,
Some(node.global.pos.get()),
true,
&mut rr,
node.preferred_scale.get(),
);
for fr in rr.frame_requests.drain(..) {
fr.send_done();
let _ = fr.client.remove_obj(&*fr);
}
node.global.perform_screencopies(&buffer.fb, &buffer.tex);
}
let cursor = self.cursor_plane.get();
let mut changes = self.master.change();
changes.change_object(plane.id, |c| {
c.change(plane.fb_id, buffer.drm.id().0 as _);
});
if self.has_damage.get() {
if !self.backend.check_render_context() {
return;
}
let buffer = &buffers[self.next_buffer.fetch_add(1) % buffers.len()];
if let Some(node) = self.state.root.outputs.get(&self.connector_id) {
let mut rr = self.render_result.borrow_mut();
buffer.fb.render(
&*node,
&self.state,
Some(node.global.pos.get()),
true,
&mut rr,
node.preferred_scale.get(),
!self.cursor_enabled.get(),
);
for fr in rr.frame_requests.drain(..) {
fr.send_done();
let _ = fr.client.remove_obj(&*fr);
}
node.global.perform_screencopies(&buffer.fb, &buffer.tex);
}
changes.change_object(plane.id, |c| {
c.change(plane.fb_id, buffer.drm.id().0 as _);
});
}
if self.cursor_changed.get() && cursor.is_some() {
let plane = cursor.unwrap();
if self.cursor_enabled.get() {
let buffers = self.cursor_buffers.get().unwrap();
let buffer = &buffers[self.cursor_front_buffer.get() % buffers.len()];
changes.change_object(plane.id, |c| {
c.change(plane.fb_id, buffer.drm.id().0 as _);
c.change(plane.crtc_id.id, crtc.id.0 as _);
c.change(plane.crtc_x.id, self.cursor_x.get() as _);
c.change(plane.crtc_y.id, self.cursor_y.get() as _);
c.change(plane.crtc_w.id, buffer.tex.width() as _);
c.change(plane.crtc_h.id, buffer.tex.height() as _);
c.change(plane.src_x.id, 0);
c.change(plane.src_y.id, 0);
c.change(plane.src_w.id, (buffer.tex.width() as u64) << 16);
c.change(plane.src_h.id, (buffer.tex.height() as u64) << 16);
});
} else {
changes.change_object(plane.id, |c| {
c.change(plane.fb_id, 0);
c.change(plane.crtc_id.id, 0);
});
}
}
if let Err(e) = changes.commit(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, 0) {
match e {
DrmError::Atomic(OsError(c::EACCES)) => {
@ -270,6 +394,7 @@ impl MetalConnector {
}
self.can_present.set(false);
self.has_damage.set(false);
self.cursor_changed.set(false);
}
}
@ -346,6 +471,8 @@ pub struct MetalPlane {
pub possible_crtcs: u32,
pub formats: AHashMap<u32, &'static Format>,
pub assigned: Cell<bool>,
pub crtc_id: MutableProperty<DrmCrtc>,
pub crtc_x: MutableProperty<i32>,
pub crtc_y: MutableProperty<i32>,
@ -404,12 +531,20 @@ fn create_connector(
can_present: Cell::new(true),
has_damage: Cell::new(true),
primary_plane: Default::default(),
cursor_plane: Default::default(),
crtc: Default::default(),
on_change: Default::default(),
present_trigger: Default::default(),
render_result: RefCell::new(Default::default()),
cursor_generation: Default::default(),
cursor_x: Cell::new(0),
cursor_y: Cell::new(0),
cursor_enabled: Cell::new(false),
cursor_buffers: Default::default(),
display: RefCell::new(display),
connect_sent: Cell::new(false),
cursor_changed: Cell::new(false),
cursor_front_buffer: Default::default(),
});
let futures = ConnectorFutures {
present: backend
@ -629,6 +764,7 @@ fn create_plane(plane: DrmPlane, master: &Rc<DrmMaster>) -> Result<MetalPlane, D
src_w: props.get("SRC_W")?.map(|v| v as u32),
src_h: props.get("SRC_H")?.map(|v| v as u32),
in_fence_fd: props.get("IN_FENCE_FD")?.id,
assigned: Cell::new(false),
})
}
@ -856,7 +992,7 @@ impl MetalBackend {
Ok(())
}
fn send_connected(&self, connector: &MetalConnector, dd: &ConnectorDisplayData) {
fn send_connected(&self, connector: &Rc<MetalConnector>, dd: &ConnectorDisplayData) {
let mut prev_mode = None;
let mut modes = vec![];
for mode in dd.modes.iter().map(|m| m.to_backend()) {
@ -874,6 +1010,7 @@ impl MetalBackend {
height_mm: dd.mm_height as _,
}));
connector.connect_sent.set(true);
connector.send_hardware_cursor();
}
pub fn create_drm_device(
@ -886,6 +1023,14 @@ impl MetalBackend {
}
let resources = master.get_resources()?;
let (cursor_width, cursor_height) = match master.get_cursor_size() {
Ok(s) => s,
Err(e) => {
log::warn!("Can't determine size of cursor planes: {}", ErrorFmt(e));
(64, 64)
}
};
let mut planes = AHashMap::new();
for plane in master.get_planes()? {
match create_plane(plane, master) {
@ -933,6 +1078,8 @@ impl MetalBackend {
max_width: resources.max_width,
min_height: resources.min_height,
max_height: resources.max_height,
cursor_width,
cursor_height,
gbm,
handle_events: HandleEvents {
handle_events: Cell::new(None),
@ -1019,6 +1166,7 @@ impl MetalBackend {
for connector in dev.connectors.lock().values() {
connector.can_present.set(true);
connector.has_damage.set(true);
connector.cursor_changed.set(true);
}
if dev.unprocessed_change.get() {
return self.handle_drm_change_(dev, false);
@ -1083,7 +1231,7 @@ impl MetalBackend {
_ => return,
};
connector.can_present.set(true);
if connector.has_damage.get() {
if connector.has_damage.get() || connector.cursor_changed.get() {
connector.schedule_present();
}
let dd = connector.display.borrow_mut();
@ -1123,6 +1271,7 @@ impl MetalBackend {
continue;
}
plane.crtc_id.value.set(DrmCrtc::NONE);
plane.assigned.set(false);
changes.change_object(plane.id, |c| {
c.change(plane.crtc_id.id, 0);
c.change(plane.fb_id, 0);
@ -1135,19 +1284,15 @@ impl MetalBackend {
&self,
dev: &MetalDrmDeviceData,
changes: &mut Change,
preserve: &mut Preserve,
preserve: &Preserve,
) {
for connector in dev.connectors.lock().values() {
if preserve.connectors.contains(&connector.id) {
if let Some(pp) = connector.primary_plane.get() {
preserve.planes.insert(pp.id);
}
if let Some(crtc) = connector.crtc.get() {
preserve.crtcs.insert(crtc.id);
}
continue;
}
connector.primary_plane.set(None);
connector.cursor_plane.set(None);
connector.cursor_enabled.set(false);
connector.crtc.set(None);
let dd = connector.display.borrow_mut();
dd.crtc_id.value.set(DrmCrtc::NONE);
@ -1220,11 +1365,31 @@ impl MetalBackend {
fail!(c.id);
}
}
if let Some(plane) = c.cursor_plane.get() {
let crtc_id = plane.crtc_id.value.get();
if crtc_id.is_some() && crtc_id != crtc.id {
log::warn!("Cannot preserve connector whose cursor plane is attached to a different crtc");
fail!(c.id);
}
}
}
}
for c in remove_connectors {
preserve.connectors.remove(&c);
}
for connector in dev.connectors.lock().values() {
if preserve.connectors.contains(&connector.id) {
if let Some(pp) = connector.primary_plane.get() {
preserve.planes.insert(pp.id);
}
if let Some(pp) = connector.cursor_plane.get() {
preserve.planes.insert(pp.id);
}
if let Some(crtc) = connector.crtc.get() {
preserve.crtcs.insert(crtc.id);
}
}
}
}
fn init_drm_device(
@ -1256,7 +1421,7 @@ impl MetalBackend {
for connector in dev.connectors.lock().values() {
if !preserve.connectors.contains(&connector.id) {
if let Err(e) =
self.assign_connector_plane(connector, &mut changes, &ctx, &mut old_buffers)
self.assign_connector_planes(connector, &mut changes, &ctx, &mut old_buffers)
{
log::error!("Could not assign a plane: {}", ErrorFmt(e));
}
@ -1265,6 +1430,12 @@ impl MetalBackend {
if let Err(e) = changes.commit(flags, 0) {
return Err(MetalError::Modeset(e));
}
for connector in dev.connectors.lock().values() {
if preserve.connectors.contains(&connector.id) {
continue;
}
connector.send_hardware_cursor();
}
Ok(())
}
@ -1355,8 +1526,9 @@ impl MetalBackend {
width: i32,
height: i32,
ctx: &MetalRenderContext,
cursor: bool,
) -> Result<[RenderBuffer; 2], MetalError> {
let create = || self.create_scanout_buffer(dev, format, width, height, ctx);
let create = || self.create_scanout_buffer(dev, format, width, height, ctx, cursor);
Ok([create()?, create()?])
}
@ -1367,9 +1539,10 @@ impl MetalBackend {
width: i32,
height: i32,
ctx: &MetalRenderContext,
cursor: bool,
) -> Result<RenderBuffer, MetalError> {
let mut usage = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT;
if ctx.dev.id != dev.id {
if cursor || ctx.dev.id != dev.id {
usage |= GBM_BO_USE_LINEAR;
};
let bo = dev.gbm.create_bo(width, height, format, usage);
@ -1439,7 +1612,7 @@ impl MetalBackend {
Ok(())
}
fn assign_connector_plane(
fn assign_connector_planes(
&self,
connector: &Rc<MetalConnector>,
changes: &mut Change,
@ -1461,7 +1634,7 @@ impl MetalBackend {
let primary_plane = 'primary_plane: {
for plane in crtc.possible_planes.values() {
if plane.ty == PlaneType::Primary
&& plane.crtc_id.value.get().is_none()
&& !plane.assigned.get()
&& plane.formats.contains_key(&XRGB8888.drm)
{
break 'primary_plane plane.clone();
@ -1469,17 +1642,51 @@ impl MetalBackend {
}
return Err(MetalError::NoPrimaryPlaneForConnector);
};
let format = ModifiedFormat {
format: XRGB8888,
modifier: INVALID_MODIFIER,
};
let buffers = Rc::new(self.create_scanout_buffers(
&connector.dev,
&format,
&ModifiedFormat {
format: XRGB8888,
modifier: INVALID_MODIFIER,
},
mode.hdisplay as _,
mode.vdisplay as _,
ctx,
false,
)?);
let mut cursor_plane = None;
for plane in crtc.possible_planes.values() {
if plane.ty == PlaneType::Cursor
&& !plane.assigned.get()
&& plane.formats.contains_key(&ARGB8888.drm)
{
cursor_plane = Some(plane.clone());
break;
}
}
let mut cursor_buffers = None;
if cursor_plane.is_some() {
let res = self.create_scanout_buffers(
&connector.dev,
&ModifiedFormat {
format: ARGB8888,
modifier: INVALID_MODIFIER,
},
connector.dev.cursor_width as _,
connector.dev.cursor_height as _,
ctx,
true,
);
match res {
Ok(r) => cursor_buffers = Some(Rc::new(r)),
Err(e) => {
log::warn!(
"Could not allocate buffers for the cursor plane: {}",
ErrorFmt(e)
);
cursor_plane = None;
}
}
}
changes.change_object(primary_plane.id, |c| {
c.change(primary_plane.fb_id, buffers[0].drm.id().0 as _);
c.change(primary_plane.crtc_id.id, crtc.id.0 as _);
@ -1492,6 +1699,7 @@ impl MetalBackend {
c.change(primary_plane.src_w.id, (mode.hdisplay as u64) << 16);
c.change(primary_plane.src_h.id, (mode.vdisplay as u64) << 16);
});
primary_plane.assigned.set(true);
primary_plane.crtc_id.value.set(crtc.id);
primary_plane.crtc_x.value.set(0);
primary_plane.crtc_y.value.set(0);
@ -1505,6 +1713,14 @@ impl MetalBackend {
old_buffers.push(old);
}
connector.primary_plane.set(Some(primary_plane.clone()));
if let Some(cp) = &cursor_plane {
cp.assigned.set(true);
}
if let Some(old) = connector.cursor_buffers.set(cursor_buffers) {
old_buffers.push(old);
}
connector.cursor_plane.set(cursor_plane);
connector.cursor_enabled.set(false);
Ok(())
}
@ -1516,6 +1732,7 @@ impl MetalBackend {
dd.mode.as_ref().unwrap(),
);
connector.has_damage.set(true);
connector.cursor_changed.set(true);
connector.schedule_present();
}
}

View file

@ -700,6 +700,7 @@ impl XBackend {
true,
rr.deref_mut(),
node.preferred_scale.get(),
true,
);
for fr in rr.frame_requests.drain(..) {
fr.send_done();

View file

@ -193,6 +193,7 @@ fn start_compositor2(
},
scales,
cursor_sizes: Default::default(),
hardware_tick_cursor: Default::default(),
});
state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state);
@ -274,6 +275,7 @@ fn start_global_event_handlers(
res.push(eng.spawn(tasks::handle_backend_events(state.clone())));
res.push(eng.spawn(tasks::handle_slow_clients(state.clone())));
res.push(eng.spawn(tasks::handle_hardware_cursor_tick(state.clone())));
res.push(eng.spawn2(Phase::Layout, container_layout(state.clone())));
res.push(eng.spawn2(Phase::PostLayout, container_render_data(state.clone())));
res.push(eng.spawn2(Phase::Layout, float_layout(state.clone())));
@ -383,6 +385,7 @@ fn create_dummy_output(state: &Rc<State>) {
pointer_positions: Default::default(),
lock_surface: Default::default(),
preferred_scale: Cell::new(Fixed::from_int(1)),
hardware_cursor: Default::default(),
});
let dummy_workspace = Rc::new(WorkspaceNode {
id: state.node_ids.next(),

View file

@ -605,6 +605,24 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_set_use_hardware_cursor(
&self,
seat: Seat,
use_hardware_cursor: bool,
) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
if use_hardware_cursor {
for other in self.state.globals.seats.lock().values() {
if other.id() != seat.id() {
other.set_hardware_cursor(false);
}
}
}
seat.set_hardware_cursor(use_hardware_cursor);
self.state.refresh_hardware_cursors();
Ok(())
}
fn handle_connector_size(&self, connector: Connector) -> Result<(), CphError> {
let connector = self.get_output(connector)?;
let pos = connector.node.global.pos.get();
@ -1171,6 +1189,12 @@ impl ConfigProxyHandler {
ClientMessage::SetDragLockEnabled { device, enabled } => self
.handle_set_drag_lock_enabled(device, enabled)
.wrn("set_drag_lock_enabled")?,
ClientMessage::SetUseHardwareCursor {
seat,
use_hardware_cursor,
} => self
.handle_set_use_hardware_cursor(seat, use_hardware_cursor)
.wrn("set_use_hardware_cursor")?,
}
Ok(())
}

View file

@ -5,6 +5,7 @@ use {
rect::Rect,
render::{RenderContext, RenderError, Renderer, Texture},
state::State,
time::Time,
tree::OutputNode,
utils::{errorfmt::ErrorFmt, numcell::NumCell, smallmap::SmallMapMut},
},
@ -22,9 +23,9 @@ use {
mem::MaybeUninit,
rc::Rc,
slice, str,
time::Duration,
},
thiserror::Error,
uapi::c,
};
const XCURSOR_MAGIC: u32 = 0x72756358;
@ -38,11 +39,19 @@ const HEADER_SIZE: u32 = 16;
pub trait Cursor {
fn render(&self, renderer: &mut Renderer, x: Fixed, y: Fixed);
fn render_hardware_cursor(&self, renderer: &mut Renderer);
fn extents_at_scale(&self, scale: Fixed) -> Rect;
fn set_output(&self, output: &Rc<OutputNode>) {
let _ = output;
}
fn handle_unset(&self) {}
fn tick(&self) {}
fn needs_tick(&self) -> bool {
false
}
fn time_until_tick(&self) -> Duration {
Duration::new(0, 0)
}
}
pub struct ServerCursors {
@ -183,19 +192,12 @@ impl ServerCursorTemplate {
ServerCursorTemplateVariant::Static(s) => Rc::new(StaticCursor {
image: s.for_size(size),
}),
ServerCursorTemplateVariant::Animated(a) => {
let mut start = c::timespec {
tv_sec: 0,
tv_nsec: 0,
};
uapi::clock_gettime(c::CLOCK_MONOTONIC, &mut start).unwrap();
Rc::new(AnimatedCursor {
start,
next: NumCell::new(a[0].delay_ns),
idx: Cell::new(0),
images: a.iter().map(|c| c.for_size(size)).collect(),
})
}
ServerCursorTemplateVariant::Animated(a) => Rc::new(AnimatedCursor {
start: Time::now_unchecked(),
next: NumCell::new(a[0].delay_ns),
idx: Cell::new(0),
images: a.iter().map(|c| c.for_size(size)).collect(),
}),
}
}
}
@ -237,7 +239,7 @@ impl CursorImage {
sizes: SmallMapMut<(Fixed, u32), Rc<CursorImageScaled>, 2>,
) -> Result<Self, CursorError> {
Ok(Self {
delay_ns: delay_ms * 1_000_000,
delay_ns: delay_ms.max(1) * 1_000_000,
sizes,
})
}
@ -291,10 +293,23 @@ impl Cursor for StaticCursor {
fn render(&self, renderer: &mut Renderer, x: Fixed, y: Fixed) {
render_img(&self.image, renderer, x, y);
}
fn render_hardware_cursor(&self, renderer: &mut Renderer) {
if let Some(img) = self.image.scales.get(&renderer.scale()) {
renderer.render_texture(&img.tex, 0, 0, ARGB8888, None, None, renderer.scale());
}
}
fn extents_at_scale(&self, scale: Fixed) -> Rect {
match self.image.scales.get(&scale) {
None => Rect::new_empty(0, 0),
Some(i) => i.extents,
}
}
}
struct AnimatedCursor {
start: c::timespec,
start: Time,
next: NumCell<u64>,
idx: Cell<usize>,
images: Vec<InstantiatedCursorImage>,
@ -306,15 +321,24 @@ impl Cursor for AnimatedCursor {
render_img(img, renderer, x, y);
}
fn render_hardware_cursor(&self, renderer: &mut Renderer) {
let img = &self.images[self.idx.get()];
if let Some(img) = img.scales.get(&renderer.scale()) {
renderer.render_texture(&img.tex, 0, 0, ARGB8888, None, None, renderer.scale());
}
}
fn extents_at_scale(&self, scale: Fixed) -> Rect {
let img = &self.images[self.idx.get()];
match img.scales.get(&scale) {
None => Rect::new_empty(0, 0),
Some(i) => i.extents,
}
}
fn tick(&self) {
let mut now = c::timespec {
tv_sec: 0,
tv_nsec: 0,
};
uapi::clock_gettime(c::CLOCK_MONOTONIC, &mut now).unwrap();
let dist = (now.tv_sec.wrapping_sub(self.start.tv_sec)) as i64 * 1_000_000_000
+ now.tv_nsec.wrapping_sub(self.start.tv_nsec) as i64;
if (dist as u64) < self.next.get() {
let dist = Time::now_unchecked() - self.start;
if (dist.as_nanos() as u64) < self.next.get() {
return;
}
let idx = (self.idx.get() + 1) % self.images.len();
@ -322,6 +346,17 @@ impl Cursor for AnimatedCursor {
let image = &self.images[idx];
self.next.fetch_add(image.delay_ns);
}
fn needs_tick(&self) -> bool {
true
}
fn time_until_tick(&self) -> Duration {
let dist = Time::now_unchecked() - self.start;
let dist = dist.as_nanos() as u64;
let nanos = self.next.get().saturating_sub(dist);
Duration::from_nanos(nanos)
}
}
struct OpenCursorResult {

View file

@ -38,6 +38,7 @@ use {
},
leaks::Tracker,
object::Object,
rect::Rect,
state::State,
tree::{
generic_node_visitor, ContainerNode, ContainerSplit, Direction, FloatNode, FoundNode,
@ -150,6 +151,7 @@ pub struct WlSeatGlobal {
desired_known_cursor: Cell<Option<KnownCursor>>,
changes: NumCell<u32>,
cursor_size: Cell<u32>,
hardware_cursor: Cell<bool>,
}
const CHANGE_CURSOR_MOVED: u32 = 1 << 0;
@ -202,6 +204,7 @@ impl WlSeatGlobal {
desired_known_cursor: Cell::new(None),
changes: NumCell::new(CHANGE_CURSOR_MOVED | CHANGE_TREE),
cursor_size: Cell::new(DEFAULT_CURSOR_SIZE),
hardware_cursor: Cell::new(state.globals.seats.len() == 0),
});
state.add_cursor_size(DEFAULT_CURSOR_SIZE);
let seat = slf.clone();
@ -218,6 +221,78 @@ impl WlSeatGlobal {
slf
}
pub fn set_hardware_cursor(&self, hardware_cursor: bool) {
self.hardware_cursor.set(hardware_cursor);
}
pub fn hardware_cursor(&self) -> bool {
self.hardware_cursor.get()
}
fn update_hardware_cursor_position(&self) {
self.update_hardware_cursor_(false);
}
pub fn update_hardware_cursor(&self) {
self.update_hardware_cursor_(true);
}
fn update_hardware_cursor_(&self, render: bool) {
if !self.hardware_cursor.get() {
return;
}
let cursor = match self.get_cursor() {
Some(c) => c,
_ => {
self.state.disable_hardware_cursors();
return;
}
};
if render {
cursor.tick();
}
let (x, y) = self.get_position();
for output in self.state.root.outputs.lock().values() {
if let Some(hc) = output.hardware_cursor.get() {
let scale = output.preferred_scale.get();
let extents = cursor.extents_at_scale(scale);
if render {
let (max_width, max_height) = hc.max_size();
if extents.width() > max_width || extents.height() > max_height {
hc.set_enabled(false);
hc.commit();
continue;
}
}
let opos = output.global.pos.get();
let (x_rel, y_rel);
if scale == 1 {
x_rel = x.round_down() - opos.x1();
y_rel = y.round_down() - opos.y1();
} else {
let scalef = scale.to_f64();
x_rel = ((x - Fixed::from_int(opos.x1())).to_f64() * scalef).round() as i32;
y_rel = ((y - Fixed::from_int(opos.y1())).to_f64() * scalef).round() as i32;
}
let mode = output.global.mode.get();
if extents
.intersects(&Rect::new_sized(-x_rel, -y_rel, mode.width, mode.height).unwrap())
{
if render {
let buffer = hc.get_buffer();
buffer.render_hardware_cursor(cursor.deref(), &self.state, scale);
hc.swap_buffer();
}
hc.set_enabled(true);
hc.set_position(x_rel + extents.x1(), y_rel + extents.y1());
} else {
hc.set_enabled(false);
}
hc.commit();
}
}
}
pub fn set_cursor_size(&self, size: u32) {
let old = self.cursor_size.replace(size);
if size != old {
@ -361,6 +436,7 @@ impl WlSeatGlobal {
pub fn set_position(&self, x: i32, y: i32) {
self.pos.set((Fixed::from_int(x), Fixed::from_int(y)));
self.update_hardware_cursor_position();
self.trigger_tree_changed();
let output = 'set_output: {
let outputs = self.state.outputs.lock();
@ -681,7 +757,9 @@ impl WlSeatGlobal {
if let Some(cursor) = cursor.as_ref() {
cursor.set_output(&self.output.get());
}
self.cursor.set(cursor);
self.cursor.set(cursor.clone());
self.state.hardware_tick_cursor.push(cursor);
self.update_hardware_cursor();
}
pub fn dnd_icon(&self) -> Option<Rc<WlSurface>> {

View file

@ -480,6 +480,7 @@ impl WlSeatGlobal {
fn set_new_position(self: &Rc<Self>, time_usec: u64, x: Fixed, y: Fixed) {
self.pos_time_usec.set(time_usec);
self.pos.set((x, y));
self.update_hardware_cursor_position();
self.changes.or_assign(CHANGE_CURSOR_MOVED);
self.apply_changes();
}

View file

@ -804,9 +804,6 @@ impl WlSurface {
}
self.buffer_abs_pos
.set(self.buffer_abs_pos.get().with_size(width, height).unwrap());
for (_, cursor) in &self.cursors {
cursor.handle_buffer_change();
}
}
{
let mut pfr = self.pending.frame_request.borrow_mut();
@ -832,6 +829,11 @@ impl WlSurface {
if self.need_extents_update.get() {
self.calculate_extents();
}
if buffer_transform_changed || transform_changed {
for (_, cursor) in &self.cursors {
cursor.handle_buffer_change();
}
}
ext.post_commit();
self.client.state.damage();
Ok(())

View file

@ -31,10 +31,12 @@ impl CursorSurface {
}
fn update_extents(&self) {
let extents = self.extents.get();
let (hot_x, hot_y) = self.hotspot.get();
self.extents
.set(Rect::new_sized(-hot_x, -hot_y, extents.width(), extents.height()).unwrap());
.set(self.surface.extents.get().move_(-hot_x, -hot_y));
if self.seat.hardware_cursor() {
self.seat.update_hardware_cursor();
}
}
pub fn handle_surface_destroy(&self) {
@ -42,9 +44,6 @@ impl CursorSurface {
}
pub fn handle_buffer_change(&self) {
let (width, height) = self.surface.buffer_abs_pos.get().size();
self.extents
.set(Rect::new_sized(0, 0, width, height).unwrap());
self.update_extents();
}
@ -62,22 +61,44 @@ impl CursorSurface {
impl Cursor for CursorSurface {
fn render(&self, renderer: &mut Renderer, x: Fixed, y: Fixed) {
let extents = self.extents.get().move_(x.round_down(), y.round_down());
let x_int = x.round_down();
let y_int = y.round_down();
let extents = self.extents.get().move_(x_int, y_int);
if extents.intersects(&renderer.logical_extents()) {
let (hot_x, hot_y) = self.hotspot.get();
let scale = renderer.scale();
if scale != 1 {
let scale = scale.to_f64();
let (hot_x, hot_y) = self.hotspot.get();
let (hot_x, hot_y) = (Fixed::from_int(hot_x), Fixed::from_int(hot_y));
let x = ((x - hot_x).to_f64() * scale).round() as _;
let y = ((y - hot_y).to_f64() * scale).round() as _;
renderer.render_surface_scaled(&self.surface, x, y, None);
} else {
renderer.render_surface(&self.surface, extents.x1(), extents.y1());
renderer.render_surface(&self.surface, x_int - hot_x, y_int - hot_y);
}
}
}
fn render_hardware_cursor(&self, renderer: &mut Renderer) {
let extents = self.surface.extents.get();
renderer.render_surface(&self.surface, -extents.x1(), -extents.y1());
}
fn extents_at_scale(&self, scale: Fixed) -> Rect {
let rect = self.extents.get();
if scale == 1 {
return rect;
}
let scale = scale.to_f64();
Rect::new(
(rect.x1() as f64 * scale).ceil() as _,
(rect.y1() as f64 * scale).ceil() as _,
(rect.x2() as f64 * scale).ceil() as _,
(rect.y2() as f64 * scale).ceil() as _,
)
.unwrap()
}
fn set_output(&self, output: &Rc<OutputNode>) {
self.surface.set_output(output);
}

View file

@ -99,13 +99,8 @@ impl Rect {
}
}
#[allow(dead_code)]
pub fn intersects(&self, other: &Self) -> bool {
let x1 = self.x1.max(other.x1);
let y1 = self.y1.max(other.y1);
let x2 = self.x2.min(other.x2);
let y2 = self.y2.min(other.y2);
x1 < x2 && y1 < y2
self.x1 < other.x2 && other.x1 < self.x2 && self.y1 < other.y2 && other.y1 < self.y2
}
pub fn intersect(&self, other: Self) -> Self {

View file

@ -1,5 +1,6 @@
use {
crate::{
cursor::Cursor,
fixed::Fixed,
format::{Format, XRGB8888},
rect::Rect,
@ -113,6 +114,7 @@ impl Framebuffer {
on_output: bool,
result: &mut RenderResult,
scale: Fixed,
render_hardware_cursor: bool,
) {
let _ = self.ctx.ctx.with_current(|| {
let c = state.theme.colors.background.get();
@ -138,6 +140,9 @@ impl Framebuffer {
if let Some(rect) = cursor_rect {
let seats = state.globals.lock_seats();
for seat in seats.values() {
if !render_hardware_cursor && seat.hardware_cursor() {
continue;
}
if let Some(cursor) = seat.get_cursor() {
let (mut x, mut y) = seat.get_position();
if let Some(dnd_icon) = seat.dnd_icon() {
@ -163,4 +168,33 @@ impl Framebuffer {
Ok(())
});
}
pub fn render_hardware_cursor(&self, cursor: &dyn Cursor, state: &State, scale: Fixed) {
let _ = self.ctx.ctx.with_current(|| {
unsafe {
glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo);
glViewport(0, 0, self.gl.width, self.gl.height);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
let mut res = RenderResult::default();
let mut renderer = Renderer {
ctx: &self.ctx,
fb: &self.gl,
state,
on_output: false,
result: &mut res,
scaled: scale != 1,
scale,
scalef: scale.to_f64(),
logical_extents: Rect::new_empty(0, 0),
};
cursor.render_hardware_cursor(&mut renderer);
unsafe {
glFlush();
}
Ok(())
});
}
}

View file

@ -61,6 +61,7 @@ pub fn take_screenshot(state: &State) -> Result<Screenshot, ScreenshooterError>
false,
&mut Default::default(),
Fixed::from_int(1),
true,
);
let drm = ctx.gbm.drm.dup_render()?.fd().clone();
Ok(Screenshot { drm, bo })

View file

@ -10,7 +10,7 @@ use {
cli::RunArgs,
client::{Client, Clients, SerialRange, NUM_CACHED_SERIAL_RANGES},
config::ConfigProxy,
cursor::ServerCursors,
cursor::{Cursor, ServerCursors},
dbus::Dbus,
fixed::Fixed,
forker::ForkerProxy,
@ -113,6 +113,7 @@ pub struct State {
pub lock: ScreenlockState,
pub scales: RefCounted<Fixed>,
pub cursor_sizes: RefCounted<u32>,
pub hardware_tick_cursor: AsyncQueue<Option<Rc<dyn Cursor>>>,
}
// impl Drop for State {
@ -594,4 +595,31 @@ impl State {
self.wheel.clear();
self.eng.clear();
}
pub fn disable_hardware_cursors(&self) {
for output in self.root.outputs.lock().values() {
if let Some(hc) = output.hardware_cursor.get() {
hc.set_enabled(false);
hc.commit();
}
}
}
pub fn refresh_hardware_cursors(&self) {
let seat = self
.globals
.seats
.lock()
.values()
.find(|s| s.hardware_cursor())
.cloned();
let seat = match seat {
Some(s) => s,
_ => {
self.disable_hardware_cursors();
return;
}
};
seat.update_hardware_cursor();
}
}

View file

@ -1,11 +1,11 @@
mod backend;
mod connector;
mod drmdev;
mod hardware_cursor;
mod idle;
mod input_device;
mod slow_clients;
pub use idle::idle;
use {
crate::{
state::State,
@ -13,6 +13,7 @@ use {
},
std::rc::Rc,
};
pub use {hardware_cursor::handle_hardware_cursor_tick, idle::idle};
pub async fn handle_backend_events(state: Rc<State>) {
let mut beh = BackendEventHandler { state };

View file

@ -123,6 +123,7 @@ impl ConnectorHandler {
pointer_positions: Default::default(),
lock_surface: Default::default(),
preferred_scale: Cell::new(Fixed::from_int(1)),
hardware_cursor: Default::default(),
});
self.state.add_output_scale(on.preferred_scale.get());
let mode = info.initial_mode;
@ -191,6 +192,10 @@ impl ConnectorHandler {
while let Some(event) = self.data.connector.event() {
match event {
ConnectorEvent::Disconnected => break 'outer,
ConnectorEvent::HardwareCursor(hc) => {
on.hardware_cursor.set(hc);
self.state.refresh_hardware_cursors();
}
ConnectorEvent::ModeChanged(mode) => {
on.update_mode(mode);
}

View file

@ -0,0 +1,31 @@
use {
crate::{state::State, utils::errorfmt::ErrorFmt},
futures_util::{select, FutureExt},
std::rc::Rc,
};
pub async fn handle_hardware_cursor_tick(state: Rc<State>) {
loop {
let cursor = match state.hardware_tick_cursor.pop().await {
Some(c) => c,
_ => continue,
};
if !cursor.needs_tick() {
continue;
}
loop {
let tick = (cursor.time_until_tick().as_nanos() + 999_999) / 1_000_000;
if tick > 0 {
let res = select! {
_ = state.hardware_tick_cursor.non_empty().fuse() => break,
res = state.wheel.timeout(tick as _).fuse() => res,
};
if let Err(e) = res {
log::error!("Could not wait for cursor tick: {}", ErrorFmt(e));
break;
}
}
state.refresh_hardware_cursors();
}
}
}

View file

@ -1,6 +1,6 @@
use {
crate::{
backend::{KeyState, Mode},
backend::{HardwareCursor, KeyState, Mode},
cursor::KnownCursor,
fixed::Fixed,
ifs::{
@ -52,6 +52,7 @@ pub struct OutputNode {
pub pointer_positions: CopyHashMap<SeatId, (i32, i32)>,
pub lock_surface: CloneCell<Option<Rc<ExtSessionLockSurfaceV1>>>,
pub preferred_scale: Cell<Fixed>,
pub hardware_cursor: CloneCell<Option<Rc<dyn HardwareCursor>>>,
}
impl OutputNode {

View file

@ -32,7 +32,11 @@ use {
use crate::{
backend,
utils::{errorfmt::ErrorFmt, stack::Stack, syncqueue::SyncQueue, vec_ext::VecExt},
video::{dmabuf::DmaBuf, INVALID_MODIFIER},
video::{
dmabuf::DmaBuf,
drm::sys::{DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH},
INVALID_MODIFIER,
},
};
pub use sys::{
drm_mode_modeinfo, DRM_CLIENT_CAP_ATOMIC, DRM_MODE_ATOMIC_ALLOW_MODESET,
@ -235,6 +239,12 @@ impl DrmMaster {
mode_getencoder(self.raw(), encoder.0)
}
pub fn get_cursor_size(&self) -> Result<(u64, u64), OsError> {
let width = self.get_cap(DRM_CAP_CURSOR_WIDTH)?;
let height = self.get_cap(DRM_CAP_CURSOR_HEIGHT)?;
Ok((width, height))
}
pub fn get_connector_info(
&self,
connector: DrmConnector,

View file

@ -217,6 +217,9 @@ const DRM_MODE_PROP_SIGNED_RANGE: u32 = drm_mode_prop_type(2);
const DRM_MODE_PROP_ATOMIC: u32 = 0x80000000;
pub const DRM_CAP_CURSOR_WIDTH: u64 = 0x8;
pub const DRM_CAP_CURSOR_HEIGHT: u64 = 0x9;
#[repr(C)]
struct drm_mode_property_enum {
value: u64,