server: unscale locked pointer cursor hint
Fix #199, fix #202, fix #204
This commit is contained in:
parent
557ebeb616
commit
07847e11d7
3 changed files with 192 additions and 28 deletions
|
|
@ -912,15 +912,31 @@ impl<C: XConnection> Dispatch<LockedPointerServer, Entity> for ServerState<C> {
|
||||||
_: &DisplayHandle,
|
_: &DisplayHandle,
|
||||||
_: &mut wayland_server::DataInit<'_, Self>,
|
_: &mut wayland_server::DataInit<'_, Self>,
|
||||||
) {
|
) {
|
||||||
|
match request {
|
||||||
|
lp::Request::SetCursorPositionHint {
|
||||||
|
surface_x,
|
||||||
|
surface_y,
|
||||||
|
} => {
|
||||||
|
let (client, scale) = state
|
||||||
|
.world
|
||||||
|
.query_one_mut::<(&LockedPointerClient, &SurfaceScaleFactor)>(*entity)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Xwayland believes that the surface is actually <surface scale factor> times bigger
|
||||||
|
// than it currently is, and therefore that the cursor position is also scaled up by the same
|
||||||
|
// amount. So we need to divide the cursor position from Xwayland by the surface scale
|
||||||
|
// to get where the cursor should actually be positioned.
|
||||||
|
|
||||||
|
client.set_cursor_position_hint(surface_x / scale.0, surface_y / scale.0);
|
||||||
|
}
|
||||||
|
lp::Request::Destroy => {
|
||||||
|
{
|
||||||
let client = state.world.get::<&LockedPointerClient>(*entity).unwrap();
|
let client = state.world.get::<&LockedPointerClient>(*entity).unwrap();
|
||||||
simple_event_shunt! {
|
client.destroy();
|
||||||
client, request: lp::Request => [
|
}
|
||||||
SetCursorPositionHint { surface_x, surface_y },
|
state.world.despawn(*entity).unwrap();
|
||||||
SetRegion {
|
}
|
||||||
|region| region.as_ref().map(|r| r.data().unwrap())
|
_ => warn!("unhandled locked pointer request: {request:?}"),
|
||||||
},
|
|
||||||
Destroy
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -983,8 +999,8 @@ impl<C: XConnection>
|
||||||
} => {
|
} => {
|
||||||
let surf_key: Entity = surface.data().copied().unwrap();
|
let surf_key: Entity = surface.data().copied().unwrap();
|
||||||
let ptr_key: Entity = pointer.data().copied().unwrap();
|
let ptr_key: Entity = pointer.data().copied().unwrap();
|
||||||
|
|
||||||
let entity = state.world.reserve_entity();
|
let entity = state.world.reserve_entity();
|
||||||
|
|
||||||
let client = {
|
let client = {
|
||||||
let c_surface = state
|
let c_surface = state
|
||||||
.world
|
.world
|
||||||
|
|
@ -1004,8 +1020,16 @@ impl<C: XConnection>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let server = data_init.init(id, entity);
|
let server = data_init.init(id, entity);
|
||||||
|
let surface_scale = state
|
||||||
|
.world
|
||||||
|
.get::<&SurfaceScaleFactor>(surf_key)
|
||||||
|
.as_deref()
|
||||||
|
.copied()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
state.world.spawn_at(entity, (client, server));
|
state
|
||||||
|
.world
|
||||||
|
.spawn_at(entity, (client, server, surface_scale));
|
||||||
}
|
}
|
||||||
Request::Destroy => {
|
Request::Destroy => {
|
||||||
client.destroy();
|
client.destroy();
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,10 @@ use wayland_client::{
|
||||||
use wayland_protocols::{
|
use wayland_protocols::{
|
||||||
wp::{
|
wp::{
|
||||||
linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||||
pointer_constraints::zv1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
|
pointer_constraints::zv1::client::{
|
||||||
|
zwp_locked_pointer_v1::ZwpLockedPointerV1,
|
||||||
|
zwp_pointer_constraints_v1::{self, ZwpPointerConstraintsV1},
|
||||||
|
},
|
||||||
relative_pointer::zv1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
relative_pointer::zv1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||||
tablet::zv2::client::{
|
tablet::zv2::client::{
|
||||||
zwp_tablet_manager_v2::{self, ZwpTabletManagerV2},
|
zwp_tablet_manager_v2::{self, ZwpTabletManagerV2},
|
||||||
|
|
@ -104,7 +107,8 @@ struct Compositor {
|
||||||
shm: TestObject<WlShm>,
|
shm: TestObject<WlShm>,
|
||||||
shell: TestObject<XwaylandShellV1>,
|
shell: TestObject<XwaylandShellV1>,
|
||||||
seat: TestObject<WlSeat>,
|
seat: TestObject<WlSeat>,
|
||||||
tablet_man: TestObject<ZwpTabletManagerV2>
|
tablet_man: TestObject<ZwpTabletManagerV2>,
|
||||||
|
pointer_constraints: TestObject<ZwpPointerConstraintsV1>,
|
||||||
} => CompositorOptional
|
} => CompositorOptional
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -190,14 +194,12 @@ impl crate::X11Selection for Vec<testwl::PasteData> {
|
||||||
mime: &str,
|
mime: &str,
|
||||||
mut pipe: smithay_client_toolkit::data_device_manager::WritePipe,
|
mut pipe: smithay_client_toolkit::data_device_manager::WritePipe,
|
||||||
) {
|
) {
|
||||||
println!("writing");
|
|
||||||
let data = self
|
let data = self
|
||||||
.iter()
|
.iter()
|
||||||
.find(|data| data.mime_type == mime)
|
.find(|data| data.mime_type == mime)
|
||||||
.unwrap_or_else(|| panic!("Couldn't find mime type {mime}"));
|
.unwrap_or_else(|| panic!("Couldn't find mime type {mime}"));
|
||||||
pipe.write_all(&data.data)
|
pipe.write_all(&data.data)
|
||||||
.expect("Couldn't write paste data");
|
.expect("Couldn't write paste data");
|
||||||
println!("goodbye pipe {mime}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -475,6 +477,9 @@ impl TestFixture {
|
||||||
x if x == XwaylandShellV1::interface().name => bind!(shell),
|
x if x == XwaylandShellV1::interface().name => bind!(shell),
|
||||||
x if x == WlSeat::interface().name => bind!(seat),
|
x if x == WlSeat::interface().name => bind!(seat),
|
||||||
x if x == ZwpTabletManagerV2::interface().name => bind!(tablet_man),
|
x if x == ZwpTabletManagerV2::interface().name => bind!(tablet_man),
|
||||||
|
x if x == ZwpPointerConstraintsV1::interface().name => {
|
||||||
|
bind!(pointer_constraints)
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2248,8 +2253,6 @@ fn subpopup_positioning() {
|
||||||
f.testwl.move_pointer_to(id_popup, 1.0, 1.0);
|
f.testwl.move_pointer_to(id_popup, 1.0, 1.0);
|
||||||
f.run();
|
f.run();
|
||||||
|
|
||||||
println!("{:?}", f.satellite.last_hovered);
|
|
||||||
|
|
||||||
let win_subpopup = unsafe { Window::new(3) };
|
let win_subpopup = unsafe { Window::new(3) };
|
||||||
|
|
||||||
f.create_popup(
|
f.create_popup(
|
||||||
|
|
@ -2493,6 +2496,53 @@ fn quick_destroy_window_with_serial() {
|
||||||
surface_data.role
|
surface_data.role
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scaled_pointer_lock_position_hint() {
|
||||||
|
let mut f = TestFixture::new_pre_connect(|testwl| {
|
||||||
|
testwl.enable_fractional_scale();
|
||||||
|
});
|
||||||
|
let comp = f.compositor();
|
||||||
|
let pointer =
|
||||||
|
TestObject::<WlPointer>::from_request(&comp.seat.obj, wl_seat::Request::GetPointer {});
|
||||||
|
|
||||||
|
let (_, output) = f.new_output(0, 0);
|
||||||
|
let win = unsafe { Window::new(1) };
|
||||||
|
let (surface, id) = f.create_toplevel(&comp, win);
|
||||||
|
let surface_data = f.testwl.get_surface_data(id).expect("No surface data");
|
||||||
|
let fractional = surface_data
|
||||||
|
.fractional
|
||||||
|
.as_ref()
|
||||||
|
.expect("No fractional scale for surface");
|
||||||
|
fractional.preferred_scale(180); // 1.5 scale
|
||||||
|
f.testwl.move_surface_to_output(id, &output);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
let locked_pointer = TestObject::<ZwpLockedPointerV1>::from_request(
|
||||||
|
&comp.pointer_constraints.obj,
|
||||||
|
zwp_pointer_constraints_v1::Request::LockPointer {
|
||||||
|
surface: surface.obj.clone(),
|
||||||
|
pointer: pointer.obj.clone(),
|
||||||
|
region: None,
|
||||||
|
lifetime: WEnum::Value(zwp_pointer_constraints_v1::Lifetime::Persistent),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
locked_pointer.set_cursor_position_hint(75.0, 75.0);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
let lock_data = f
|
||||||
|
.testwl
|
||||||
|
.locked_pointer()
|
||||||
|
.expect("Missing locked pointer data");
|
||||||
|
assert_eq!(lock_data.surface, id);
|
||||||
|
assert_eq!(
|
||||||
|
lock_data.cursor_hint,
|
||||||
|
Some(testwl::Vec2f { x: 50.0, y: 50.0 })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// See Pointer::handle_event for an explanation.
|
/// See Pointer::handle_event for an explanation.
|
||||||
#[test]
|
#[test]
|
||||||
fn popup_pointer_motion_workaround() {}
|
fn popup_pointer_motion_workaround() {}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ use wayland_protocols::{
|
||||||
wp_fractional_scale_v1::{self, WpFractionalScaleV1},
|
wp_fractional_scale_v1::{self, WpFractionalScaleV1},
|
||||||
},
|
},
|
||||||
linux_dmabuf::zv1::server::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
linux_dmabuf::zv1::server::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||||
pointer_constraints::zv1::server::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
|
pointer_constraints::zv1::server::{
|
||||||
|
zwp_locked_pointer_v1::{self, ZwpLockedPointerV1},
|
||||||
|
zwp_pointer_constraints_v1::{self, ZwpPointerConstraintsV1},
|
||||||
|
},
|
||||||
relative_pointer::zv1::server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
relative_pointer::zv1::server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||||
tablet::zv2::server::{
|
tablet::zv2::server::{
|
||||||
zwp_tablet_manager_v2::ZwpTabletManagerV2,
|
zwp_tablet_manager_v2::ZwpTabletManagerV2,
|
||||||
|
|
@ -165,6 +168,12 @@ pub struct Vec2 {
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Default)]
|
||||||
|
pub struct Vec2f {
|
||||||
|
pub x: f64,
|
||||||
|
pub y: f64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct XdgSurfaceData {
|
pub struct XdgSurfaceData {
|
||||||
pub surface: XdgSurface,
|
pub surface: XdgSurface,
|
||||||
|
|
@ -187,6 +196,11 @@ impl XdgSurfaceData {
|
||||||
|
|
||||||
#[derive(Debug, Hash, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Hash, Clone, Copy, Eq, PartialEq)]
|
||||||
pub struct SurfaceId(u32);
|
pub struct SurfaceId(u32);
|
||||||
|
impl From<&WlSurface> for SurfaceId {
|
||||||
|
fn from(value: &WlSurface) -> Self {
|
||||||
|
Self(value.id().protocol_id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Hash, Clone, Copy, Eq, PartialEq)]
|
#[derive(Hash, Clone, Copy, Eq, PartialEq)]
|
||||||
struct PositionerId(u32);
|
struct PositionerId(u32);
|
||||||
|
|
@ -214,6 +228,15 @@ struct ActivationTokenData {
|
||||||
constructed: bool,
|
constructed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct LockedPointer {
|
||||||
|
pub surface: SurfaceId,
|
||||||
|
pub cursor_hint: Option<Vec2f>,
|
||||||
|
}
|
||||||
|
struct PointerState {
|
||||||
|
pointer: WlPointer,
|
||||||
|
locked: Option<LockedPointer>,
|
||||||
|
}
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
surfaces: HashMap<SurfaceId, SurfaceData>,
|
surfaces: HashMap<SurfaceId, SurfaceData>,
|
||||||
outputs: HashMap<WlOutput, Output>,
|
outputs: HashMap<WlOutput, Output>,
|
||||||
|
|
@ -224,7 +247,7 @@ struct State {
|
||||||
last_output: Option<WlOutput>,
|
last_output: Option<WlOutput>,
|
||||||
callbacks: Vec<WlCallback>,
|
callbacks: Vec<WlCallback>,
|
||||||
seat: Option<WlSeat>,
|
seat: Option<WlSeat>,
|
||||||
pointer: Option<WlPointer>,
|
pointer: Option<PointerState>,
|
||||||
keyboard: Option<KeyboardState>,
|
keyboard: Option<KeyboardState>,
|
||||||
touch: Option<WlTouch>,
|
touch: Option<WlTouch>,
|
||||||
tablet: Option<ZwpTabletV2>,
|
tablet: Option<ZwpTabletV2>,
|
||||||
|
|
@ -417,9 +440,9 @@ impl Server {
|
||||||
dh.create_global::<State, XdgActivationV1, _>(1, ());
|
dh.create_global::<State, XdgActivationV1, _>(1, ());
|
||||||
dh.create_global::<State, ZxdgDecorationManagerV1, _>(1, ());
|
dh.create_global::<State, ZxdgDecorationManagerV1, _>(1, ());
|
||||||
dh.create_global::<State, WpViewporter, _>(1, ());
|
dh.create_global::<State, WpViewporter, _>(1, ());
|
||||||
|
dh.create_global::<State, ZwpPointerConstraintsV1, _>(1, ());
|
||||||
global_noop!(ZwpLinuxDmabufV1);
|
global_noop!(ZwpLinuxDmabufV1);
|
||||||
global_noop!(ZwpRelativePointerManagerV1);
|
global_noop!(ZwpRelativePointerManagerV1);
|
||||||
global_noop!(ZwpPointerConstraintsV1);
|
|
||||||
|
|
||||||
struct HandlerData;
|
struct HandlerData;
|
||||||
impl ObjectData<State> for HandlerData {
|
impl ObjectData<State> for HandlerData {
|
||||||
|
|
@ -577,7 +600,12 @@ impl Server {
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn pointer(&self) -> &WlPointer {
|
pub fn pointer(&self) -> &WlPointer {
|
||||||
self.state.pointer.as_ref().unwrap()
|
self.state.pointer.as_ref().map(|p| &p.pointer).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn locked_pointer(&self) -> Option<&LockedPointer> {
|
||||||
|
self.state.pointer.as_ref().unwrap().locked.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|
@ -714,8 +742,8 @@ impl Server {
|
||||||
let pointer = self.state.pointer.as_ref().expect("No pointer created");
|
let pointer = self.state.pointer.as_ref().expect("No pointer created");
|
||||||
let data = self.state.surfaces.get(&surface).expect("No such surface");
|
let data = self.state.surfaces.get(&surface).expect("No such surface");
|
||||||
|
|
||||||
pointer.enter(24, &data.surface, x, y);
|
pointer.pointer.enter(24, &data.surface, x, y);
|
||||||
pointer.frame();
|
pointer.pointer.frame();
|
||||||
self.display.flush_clients().unwrap();
|
self.display.flush_clients().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -807,6 +835,7 @@ simple_global_dispatch!(ZwpTabletManagerV2);
|
||||||
simple_global_dispatch!(ZxdgDecorationManagerV1);
|
simple_global_dispatch!(ZxdgDecorationManagerV1);
|
||||||
simple_global_dispatch!(WpViewporter);
|
simple_global_dispatch!(WpViewporter);
|
||||||
simple_global_dispatch!(WpFractionalScaleManagerV1);
|
simple_global_dispatch!(WpFractionalScaleManagerV1);
|
||||||
|
simple_global_dispatch!(ZwpPointerConstraintsV1);
|
||||||
|
|
||||||
impl Dispatch<ZwpTabletManagerV2, ()> for State {
|
impl Dispatch<ZwpTabletManagerV2, ()> for State {
|
||||||
fn request(
|
fn request(
|
||||||
|
|
@ -1100,7 +1129,10 @@ impl Dispatch<WlSeat, ()> for State {
|
||||||
) {
|
) {
|
||||||
match request {
|
match request {
|
||||||
wl_seat::Request::GetPointer { id } => {
|
wl_seat::Request::GetPointer { id } => {
|
||||||
state.pointer = Some(data_init.init(id, ()));
|
state.pointer = Some(PointerState {
|
||||||
|
pointer: data_init.init(id, ()),
|
||||||
|
locked: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
wl_seat::Request::GetKeyboard { id } => {
|
wl_seat::Request::GetKeyboard { id } => {
|
||||||
state.keyboard = Some(KeyboardState {
|
state.keyboard = Some(KeyboardState {
|
||||||
|
|
@ -1130,10 +1162,7 @@ impl Dispatch<WlPointer, ()> for State {
|
||||||
match request {
|
match request {
|
||||||
wl_pointer::Request::SetCursor { surface, .. } => {
|
wl_pointer::Request::SetCursor { surface, .. } => {
|
||||||
if let Some(surface) = surface {
|
if let Some(surface) = surface {
|
||||||
let data = state
|
let data = state.surfaces.get_mut(&SurfaceId::from(&surface)).unwrap();
|
||||||
.surfaces
|
|
||||||
.get_mut(&SurfaceId(surface.id().protocol_id()))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
matches!(
|
matches!(
|
||||||
|
|
@ -2031,3 +2060,64 @@ impl Dispatch<WpFractionalScaleV1, SurfaceId> for State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Dispatch<ZwpPointerConstraintsV1, ()> for State {
|
||||||
|
fn request(
|
||||||
|
state: &mut Self,
|
||||||
|
_: &Client,
|
||||||
|
_: &ZwpPointerConstraintsV1,
|
||||||
|
request: <ZwpPointerConstraintsV1 as Resource>::Request,
|
||||||
|
_: &(),
|
||||||
|
_: &DisplayHandle,
|
||||||
|
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||||
|
) {
|
||||||
|
match request {
|
||||||
|
zwp_pointer_constraints_v1::Request::LockPointer {
|
||||||
|
id,
|
||||||
|
surface,
|
||||||
|
pointer,
|
||||||
|
region: _,
|
||||||
|
lifetime: _,
|
||||||
|
} => {
|
||||||
|
let pointer_state = state.pointer.as_mut().unwrap();
|
||||||
|
assert_eq!(pointer, pointer_state.pointer);
|
||||||
|
let surface_id = SurfaceId::from(&surface);
|
||||||
|
|
||||||
|
assert!(pointer_state.locked.is_none());
|
||||||
|
data_init.init(id, ());
|
||||||
|
pointer_state.locked = Some(LockedPointer {
|
||||||
|
surface: surface_id,
|
||||||
|
cursor_hint: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => todo!("{request:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<ZwpLockedPointerV1, ()> for State {
|
||||||
|
fn request(
|
||||||
|
state: &mut Self,
|
||||||
|
_: &Client,
|
||||||
|
_: &ZwpLockedPointerV1,
|
||||||
|
request: <ZwpLockedPointerV1 as Resource>::Request,
|
||||||
|
_: &(),
|
||||||
|
_: &DisplayHandle,
|
||||||
|
_: &mut wayland_server::DataInit<'_, Self>,
|
||||||
|
) {
|
||||||
|
match request {
|
||||||
|
zwp_locked_pointer_v1::Request::SetCursorPositionHint {
|
||||||
|
surface_x,
|
||||||
|
surface_y,
|
||||||
|
} => {
|
||||||
|
let state = state.pointer.as_mut().unwrap();
|
||||||
|
let lock = state.locked.as_mut().unwrap();
|
||||||
|
lock.cursor_hint = Some(Vec2f {
|
||||||
|
x: surface_x,
|
||||||
|
y: surface_y,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => todo!("{request:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue