[weston,14/17] Introduce pointer locking and confinement protocol

Submitted by Jonas Ådahl on Dec. 2, 2014, 1:49 p.m.

Details

Message ID 1417528165-9319-15-git-send-email-jadahl@gmail.com
State Superseded
Delegated to: Daniel Stone
Headers show

Not browsing as part of any series.

Commit Message

Jonas Ådahl Dec. 2, 2014, 1:49 p.m.
This patch introduces a new protocol for locking and confining a
pointer. It consists of a new global object with two requests; one for
locking the surface to a position, one for confining the pointer to a
given region.

See pointer-lock.xml for details of the protocol.

In this patch, only the locking part is fully implemented as in
specified in the protocol, while confinement is only implemented for
when the union of the passed region and the input region of the confined
surface is a single rectangle.

Note that the interfaces are prefixed with an underscore in order to
avoid future incompatibilities with a future stable interface with an
equivalent name.

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

What should the serial in locked_pointer_set_cursor_position_hint be
checked against? It doesn't seem to make sense to use the same method as
in pointer_set_cursor. As far as I understand, it should be equal to the
last serial sent via that pointer device, but I can't see we do that
anywhere else. What am I missing?


 Makefile.am               |   3 +
 protocol/pointer-lock.xml | 208 ++++++++++++++++
 src/compositor.c          |   4 +
 src/compositor.h          |  21 ++
 src/input.c               | 589 ++++++++++++++++++++++++++++++++++++++++++++--
 5 files changed, 806 insertions(+), 19 deletions(-)
 create mode 100644 protocol/pointer-lock.xml

Patch hide | download patch | download mbox

diff --git a/Makefile.am b/Makefile.am
index 860564d..8adc343 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -82,6 +82,8 @@  nodist_weston_SOURCES =					\
 	protocol/presentation_timing-server-protocol.h	\
 	protocol/scaler-protocol.c			\
 	protocol/scaler-server-protocol.h		\
+	protocol/pointer-lock-protocol.c		\
+	protocol/pointer-lock-server-protocol.h		\
 	protocol/relative-pointer-protocol.c		\
 	protocol/relative-pointer-server-protocol.h
 
@@ -1006,6 +1008,7 @@  EXTRA_DIST +=					\
 	protocol/fullscreen-shell.xml		\
 	protocol/presentation_timing.xml	\
 	protocol/scaler.xml			\
+	protocol/pointer-lock.xml		\
 	protocol/relative-pointer.xml
 
 man_MANS = weston.1 weston.ini.5
diff --git a/protocol/pointer-lock.xml b/protocol/pointer-lock.xml
new file mode 100644
index 0000000..3233ede
--- /dev/null
+++ b/protocol/pointer-lock.xml
@@ -0,0 +1,208 @@ 
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="pointer_lock">
+
+  <copyright>
+    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">
+      The global interface exposing pointer locking functionality. It exposes
+      two requests; lock_pointer for locking the pointer to its position, and
+      confine_pointer for locking the pointer to a region.
+
+      The lock_pointer and confine_pointer creates the objects wl_locked_pointer
+      and wl_confined_pointer respectively, and the client can use these objects
+      to interact with the lock.
+
+      There may not be another lock of any kind active when requesting a lock,
+      and if there is, an error will be raised.
+    </description>
+
+    <request name="lock_pointer">
+      <description summary="lock pointer to a position">
+        The lock_pointer request lets the client disable absolute pointer
+        movements, locking the pointer to a position.
+
+        There may not be another lock of any kind active when requesting a lock,
+        and if there is, an error will be raised.
+
+        The intersection of the region passed with this request and the input
+        region of the surface is used to determine where the pointer must be
+        in order for the lock to activate. It is up to the compositor to warp
+        the pointer, or require some kind of user interaction for the lock to
+        activate. If the region is null, then an infinit region is used.
+
+        The request will create a new object wl_locked_pointer which is used to
+        interact with the lock as well as receive updates about its state. See
+        the the description of wl_locked_pointer for further information.
+
+        Note that while a locked pointer doesn't move its absolute position, it
+        may still emit relative motion events via the wl_relative_pointer
+        object.
+      </description>
+
+      <arg name="id" type="new_id" interface="_wl_locked_pointer"/>
+      <arg name="surface" type="object" interface="wl_surface"
+           summary="surface to lock pointer to"/>
+      <arg name="seat" type="object" interface="wl_seat"
+           summary="seat where the pointer should be locked"/>
+      <arg name="region" type="object" interface="wl_region" allow-null="true"
+           summary="region of surface"/>
+    </request>
+
+    <request name="confine_pointer">
+      <description summary="confine pointer to a region">
+        The confine_pointer request lets the client confine the pointer cursor
+        to a given region.
+
+        The intersection of the region passed with this request and the input
+        region of the surface is used to determine where the pointer must be
+        in order for the confinement to activate. It is up to the compositor to
+        warp the pointer, or require some kind of user interaction for the
+        confinement to activate. If the region is null, then an infinite region
+        is used.
+
+        The request will create a new object wl_confined_pointer which is used
+        to interact with the confinement as well as receive updates about its
+        state. See the the description of wl_confined_pointer for further
+        information.
+      </description>
+
+      <arg name="id" type="new_id" interface="_wl_confined_pointer"/>
+      <arg name="surface" type="object" interface="wl_surface"
+           summary="surface to lock pointer to"/>
+      <arg name="seat" type="object" interface="wl_seat"
+           summary="seat where the pointer should be locked"/>
+      <arg name="region" type="object" interface="wl_region" allow-null="true"
+           summary="region of surface"/>
+    </request>
+
+  </interface>
+
+  <interface name="_wl_locked_pointer" version="1">
+    <description summary="receive relative pointer motion events">
+      The wl_locked_pointer interface represents a locked pointer state.
+
+      While the lock of this object is active, the pointer of the associated
+      seat will not move.
+
+      This object will send the event 'locked' when the lock is activated.
+      Whenever the lock is activated, it is guaranteed that the locked surface
+      will already have received pointer focus and that the pointer will be
+      within the region passed to the request creating this object.
+
+      To unlock the pointer, send the destroy request. This will also destroy
+      the wl_locked_pointer object.
+
+      If the compositor decides to unlock the pointer the unlocked event is sent.
+      The wl_locked_pointer object is at this point defunct and should be
+      destoryed.
+
+      When unlocking, the compositor may or may not take the cursor position
+      hint provided using the set_cursor_position_hint request and warp the
+      pointer. If it does, it will not result in any relative motion events.
+    </description>
+
+    <request name="set_cursor_position_hint">
+      <description summary="set the pointer cursor position hint">
+        Set the cursor position hint relative to the top left corner of the
+        surface.
+      </description>
+
+      <arg name="serial" type="uint" summary="serial of the enter event"/>
+      <arg name="surface_x" type="fixed"
+           summary="x coordinate in surface-relative coordinates"/>
+      <arg name="surface_y" type="fixed"
+           summary="y coordinate in surface-relative coordinates"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the locked pointer object">
+        Destroy the locked pointer object. The compositor will unlock the
+        pointer.
+      </description>
+    </request>
+
+    <event name="locked">
+      <description summary="enter event">
+        Notification that the pointer lock of this seat's pointer is activated.
+      </description>
+
+      <arg name="serial" type="uint"/>
+    </event>
+
+    <event name="unlocked">
+      <description summary="leave event">
+        Notification that the pointer lock of seat's pointer is no longer
+        active. This object is no defunct and should be destroyed.
+      </description>
+    </event>
+  </interface>
+
+  <interface name="_wl_confined_pointer" version="1">
+    <description summary="confined pointer object">
+      The wl_confined_pointer interface represents a confined pointer state.
+
+      This object will send the event 'confined' when the confinement is
+      activated. Whenever the confinement is activated, it is guaranteed that
+      the surface the pointer is confined to will already have received pointer
+      focus and that the pointer will be within the region passed to the request
+      creating this object. It is up to the compositor to decide whether this
+      requires some user interaction and if the pointer will warp to within the
+      passed region if outside.
+
+      To unconfine the pointer, send the destroy request. This will also destroy
+      the wl_confined_pointer object.
+
+      If the compositor decides to unconfine the pointer the unconfined event is
+      sent. The wl_confined_pointer object is at this point defunct and should
+      be destoryed.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the confined pointer object">
+        Destroy the confined pointer object. The compositor will unconfine the
+        pointer.
+      </description>
+    </request>
+
+    <event name="confined">
+      <description summary="enter event">
+        Notification that the pointer confinement of this seat's pointer is
+        activated.
+      </description>
+
+      <arg name="serial" type="uint"/>
+    </event>
+
+    <event name="unconfined">
+      <description summary="leave event">
+        Notification that the pointer confinement of seat's pointer is no
+        longer active. This object is no defunct and should be destroyed.
+      </description>
+    </event>
+  </interface>
+
+</protocol>
diff --git a/src/compositor.c b/src/compositor.c
index 767cb26..242bdf6 100644
--- a/src/compositor.c
+++ b/src/compositor.c
@@ -645,6 +645,8 @@  weston_surface_create(struct weston_compositor *compositor)
 	wl_list_init(&surface->subsurface_list);
 	wl_list_init(&surface->subsurface_list_pending);
 
+	region_init_infinite(&surface->pointer_lock.region);
+
 	return surface;
 }
 
@@ -1659,6 +1661,8 @@  weston_surface_destroy(struct weston_surface *surface)
 
 	weston_presentation_feedback_discard_list(&surface->feedback_list);
 
+	pixman_region32_fini(&surface->pointer_lock.region);
+
 	free(surface);
 }
 
diff --git a/src/compositor.h b/src/compositor.h
index b373ecf..9a6fa7e 100644
--- a/src/compositor.h
+++ b/src/compositor.h
@@ -339,6 +339,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;
@@ -692,6 +693,8 @@  struct weston_compositor {
 	int exit_code;
 
 	unsigned int activate_serial;
+
+	struct wl_global *pointer_lock;
 };
 
 struct weston_buffer {
@@ -943,6 +946,24 @@  struct weston_surface {
 	 * and replace role_name with configure.
 	 */
 	const char *role_name;
+
+	struct {
+		struct weston_view *view;
+		pixman_region32_t region;
+		struct wl_resource *resource;
+		struct weston_pointer_grab grab;
+		struct weston_pointer *pointer;
+
+		bool hint_set;
+		wl_fixed_t x_hint;
+		wl_fixed_t y_hint;
+
+		struct wl_listener pointer_destroy_listener;
+		struct wl_listener surface_destroy_listener;
+
+		struct wl_listener keyboard_focus_listener;
+		struct wl_listener surface_activate_listener;
+	} pointer_lock;
 };
 
 struct weston_subsurface {
diff --git a/src/input.c b/src/input.c
index 9d02eb8..63283f2 100644
--- a/src/input.c
+++ b/src/input.c
@@ -22,6 +22,7 @@ 
 
 #include "config.h"
 
+#include <stdbool.h>
 #include <stdlib.h>
 #include <stdint.h>
 #include <string.h>
@@ -33,6 +34,7 @@ 
 
 #include "../shared/os-compatibility.h"
 #include "compositor.h"
+#include "protocol/pointer-lock-server-protocol.h"
 #include "protocol/relative-pointer-server-protocol.h"
 
 static void
@@ -42,6 +44,13 @@  empty_region(pixman_region32_t *region)
 	pixman_region32_init(region);
 }
 
+static void
+region_init_infinite(pixman_region32_t *region)
+{
+	pixman_region32_init_rect(region, INT32_MIN, INT32_MIN,
+				  UINT32_MAX, UINT32_MAX);
+}
+
 static void unbind_resource(struct wl_resource *resource)
 {
 	wl_list_remove(wl_resource_get_link(resource));
@@ -227,12 +236,22 @@  weston_pointer_send_relative_motion(struct weston_pointer *pointer,
 }
 
 static void
+weston_pointer_send_motion(struct weston_pointer *pointer, uint32_t time,
+			   wl_fixed_t sx, wl_fixed_t sy)
+{
+	struct wl_list *resource_list;
+	struct wl_resource *resource;
+
+	resource_list = &pointer->focus_resource_list;
+	wl_resource_for_each(resource, resource_list)
+		wl_pointer_send_motion(resource, time, sx, sy);
+}
+
+static void
 default_grab_pointer_motion(struct weston_pointer_grab *grab, uint32_t time,
 			    struct weston_pointer_motion_event *event)
 {
 	struct weston_pointer *pointer = grab->pointer;
-	struct wl_list *resource_list;
-	struct wl_resource *resource;
 	wl_fixed_t x, y;
 	wl_fixed_t old_sx = pointer->sx;
 	wl_fixed_t old_sy = pointer->sy;
@@ -246,40 +265,46 @@  default_grab_pointer_motion(struct weston_pointer_grab *grab, uint32_t time,
 	weston_pointer_move(pointer, event);
 
 	if (old_sx != pointer->sx || old_sy != pointer->sy) {
-		resource_list = &pointer->focus_resource_list;
-		wl_resource_for_each(resource, resource_list) {
-			wl_pointer_send_motion(resource, time,
-					       pointer->sx, pointer->sy);
-		}
+		weston_pointer_send_motion(pointer, time,
+					   pointer->sx, pointer->sy);
 	}
 
 	weston_pointer_send_relative_motion(pointer, time, event);
 }
 
 static void
-default_grab_pointer_button(struct weston_pointer_grab *grab,
-			    uint32_t time, uint32_t button, uint32_t state_w)
+weston_pointer_send_button(struct weston_pointer *pointer,
+			   uint32_t time, uint32_t button, uint32_t state_w)
 {
-	struct weston_pointer *pointer = grab->pointer;
-	struct weston_compositor *compositor = pointer->seat->compositor;
-	struct weston_view *view;
 	struct wl_resource *resource;
 	uint32_t serial;
-	enum wl_pointer_button_state state = state_w;
-	struct wl_display *display = compositor->wl_display;
-	wl_fixed_t sx, sy;
 	struct wl_list *resource_list;
+	struct wl_display *display = pointer->seat->compositor->wl_display;
 
 	resource_list = &pointer->focus_resource_list;
 	if (!wl_list_empty(resource_list)) {
 		serial = wl_display_next_serial(display);
-		wl_resource_for_each(resource, resource_list)
+		wl_resource_for_each(resource, resource_list) {
 			wl_pointer_send_button(resource,
 					       serial,
 					       time,
 					       button,
 					       state_w);
+		}
 	}
+}
+
+static void
+default_grab_pointer_button(struct weston_pointer_grab *grab,
+			    uint32_t time, uint32_t button, uint32_t state_w)
+{
+	struct weston_pointer *pointer = grab->pointer;
+	struct weston_compositor *compositor = pointer->seat->compositor;
+	struct weston_view *view;
+	enum wl_pointer_button_state state = state_w;
+	wl_fixed_t sx, sy;
+
+	weston_pointer_send_button(pointer, time, button, state_w);
 
 	if (pointer->button_count == 0 &&
 	    state == WL_POINTER_BUTTON_STATE_RELEASED) {
@@ -292,10 +317,9 @@  default_grab_pointer_button(struct weston_pointer_grab *grab,
 }
 
 static void
-default_grab_pointer_axis(struct weston_pointer_grab *grab,
-			  uint32_t time, uint32_t axis, wl_fixed_t value)
+weston_pointer_send_axis(struct weston_pointer *pointer,
+			 uint32_t time, uint32_t axis, wl_fixed_t value)
 {
-	struct weston_pointer *pointer = grab->pointer;
 	struct wl_resource *resource;
 	struct wl_list *resource_list;
 
@@ -305,6 +329,13 @@  default_grab_pointer_axis(struct weston_pointer_grab *grab,
 }
 
 static void
+default_grab_pointer_axis(struct weston_pointer_grab *grab,
+			  uint32_t time, uint32_t axis, wl_fixed_t value)
+{
+	weston_pointer_send_axis(grab->pointer, time, axis, value);
+}
+
+static void
 default_grab_pointer_cancel(struct weston_pointer_grab *grab)
 {
 }
@@ -564,6 +595,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;
 
@@ -582,6 +614,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);
 
@@ -2513,6 +2547,518 @@  weston_seat_release(struct weston_seat *seat)
 	wl_signal_emit(&seat->destroy_signal, seat);
 }
 
+static const struct _wl_locked_pointer_interface locked_pointer_interface;
+static const struct _wl_confined_pointer_interface confined_pointer_interface;
+
+static void
+pointer_lock_notify_activated(struct weston_surface *surface)
+{
+	struct weston_compositor *compositor = surface->compositor;
+	struct wl_resource *resource = surface->pointer_lock.resource;
+	uint32_t serial;
+
+	if (wl_resource_instance_of(resource,
+				    &_wl_locked_pointer_interface,
+				    &locked_pointer_interface)) {
+		serial = wl_display_next_serial(compositor->wl_display);
+		_wl_locked_pointer_send_locked(resource, serial);
+	} else if (wl_resource_instance_of(resource,
+					   &_wl_confined_pointer_interface,
+					   &confined_pointer_interface)) {
+		serial = wl_display_next_serial(compositor->wl_display);
+		_wl_confined_pointer_send_confined(resource, serial);
+	}
+}
+
+static void
+pointer_lock_notify_deactivated(struct weston_surface *surface)
+{
+	struct wl_resource *resource = surface->pointer_lock.resource;
+
+	if (wl_resource_instance_of(resource,
+				    &_wl_locked_pointer_interface,
+				    &locked_pointer_interface)) {
+		_wl_locked_pointer_send_unlocked(resource);
+	} else if (wl_resource_instance_of(resource,
+					   &_wl_confined_pointer_interface,
+					   &confined_pointer_interface)) {
+		_wl_confined_pointer_send_unconfined(resource);
+	}
+}
+
+static void
+enable_pointer_lock(struct weston_view *view,
+		    struct weston_pointer *pointer)
+{
+	struct weston_surface *surface = view->surface;
+
+	assert(surface->pointer_lock.view == NULL);
+	surface->pointer_lock.view = view;
+	pointer_lock_notify_activated(surface);
+	weston_pointer_start_grab(pointer, &surface->pointer_lock.grab);
+}
+
+static bool
+is_pointer_lock_enabled(struct weston_surface *surface)
+{
+	return surface->pointer_lock.view != NULL;
+}
+
+static void
+disable_pointer_lock(struct weston_surface *surface)
+{
+	if (is_pointer_lock_enabled(surface)) {
+		pointer_lock_notify_deactivated(surface);
+		weston_pointer_end_grab(surface->pointer_lock.grab.pointer);
+		surface->pointer_lock.view = NULL;
+	}
+
+	surface->pointer_lock.resource = NULL;
+	surface->pointer_lock.pointer = NULL;
+
+	surface->pointer_lock.hint_set = false;
+
+	wl_list_remove(&surface->pointer_lock.pointer_destroy_listener.link);
+	wl_list_remove(&surface->pointer_lock.surface_destroy_listener.link);
+	wl_list_remove(&surface->pointer_lock.surface_activate_listener.link);
+}
+
+static bool
+is_within_lock_region(struct weston_surface *surface,
+		      wl_fixed_t sx, wl_fixed_t sy)
+{
+	pixman_region32_t lock_region;
+	bool result;
+
+	pixman_region32_init(&lock_region);
+	pixman_region32_intersect(&lock_region,
+				  &surface->input,
+				  &surface->pointer_lock.region);
+	result = pixman_region32_contains_point(&lock_region,
+						wl_fixed_to_int(sx),
+						wl_fixed_to_int(sy),
+						NULL);
+	pixman_region32_fini(&lock_region);
+
+	return result;
+}
+
+static void
+maybe_enable_pointer_lock(struct weston_surface *surface)
+{
+	struct weston_view *vit;
+	struct weston_view *view = NULL;
+	struct weston_pointer *pointer = surface->pointer_lock.pointer;
+	struct weston_seat *seat = pointer->seat;
+	int32_t x, y;
+
+	/* Postpone if no view of the surface was most recently clicked. */
+	wl_list_for_each(vit, &surface->views, surface_link) {
+		if (vit->click_to_activate_serial ==
+		    surface->compositor->activate_serial) {
+			view = vit;
+		}
+	}
+	if (view == NULL)
+		return;
+
+	/* Postpone if surface doesn't have keyboard focus. */
+	if (seat->keyboard->focus != surface)
+		return;
+
+	/* Postpone lock if the pointer is not within the lock region. */
+	weston_view_from_global(view,
+				wl_fixed_to_int(pointer->x),
+				wl_fixed_to_int(pointer->y),
+				&x, &y);
+	if (!is_within_lock_region(surface,
+				   wl_fixed_from_int(x),
+				   wl_fixed_from_int(y)))
+		return;
+
+	enable_pointer_lock(view, pointer);
+}
+
+static void
+locked_pointer_grab_pointer_focus(struct weston_pointer_grab *grab)
+{
+}
+
+static void
+locked_pointer_grab_pointer_motion(struct weston_pointer_grab *grab,
+				   uint32_t time,
+				   struct weston_pointer_motion_event *event)
+{
+	weston_pointer_send_relative_motion(grab->pointer, time, event);
+}
+
+static void
+locked_pointer_grab_pointer_button(struct weston_pointer_grab *grab,
+				   uint32_t time,
+				   uint32_t button,
+				   uint32_t state_w)
+{
+	weston_pointer_send_button(grab->pointer, time, button, state_w);
+}
+
+static void
+locked_pointer_grab_pointer_axis(struct weston_pointer_grab *grab,
+				 uint32_t time, uint32_t axis, wl_fixed_t value)
+{
+	weston_pointer_send_axis(grab->pointer, time, axis, value);
+}
+
+static void
+locked_pointer_grab_pointer_cancel(struct weston_pointer_grab *grab)
+{
+	struct weston_surface *surface =
+		container_of(grab, struct weston_surface, pointer_lock.grab);
+
+	disable_pointer_lock(surface);
+}
+
+static const struct weston_pointer_grab_interface
+				locked_pointer_grab_interface = {
+	locked_pointer_grab_pointer_focus,
+	locked_pointer_grab_pointer_motion,
+	locked_pointer_grab_pointer_button,
+	locked_pointer_grab_pointer_axis,
+	locked_pointer_grab_pointer_cancel,
+};
+
+static void
+pointer_lock_lock_resource_destroyed(struct wl_resource *resource)
+{
+	struct weston_surface *surface = wl_resource_get_user_data(resource);
+
+	if (!surface->pointer_lock.resource)
+		return;
+
+	disable_pointer_lock(surface);
+}
+
+static void
+pointer_lock_surface_activate(struct wl_listener *listener, void *data)
+{
+	struct weston_surface *focus = data;
+	struct weston_surface *locked_surface =
+		container_of(listener, struct weston_surface,
+			     pointer_lock.surface_activate_listener);
+
+	if (focus == locked_surface && !is_pointer_lock_enabled(locked_surface))
+		maybe_enable_pointer_lock(locked_surface);
+	else if (focus != locked_surface &&
+		 is_pointer_lock_enabled(locked_surface))
+		disable_pointer_lock(locked_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);
+
+	disable_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);
+
+	disable_pointer_lock(surface);
+}
+
+static void
+init_pointer_lock(struct wl_resource *pointer_lock_resource,
+		  uint32_t id,
+		  struct weston_surface *surface,
+		  struct weston_seat *seat,
+		  struct weston_region *region,
+		  const struct wl_interface *interface,
+		  const void *implementation,
+		  const struct weston_pointer_grab_interface *grab_interface)
+{
+	struct wl_client *client =
+		wl_resource_get_client(pointer_lock_resource);
+	struct weston_pointer *pointer = seat->pointer;
+	struct wl_resource *cr;
+
+	if (surface->pointer_lock.resource) {
+		wl_resource_post_error(pointer_lock_resource,
+				       WL_DISPLAY_ERROR_INVALID_OBJECT,
+				       "pointer already locked or confined");
+		return;
+	}
+
+        cr = wl_resource_create(client, interface,
+				wl_resource_get_version(pointer_lock_resource),
+				id);
+	if (cr == NULL) {
+		wl_client_post_no_memory(client);
+		return;
+	}
+
+	wl_resource_set_implementation(cr, implementation, surface,
+				       pointer_lock_lock_resource_destroyed);
+
+	surface->pointer_lock.pointer = pointer;
+	surface->pointer_lock.resource = cr;
+	surface->pointer_lock.grab.interface = grab_interface;
+	if (region) {
+		pixman_region32_copy(&surface->pointer_lock.region,
+				     &region->region);
+	} else {
+		pixman_region32_fini(&surface->pointer_lock.region);
+		region_init_infinite(&surface->pointer_lock.region);
+	}
+
+	surface->pointer_lock.surface_activate_listener.notify =
+		pointer_lock_surface_activate;
+	surface->pointer_lock.surface_destroy_listener.notify =
+		pointer_lock_surface_destroyed;
+	surface->pointer_lock.pointer_destroy_listener.notify =
+		pointer_lock_pointer_destroyed;
+
+	wl_signal_add(&surface->compositor->activate_signal,
+		      &surface->pointer_lock.surface_activate_listener);
+	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);
+
+	maybe_enable_pointer_lock(surface);
+}
+
+
+static void
+locked_pointer_set_cursor_position_hint(struct wl_client *client,
+					struct wl_resource *resource,
+					uint32_t serial,
+					wl_fixed_t surface_x,
+					wl_fixed_t surface_y)
+{
+	struct weston_surface *surface = wl_resource_get_user_data(resource);
+
+	/* TODO: Check serial. */
+
+	surface->pointer_lock.hint_set = true;
+	surface->pointer_lock.x_hint = surface_x;
+	surface->pointer_lock.y_hint = surface_y;
+}
+
+static void
+locked_pointer_destroy(struct wl_client *client,
+		       struct wl_resource *resource)
+{
+	struct weston_surface *surface = wl_resource_get_user_data(resource);
+	wl_fixed_t x_hint = surface->pointer_lock.x_hint;
+	wl_fixed_t y_hint = surface->pointer_lock.y_hint;
+	wl_fixed_t x, y;
+
+	if (surface->pointer_lock.view &&
+	    surface->pointer_lock.hint_set &&
+	    is_within_lock_region(surface, x_hint, y_hint)) {
+		weston_view_to_global_fixed(surface->pointer_lock.view,
+					    x_hint, y_hint,
+					    &x, &y);
+		weston_pointer_move_to(surface->pointer_lock.pointer, x, y);
+	}
+	wl_resource_destroy(resource);
+}
+
+static const struct _wl_locked_pointer_interface locked_pointer_interface = {
+	locked_pointer_set_cursor_position_hint,
+	locked_pointer_destroy,
+};
+
+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 *seat_resource,
+			  struct wl_resource *region_resource)
+{
+	struct weston_surface *surface =
+		wl_resource_get_user_data(surface_resource);
+	struct weston_seat *seat = wl_resource_get_user_data(seat_resource);
+	struct weston_region *region = region_resource ?
+		wl_resource_get_user_data(region_resource) : NULL;
+
+	init_pointer_lock(resource, id, surface, seat, region,
+			  &_wl_locked_pointer_interface,
+			  &locked_pointer_interface,
+			  &locked_pointer_grab_interface);
+}
+
+static void
+confined_pointer_grab_pointer_focus(struct weston_pointer_grab *grab)
+{
+}
+
+static void
+weston_pointer_clamp_event_to_region(struct weston_pointer *pointer,
+				     struct weston_pointer_motion_event *event,
+				     pixman_region32_t *region,
+				     wl_fixed_t *clamped_x,
+				     wl_fixed_t *clamped_y)
+{
+	wl_fixed_t x, y;
+	wl_fixed_t sx, sy;
+	wl_fixed_t min_sx = wl_fixed_from_int(region->extents.x1);
+	wl_fixed_t max_sx = wl_fixed_from_int(region->extents.x2 - 1);
+	wl_fixed_t max_sy = wl_fixed_from_int(region->extents.y2 - 1);
+	wl_fixed_t min_sy = wl_fixed_from_int(region->extents.y1);
+
+	weston_pointer_motion_to_abs(pointer, event, &x, &y);
+	weston_view_from_global_fixed(pointer->focus, x, y, &sx, &sy);
+
+	if (sx < min_sx)
+		sx = min_sx;
+	else if (sx > max_sx)
+		sx = max_sx;
+
+	if (sy < min_sy)
+		sy = min_sy;
+	else if (sy > max_sy)
+		sy = max_sy;
+
+	weston_view_to_global_fixed(pointer->focus, sx, sy,
+				    clamped_x, clamped_y);
+}
+
+static void
+confined_pointer_grab_pointer_motion(struct weston_pointer_grab *grab,
+				     uint32_t time,
+				     struct weston_pointer_motion_event *event)
+{
+	struct weston_pointer *pointer = grab->pointer;
+	struct weston_surface *surface;
+	wl_fixed_t x, y;
+	wl_fixed_t old_sx = pointer->sx;
+	wl_fixed_t old_sy = pointer->sy;
+	pixman_region32_t confine_region;
+
+	assert(pointer->focus);
+	assert(pointer->focus->surface->pointer_lock.pointer == pointer);
+
+	surface = pointer->focus->surface;
+
+	pixman_region32_init(&confine_region);
+	pixman_region32_intersect(&confine_region,
+				  &surface->input,
+				  &surface->pointer_lock.region);
+	weston_pointer_clamp_event_to_region(pointer, event,
+					     &confine_region, &x, &y);
+	weston_pointer_move_to(pointer, x, y);
+	pixman_region32_fini(&confine_region);
+
+	weston_view_from_global_fixed(pointer->focus, x, y,
+				      &pointer->sx, &pointer->sy);
+
+	if (old_sx != pointer->sx || old_sy != pointer->sy) {
+		weston_pointer_send_motion(pointer, time,
+					   pointer->sx, pointer->sy);
+	}
+
+	weston_pointer_send_relative_motion(pointer, time, event);
+}
+
+static void
+confined_pointer_grab_pointer_button(struct weston_pointer_grab *grab,
+				     uint32_t time,
+				     uint32_t button,
+				     uint32_t state_w)
+{
+	weston_pointer_send_button(grab->pointer, time, button, state_w);
+}
+
+static void
+confined_pointer_grab_pointer_axis(struct weston_pointer_grab *grab,
+				   uint32_t time,
+				   uint32_t axis,
+				   wl_fixed_t value)
+{
+	weston_pointer_send_axis(grab->pointer, time, axis, value);
+}
+
+static void
+confined_pointer_grab_pointer_cancel(struct weston_pointer_grab *grab)
+{
+	struct weston_surface *surface =
+		container_of(grab, struct weston_surface, pointer_lock.grab);
+
+	disable_pointer_lock(surface);
+}
+
+static const struct weston_pointer_grab_interface
+				confined_pointer_grab_interface = {
+	confined_pointer_grab_pointer_focus,
+	confined_pointer_grab_pointer_motion,
+	confined_pointer_grab_pointer_button,
+	confined_pointer_grab_pointer_axis,
+	confined_pointer_grab_pointer_cancel,
+};
+
+static void
+confined_pointer_destroy(struct wl_client *client,
+			 struct wl_resource *resource)
+{
+	wl_resource_destroy(resource);
+}
+
+static const struct _wl_confined_pointer_interface confined_pointer_interface = {
+	confined_pointer_destroy,
+};
+
+static void
+pointer_lock_confine_pointer(struct wl_client *client,
+			     struct wl_resource *resource,
+			     uint32_t id,
+			     struct wl_resource *surface_resource,
+			     struct wl_resource *seat_resource,
+			     struct wl_resource *region_resource)
+{
+	struct weston_surface *surface =
+		wl_resource_get_user_data(surface_resource);
+	struct weston_seat *seat = wl_resource_get_user_data(seat_resource);
+	struct weston_region *region = region_resource ?
+		wl_resource_get_user_data(region_resource) : NULL;
+
+	if ((region && pixman_region32_n_rects(&region->region) != 1) ||
+	    pixman_region32_n_rects(&surface->input) != 1) {
+		weston_log("warning: confinement only implemented for"
+			   "rectangular regions\n");
+		return;
+	}
+
+	init_pointer_lock(resource, id, surface, seat, region,
+			  &_wl_confined_pointer_interface,
+			  &confined_pointer_interface,
+			  &confined_pointer_grab_interface);
+}
+
+static const struct _wl_pointer_lock_interface pointer_lock_interface = {
+	pointer_lock_lock_pointer,
+	pointer_lock_confine_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);
+}
+
 int
 weston_input_init(struct weston_compositor *compositor)
 {
@@ -2521,5 +3067,10 @@  weston_input_init(struct weston_compositor *compositor)
 			      compositor, bind_relative_pointer_manager))
 		return -1;
 
+	if (!wl_global_create(compositor->wl_display,
+			      &_wl_pointer_lock_interface, 1,
+			      NULL, bind_pointer_lock))
+		return -1;
+
 	return 0;
 }

Comments

A couple of doc comments below, but the protocol otherwise looks pretty
good.  Again, I've only glanced at the implementation.

On Tue, Dec 2, 2014 at 5:49 AM, Jonas Ådahl <jadahl@gmail.com> wrote:

> This patch introduces a new protocol for locking and confining a
> pointer. It consists of a new global object with two requests; one for
> locking the surface to a position, one for confining the pointer to a
> given region.
>
> See pointer-lock.xml for details of the protocol.
>
> In this patch, only the locking part is fully implemented as in
> specified in the protocol, while confinement is only implemented for
> when the union of the passed region and the input region of the confined
> surface is a single rectangle.
>
> Note that the interfaces are prefixed with an underscore in order to
> avoid future incompatibilities with a future stable interface with an
> equivalent name.
>
> Signed-off-by: Jonas Ådahl <jadahl@gmail.com>
> ---
>
> What should the serial in locked_pointer_set_cursor_position_hint be
> checked against? It doesn't seem to make sense to use the same method as
> in pointer_set_cursor. As far as I understand, it should be equal to the
> last serial sent via that pointer device, but I can't see we do that
> anywhere else. What am I missing?
>
>
>  Makefile.am               |   3 +
>  protocol/pointer-lock.xml | 208 ++++++++++++++++
>  src/compositor.c          |   4 +
>  src/compositor.h          |  21 ++
>  src/input.c               | 589
> ++++++++++++++++++++++++++++++++++++++++++++--
>  5 files changed, 806 insertions(+), 19 deletions(-)
>  create mode 100644 protocol/pointer-lock.xml
>
> diff --git a/Makefile.am b/Makefile.am
> index 860564d..8adc343 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -82,6 +82,8 @@ nodist_weston_SOURCES =
>      \
>         protocol/presentation_timing-server-protocol.h  \
>         protocol/scaler-protocol.c                      \
>         protocol/scaler-server-protocol.h               \
> +       protocol/pointer-lock-protocol.c                \
> +       protocol/pointer-lock-server-protocol.h         \
>         protocol/relative-pointer-protocol.c            \
>         protocol/relative-pointer-server-protocol.h
>
> @@ -1006,6 +1008,7 @@ EXTRA_DIST +=                                     \
>         protocol/fullscreen-shell.xml           \
>         protocol/presentation_timing.xml        \
>         protocol/scaler.xml                     \
> +       protocol/pointer-lock.xml               \
>         protocol/relative-pointer.xml
>
>  man_MANS = weston.1 weston.ini.5
> diff --git a/protocol/pointer-lock.xml b/protocol/pointer-lock.xml
> new file mode 100644
> index 0000000..3233ede
> --- /dev/null
> +++ b/protocol/pointer-lock.xml
> @@ -0,0 +1,208 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<protocol name="pointer_lock">
> +
> +  <copyright>
> +    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">
> +      The global interface exposing pointer locking functionality. It
> exposes
> +      two requests; lock_pointer for locking the pointer to its position,
> and
> +      confine_pointer for locking the pointer to a region.
> +
> +      The lock_pointer and confine_pointer creates the objects
> wl_locked_pointer
> +      and wl_confined_pointer respectively, and the client can use these
> objects
> +      to interact with the lock.
> +
> +      There may not be another lock of any kind active when requesting a
> lock,
> +      and if there is, an error will be raised.
> +    </description>
> +
> +    <request name="lock_pointer">
> +      <description summary="lock pointer to a position">
> +        The lock_pointer request lets the client disable absolute pointer
> +        movements, locking the pointer to a position.
> +
> +        There may not be another lock of any kind active when requesting
> a lock,
> +        and if there is, an error will be raised.
> +
> +        The intersection of the region passed with this request and the
> input
> +        region of the surface is used to determine where the pointer must
> be
> +        in order for the lock to activate. It is up to the compositor to
> warp
> +        the pointer, or require some kind of user interaction for the
> lock to
> +        activate. If the region is null, then an infinit region is used.
>

It might be better to say "the input region is used".  The infinite region
will be clampped to this region so it's effectively the same.  However,
that makes it blatantly clear to clients and implementers that they can't
use this interface to globally lock and steal the cursor.


> +
> +        The request will create a new object wl_locked_pointer which is
> used to
> +        interact with the lock as well as receive updates about its
> state. See
> +        the the description of wl_locked_pointer for further information.
> +
> +        Note that while a locked pointer doesn't move its absolute
> position, it
> +        may still emit relative motion events via the wl_relative_pointer
> +        object.
> +      </description>
> +
> +      <arg name="id" type="new_id" interface="_wl_locked_pointer"/>
> +      <arg name="surface" type="object" interface="wl_surface"
> +           summary="surface to lock pointer to"/>
> +      <arg name="seat" type="object" interface="wl_seat"
> +           summary="seat where the pointer should be locked"/>
> +      <arg name="region" type="object" interface="wl_region"
> allow-null="true"
> +           summary="region of surface"/>
> +    </request>
> +
> +    <request name="confine_pointer">
> +      <description summary="confine pointer to a region">
> +        The confine_pointer request lets the client confine the pointer
> cursor
> +        to a given region.
> +
> +        The intersection of the region passed with this request and the
> input
> +        region of the surface is used to determine where the pointer must
> be
> +        in order for the confinement to activate. It is up to the
> compositor to
> +        warp the pointer, or require some kind of user interaction for the
> +        confinement to activate. If the region is null, then an infinite
> region
> +        is used.
>

Same comment as above.  Input region might be better


> +
> +        The request will create a new object wl_confined_pointer which is
> used
> +        to interact with the confinement as well as receive updates about
> its
> +        state. See the the description of wl_confined_pointer for further
> +        information.
> +      </description>
> +
> +      <arg name="id" type="new_id" interface="_wl_confined_pointer"/>
> +      <arg name="surface" type="object" interface="wl_surface"
> +           summary="surface to lock pointer to"/>
> +      <arg name="seat" type="object" interface="wl_seat"
> +           summary="seat where the pointer should be locked"/>
> +      <arg name="region" type="object" interface="wl_region"
> allow-null="true"
> +           summary="region of surface"/>
> +    </request>
> +
> +  </interface>
> +
> +  <interface name="_wl_locked_pointer" version="1">
> +    <description summary="receive relative pointer motion events">
> +      The wl_locked_pointer interface represents a locked pointer state.
> +
> +      While the lock of this object is active, the pointer of the
> associated
> +      seat will not move.
> +
> +      This object will send the event 'locked' when the lock is activated.
> +      Whenever the lock is activated, it is guaranteed that the locked
> surface
> +      will already have received pointer focus and that the pointer will
> be
> +      within the region passed to the request creating this object.
> +
> +      To unlock the pointer, send the destroy request. This will also
> destroy
> +      the wl_locked_pointer object.
> +
> +      If the compositor decides to unlock the pointer the unlocked event
> is sent.
> +      The wl_locked_pointer object is at this point defunct and should be
> +      destoryed.
>

"destroyed"


> +
> +      When unlocking, the compositor may or may not take the cursor
> position
> +      hint provided using the set_cursor_position_hint request and warp
> the
> +      pointer. If it does, it will not result in any relative motion
> events.
>
+    </description>
> +
> +    <request name="set_cursor_position_hint">
> +      <description summary="set the pointer cursor position hint">
> +        Set the cursor position hint relative to the top left corner of
> the
> +        surface.
>

I think we want a bit more description here.  In particular, if clients are
using relative motion to draw their own cursor, they should be periodically
updating the hint.  We should also tell compositors what they can/should do
with it.  It's not 100% clear from your description what the hint does.


> +      </description>
> +
> +      <arg name="serial" type="uint" summary="serial of the enter event"/>
>

I think we may want this to be the serial of the "locked" event.  It
shouldn't matter much since once it's locked it shouldn't ever leave until
after the lock is broken.


> +      <arg name="surface_x" type="fixed"
> +           summary="x coordinate in surface-relative coordinates"/>
> +      <arg name="surface_y" type="fixed"
> +           summary="y coordinate in surface-relative coordinates"/>
> +    </request>
> +
> +    <request name="destroy" type="destructor">
> +      <description summary="destroy the locked pointer object">
> +        Destroy the locked pointer object. The compositor will unlock the
> +        pointer.
> +      </description>
> +    </request>
> +
> +    <event name="locked">
> +      <description summary="enter event">
> +        Notification that the pointer lock of this seat's pointer is
> activated.
> +      </description>
> +
> +      <arg name="serial" type="uint"/>
> +    </event>
> +
> +    <event name="unlocked">
> +      <description summary="leave event">
> +        Notification that the pointer lock of seat's pointer is no longer
> +        active. This object is no defunct and should be destroyed.
> +      </description>
> +    </event>
> +  </interface>
> +
> +  <interface name="_wl_confined_pointer" version="1">
> +    <description summary="confined pointer object">
> +      The wl_confined_pointer interface represents a confined pointer
> state.
> +
> +      This object will send the event 'confined' when the confinement is
> +      activated. Whenever the confinement is activated, it is guaranteed
> that
> +      the surface the pointer is confined to will already have received
> pointer
> +      focus and that the pointer will be within the region passed to the
> request
> +      creating this object. It is up to the compositor to decide whether
> this
> +      requires some user interaction and if the pointer will warp to
> within the
> +      passed region if outside.
> +
> +      To unconfine the pointer, send the destroy request. This will also
> destroy
> +      the wl_confined_pointer object.
> +
> +      If the compositor decides to unconfine the pointer the unconfined
> event is
> +      sent. The wl_confined_pointer object is at this point defunct and
> should
> +      be destoryed.
> +    </description>
> +
> +    <request name="destroy" type="destructor">
> +      <description summary="destroy the confined pointer object">
> +        Destroy the confined pointer object. The compositor will
> unconfine the
> +        pointer.
> +      </description>
> +    </request>
> +
> +    <event name="confined">
> +      <description summary="enter event">
> +        Notification that the pointer confinement of this seat's pointer
> is
> +        activated.
> +      </description>
> +
> +      <arg name="serial" type="uint"/>
> +    </event>
> +
> +    <event name="unconfined">
> +      <description summary="leave event">
> +        Notification that the pointer confinement of seat's pointer is no
> +        longer active. This object is no defunct and should be destroyed.
> +      </description>
> +    </event>
> +  </interface>
> +
> +</protocol>
> diff --git a/src/compositor.c b/src/compositor.c
> index 767cb26..242bdf6 100644
> --- a/src/compositor.c
> +++ b/src/compositor.c
> @@ -645,6 +645,8 @@ weston_surface_create(struct weston_compositor
> *compositor)
>         wl_list_init(&surface->subsurface_list);
>         wl_list_init(&surface->subsurface_list_pending);
>
> +       region_init_infinite(&surface->pointer_lock.region);
> +
>         return surface;
>  }
>
> @@ -1659,6 +1661,8 @@ weston_surface_destroy(struct weston_surface
> *surface)
>
>         weston_presentation_feedback_discard_list(&surface->feedback_list);
>
> +       pixman_region32_fini(&surface->pointer_lock.region);
> +
>         free(surface);
>  }
>
> diff --git a/src/compositor.h b/src/compositor.h
> index b373ecf..9a6fa7e 100644
> --- a/src/compositor.h
> +++ b/src/compositor.h
> @@ -339,6 +339,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;
> @@ -692,6 +693,8 @@ struct weston_compositor {
>         int exit_code;
>
>         unsigned int activate_serial;
> +
> +       struct wl_global *pointer_lock;
>  };
>
>  struct weston_buffer {
> @@ -943,6 +946,24 @@ struct weston_surface {
>          * and replace role_name with configure.
>          */
>         const char *role_name;
> +
> +       struct {
> +               struct weston_view *view;
> +               pixman_region32_t region;
> +               struct wl_resource *resource;
> +               struct weston_pointer_grab grab;
> +               struct weston_pointer *pointer;
> +
> +               bool hint_set;
> +               wl_fixed_t x_hint;
> +               wl_fixed_t y_hint;
> +
> +               struct wl_listener pointer_destroy_listener;
> +               struct wl_listener surface_destroy_listener;
> +
> +               struct wl_listener keyboard_focus_listener;
> +               struct wl_listener surface_activate_listener;
> +       } pointer_lock;
>  };
>
>  struct weston_subsurface {
> diff --git a/src/input.c b/src/input.c
> index 9d02eb8..63283f2 100644
> --- a/src/input.c
> +++ b/src/input.c
> @@ -22,6 +22,7 @@
>
>  #include "config.h"
>
> +#include <stdbool.h>
>  #include <stdlib.h>
>  #include <stdint.h>
>  #include <string.h>
> @@ -33,6 +34,7 @@
>
>  #include "../shared/os-compatibility.h"
>  #include "compositor.h"
> +#include "protocol/pointer-lock-server-protocol.h"
>  #include "protocol/relative-pointer-server-protocol.h"
>
>  static void
> @@ -42,6 +44,13 @@ empty_region(pixman_region32_t *region)
>         pixman_region32_init(region);
>  }
>
> +static void
> +region_init_infinite(pixman_region32_t *region)
> +{
> +       pixman_region32_init_rect(region, INT32_MIN, INT32_MIN,
> +                                 UINT32_MAX, UINT32_MAX);
> +}
> +
>  static void unbind_resource(struct wl_resource *resource)
>  {
>         wl_list_remove(wl_resource_get_link(resource));
> @@ -227,12 +236,22 @@ weston_pointer_send_relative_motion(struct
> weston_pointer *pointer,
>  }
>
>  static void
> +weston_pointer_send_motion(struct weston_pointer *pointer, uint32_t time,
> +                          wl_fixed_t sx, wl_fixed_t sy)
> +{
> +       struct wl_list *resource_list;
> +       struct wl_resource *resource;
> +
> +       resource_list = &pointer->focus_resource_list;
> +       wl_resource_for_each(resource, resource_list)
> +               wl_pointer_send_motion(resource, time, sx, sy);
> +}
> +
> +static void
>  default_grab_pointer_motion(struct weston_pointer_grab *grab, uint32_t
> time,
>                             struct weston_pointer_motion_event *event)
>  {
>         struct weston_pointer *pointer = grab->pointer;
> -       struct wl_list *resource_list;
> -       struct wl_resource *resource;
>         wl_fixed_t x, y;
>         wl_fixed_t old_sx = pointer->sx;
>         wl_fixed_t old_sy = pointer->sy;
> @@ -246,40 +265,46 @@ default_grab_pointer_motion(struct
> weston_pointer_grab *grab, uint32_t time,
>         weston_pointer_move(pointer, event);
>
>         if (old_sx != pointer->sx || old_sy != pointer->sy) {
> -               resource_list = &pointer->focus_resource_list;
> -               wl_resource_for_each(resource, resource_list) {
> -                       wl_pointer_send_motion(resource, time,
> -                                              pointer->sx, pointer->sy);
> -               }
> +               weston_pointer_send_motion(pointer, time,
> +                                          pointer->sx, pointer->sy);
>         }
>
>         weston_pointer_send_relative_motion(pointer, time, event);
>  }
>
>  static void
> -default_grab_pointer_button(struct weston_pointer_grab *grab,
> -                           uint32_t time, uint32_t button, uint32_t
> state_w)
> +weston_pointer_send_button(struct weston_pointer *pointer,
> +                          uint32_t time, uint32_t button, uint32_t
> state_w)
>  {
> -       struct weston_pointer *pointer = grab->pointer;
> -       struct weston_compositor *compositor = pointer->seat->compositor;
> -       struct weston_view *view;
>         struct wl_resource *resource;
>         uint32_t serial;
> -       enum wl_pointer_button_state state = state_w;
> -       struct wl_display *display = compositor->wl_display;
> -       wl_fixed_t sx, sy;
>         struct wl_list *resource_list;
> +       struct wl_display *display = pointer->seat->compositor->wl_display;
>
>         resource_list = &pointer->focus_resource_list;
>         if (!wl_list_empty(resource_list)) {
>                 serial = wl_display_next_serial(display);
> -               wl_resource_for_each(resource, resource_list)
> +               wl_resource_for_each(resource, resource_list) {
>                         wl_pointer_send_button(resource,
>                                                serial,
>                                                time,
>                                                button,
>                                                state_w);
> +               }
>         }
> +}
> +
> +static void
> +default_grab_pointer_button(struct weston_pointer_grab *grab,
> +                           uint32_t time, uint32_t button, uint32_t
> state_w)
> +{
> +       struct weston_pointer *pointer = grab->pointer;
> +       struct weston_compositor *compositor = pointer->seat->compositor;
> +       struct weston_view *view;
> +       enum wl_pointer_button_state state = state_w;
> +       wl_fixed_t sx, sy;
> +
> +       weston_pointer_send_button(pointer, time, button, state_w);
>
>         if (pointer->button_count == 0 &&
>             state == WL_POINTER_BUTTON_STATE_RELEASED) {
> @@ -292,10 +317,9 @@ default_grab_pointer_button(struct
> weston_pointer_grab *grab,
>  }
>
>  static void
> -default_grab_pointer_axis(struct weston_pointer_grab *grab,
> -                         uint32_t time, uint32_t axis, wl_fixed_t value)
> +weston_pointer_send_axis(struct weston_pointer *pointer,
> +                        uint32_t time, uint32_t axis, wl_fixed_t value)
>  {
> -       struct weston_pointer *pointer = grab->pointer;
>         struct wl_resource *resource;
>         struct wl_list *resource_list;
>
> @@ -305,6 +329,13 @@ default_grab_pointer_axis(struct weston_pointer_grab
> *grab,
>  }
>
>  static void
> +default_grab_pointer_axis(struct weston_pointer_grab *grab,
> +                         uint32_t time, uint32_t axis, wl_fixed_t value)
> +{
> +       weston_pointer_send_axis(grab->pointer, time, axis, value);
> +}
> +
> +static void
>  default_grab_pointer_cancel(struct weston_pointer_grab *grab)
>  {
>  }
> @@ -564,6 +595,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;
>
> @@ -582,6 +614,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);
>
> @@ -2513,6 +2547,518 @@ weston_seat_release(struct weston_seat *seat)
>         wl_signal_emit(&seat->destroy_signal, seat);
>  }
>
> +static const struct _wl_locked_pointer_interface locked_pointer_interface;
> +static const struct _wl_confined_pointer_interface
> confined_pointer_interface;
> +
> +static void
> +pointer_lock_notify_activated(struct weston_surface *surface)
> +{
> +       struct weston_compositor *compositor = surface->compositor;
> +       struct wl_resource *resource = surface->pointer_lock.resource;
> +       uint32_t serial;
> +
> +       if (wl_resource_instance_of(resource,
> +                                   &_wl_locked_pointer_interface,
> +                                   &locked_pointer_interface)) {
> +               serial = wl_display_next_serial(compositor->wl_display);
> +               _wl_locked_pointer_send_locked(resource, serial);
> +       } else if (wl_resource_instance_of(resource,
> +                                          &_wl_confined_pointer_interface,
> +                                          &confined_pointer_interface)) {
> +               serial = wl_display_next_serial(compositor->wl_display);
> +               _wl_confined_pointer_send_confined(resource, serial);
> +       }
> +}
> +
> +static void
> +pointer_lock_notify_deactivated(struct weston_surface *surface)
> +{
> +       struct wl_resource *resource = surface->pointer_lock.resource;
> +
> +       if (wl_resource_instance_of(resource,
> +                                   &_wl_locked_pointer_interface,
> +                                   &locked_pointer_interface)) {
> +               _wl_locked_pointer_send_unlocked(resource);
> +       } else if (wl_resource_instance_of(resource,
> +                                          &_wl_confined_pointer_interface,
> +                                          &confined_pointer_interface)) {
> +               _wl_confined_pointer_send_unconfined(resource);
> +       }
> +}
> +
> +static void
> +enable_pointer_lock(struct weston_view *view,
> +                   struct weston_pointer *pointer)
> +{
> +       struct weston_surface *surface = view->surface;
> +
> +       assert(surface->pointer_lock.view == NULL);
> +       surface->pointer_lock.view = view;
> +       pointer_lock_notify_activated(surface);
> +       weston_pointer_start_grab(pointer, &surface->pointer_lock.grab);
> +}
> +
> +static bool
> +is_pointer_lock_enabled(struct weston_surface *surface)
> +{
> +       return surface->pointer_lock.view != NULL;
> +}
> +
> +static void
> +disable_pointer_lock(struct weston_surface *surface)
> +{
> +       if (is_pointer_lock_enabled(surface)) {
> +               pointer_lock_notify_deactivated(surface);
> +
>  weston_pointer_end_grab(surface->pointer_lock.grab.pointer);
> +               surface->pointer_lock.view = NULL;
> +       }
> +
> +       surface->pointer_lock.resource = NULL;
> +       surface->pointer_lock.pointer = NULL;
> +
> +       surface->pointer_lock.hint_set = false;
> +
> +
>  wl_list_remove(&surface->pointer_lock.pointer_destroy_listener.link);
> +
>  wl_list_remove(&surface->pointer_lock.surface_destroy_listener.link);
> +
>  wl_list_remove(&surface->pointer_lock.surface_activate_listener.link);
> +}
> +
> +static bool
> +is_within_lock_region(struct weston_surface *surface,
> +                     wl_fixed_t sx, wl_fixed_t sy)
> +{
> +       pixman_region32_t lock_region;
> +       bool result;
> +
> +       pixman_region32_init(&lock_region);
> +       pixman_region32_intersect(&lock_region,
> +                                 &surface->input,
> +                                 &surface->pointer_lock.region);
> +       result = pixman_region32_contains_point(&lock_region,
> +                                               wl_fixed_to_int(sx),
> +                                               wl_fixed_to_int(sy),
> +                                               NULL);
> +       pixman_region32_fini(&lock_region);
> +
> +       return result;
> +}
> +
> +static void
> +maybe_enable_pointer_lock(struct weston_surface *surface)
> +{
> +       struct weston_view *vit;
> +       struct weston_view *view = NULL;
> +       struct weston_pointer *pointer = surface->pointer_lock.pointer;
> +       struct weston_seat *seat = pointer->seat;
> +       int32_t x, y;
> +
> +       /* Postpone if no view of the surface was most recently clicked. */
> +       wl_list_for_each(vit, &surface->views, surface_link) {
> +               if (vit->click_to_activate_serial ==
> +                   surface->compositor->activate_serial) {
> +                       view = vit;
> +               }
> +       }
> +       if (view == NULL)
> +               return;
> +
> +       /* Postpone if surface doesn't have keyboard focus. */
> +       if (seat->keyboard->focus != surface)
> +               return;
> +
> +       /* Postpone lock if the pointer is not within the lock region. */
> +       weston_view_from_global(view,
> +                               wl_fixed_to_int(pointer->x),
> +                               wl_fixed_to_int(pointer->y),
> +                               &x, &y);
> +       if (!is_within_lock_region(surface,
> +                                  wl_fixed_from_int(x),
> +                                  wl_fixed_from_int(y)))
> +               return;
> +
> +       enable_pointer_lock(view, pointer);
> +}
> +
> +static void
> +locked_pointer_grab_pointer_focus(struct weston_pointer_grab *grab)
> +{
> +}
> +
> +static void
> +locked_pointer_grab_pointer_motion(struct weston_pointer_grab *grab,
> +                                  uint32_t time,
> +                                  struct weston_pointer_motion_event
> *event)
> +{
> +       weston_pointer_send_relative_motion(grab->pointer, time, event);
> +}
> +
> +static void
> +locked_pointer_grab_pointer_button(struct weston_pointer_grab *grab,
> +                                  uint32_t time,
> +                                  uint32_t button,
> +                                  uint32_t state_w)
> +{
> +       weston_pointer_send_button(grab->pointer, time, button, state_w);
> +}
> +
> +static void
> +locked_pointer_grab_pointer_axis(struct weston_pointer_grab *grab,
> +                                uint32_t time, uint32_t axis, wl_fixed_t
> value)
> +{
> +       weston_pointer_send_axis(grab->pointer, time, axis, value);
> +}
> +
> +static void
> +locked_pointer_grab_pointer_cancel(struct weston_pointer_grab *grab)
> +{
> +       struct weston_surface *surface =
> +               container_of(grab, struct weston_surface,
> pointer_lock.grab);
> +
> +       disable_pointer_lock(surface);
> +}
> +
> +static const struct weston_pointer_grab_interface
> +                               locked_pointer_grab_interface = {
> +       locked_pointer_grab_pointer_focus,
> +       locked_pointer_grab_pointer_motion,
> +       locked_pointer_grab_pointer_button,
> +       locked_pointer_grab_pointer_axis,
> +       locked_pointer_grab_pointer_cancel,
> +};
> +
> +static void
> +pointer_lock_lock_resource_destroyed(struct wl_resource *resource)
> +{
> +       struct weston_surface *surface =
> wl_resource_get_user_data(resource);
> +
> +       if (!surface->pointer_lock.resource)
> +               return;
> +
> +       disable_pointer_lock(surface);
> +}
> +
> +static void
> +pointer_lock_surface_activate(struct wl_listener *listener, void *data)
> +{
> +       struct weston_surface *focus = data;
> +       struct weston_surface *locked_surface =
> +               container_of(listener, struct weston_surface,
> +                            pointer_lock.surface_activate_listener);
> +
> +       if (focus == locked_surface &&
> !is_pointer_lock_enabled(locked_surface))
> +               maybe_enable_pointer_lock(locked_surface);
> +       else if (focus != locked_surface &&
> +                is_pointer_lock_enabled(locked_surface))
> +               disable_pointer_lock(locked_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);
> +
> +       disable_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);
> +
> +       disable_pointer_lock(surface);
> +}
> +
> +static void
> +init_pointer_lock(struct wl_resource *pointer_lock_resource,
> +                 uint32_t id,
> +                 struct weston_surface *surface,
> +                 struct weston_seat *seat,
> +                 struct weston_region *region,
> +                 const struct wl_interface *interface,
> +                 const void *implementation,
> +                 const struct weston_pointer_grab_interface
> *grab_interface)
> +{
> +       struct wl_client *client =
> +               wl_resource_get_client(pointer_lock_resource);
> +       struct weston_pointer *pointer = seat->pointer;
> +       struct wl_resource *cr;
> +
> +       if (surface->pointer_lock.resource) {
> +               wl_resource_post_error(pointer_lock_resource,
> +                                      WL_DISPLAY_ERROR_INVALID_OBJECT,
> +                                      "pointer already locked or
> confined");
> +               return;
> +       }
> +
> +        cr = wl_resource_create(client, interface,
> +
>  wl_resource_get_version(pointer_lock_resource),
> +                               id);
> +       if (cr == NULL) {
> +               wl_client_post_no_memory(client);
> +               return;
> +       }
> +
> +       wl_resource_set_implementation(cr, implementation, surface,
> +
> pointer_lock_lock_resource_destroyed);
> +
> +       surface->pointer_lock.pointer = pointer;
> +       surface->pointer_lock.resource = cr;
> +       surface->pointer_lock.grab.interface = grab_interface;
> +       if (region) {
> +               pixman_region32_copy(&surface->pointer_lock.region,
> +                                    &region->region);
> +       } else {
> +               pixman_region32_fini(&surface->pointer_lock.region);
> +               region_init_infinite(&surface->pointer_lock.region);
> +       }
> +
> +       surface->pointer_lock.surface_activate_listener.notify =
> +               pointer_lock_surface_activate;
> +       surface->pointer_lock.surface_destroy_listener.notify =
> +               pointer_lock_surface_destroyed;
> +       surface->pointer_lock.pointer_destroy_listener.notify =
> +               pointer_lock_pointer_destroyed;
> +
> +       wl_signal_add(&surface->compositor->activate_signal,
> +                     &surface->pointer_lock.surface_activate_listener);
> +       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);
> +
> +       maybe_enable_pointer_lock(surface);
> +}
> +
> +
> +static void
> +locked_pointer_set_cursor_position_hint(struct wl_client *client,
> +                                       struct wl_resource *resource,
> +                                       uint32_t serial,
> +                                       wl_fixed_t surface_x,
> +                                       wl_fixed_t surface_y)
> +{
> +       struct weston_surface *surface =
> wl_resource_get_user_data(resource);
> +
> +       /* TODO: Check serial. */
> +
> +       surface->pointer_lock.hint_set = true;
> +       surface->pointer_lock.x_hint = surface_x;
> +       surface->pointer_lock.y_hint = surface_y;
> +}
> +
> +static void
> +locked_pointer_destroy(struct wl_client *client,
> +                      struct wl_resource *resource)
> +{
> +       struct weston_surface *surface =
> wl_resource_get_user_data(resource);
> +       wl_fixed_t x_hint = surface->pointer_lock.x_hint;
> +       wl_fixed_t y_hint = surface->pointer_lock.y_hint;
> +       wl_fixed_t x, y;
> +
> +       if (surface->pointer_lock.view &&
> +           surface->pointer_lock.hint_set &&
> +           is_within_lock_region(surface, x_hint, y_hint)) {
> +               weston_view_to_global_fixed(surface->pointer_lock.view,
> +                                           x_hint, y_hint,
> +                                           &x, &y);
> +               weston_pointer_move_to(surface->pointer_lock.pointer, x,
> y);
> +       }
> +       wl_resource_destroy(resource);
> +}
> +
> +static const struct _wl_locked_pointer_interface locked_pointer_interface
> = {
> +       locked_pointer_set_cursor_position_hint,
> +       locked_pointer_destroy,
> +};
> +
> +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 *seat_resource,
> +                         struct wl_resource *region_resource)
> +{
> +       struct weston_surface *surface =
> +               wl_resource_get_user_data(surface_resource);
> +       struct weston_seat *seat =
> wl_resource_get_user_data(seat_resource);
> +       struct weston_region *region = region_resource ?
> +               wl_resource_get_user_data(region_resource) : NULL;
> +
> +       init_pointer_lock(resource, id, surface, seat, region,
> +                         &_wl_locked_pointer_interface,
> +                         &locked_pointer_interface,
> +                         &locked_pointer_grab_interface);
> +}
> +
> +static void
> +confined_pointer_grab_pointer_focus(struct weston_pointer_grab *grab)
> +{
> +}
> +
> +static void
> +weston_pointer_clamp_event_to_region(struct weston_pointer *pointer,
> +                                    struct weston_pointer_motion_event
> *event,
> +                                    pixman_region32_t *region,
> +                                    wl_fixed_t *clamped_x,
> +                                    wl_fixed_t *clamped_y)
> +{
> +       wl_fixed_t x, y;
> +       wl_fixed_t sx, sy;
> +       wl_fixed_t min_sx = wl_fixed_from_int(region->extents.x1);
> +       wl_fixed_t max_sx = wl_fixed_from_int(region->extents.x2 - 1);
> +       wl_fixed_t max_sy = wl_fixed_from_int(region->extents.y2 - 1);
> +       wl_fixed_t min_sy = wl_fixed_from_int(region->extents.y1);
> +
> +       weston_pointer_motion_to_abs(pointer, event, &x, &y);
> +       weston_view_from_global_fixed(pointer->focus, x, y, &sx, &sy);
> +
> +       if (sx < min_sx)
> +               sx = min_sx;
> +       else if (sx > max_sx)
> +               sx = max_sx;
> +
> +       if (sy < min_sy)
> +               sy = min_sy;
> +       else if (sy > max_sy)
> +               sy = max_sy;
> +
> +       weston_view_to_global_fixed(pointer->focus, sx, sy,
> +                                   clamped_x, clamped_y);
> +}
> +
> +static void
> +confined_pointer_grab_pointer_motion(struct weston_pointer_grab *grab,
> +                                    uint32_t time,
> +                                    struct weston_pointer_motion_event
> *event)
> +{
> +       struct weston_pointer *pointer = grab->pointer;
> +       struct weston_surface *surface;
> +       wl_fixed_t x, y;
> +       wl_fixed_t old_sx = pointer->sx;
> +       wl_fixed_t old_sy = pointer->sy;
> +       pixman_region32_t confine_region;
> +
> +       assert(pointer->focus);
> +       assert(pointer->focus->surface->pointer_lock.pointer == pointer);
> +
> +       surface = pointer->focus->surface;
> +
> +       pixman_region32_init(&confine_region);
> +       pixman_region32_intersect(&confine_region,
> +                                 &surface->input,
> +                                 &surface->pointer_lock.region);
> +       weston_pointer_clamp_event_to_region(pointer, event,
> +                                            &confine_region, &x, &y);
> +       weston_pointer_move_to(pointer, x, y);
> +       pixman_region32_fini(&confine_region);
> +
> +       weston_view_from_global_fixed(pointer->focus, x, y,
> +                                     &pointer->sx, &pointer->sy);
> +
> +       if (old_sx != pointer->sx || old_sy != pointer->sy) {
> +               weston_pointer_send_motion(pointer, time,
> +                                          pointer->sx, pointer->sy);
> +       }
> +
> +       weston_pointer_send_relative_motion(pointer, time, event);
> +}
> +
> +static void
> +confined_pointer_grab_pointer_button(struct weston_pointer_grab *grab,
> +                                    uint32_t time,
> +                                    uint32_t button,
> +                                    uint32_t state_w)
> +{
> +       weston_pointer_send_button(grab->pointer, time, button, state_w);
> +}
> +
> +static void
> +confined_pointer_grab_pointer_axis(struct weston_pointer_grab *grab,
> +                                  uint32_t time,
> +                                  uint32_t axis,
> +                                  wl_fixed_t value)
> +{
> +       weston_pointer_send_axis(grab->pointer, time, axis, value);
> +}
> +
> +static void
> +confined_pointer_grab_pointer_cancel(struct weston_pointer_grab *grab)
> +{
> +       struct weston_surface *surface =
> +               container_of(grab, struct weston_surface,
> pointer_lock.grab);
> +
> +       disable_pointer_lock(surface);
> +}
> +
> +static const struct weston_pointer_grab_interface
> +                               confined_pointer_grab_interface = {
> +       confined_pointer_grab_pointer_focus,
> +       confined_pointer_grab_pointer_motion,
> +       confined_pointer_grab_pointer_button,
> +       confined_pointer_grab_pointer_axis,
> +       confined_pointer_grab_pointer_cancel,
> +};
> +
> +static void
> +confined_pointer_destroy(struct wl_client *client,
> +                        struct wl_resource *resource)
> +{
> +       wl_resource_destroy(resource);
> +}
> +
> +static const struct _wl_confined_pointer_interface
> confined_pointer_interface = {
> +       confined_pointer_destroy,
> +};
> +
> +static void
> +pointer_lock_confine_pointer(struct wl_client *client,
> +                            struct wl_resource *resource,
> +                            uint32_t id,
> +                            struct wl_resource *surface_resource,
> +                            struct wl_resource *seat_resource,
> +                            struct wl_resource *region_resource)
> +{
> +       struct weston_surface *surface =
> +               wl_resource_get_user_data(surface_resource);
> +       struct weston_seat *seat =
> wl_resource_get_user_data(seat_resource);
> +       struct weston_region *region = region_resource ?
> +               wl_resource_get_user_data(region_resource) : NULL;
> +
> +       if ((region && pixman_region32_n_rects(&region->region) != 1) ||
> +           pixman_region32_n_rects(&surface->input) != 1) {
> +               weston_log("warning: confinement only implemented for"
> +                          "rectangular regions\n");
> +               return;
> +       }
> +
> +       init_pointer_lock(resource, id, surface, seat, region,
> +                         &_wl_confined_pointer_interface,
> +                         &confined_pointer_interface,
> +                         &confined_pointer_grab_interface);
> +}
> +
> +static const struct _wl_pointer_lock_interface pointer_lock_interface = {
> +       pointer_lock_lock_pointer,
> +       pointer_lock_confine_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);
> +}
> +
>  int
>  weston_input_init(struct weston_compositor *compositor)
>  {
> @@ -2521,5 +3067,10 @@ weston_input_init(struct weston_compositor
> *compositor)
>                               compositor, bind_relative_pointer_manager))
>                 return -1;
>
> +       if (!wl_global_create(compositor->wl_display,
> +                             &_wl_pointer_lock_interface, 1,
> +                             NULL, bind_pointer_lock))
> +               return -1;
> +
>         return 0;
>  }
> --
> 1.8.5.1
>
> _______________________________________________
> wayland-devel mailing list
> wayland-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/wayland-devel
>
On 12/02/2014 05:49 AM, Jonas Ådahl wrote:

> +    <request name="lock_pointer">
> +      <description summary="lock pointer to a position">
> +        The lock_pointer request lets the client disable absolute pointer
> +        movements, locking the pointer to a position.
> +
> +        There may not be another lock of any kind active when requesting a lock,
> +        and if there is, an error will be raised.
> +
> +        The intersection of the region passed with this request and the input
> +        region of the surface is used to determine where the pointer must be
> +        in order for the lock to activate. It is up to the compositor to warp
> +        the pointer, or require some kind of user interaction for the lock to
> +        activate. If the region is null, then an infinit region is used.
> +
> +        The request will create a new object wl_locked_pointer which is used to
> +        interact with the lock as well as receive updates about its state. See
> +        the the description of wl_locked_pointer for further information.
> +
> +        Note that while a locked pointer doesn't move its absolute position, it
> +        may still emit relative motion events via the wl_relative_pointer
> +        object.
> +      </description>
> +
> +      <arg name="id" type="new_id" interface="_wl_locked_pointer"/>
> +      <arg name="surface" type="object" interface="wl_surface"
> +           summary="surface to lock pointer to"/>
> +      <arg name="seat" type="object" interface="wl_seat"
> +           summary="seat where the pointer should be locked"/>
> +      <arg name="region" type="object" interface="wl_region" allow-null="true"
> +           summary="region of surface"/>
> +    </request>

- Does this need some id of the triggering event? Mostly to determine if 
the surface had the pointer focus at the time the request was made.

A lock that is lost when the mouse button is released might be nice. It 
would be used for the "slow scrollbar" and similar widgets, avoid a 
round-trip on the release event, and would probably share code with 
normal mouse-up handling. I think this could be determined based on what 
type of event triggered the lock.

- I am very unclear on what "require some kind of user interaction for 
the lock to activate" means. What I expect is that it will work if and 
only if the surface has the pointer focus. Any "user interaction" has 
already happened (ie they may have moved to another surface). Can 
somebody explain? Or perhaps this is just badly worded.

- Do not warp the pointer. The pointer should freeze exactly where it is 
(even if outside the region), and only client requests to set the cursor 
position should move it. Any compositor-chosen position may be incorrect 
and will result in a flicker as the cursor is placed in this wrong 
position temporarily.

- Is the region really necessary? I think it would be easy for a client 
to see that the cursor has moved out of any desired region and just not 
do this request. Also this removes any questions about complex regions.

 > +    <request name="set_cursor_position_hint">
 > +      <description summary="set the pointer cursor position hint">

Why is this called a "hint"? This better be a lot stronger than a 
"hint", it is pretty near useless if the cursor does not move to where 
the client wants. It's true that it sometimes won't work (for instance 
if the lock has been lost) but if that was the rule *every* request in 
Wayland would have to be called a "hint"!

It would be really nice if you removed this excess verbage and called 
this "set_cursor_position" or even "set_position".

What happens is the compositor cannot set the position, for instance if 
it is off-screen? Does it send a motion event saying where it was really 
placed?

> +    <request name="confine_pointer">
> +      <description summary="confine pointer to a region">
> +        The confine_pointer request lets the client confine the pointer cursor
> +        to a given region.

I don't think this should be necessary. The plain pointer-lock can be 
used, and the client uses set_cursor_position to do the confinement. And 
that removes any questions about complicated regions.

> +    <event name="locked">
> +      <description summary="enter event">
> +        Notification that the pointer lock of this seat's pointer is activated.
> +      </description>

Can't this just be assumed by the fact that you created the pointer lock 
object and have not gotten the unlocked event yet?

> +    <event name="unlocked">
> +      <description summary="leave event">
> +        Notification that the pointer lock of seat's pointer is no longer
> +        active. This object is no defunct and should be destroyed.
> +      </description>

May want to point out that this is also sent immediately if there is a 
failure to get the pointer lock.
On Tue, Dec 02, 2014 at 02:39:21PM -0800, Bill Spitzak wrote:
> On 12/02/2014 05:49 AM, Jonas Ådahl wrote:
> 
> >+    <request name="lock_pointer">
> >+      <description summary="lock pointer to a position">
> >+        The lock_pointer request lets the client disable absolute pointer
> >+        movements, locking the pointer to a position.
> >+
> >+        There may not be another lock of any kind active when requesting a lock,
> >+        and if there is, an error will be raised.
> >+
> >+        The intersection of the region passed with this request and the input
> >+        region of the surface is used to determine where the pointer must be
> >+        in order for the lock to activate. It is up to the compositor to warp
> >+        the pointer, or require some kind of user interaction for the lock to
> >+        activate. If the region is null, then an infinit region is used.
> >+
> >+        The request will create a new object wl_locked_pointer which is used to
> >+        interact with the lock as well as receive updates about its state. See
> >+        the the description of wl_locked_pointer for further information.
> >+
> >+        Note that while a locked pointer doesn't move its absolute position, it
> >+        may still emit relative motion events via the wl_relative_pointer
> >+        object.
> >+      </description>
> >+
> >+      <arg name="id" type="new_id" interface="_wl_locked_pointer"/>
> >+      <arg name="surface" type="object" interface="wl_surface"
> >+           summary="surface to lock pointer to"/>
> >+      <arg name="seat" type="object" interface="wl_seat"
> >+           summary="seat where the pointer should be locked"/>
> >+      <arg name="region" type="object" interface="wl_region" allow-null="true"
> >+           summary="region of surface"/>
> >+    </request>
> 
> - Does this need some id of the triggering event? Mostly to determine if the
> surface had the pointer focus at the time the request was made.

It does not, and the lock is postponed until that trigger event happens.
For example a surface can lock the pointer, but the lock may not
activate until the user clicks the surface. It should not be hard coded
exactly what type of event should trigger a lock.

> 
> A lock that is lost when the mouse button is released might be nice. It
> would be used for the "slow scrollbar" and similar widgets, avoid a
> round-trip on the release event, and would probably share code with normal
> mouse-up handling. I think this could be determined based on what type of
> event triggered the lock.

Don't think it's very beneficial to add various kinds of locks that
unlocks at specific events. The client can simply unlock on mouse
button release. Not sure what round trip you are talking about here, but
the client has no need for doing a round trip for unlocking, it simply
destroys its lock object and when the pointer moves, it'll receive new
motion events.

> 
> - I am very unclear on what "require some kind of user interaction for the
> lock to activate" means. What I expect is that it will work if and only if
> the surface has the pointer focus. Any "user interaction" has already
> happened (ie they may have moved to another surface). Can somebody explain?
> Or perhaps this is just badly worded.

It simply means that the compositor may, if it wants to, require certain
user interaction, such as clicking, to actually activate the lock. This
is because the compositor might want to avoid letting clients "take" the
lock without any interaction (for example locking on just pointer focus a
surface could steal the cursor when the user just crosses some area of
the surface).

In the proposed implementation, the heuristics currently are that a lock
is activated on click, or immediately if the surface was already
activated by clicking; but the heuristics could be different from that.

Do you have a suggestion for a better wording?

> 
> - Do not warp the pointer. The pointer should freeze exactly where it is
> (even if outside the region), and only client requests to set the cursor
> position should move it. Any compositor-chosen position may be incorrect and
> will result in a flicker as the cursor is placed in this wrong position
> temporarily.

The pointer is only potentially warped when unlocking, and the hint is
used for getting a potentially good position. While locked, the pointer
is not going to move, neither by moving the mouse or setting the hint.
We don't want clients to start assuming they can control the cursor,
that's the job of the compositor.

> 
> - Is the region really necessary? I think it would be easy for a client to
> see that the cursor has moved out of any desired region and just not do this
> request. Also this removes any questions about complex regions.

Locking may only be relevant for a portion of a surface. One might still
want to have usable window decorations for example. Complex regions are
only really hard to deal with for confinement, not for locking.

> 
> > +    <request name="set_cursor_position_hint">
> > +      <description summary="set the pointer cursor position hint">
> 
> Why is this called a "hint"? This better be a lot stronger than a "hint", it
> is pretty near useless if the cursor does not move to where the client
> wants. It's true that it sometimes won't work (for instance if the lock has
> been lost) but if that was the rule *every* request in Wayland would have to
> be called a "hint"!
> 
> It would be really nice if you removed this excess verbage and called this
> "set_cursor_position" or even "set_position".
> 
> What happens is the compositor cannot set the position, for instance if it
> is off-screen? Does it send a motion event saying where it was really
> placed?

It's called a hint simply because it only is a hint. It's not the job of
the client to move the cursor around, it's the job of the compositor. We
don't want to make clients assume they can control the cursor but we
still want to enable the client to draw its own cursor and then not have
an awkward warp when unlocking and displaying the real cursor again, and
this is what the hint is for.

> 
> >+    <request name="confine_pointer">
> >+      <description summary="confine pointer to a region">
> >+        The confine_pointer request lets the client confine the pointer cursor
> >+        to a given region.
> 
> I don't think this should be necessary. The plain pointer-lock can be used,
> and the client uses set_cursor_position to do the confinement. And that
> removes any questions about complicated regions.

We don't want clients to control the cursor position. If the client
really wants to be in control, it should lock the pointer, hide the
cursor, and then draw its own cursor sprite, either as part of its own
scene, or as a subsurface. Confinement, however, allows for lower
latency confinement. Consider playing an RTS game; moving the pointer
cursor without having to do an extra round trip via the client to move
the cursor could make the experience better. This cannot be done with
either the client drawing the cursor, or the client setting the cursor
position.

> 
> >+    <event name="locked">
> >+      <description summary="enter event">
> >+        Notification that the pointer lock of this seat's pointer is activated.
> >+      </description>
> 
> Can't this just be assumed by the fact that you created the pointer lock
> object and have not gotten the unlocked event yet?

The lock may be postponed, and this event is used to let the client know
that the lock was actually activated.

> 
> >+    <event name="unlocked">
> >+      <description summary="leave event">
> >+        Notification that the pointer lock of seat's pointer is no longer
> >+        active. This object is no defunct and should be destroyed.
> >+      </description>
> 
> May want to point out that this is also sent immediately if there is a
> failure to get the pointer lock.

This is not the case though. If the lock could not be immediately be
activated, it is postponed until when the compositor thinks it is a good
idea to let the client steal the cursor control.


Jonas
On 12/08/2014 06:07 AM, Jonas Ådahl wrote:
> On Tue, Dec 02, 2014 at 02:39:21PM -0800, Bill Spitzak wrote:

>> - Does this need some id of the triggering event? Mostly to determine if the
>> surface had the pointer focus at the time the request was made.
>
> It does not, and the lock is postponed until that trigger event happens.
> For example a surface can lock the pointer, but the lock may not
> activate until the user clicks the surface. It should not be hard coded
> exactly what type of event should trigger a lock.

Okay I think a lot of my confusion was that I figured the pointer lock 
was created in response to an event such as a button press. It sounds 
like it is instead some kind of setup that a client would create asap 
that means it will get the pointer lock on click.

Though I am still rather confused that the only way to "release" a 
pointer lock is to destroy this object. Is the client supposed to 
immediately recreate it in preparation for the next click?

Can you explain how a client would use this to make a slow scrollbar 
that works like this:

1. User presses the mouse down on the scrollbar

2. Until the release the mouse it acts like it is moving much slower, 
and the cursor and scrollbar move together

3. When user releases it, the cursor either continues to move at normal 
speed relative to where it was, or it may jump to an absolute position 
sending the correct enter/exit/move events. This can be chosen by the 
compositor, perhaps depending on the input device type.

Also an explanation of exactly how an FPS shooter program will work, as 
this seems to be your primary goal. It is not very clear how the game 
can request that the user be allowed to click on another client, and 
what the game has to do to regain pointer lock.

> Don't think it's very beneficial to add various kinds of locks that
> unlocks at specific events. The client can simply unlock on mouse
> button release. Not sure what round trip you are talking about here

The round trip is that after the mouse-up, the compositor cannot send 
enter/move to the surface the mouse is over until it receives the 
destruction notify from the client.

I agree this is not very important but it is how the mouse works 
normally on mouse press (it's called "grab" then, but it basically is a 
form of this pointer lock in that all events are sent to the first 
client until release).

> Do you have a suggestion for a better wording?

Make it clear that this object is not in *response* to the lock-causing 
event, but instead a setup that, I guess, a client does immediately 
after creating the surface and immediatly after any release.

Examples of the slow scrollbar and fps shooter would help a lot.

> The pointer is only potentially warped when unlocking

Your docs claim the cursor is moved when locking.

> We don't want clients to control the cursor position. If the client
> really wants to be in control, it should lock the pointer, hide the
> cursor, and then draw its own cursor sprite, either as part of its own
> scene, or as a subsurface.

That will blink unless api is added to lock a change in cursor image to 
the showing of another surface. Seems really complicated.

I also find it rather incredible that clients can assume they can change 
the cursor image, including hiding it, but are not allowed to assume 
they can move it!
On 08/12/14 08:07 AM, Jonas Ådahl wrote:
> On Tue, Dec 02, 2014 at 02:39:21PM -0800, Bill Spitzak wrote:
>> On 12/02/2014 05:49 AM, Jonas Ådahl wrote:
>>
>>> +    <request name="lock_pointer">
>>> +      <description summary="lock pointer to a position">
>>> +        The lock_pointer request lets the client disable absolute pointer
>>> +        movements, locking the pointer to a position.
>>> +
>>> +        There may not be another lock of any kind active when requesting a lock,
>>> +        and if there is, an error will be raised.
>>> +
>>> +        The intersection of the region passed with this request and the input
>>> +        region of the surface is used to determine where the pointer must be
>>> +        in order for the lock to activate. It is up to the compositor to warp
>>> +        the pointer, or require some kind of user interaction for the lock to
>>> +        activate. If the region is null, then an infinit region is used.

s/infinit/infinite/

>>> +
>>> +        The request will create a new object wl_locked_pointer which is used to
>>> +        interact with the lock as well as receive updates about its state. See
>>> +        the the description of wl_locked_pointer for further information.
>>> +
>>> +        Note that while a locked pointer doesn't move its absolute position, it
>>> +        may still emit relative motion events via the wl_relative_pointer
>>> +        object.
>>> +      </description>
>>> +
>>> +      <arg name="id" type="new_id" interface="_wl_locked_pointer"/>
>>> +      <arg name="surface" type="object" interface="wl_surface"
>>> +           summary="surface to lock pointer to"/>
>>> +      <arg name="seat" type="object" interface="wl_seat"
>>> +           summary="seat where the pointer should be locked"/>
>>> +      <arg name="region" type="object" interface="wl_region" allow-null="true"
>>> +           summary="region of surface"/>
>>> +    </request>
>>
>> - Does this need some id of the triggering event? Mostly to determine if the
>> surface had the pointer focus at the time the request was made.
> 
> It does not, and the lock is postponed until that trigger event happens.
> For example a surface can lock the pointer, but the lock may not
> activate until the user clicks the surface. It should not be hard coded
> exactly what type of event should trigger a lock.

This bit is confusing me.

A windowed game starts and asks for a pointer lock, but the window isn't
focused, so the lock is postponed...

At this point another app could request a lock and get a lock object,
which may or may not also be postponed.

However, if the first lock was already active at the time the second app
requested a lock, it wouldn't received a postponed lock object, just an
error?

So there could be a dozen postponed locks across many applications as
long as they're all set up while there isn't an active lock, and that's
ok.  But as soon as a lock triggers no new ones can be set up until it's
released?

Why the restriction?  Why can't apps set up locks at any time they want,
since they won't trigger until some time after the old lock is released
anyway?