From a6e85e6330adcdcbcd939c0963daaecc96d41a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Tue, 13 Sep 2016 15:17:08 +0800 Subject: [PATCH] xwayland: Add pointer warp emulator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- hw/xwayland/xwayland-cursor.c | 7 + hw/xwayland/xwayland-input.c | 273 +++++++++++++++++++++++++++++++++- hw/xwayland/xwayland.c | 27 ++++ hw/xwayland/xwayland.h | 17 +++ 4 files changed, 322 insertions(+), 2 deletions(-) diff --git a/hw/xwayland/xwayland-cursor.c b/hw/xwayland/xwayland-cursor.c index b2ae93ff9..0c1cd34ca 100644 --- a/hw/xwayland/xwayland-cursor.c +++ b/hw/xwayland/xwayland-cursor.c @@ -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); } diff --git a/hw/xwayland/xwayland-input.c b/hw/xwayland/xwayland-input.c index 2f0aa4a26..4d447a5a5 100644 --- a/hw/xwayland/xwayland-input.c +++ b/hw/xwayland/xwayland-input.c @@ -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, diff --git a/hw/xwayland/xwayland.c b/hw/xwayland/xwayland.c index 5d96fb243..c27787018 100644 --- a/hw/xwayland/xwayland.c +++ b/hw/xwayland/xwayland.c @@ -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; diff --git a/hw/xwayland/xwayland.h b/hw/xwayland/xwayland.h index d32235b2f..5e5624be0 100644 --- a/hw/xwayland/xwayland.h +++ b/hw/xwayland/xwayland.h @@ -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);