[PATCH weston 1/3] Introduce pointer lock interface

Jonas Ådahl jadahl at gmail.com
Wed Sep 17 12:39:51 PDT 2014


Introduce a pointer lock interface and implementation. The interface
consists of a global currently called _wl_pointer_lock. It is prefixed
with an underscore (_) in order to not conflict with a potential
official protocol in Wayland, and if moving it there, the prefixed
should be removed.

The protocol works by exposing the global _wl_pointer_lock to the
client, and when the client wants to lock the pointer, it creates a new
wl_pointer object using the 'lock_pointer' request. A more detailed
description of the protocol seen in pointer-lock.xml file.

The interface is based on the W3C pointer lock interface [0].

[0] http://www.w3.org/TR/pointerlock/

Signed-off-by: Jonas Ådahl <jadahl at gmail.com>
---

The part of the implementation I'm not very happy with is the internal
API change: the extra argument in the pointer lock motion callback used
to determine if the motion event was a result of a relative motion or an
absolute motion. In this implementation absolute events are simply
ignored as they don't tend to behave in any sensible way. One could
generate relative motion events from absolute ones as done from a
touchpad, but one would need some kind of threshold as there are no
"touch down" or "touch up" events.

With backends where relative and absolute events are indistinguishable
(X11, Wayland) is tricker, as there is no way to know whether the event
is suitable or not. One could also just let the client deal with it
making "detached" absolute motions just result in very long vectors.


Jonas

 Makefile.am               |   7 +-
 desktop-shell/exposay.c   |   2 +-
 desktop-shell/shell.c     |  10 +-
 protocol/pointer-lock.xml |  85 ++++++++++++++
 src/compositor.c          |   4 +
 src/compositor.h          |  21 +++-
 src/input.c               | 277 +++++++++++++++++++++++++++++++++++++++++++++-
 7 files changed, 394 insertions(+), 12 deletions(-)
 create mode 100644 protocol/pointer-lock.xml

diff --git a/Makefile.am b/Makefile.am
index b2d6893..ed74983 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -79,7 +79,9 @@ nodist_weston_SOURCES =					\
 	protocol/workspaces-protocol.c			\
 	protocol/workspaces-server-protocol.h		\
 	protocol/scaler-protocol.c			\
-	protocol/scaler-server-protocol.h
+	protocol/scaler-server-protocol.h		\
+	protocol/pointer-lock-protocol.c		\
+	protocol/pointer-lock-server-protocol.h
 
 BUILT_SOURCES += $(nodist_weston_SOURCES)
 
@@ -987,7 +989,8 @@ EXTRA_DIST +=					\
 	protocol/wayland-test.xml		\
 	protocol/xdg-shell.xml			\
 	protocol/fullscreen-shell.xml		\
-	protocol/scaler.xml
+	protocol/scaler.xml			\
+	protocol/pointer-lock.xml
 
 man_MANS = weston.1 weston.ini.5
 
diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c
index 4b65cbd..d6ec6a1 100644
--- a/desktop-shell/exposay.c
+++ b/desktop-shell/exposay.c
@@ -340,7 +340,7 @@ exposay_focus(struct weston_pointer_grab *grab)
 
 static void
 exposay_motion(struct weston_pointer_grab *grab, uint32_t time,
-	       wl_fixed_t x, wl_fixed_t y)
+	       wl_fixed_t x, wl_fixed_t y, int rel)
 {
 	struct desktop_shell *shell =
 		container_of(grab, struct desktop_shell, exposay.grab_ptr);
diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c
index 3a5a702..35b05d0 100644
--- a/desktop-shell/shell.c
+++ b/desktop-shell/shell.c
@@ -1651,7 +1651,7 @@ constrain_position(struct weston_move_grab *move, int *cx, int *cy)
 
 static void
 move_grab_motion(struct weston_pointer_grab *grab, uint32_t time,
-		 wl_fixed_t x, wl_fixed_t y)
+		 wl_fixed_t x, wl_fixed_t y, int rel)
 {
 	struct weston_move_grab *move = (struct weston_move_grab *) grab;
 	struct weston_pointer *pointer = grab->pointer;
@@ -1772,7 +1772,7 @@ struct weston_resize_grab {
 
 static void
 resize_grab_motion(struct weston_pointer_grab *grab, uint32_t time,
-		   wl_fixed_t x, wl_fixed_t y)
+		   wl_fixed_t x, wl_fixed_t y, int rel)
 {
 	struct weston_resize_grab *resize = (struct weston_resize_grab *) grab;
 	struct weston_pointer *pointer = grab->pointer;
@@ -1981,7 +1981,7 @@ busy_cursor_grab_focus(struct weston_pointer_grab *base)
 
 static void
 busy_cursor_grab_motion(struct weston_pointer_grab *grab, uint32_t time,
-			wl_fixed_t x, wl_fixed_t y)
+			wl_fixed_t x, wl_fixed_t y, int rel)
 {
 	weston_pointer_move(grab->pointer, x, y);
 }
@@ -3063,7 +3063,7 @@ popup_grab_focus(struct weston_pointer_grab *grab)
 
 static void
 popup_grab_motion(struct weston_pointer_grab *grab, uint32_t time,
-		  wl_fixed_t x, wl_fixed_t y)
+		  wl_fixed_t x, wl_fixed_t y, int rel)
 {
 	struct weston_pointer *pointer = grab->pointer;
 	struct wl_resource *resource;
@@ -4649,7 +4649,7 @@ terminate_binding(struct weston_seat *seat, uint32_t time, uint32_t key,
 
 static void
 rotate_grab_motion(struct weston_pointer_grab *grab, uint32_t time,
-		   wl_fixed_t x, wl_fixed_t y)
+		   wl_fixed_t x, wl_fixed_t y, int rel)
 {
 	struct rotate_grab *rotate =
 		container_of(grab, struct rotate_grab, base.grab);
diff --git a/protocol/pointer-lock.xml b/protocol/pointer-lock.xml
new file mode 100644
index 0000000..e00ff80
--- /dev/null
+++ b/protocol/pointer-lock.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="pointer_lock">
+
+  <copyright>
+    Copyright © 2013      Intel Corporation
+    Copyright © 2014      Jonas Ådahl
+
+    Permission to use, copy, modify, distribute, and sell this
+    software and its documentation for any purpose is hereby granted
+    without fee, provided that the above copyright notice appear in
+    all copies and that both that copyright notice and this permission
+    notice appear in supporting documentation, and that the name of
+    the copyright holders not be used in advertising or publicity
+    pertaining to distribution of the software without specific,
+    written prior permission.  The copyright holders make no
+    representations about the suitability of this software for any
+    purpose.  It is provided "as is" without express or implied
+    warranty.
+
+    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+    THIS SOFTWARE.
+  </copyright>
+
+  <interface name="_wl_pointer_lock" version="1">
+    <description summary="lock pointer to a surface">
+      This interface enables clients to lock pointers to a surface in order
+      to only receive relative pointer motion events.
+    </description>
+
+    <request name="lock_pointer">
+      <description summary="return pointer object">
+        The lock_pointer request lets the client disable pointer
+        motion and request relative motion events.
+
+        Relative motion events have the same dimension as regular
+        absolute motion events on a non-transformed surface, meaning that
+        a relative motion event (dx, dy) is equivalent to the transition
+        from the absolute coordinate (x y) to (x + dx, y + dy).
+
+        If the surface where the pointer is locked is transformed in
+        any way (for example shown as a preview, rotated, etc), the
+        motion events of the locked pointer object are sent as if the
+        surface was not transformed.
+
+        This request initializes the pointer lock and activates it in
+        case the surface is active. If the surface isn't active when
+        the server receives the request, the compositor will activate
+        the pointer_lock when the surface is eventually activated. It's
+        up to the compositor and its shell to decide when a surface is
+        active.
+
+        The lock_pointer request will create a new wl_pointer object.
+        When the pointer_lock is activated, the original wl_pointer will
+        send a leave event and the pointer_lock wl_pointer object
+        will send an enter event. The enter event will contain the
+        motion delta (0, 0). The wl_pointer object created with this
+        request will always sends relative motion deltas, in contrast
+        with a regular wl_pointer which sends surface local absolute
+        coordinates.
+
+        The compositor can suspend the pointer_lock at any time, for
+        example when switching to a different application (eg,
+        alt-tab), if a notification pops up or when the screensaver
+        starts.  When this happens the pointer lock wl_pointer will
+        send a leave event. When or if the server activates the
+        surface again, the client will receive an enter event again.
+
+        The client can break the pointer lock at any time by releasing
+        the pointer lock wl_pointer using the wl_pointer.release request.
+
+        The interface version of the locked wl_pointer object is the same
+        as the original wl_pointer object.
+      </description>
+      <arg name="id" type="new_id" interface="wl_pointer"/>
+      <arg name="surface" type="object" interface="wl_surface"/>
+      <arg name="pointer" type="object" interface="wl_pointer"/>
+    </request>
+  </interface>
+</protocol>
diff --git a/src/compositor.c b/src/compositor.c
index a219766..43b4b95 100644
--- a/src/compositor.c
+++ b/src/compositor.c
@@ -3871,6 +3871,8 @@ weston_compositor_init(struct weston_compositor *ec,
 
 	wl_display_init_shm(display);
 
+	init_pointer_lock(ec);
+
 	loop = wl_display_get_event_loop(ec->wl_display);
 	ec->idle_source = wl_event_loop_add_timer(loop, idle_handler, ec);
 	wl_event_source_timer_update(ec->idle_source, ec->idle_time * 1000);
@@ -3890,6 +3892,8 @@ weston_compositor_shutdown(struct weston_compositor *ec)
 {
 	struct weston_output *output, *next;
 
+	destroy_pointer_lock(ec);
+
 	wl_event_source_remove(ec->idle_source);
 	if (ec->input_loop_source)
 		wl_event_source_remove(ec->input_loop_source);
diff --git a/src/compositor.h b/src/compositor.h
index 9611dea..e33e341 100644
--- a/src/compositor.h
+++ b/src/compositor.h
@@ -242,7 +242,8 @@ struct weston_pointer_grab;
 struct weston_pointer_grab_interface {
 	void (*focus)(struct weston_pointer_grab *grab);
 	void (*motion)(struct weston_pointer_grab *grab, uint32_t time,
-		       wl_fixed_t x, wl_fixed_t y);
+		       wl_fixed_t x, wl_fixed_t y,
+		       int is_relative);
 	void (*button)(struct weston_pointer_grab *grab,
 		       uint32_t time, uint32_t button, uint32_t state);
 	void (*cancel)(struct weston_pointer_grab *grab);
@@ -321,6 +322,7 @@ struct weston_pointer {
 	struct wl_listener focus_resource_listener;
 	struct wl_signal focus_signal;
 	struct wl_signal motion_signal;
+	struct wl_signal destroy_signal;
 
 	struct weston_view *sprite;
 	struct wl_listener sprite_destroy_listener;
@@ -663,6 +665,8 @@ struct weston_compositor {
 
 	int32_t kb_repeat_rate;
 	int32_t kb_repeat_delay;
+
+	struct wl_global *pointer_lock;
 };
 
 struct weston_buffer {
@@ -899,6 +903,15 @@ struct weston_surface {
 	 */
 	struct wl_list subsurface_list; /* weston_subsurface::parent_link */
 	struct wl_list subsurface_list_pending; /* ...::parent_link_pending */
+
+	struct {
+		struct wl_resource *pointer_resource;
+		struct weston_pointer_grab grab;
+		struct weston_pointer *pointer;
+		struct wl_listener pointer_destroy_listener;
+		struct wl_listener keyboard_focus_listener;
+		struct wl_listener surface_destroy_listener;
+	} pointer_lock;
 };
 
 struct weston_subsurface {
@@ -1020,6 +1033,12 @@ void
 notify_touch_frame(struct weston_seat *seat);
 
 void
+init_pointer_lock(struct weston_compositor *compositor);
+
+void
+destroy_pointer_lock(struct weston_compositor *compositor);
+
+void
 weston_layer_entry_insert(struct weston_layer_entry *list,
 			  struct weston_layer_entry *entry);
 void
diff --git a/src/input.c b/src/input.c
index 530904d..ffdec49 100644
--- a/src/input.c
+++ b/src/input.c
@@ -33,6 +33,7 @@
 
 #include "../shared/os-compatibility.h"
 #include "compositor.h"
+#include "protocol/pointer-lock-server-protocol.h"
 
 static void
 empty_region(pixman_region32_t *region)
@@ -163,7 +164,7 @@ default_grab_pointer_focus(struct weston_pointer_grab *grab)
 
 static void
 default_grab_pointer_motion(struct weston_pointer_grab *grab, uint32_t time,
-			    wl_fixed_t x, wl_fixed_t y)
+			    wl_fixed_t x, wl_fixed_t y, int rel)
 {
 	struct weston_pointer *pointer = grab->pointer;
 	struct wl_list *resource_list;
@@ -472,6 +473,7 @@ weston_pointer_create(struct weston_seat *seat)
 	wl_signal_init(&pointer->motion_signal);
 	wl_signal_init(&pointer->focus_signal);
 	wl_list_init(&pointer->focus_view_listener.link);
+	wl_signal_init(&pointer->destroy_signal);
 
 	pointer->sprite_destroy_listener.notify = pointer_handle_sprite_destroy;
 
@@ -490,6 +492,8 @@ weston_pointer_create(struct weston_seat *seat)
 WL_EXPORT void
 weston_pointer_destroy(struct weston_pointer *pointer)
 {
+	wl_signal_emit(&pointer->destroy_signal, pointer);
+
 	if (pointer->sprite)
 		pointer_unmap_sprite(pointer);
 
@@ -943,7 +947,8 @@ notify_motion(struct weston_seat *seat,
 	struct weston_pointer *pointer = seat->pointer;
 
 	weston_compositor_wake(ec);
-	pointer->grab->interface->motion(pointer->grab, time, pointer->x + dx, pointer->y + dy);
+	pointer->grab->interface->motion(pointer->grab, time,
+					 pointer->x + dx, pointer->y + dy, 1);
 }
 
 static void
@@ -990,7 +995,7 @@ notify_motion_absolute(struct weston_seat *seat,
 	struct weston_pointer *pointer = seat->pointer;
 
 	weston_compositor_wake(ec);
-	pointer->grab->interface->motion(pointer->grab, time, x, y);
+	pointer->grab->interface->motion(pointer->grab, time, x, y, 0);
 }
 
 WL_EXPORT void
@@ -2309,3 +2314,269 @@ weston_seat_release(struct weston_seat *seat)
 
 	wl_signal_emit(&seat->destroy_signal, seat);
 }
+
+static void
+pointer_lock_grab_pointer_focus(struct weston_pointer_grab *grab)
+{
+}
+
+static void
+pointer_lock_grab_pointer_motion(struct weston_pointer_grab *grab,
+				 uint32_t time,
+				 wl_fixed_t x, wl_fixed_t y, int rel)
+{
+	struct weston_surface *surface =
+		container_of(grab, struct weston_surface, pointer_lock.grab);
+	struct weston_pointer *pointer = surface->pointer_lock.grab.pointer;
+	wl_fixed_t dx, dy;
+
+	/* Ignore motion events from non-relative sources. */
+	if (!rel)
+		return;
+
+	dx = x - pointer->x;
+	dy = y - pointer->y;
+	wl_pointer_send_motion(surface->pointer_lock.pointer_resource,
+			       time, dx, dy);
+}
+
+static void
+pointer_lock_grab_pointer_button(struct weston_pointer_grab *grab,
+				 uint32_t time, uint32_t button, uint32_t state)
+{
+	struct weston_surface *surface =
+		container_of(grab, struct weston_surface, pointer_lock.grab);
+	struct wl_resource *resource = surface->pointer_lock.pointer_resource;
+	uint32_t serial;
+
+	serial = wl_display_next_serial(surface->compositor->wl_display);
+	wl_pointer_send_button(resource, serial, time, button, state);
+}
+
+static void
+pointer_lock_grab_pointer_cancel(struct weston_pointer_grab *grab)
+{
+}
+
+static const struct weston_pointer_grab_interface
+				pointer_lock_grab_interface = {
+	pointer_lock_grab_pointer_focus,
+	pointer_lock_grab_pointer_motion,
+	pointer_lock_grab_pointer_button,
+	pointer_lock_grab_pointer_cancel,
+};
+
+static void
+pointer_lock_pointer_set_cursor(struct wl_client *client,
+				struct wl_resource *resource,
+				uint32_t serial,
+				struct wl_resource *surface_resource,
+				int32_t x, int32_t y)
+{
+}
+
+static void
+pointer_lock_pointer_release(struct wl_client *client,
+			     struct wl_resource *resource)
+{
+	wl_resource_destroy(resource);
+}
+
+static const struct wl_pointer_interface pointer_lock_pointer_interface = {
+	pointer_lock_pointer_set_cursor,
+	pointer_lock_pointer_release
+};
+
+static void
+lock_pointer(struct weston_surface *surface, struct weston_pointer *pointer)
+{
+	struct weston_compositor *compositor = surface->compositor;
+	uint32_t serial;
+
+	if (surface->pointer_lock.pointer->focus == surface)
+		return;
+
+	weston_pointer_set_focus(pointer, NULL, 0, 0);
+
+	serial = wl_display_next_serial(compositor->wl_display);
+	wl_pointer_send_enter(surface->pointer_lock.pointer_resource,
+			      serial,
+			      surface->resource,
+			      0, 0);
+
+	if (pointer->sprite)
+		pointer_unmap_sprite(pointer);
+
+	weston_pointer_start_grab(pointer, &surface->pointer_lock.grab);
+}
+
+static void
+unlock_pointer(struct weston_surface *surface)
+{
+	struct weston_compositor *compositor = surface->compositor;
+	uint32_t serial;
+
+	if (surface->pointer_lock.pointer->focus != surface)
+		return;
+
+	serial = wl_display_next_serial(compositor->wl_display);
+	wl_pointer_send_leave(surface->pointer_lock.pointer_resource,
+			      serial, surface->resource);
+	weston_pointer_end_grab(surface->pointer_lock.grab.pointer);
+}
+
+static void
+start_pointer_lock(struct weston_surface *surface,
+		   struct weston_pointer *pointer)
+{
+	struct weston_seat *seat = pointer->seat;
+	struct wl_listener *keyboard_focus_listener =
+		&surface->pointer_lock.keyboard_focus_listener;
+
+	surface->pointer_lock.pointer = pointer;
+
+	wl_signal_add(&seat->pointer->destroy_signal,
+		      &surface->pointer_lock.pointer_destroy_listener);
+	wl_signal_add(&surface->destroy_signal,
+		      &surface->pointer_lock.surface_destroy_listener);
+
+	if (seat->keyboard) {
+		wl_signal_add(&seat->keyboard->focus_signal,
+			      keyboard_focus_listener);
+	} else {
+		wl_list_init(&keyboard_focus_listener->link);
+	}
+
+	if (seat->keyboard->focus == surface)
+		lock_pointer(surface, pointer);
+}
+
+static void
+end_pointer_lock(struct weston_surface *surface)
+{
+	unlock_pointer(surface);
+	surface->pointer_lock.pointer_resource = NULL;
+	surface->pointer_lock.pointer = NULL;
+
+	wl_list_remove(&surface->pointer_lock.pointer_destroy_listener.link);
+	wl_list_remove(&surface->pointer_lock.keyboard_focus_listener.link);
+	wl_list_remove(&surface->pointer_lock.surface_destroy_listener.link);
+}
+
+static void
+pointer_lock_resource_destroyed(struct wl_resource *resource)
+{
+	struct weston_surface *surface = wl_resource_get_user_data(resource);
+
+	end_pointer_lock(surface);
+}
+
+static void
+pointer_lock_keyboard_focused(struct wl_listener *listener, void *data)
+{
+	struct weston_keyboard *keyboard = data;
+	struct weston_surface *surface =
+		container_of(listener, struct weston_surface,
+			     pointer_lock.keyboard_focus_listener);
+	struct weston_pointer *pointer = surface->pointer_lock.pointer;
+
+	if (keyboard->focus == surface)
+		lock_pointer(surface, pointer);
+	else
+		unlock_pointer(surface);
+}
+
+static void
+pointer_lock_pointer_destroyed(struct wl_listener *listener, void *data)
+{
+	struct weston_surface *surface =
+		container_of(listener, struct weston_surface,
+			     pointer_lock.pointer_destroy_listener);
+
+	end_pointer_lock(surface);
+}
+
+static void
+pointer_lock_surface_destroyed(struct wl_listener *listener, void *data)
+{
+	struct weston_surface *surface =
+		container_of(listener, struct weston_surface,
+			     pointer_lock.surface_destroy_listener);
+
+	end_pointer_lock(surface);
+}
+
+static void
+pointer_lock_lock_pointer(struct wl_client *client,
+			  struct wl_resource *resource,
+			  uint32_t id,
+			  struct wl_resource *surface_resource,
+			  struct wl_resource *pointer_resource)
+{
+	struct weston_pointer *pointer =
+		wl_resource_get_user_data(pointer_resource);
+	struct weston_surface *surface;
+	struct wl_resource *cr;
+
+	surface = wl_resource_get_user_data(surface_resource);
+	if (surface->pointer_lock.pointer_resource) {
+		wl_resource_post_error(resource,
+				       WL_DISPLAY_ERROR_INVALID_OBJECT,
+				       "pointer already locked");
+		return;
+	}
+
+        cr = wl_resource_create(client, &wl_pointer_interface,
+				wl_resource_get_version(pointer_resource), id);
+	if (cr == NULL) {
+		wl_client_post_no_memory(client);
+		return;
+	}
+
+	wl_resource_set_implementation(cr, &pointer_lock_pointer_interface,
+				       surface,
+				       pointer_lock_resource_destroyed);
+
+	surface->pointer_lock.pointer_resource = cr;
+	surface->pointer_lock.grab.interface = &pointer_lock_grab_interface;
+	surface->pointer_lock.pointer_destroy_listener.notify =
+		pointer_lock_pointer_destroyed;
+	surface->pointer_lock.keyboard_focus_listener.notify =
+		pointer_lock_keyboard_focused;
+	surface->pointer_lock.surface_destroy_listener.notify =
+		pointer_lock_surface_destroyed;
+
+	start_pointer_lock(surface, pointer);
+}
+
+static const struct _wl_pointer_lock_interface pointer_lock_interface = {
+	pointer_lock_lock_pointer
+};
+
+static void
+bind_pointer_lock(struct wl_client *client, void *data,
+		  uint32_t version, uint32_t id)
+{
+	struct wl_resource *resource;
+
+	resource = wl_resource_create(client, &_wl_pointer_lock_interface,
+				      1, id);
+	wl_resource_set_implementation(resource, &pointer_lock_interface,
+				       NULL, NULL);
+}
+
+void
+init_pointer_lock(struct weston_compositor *compositor)
+{
+	compositor->pointer_lock =
+		wl_global_create(compositor->wl_display,
+				 &_wl_pointer_lock_interface, 1,
+				 NULL,
+				 bind_pointer_lock);
+}
+
+void
+destroy_pointer_lock(struct weston_compositor *compositor)
+{
+	wl_global_destroy(compositor->pointer_lock);
+}
-- 
1.8.5.1



More information about the wayland-devel mailing list