xwayland: Add pointer warp emulator

Emulate pointer warps by locking the pointer and sending relative
motion events instead of absolute. X will keep track of the "fake"
pointer cursor position given the relative motion events, and the
client warping the cursor will warp the faked cursor position.

Various requirements need to be met for the pointer warp emulator to
enable:

The cursor must be invisible: since it would not be acceptable that a
fake cursor position would be different from the visual representation
of the cursor, emulation can only be done when there is no visual
representation done by the Wayland compositor. Thus, for the emulator
to enable, the cursor must be hidden, and would the cursor be displayed
while the emulator is active, the emulator would be destroyed.

The window that is warped within must be likely to have pointer focus.
For example, warping outside of the window region will be ignored.

The pointer warp emulator will disable itself once the fake cursor
position leaves the window region, or the cursor is made visible.

This makes various games depending on pointer warping (such as 3D
first-person shooters and stategy games using click-to-drag-map like
things) work.

Signed-off-by: Jonas Ådahl <jadahl@gmail.com>
This commit is contained in:
Jonas Ådahl 2016-09-13 15:17:08 +08:00 committed by Adam Jackson
parent 467ab142ff
commit a6e85e6330
4 changed files with 322 additions and 2 deletions

View File

@ -169,12 +169,19 @@ xwl_set_cursor(DeviceIntPtr device,
ScreenPtr screen, CursorPtr cursor, int x, int y)
{
struct xwl_seat *xwl_seat;
Bool cursor_visibility_changed;
xwl_seat = device->public.devicePrivate;
if (xwl_seat == NULL)
return;
cursor_visibility_changed = !!xwl_seat->x_cursor ^ !!cursor;
xwl_seat->x_cursor = cursor;
if (cursor_visibility_changed)
xwl_seat_cursor_visibility_changed(xwl_seat);
xwl_seat_set_cursor(xwl_seat);
}

View File

@ -46,6 +46,21 @@ struct sync_pending {
DeviceIntPtr pending_dev;
};
static void
xwl_pointer_warp_emulator_handle_motion(struct xwl_pointer_warp_emulator *warp_emulator,
double dx,
double dy,
double dx_unaccel,
double dy_unaccel);
static void
xwl_pointer_warp_emulator_maybe_lock(struct xwl_pointer_warp_emulator *warp_emulator,
struct xwl_window *xwl_window,
SpritePtr sprite,
int x, int y);
static void
xwl_seat_destroy_confined_pointer(struct xwl_seat *xwl_seat);
static void
xwl_pointer_control(DeviceIntPtr device, PtrCtrl *ctrl)
{
@ -331,6 +346,12 @@ pointer_handle_enter(void *data, struct wl_pointer *pointer,
xwl_seat->cursor_frame_cb = NULL;
xwl_seat_set_cursor(xwl_seat);
}
if (xwl_seat->pointer_warp_emulator) {
xwl_pointer_warp_emulator_maybe_lock(xwl_seat->pointer_warp_emulator,
xwl_seat->focus_window,
NULL, 0, 0);
}
}
static void
@ -351,8 +372,22 @@ dispatch_pointer_motion_event(struct xwl_seat *xwl_seat)
{
ValuatorMask mask;
if (xwl_seat->pending_pointer_event.has_absolute ||
if (xwl_seat->pointer_warp_emulator &&
xwl_seat->pending_pointer_event.has_relative) {
double dx;
double dy;
double dx_unaccel;
double dy_unaccel;
dx = xwl_seat->pending_pointer_event.dx;
dy = xwl_seat->pending_pointer_event.dy;
dx_unaccel = xwl_seat->pending_pointer_event.dx_unaccel;
dy_unaccel = xwl_seat->pending_pointer_event.dy_unaccel;
xwl_pointer_warp_emulator_handle_motion(xwl_seat->pointer_warp_emulator,
dx, dy,
dx_unaccel, dy_unaccel);
} else if (xwl_seat->pending_pointer_event.has_absolute ||
xwl_seat->pending_pointer_event.has_relative) {
int x;
int y;
@ -1271,6 +1306,236 @@ xwl_seat_clear_touch(struct xwl_seat *xwl_seat, WindowPtr window)
}
}
static void
xwl_pointer_warp_emulator_set_fake_pos(struct xwl_pointer_warp_emulator *warp_emulator,
int x,
int y)
{
struct zwp_locked_pointer_v1 *locked_pointer =
warp_emulator->locked_pointer;
WindowPtr window;
int sx, sy;
if (!warp_emulator->locked_pointer)
return;
if (!warp_emulator->xwl_seat->focus_window)
return;
window = warp_emulator->xwl_seat->focus_window->window;
if (x >= window->drawable.x ||
y >= window->drawable.y ||
x < (window->drawable.x + window->drawable.width) ||
y < (window->drawable.y + window->drawable.height)) {
sx = x - window->drawable.x;
sy = y - window->drawable.y;
zwp_locked_pointer_v1_set_cursor_position_hint(locked_pointer,
wl_fixed_from_int(sx),
wl_fixed_from_int(sy));
wl_surface_commit(warp_emulator->xwl_seat->focus_window->surface);
}
}
static Bool
xwl_pointer_warp_emulator_is_locked(struct xwl_pointer_warp_emulator *warp_emulator)
{
if (warp_emulator->locked_pointer)
return TRUE;
else
return FALSE;
}
static void
xwl_pointer_warp_emulator_lock(struct xwl_pointer_warp_emulator *warp_emulator)
{
struct xwl_seat *xwl_seat = warp_emulator->xwl_seat;
struct xwl_screen *xwl_screen = xwl_seat->xwl_screen;
struct zwp_pointer_constraints_v1 *pointer_constraints =
xwl_screen->pointer_constraints;
struct xwl_window *lock_window = xwl_seat->focus_window;
warp_emulator->locked_window = lock_window;
warp_emulator->locked_pointer =
zwp_pointer_constraints_v1_lock_pointer(pointer_constraints,
lock_window->surface,
xwl_seat->wl_pointer,
NULL,
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
}
static void
xwl_pointer_warp_emulator_maybe_lock(struct xwl_pointer_warp_emulator *warp_emulator,
struct xwl_window *xwl_window,
SpritePtr sprite,
int x, int y)
{
struct xwl_seat *xwl_seat = warp_emulator->xwl_seat;
GrabPtr pointer_grab = xwl_seat->pointer->deviceGrab.grab;
if (warp_emulator->locked_pointer)
return;
/*
* If there is no grab, and the window doesn't have pointer focus, ignore
* the warp, as under Wayland it won't receive input anyway.
*/
if (!pointer_grab && xwl_seat->focus_window != xwl_window)
return;
/*
* If there is a grab, but it's not an ownerEvents grab and the destination
* is not the pointer focus, ignore it, as events wouldn't be delivered
* there anyway.
*/
if (pointer_grab &&
!pointer_grab->ownerEvents &&
XYToWindow(sprite, x, y) != xwl_seat->focus_window->window)
return;
xwl_pointer_warp_emulator_lock(warp_emulator);
}
static void
xwl_pointer_warp_emulator_warp(struct xwl_pointer_warp_emulator *warp_emulator,
struct xwl_window *xwl_window,
SpritePtr sprite,
int x, int y)
{
xwl_pointer_warp_emulator_maybe_lock(warp_emulator,
xwl_window,
sprite,
x, y);
xwl_pointer_warp_emulator_set_fake_pos(warp_emulator, x, y);
}
static void
xwl_pointer_warp_emulator_handle_motion(struct xwl_pointer_warp_emulator *warp_emulator,
double dx,
double dy,
double dx_unaccel,
double dy_unaccel)
{
struct xwl_seat *xwl_seat = warp_emulator->xwl_seat;
ValuatorMask mask;
WindowPtr window;
int x, y;
valuator_mask_zero(&mask);
valuator_mask_set_unaccelerated(&mask, 0, dx, dx_unaccel);
valuator_mask_set_unaccelerated(&mask, 1, dy, dy_unaccel);
QueuePointerEvents(xwl_seat->relative_pointer, MotionNotify, 0,
POINTER_RELATIVE, &mask);
window = xwl_seat->focus_window->window;
miPointerGetPosition(xwl_seat->pointer, &x, &y);
if (xwl_pointer_warp_emulator_is_locked(warp_emulator) &&
xwl_seat->cursor_confinement_window != warp_emulator->locked_window &&
(x < window->drawable.x ||
y < window->drawable.y ||
x >= (window->drawable.x + window->drawable.width) ||
y >= (window->drawable.y + window->drawable.height)))
xwl_seat_destroy_pointer_warp_emulator(xwl_seat);
else
xwl_pointer_warp_emulator_set_fake_pos(warp_emulator, x, y);
}
static struct xwl_pointer_warp_emulator *
xwl_pointer_warp_emulator_create(struct xwl_seat *xwl_seat)
{
struct xwl_pointer_warp_emulator *warp_emulator;
warp_emulator = calloc(sizeof *warp_emulator, 1);
if (!warp_emulator) {
ErrorF("%s: ENOMEM", __func__);
return NULL;
}
warp_emulator->xwl_seat = xwl_seat;
return warp_emulator;
}
static void
xwl_pointer_warp_emulator_destroy(struct xwl_pointer_warp_emulator *warp_emulator)
{
if (warp_emulator->locked_pointer)
zwp_locked_pointer_v1_destroy(warp_emulator->locked_pointer);
free(warp_emulator);
}
static void
xwl_seat_create_pointer_warp_emulator(struct xwl_seat *xwl_seat)
{
if (xwl_seat->confined_pointer)
xwl_seat_destroy_confined_pointer(xwl_seat);
xwl_seat->pointer_warp_emulator =
xwl_pointer_warp_emulator_create(xwl_seat);
}
static Bool
xwl_seat_can_emulate_pointer_warp(struct xwl_seat *xwl_seat)
{
struct xwl_screen *xwl_screen = xwl_seat->xwl_screen;
if (!xwl_screen->relative_pointer_manager)
return FALSE;
if (!xwl_screen->pointer_constraints)
return FALSE;
return TRUE;
}
void
xwl_seat_emulate_pointer_warp(struct xwl_seat *xwl_seat,
struct xwl_window *xwl_window,
SpritePtr sprite,
int x, int y)
{
if (!xwl_seat_can_emulate_pointer_warp(xwl_seat))
return;
if (xwl_seat->x_cursor != NULL)
return;
if (!xwl_seat->pointer_warp_emulator)
xwl_seat_create_pointer_warp_emulator(xwl_seat);
if (!xwl_seat->pointer_warp_emulator)
return;
xwl_pointer_warp_emulator_warp(xwl_seat->pointer_warp_emulator,
xwl_window,
sprite,
x, y);
}
void
xwl_seat_cursor_visibility_changed(struct xwl_seat *xwl_seat)
{
if (xwl_seat->pointer_warp_emulator && xwl_seat->x_cursor != NULL)
xwl_seat_destroy_pointer_warp_emulator(xwl_seat);
}
void
xwl_seat_destroy_pointer_warp_emulator(struct xwl_seat *xwl_seat)
{
if (!xwl_seat->pointer_warp_emulator)
return;
xwl_pointer_warp_emulator_destroy(xwl_seat->pointer_warp_emulator);
xwl_seat->pointer_warp_emulator = NULL;
if (xwl_seat->cursor_confinement_window) {
xwl_seat_confine_pointer(xwl_seat,
xwl_seat->cursor_confinement_window);
}
}
void
xwl_seat_confine_pointer(struct xwl_seat *xwl_seat,
struct xwl_window *xwl_window)
@ -1281,13 +1546,17 @@ xwl_seat_confine_pointer(struct xwl_seat *xwl_seat,
if (!pointer_constraints)
return;
if (xwl_seat->cursor_confinement_window == xwl_window)
if (xwl_seat->cursor_confinement_window == xwl_window &&
xwl_seat->confined_pointer)
return;
xwl_seat_unconfine_pointer(xwl_seat);
xwl_seat->cursor_confinement_window = xwl_window;
if (xwl_seat->pointer_warp_emulator)
return;
xwl_seat->confined_pointer =
zwp_pointer_constraints_v1_confine_pointer(pointer_constraints,
xwl_window->surface,

View File

@ -164,6 +164,28 @@ xwl_screen_get_default_seat(struct xwl_screen *xwl_screen)
link);
}
static void
xwl_cursor_warped_to(DeviceIntPtr device,
ScreenPtr screen,
ClientPtr client,
WindowPtr window,
SpritePtr sprite,
int x, int y)
{
struct xwl_screen *xwl_screen = xwl_screen_get(screen);
struct xwl_seat *xwl_seat = device->public.devicePrivate;
struct xwl_window *xwl_window;
if (!xwl_seat)
xwl_seat = xwl_screen_get_default_seat(xwl_screen);
xwl_window = xwl_window_from_window(window);
if (!xwl_window)
return;
xwl_seat_emulate_pointer_warp(xwl_seat, xwl_window, sprite, x, y);
}
static void
xwl_cursor_confined_to(DeviceIntPtr device,
ScreenPtr screen,
@ -384,6 +406,10 @@ xwl_unrealize_window(WindowPtr window)
if (xwl_seat->cursor_confinement_window &&
xwl_seat->cursor_confinement_window->window == window)
xwl_seat_unconfine_pointer(xwl_seat);
if (xwl_seat->pointer_warp_emulator &&
xwl_seat->pointer_warp_emulator->locked_window &&
xwl_seat->pointer_warp_emulator->locked_window->window == window)
xwl_seat_destroy_pointer_warp_emulator(xwl_seat);
xwl_seat_clear_touch(xwl_seat, window);
}
@ -810,6 +836,7 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
xwl_screen->CloseScreen = pScreen->CloseScreen;
pScreen->CloseScreen = xwl_close_screen;
pScreen->CursorWarpedTo = xwl_cursor_warped_to;
pScreen->CursorConfinedTo = xwl_cursor_confined_to;
return ret;

View File

@ -120,6 +120,12 @@ struct xwl_touch {
struct xorg_list link_touch;
};
struct xwl_pointer_warp_emulator {
struct xwl_seat *xwl_seat;
struct xwl_window *locked_window;
struct zwp_locked_pointer_v1 *locked_pointer;
};
struct xwl_seat {
DeviceIntPtr pointer;
DeviceIntPtr relative_pointer;
@ -150,6 +156,8 @@ struct xwl_seat {
struct xorg_list sync_pending;
struct xwl_pointer_warp_emulator *pointer_warp_emulator;
struct xwl_window *cursor_confinement_window;
struct zwp_confined_pointer_v1 *confined_pointer;
@ -191,6 +199,15 @@ void xwl_seat_destroy(struct xwl_seat *xwl_seat);
void xwl_seat_clear_touch(struct xwl_seat *xwl_seat, WindowPtr window);
void xwl_seat_emulate_pointer_warp(struct xwl_seat *xwl_seat,
struct xwl_window *xwl_window,
SpritePtr sprite,
int x, int y);
void xwl_seat_destroy_pointer_warp_emulator(struct xwl_seat *xwl_seat);
void xwl_seat_cursor_visibility_changed(struct xwl_seat *xwl_seat);
void xwl_seat_confine_pointer(struct xwl_seat *xwl_seat,
struct xwl_window *xwl_window);
void xwl_seat_unconfine_pointer(struct xwl_seat *xwl_seat);