Support axis source, axis discrete, frame and axis stop events

[jonas: only send focus wl_pointer.frame if resource supports it]

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Reviewed-by: Jonas Ådahl <jadahl@gmail.com>
Signed-off-by: Jonas Ådahl <jadahl@gmail.com>
diff --git a/src/compositor-rdp.c b/src/compositor-rdp.c
index d6d2fa1..3526ad1 100644
--- a/src/compositor-rdp.c
+++ b/src/compositor-rdp.c
@@ -70,7 +70,7 @@
 #include "pixman-renderer.h"
 
 #define MAX_FREERDP_FDS 32
-#define DEFAULT_AXIS_STEP_DISTANCE wl_fixed_from_int(10)
+#define DEFAULT_AXIS_STEP_DISTANCE 10
 #define RDP_MODE_FREQ 60 * 1000
 
 struct rdp_backend_config {
@@ -942,10 +942,11 @@
 static FREERDP_CB_RET_TYPE
 xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y)
 {
-	wl_fixed_t wl_x, wl_y, axis;
+	wl_fixed_t wl_x, wl_y;
 	RdpPeerContext *peerContext = (RdpPeerContext *)input->context;
 	struct rdp_output *output;
 	uint32_t button = 0;
+	bool need_frame = false;
 
 	if (flags & PTR_FLAGS_MOVE) {
 		output = peerContext->rdpBackend->output;
@@ -954,6 +955,7 @@
 			wl_y = wl_fixed_from_int((int)y);
 			notify_motion_absolute(&peerContext->item.seat, weston_compositor_get_time(),
 					wl_x, wl_y);
+			need_frame = true;
 		}
 	}
 
@@ -968,10 +970,12 @@
 		notify_button(&peerContext->item.seat, weston_compositor_get_time(), button,
 			(flags & PTR_FLAGS_DOWN) ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED
 		);
+		need_frame = true;
 	}
 
 	if (flags & PTR_FLAGS_WHEEL) {
 		struct weston_pointer_axis_event weston_event;
+		double value;
 
 		/* DEFAULT_AXIS_STEP_DISTANCE is stolen from compositor-x11.c
 		 * The RDP specs says the lower bits of flags contains the "the number of rotation
@@ -979,17 +983,23 @@
 		 *
 		 * https://blogs.msdn.microsoft.com/oldnewthing/20130123-00/?p=5473 explains the 120 value
 		 */
-		axis = (DEFAULT_AXIS_STEP_DISTANCE * (flags & 0xff)) / 120;
+		value = (flags & 0xff) / 120.0;
 		if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
-			axis = -axis;
+			value = -value;
 
 		weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL;
-		weston_event.value = axis;
+		weston_event.value = wl_fixed_from_double(DEFAULT_AXIS_STEP_DISTANCE * value);
+		weston_event.discrete = (int)value;
+		weston_event.has_discrete = true;
 
 		notify_axis(&peerContext->item.seat, weston_compositor_get_time(),
 			    &weston_event);
+		need_frame = true;
 	}
 
+	if (need_frame)
+		notify_pointer_frame(&peerContext->item.seat);
+
 	FREERDP_CB_RETURN(TRUE);
 }
 
diff --git a/src/compositor-wayland.c b/src/compositor-wayland.c
index 48636fe..d1c020d 100644
--- a/src/compositor-wayland.c
+++ b/src/compositor-wayland.c
@@ -185,6 +185,8 @@
 	struct wayland_output *output;
 	struct wayland_output *touch_focus;
 	struct wayland_output *keyboard_focus;
+
+	struct weston_pointer_axis_event vert, horiz;
 };
 
 struct gl_renderer_interface *gl_renderer;
@@ -1340,6 +1342,7 @@
 	struct wayland_input *input = data;
 	int32_t fx, fy;
 	enum theme_location location;
+	bool want_frame = false;
 
 	if (!input->output)
 		return;
@@ -1364,16 +1367,23 @@
 		input_set_cursor(input);
 		notify_pointer_focus(&input->base, NULL, 0, 0);
 		input->has_focus = false;
+		want_frame = true;
 	} else if (!input->has_focus &&
 		   location == THEME_LOCATION_CLIENT_AREA) {
 		wl_pointer_set_cursor(input->parent.pointer,
 				      input->enter_serial, NULL, 0, 0);
 		notify_pointer_focus(&input->base, &input->output->base, x, y);
 		input->has_focus = true;
+		want_frame = true;
 	}
 
-	if (location == THEME_LOCATION_CLIENT_AREA)
+	if (location == THEME_LOCATION_CLIENT_AREA) {
 		notify_motion_absolute(&input->base, time, x, y);
+		want_frame = true;
+	}
+
+	if (want_frame && input->seat_version < WL_POINTER_FRAME_SINCE_VERSION)
+		notify_pointer_frame(&input->base);
 }
 
 static void
@@ -1422,8 +1432,11 @@
 		location = THEME_LOCATION_CLIENT_AREA;
 	}
 
-	if (location == THEME_LOCATION_CLIENT_AREA)
+	if (location == THEME_LOCATION_CLIENT_AREA) {
 		notify_button(&input->base, time, button, state);
+		if (input->seat_version < WL_POINTER_FRAME_SINCE_VERSION)
+			notify_pointer_frame(&input->base);
+	}
 }
 
 static void
@@ -1436,7 +1449,67 @@
 	weston_event.axis = axis;
 	weston_event.value = value;
 
+	if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL &&
+	    input->vert.has_discrete) {
+		weston_event.has_discrete = true;
+		weston_event.discrete = input->vert.discrete;
+		input->vert.has_discrete = false;
+	} else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL &&
+		   input->horiz.has_discrete) {
+		weston_event.has_discrete = true;
+		weston_event.discrete = input->horiz.discrete;
+		input->horiz.has_discrete = false;
+	}
+
 	notify_axis(&input->base, time, &weston_event);
+
+	if (input->seat_version < WL_POINTER_FRAME_SINCE_VERSION)
+		notify_pointer_frame(&input->base);
+}
+
+static void
+input_handle_frame(void *data, struct wl_pointer *pointer)
+{
+	struct wayland_input *input = data;
+
+	notify_pointer_frame(&input->base);
+}
+
+static void
+input_handle_axis_source(void *data, struct wl_pointer *pointer,
+			 uint32_t source)
+{
+	struct wayland_input *input = data;
+
+	notify_axis_source(&input->base, source);
+}
+
+static void
+input_handle_axis_stop(void *data, struct wl_pointer *pointer,
+		       uint32_t time, uint32_t axis)
+{
+	struct wayland_input *input = data;
+	struct weston_pointer_axis_event weston_event;
+
+	weston_event.axis = axis;
+	weston_event.value = 0;
+
+	notify_axis(&input->base, time, &weston_event);
+}
+
+static void
+input_handle_axis_discrete(void *data, struct wl_pointer *pointer,
+			   uint32_t axis, int32_t discrete)
+{
+	struct wayland_input *input = data;
+
+	if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
+		input->vert.has_discrete = true;
+		input->vert.discrete = discrete;
+	} else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
+		input->horiz.has_discrete = true;
+		input->horiz.discrete = discrete;
+	}
 }
 
 static const struct wl_pointer_listener pointer_listener = {
@@ -1445,6 +1518,10 @@
 	input_handle_motion,
 	input_handle_button,
 	input_handle_axis,
+	input_handle_frame,
+	input_handle_axis_source,
+	input_handle_axis_stop,
+	input_handle_axis_discrete,
 };
 
 static void
@@ -1851,6 +1928,9 @@
 
 	input->parent.cursor.surface =
 		wl_compositor_create_surface(b->parent.compositor);
+
+	input->vert.axis = WL_POINTER_AXIS_VERTICAL_SCROLL;
+	input->horiz.axis = WL_POINTER_AXIS_HORIZONTAL_SCROLL;
 }
 
 static void
diff --git a/src/compositor-x11.c b/src/compositor-x11.c
index 93018da..b70c119 100644
--- a/src/compositor-x11.c
+++ b/src/compositor-x11.c
@@ -1085,6 +1085,8 @@
 		 * steps. Therefore move the axis by some pixels every step. */
 		if (state) {
 			weston_event.value = -DEFAULT_AXIS_STEP_DISTANCE;
+			weston_event.discrete = -1;
+			weston_event.has_discrete = true;
 			weston_event.axis =
 				WL_POINTER_AXIS_VERTICAL_SCROLL;
 			notify_axis(&b->core_seat,
@@ -1095,6 +1097,8 @@
 	case 5:
 		if (state) {
 			weston_event.value = DEFAULT_AXIS_STEP_DISTANCE;
+			weston_event.discrete = 1;
+			weston_event.has_discrete = true;
 			weston_event.axis =
 				WL_POINTER_AXIS_VERTICAL_SCROLL;
 			notify_axis(&b->core_seat,
@@ -1105,6 +1109,8 @@
 	case 6:
 		if (state) {
 			weston_event.value = -DEFAULT_AXIS_STEP_DISTANCE;
+			weston_event.discrete = -1;
+			weston_event.has_discrete = true;
 			weston_event.axis =
 				WL_POINTER_AXIS_HORIZONTAL_SCROLL;
 			notify_axis(&b->core_seat,
@@ -1115,6 +1121,8 @@
 	case 7:
 		if (state) {
 			weston_event.value = DEFAULT_AXIS_STEP_DISTANCE;
+			weston_event.discrete = 1;
+			weston_event.has_discrete = true;
 			weston_event.axis =
 				WL_POINTER_AXIS_HORIZONTAL_SCROLL;
 			notify_axis(&b->core_seat,
@@ -1131,6 +1139,7 @@
 		      weston_compositor_get_time(), button,
 		      state ? WL_POINTER_BUTTON_STATE_PRESSED :
 			      WL_POINTER_BUTTON_STATE_RELEASED);
+	notify_pointer_frame(&b->core_seat);
 }
 
 static void
@@ -1162,6 +1171,7 @@
 
 	notify_motion(&b->core_seat, weston_compositor_get_time(),
 		      &motion_event);
+	notify_pointer_frame(&b->core_seat);
 
 	b->prev_x = x;
 	b->prev_y = y;
diff --git a/src/compositor.h b/src/compositor.h
index ddcafc6..5970e57 100644
--- a/src/compositor.h
+++ b/src/compositor.h
@@ -254,6 +254,8 @@
 struct weston_pointer_axis_event {
 	uint32_t axis;
 	wl_fixed_t value;
+	bool has_discrete;
+	int32_t discrete;
 };
 
 struct weston_pointer_grab;
@@ -266,6 +268,8 @@
 	void (*axis)(struct weston_pointer_grab *grab,
 		     uint32_t time,
 		     struct weston_pointer_axis_event *event);
+	void (*axis_source)(struct weston_pointer_grab *grab, uint32_t source);
+	void (*frame)(struct weston_pointer_grab *grab);
 	void (*cancel)(struct weston_pointer_grab *grab);
 };
 
@@ -404,6 +408,12 @@
 			 uint32_t time,
 			 struct weston_pointer_axis_event *event);
 void
+weston_pointer_send_axis_source(struct weston_pointer *pointer,
+				uint32_t source);
+void
+weston_pointer_send_frame(struct weston_pointer *pointer);
+
+void
 weston_pointer_set_focus(struct weston_pointer *pointer,
 			 struct weston_view *view,
 			 wl_fixed_t sx, wl_fixed_t sy);
@@ -1143,6 +1153,12 @@
 notify_axis(struct weston_seat *seat, uint32_t time,
 	    struct weston_pointer_axis_event *event);
 void
+notify_axis_source(struct weston_seat *seat, uint32_t source);
+
+void
+notify_pointer_frame(struct weston_seat *seat);
+
+void
 notify_key(struct weston_seat *seat, uint32_t time, uint32_t key,
 	   enum wl_keyboard_key_state state,
 	   enum weston_key_state_update update_state);
diff --git a/src/data-device.c b/src/data-device.c
index 4a1c1b8..545a895 100644
--- a/src/data-device.c
+++ b/src/data-device.c
@@ -417,6 +417,16 @@
 }
 
 static void
+drag_grab_axis_source(struct weston_pointer_grab *grab, uint32_t source)
+{
+}
+
+static void
+drag_grab_frame(struct weston_pointer_grab *grab)
+{
+}
+
+static void
 drag_grab_cancel(struct weston_pointer_grab *grab)
 {
 	struct weston_pointer_drag *drag =
@@ -433,6 +443,8 @@
 	drag_grab_motion,
 	drag_grab_button,
 	drag_grab_axis,
+	drag_grab_axis_source,
+	drag_grab_frame,
 	drag_grab_cancel,
 };
 
diff --git a/src/input.c b/src/input.c
index 9382bb1..91813ec 100644
--- a/src/input.c
+++ b/src/input.c
@@ -343,9 +343,56 @@
 		return;
 
 	resource_list = &pointer->focus_client->pointer_resources;
+	wl_resource_for_each(resource, resource_list) {
+		if (event->has_discrete &&
+		    wl_resource_get_version(resource) >=
+		    WL_POINTER_AXIS_DISCRETE_SINCE_VERSION)
+			wl_pointer_send_axis_discrete(resource, event->axis,
+						      event->discrete);
+
+		if (event->value)
+			wl_pointer_send_axis(resource, time,
+					     event->axis, event->value);
+		else if (wl_resource_get_version(resource) >=
+			 WL_POINTER_AXIS_STOP_SINCE_VERSION)
+			wl_pointer_send_axis_stop(resource, time,
+						  event->axis);
+	}
+}
+
+WL_EXPORT void
+weston_pointer_send_axis_source(struct weston_pointer *pointer, uint32_t source)
+{
+	struct wl_resource *resource;
+	struct wl_list *resource_list;
+
+	resource_list = &pointer->focus_client->pointer_resources;
+	wl_resource_for_each(resource, resource_list) {
+		if (wl_resource_get_version(resource) >=
+		    WL_POINTER_AXIS_SOURCE_SINCE_VERSION) {
+			wl_pointer_send_axis_source(resource, source);
+		}
+	}
+}
+
+static void
+pointer_send_frame(struct wl_resource *resource)
+{
+	if (wl_resource_get_version(resource) >=
+	    WL_POINTER_FRAME_SINCE_VERSION) {
+		wl_pointer_send_frame(resource);
+	}
+}
+
+WL_EXPORT void
+weston_pointer_send_frame(struct weston_pointer *pointer)
+{
+	struct wl_resource *resource;
+	struct wl_list *resource_list;
+
+	resource_list = &pointer->focus_client->pointer_resources;
 	wl_resource_for_each(resource, resource_list)
-		wl_pointer_send_axis(resource, time,
-				     event->axis, event->value);
+		pointer_send_frame(resource);
 }
 
 static void
@@ -357,6 +404,19 @@
 }
 
 static void
+default_grab_pointer_axis_source(struct weston_pointer_grab *grab,
+				 uint32_t source)
+{
+	weston_pointer_send_axis_source(grab->pointer, source);
+}
+
+static void
+default_grab_pointer_frame(struct weston_pointer_grab *grab)
+{
+	weston_pointer_send_frame(grab->pointer);
+}
+
+static void
 default_grab_pointer_cancel(struct weston_pointer_grab *grab)
 {
 }
@@ -367,6 +427,8 @@
 	default_grab_pointer_motion,
 	default_grab_pointer_button,
 	default_grab_pointer_axis,
+	default_grab_pointer_axis_source,
+	default_grab_pointer_frame,
 	default_grab_pointer_cancel,
 };
 
@@ -830,6 +892,7 @@
 			wl_resource_for_each(resource, focus_resource_list) {
 				wl_pointer_send_leave(resource, serial,
 						      surface_resource);
+				pointer_send_frame(resource);
 			}
 		}
 
@@ -856,6 +919,7 @@
 					      serial,
 					      view->surface->resource,
 					      sx, sy);
+			pointer_send_frame(resource);
 		}
 
 		pointer->focus_serial = serial;
@@ -1277,9 +1341,6 @@
 
 	weston_compositor_wake(compositor);
 
-	if (!event->value)
-		return;
-
 	if (weston_compositor_run_axis_binding(compositor, pointer,
 					       time, event))
 		return;
@@ -1287,6 +1348,28 @@
 	pointer->grab->interface->axis(pointer->grab, time, event);
 }
 
+WL_EXPORT void
+notify_axis_source(struct weston_seat *seat, uint32_t source)
+{
+	struct weston_compositor *compositor = seat->compositor;
+	struct weston_pointer *pointer = weston_seat_get_pointer(seat);
+
+	weston_compositor_wake(compositor);
+
+	pointer->grab->interface->axis_source(pointer->grab, source);
+}
+
+WL_EXPORT void
+notify_pointer_frame(struct weston_seat *seat)
+{
+	struct weston_compositor *compositor = seat->compositor;
+	struct weston_pointer *pointer = weston_seat_get_pointer(seat);
+
+	weston_compositor_wake(compositor);
+
+	pointer->grab->interface->frame(pointer->grab);
+}
+
 WL_EXPORT int
 weston_keyboard_set_locks(struct weston_keyboard *keyboard,
 			  uint32_t mask, uint32_t value)
@@ -1979,6 +2062,7 @@
 				      pointer->focus_serial,
 				      pointer->focus->surface->resource,
 				      sx, sy);
+		pointer_send_frame(cr);
 	}
 }
 
@@ -2557,7 +2641,7 @@
 	wl_signal_init(&seat->destroy_signal);
 	wl_signal_init(&seat->updated_caps_signal);
 
-	seat->global = wl_global_create(ec->wl_display, &wl_seat_interface, 4,
+	seat->global = wl_global_create(ec->wl_display, &wl_seat_interface, 5,
 					seat, bind_seat);
 
 	seat->compositor = ec;
diff --git a/src/libinput-device.c b/src/libinput-device.c
index 9860d6e..99b2916 100644
--- a/src/libinput-device.c
+++ b/src/libinput-device.c
@@ -80,7 +80,7 @@
 		   STATE_UPDATE_AUTOMATIC);
 }
 
-static void
+static bool
 handle_pointer_motion(struct libinput_device *libinput_device,
 		      struct libinput_event_pointer *pointer_event)
 {
@@ -97,9 +97,11 @@
 	notify_motion(device->seat,
 		      libinput_event_pointer_get_time(pointer_event),
 		      &event);
+
+	return true;
 }
 
-static void
+static bool
 handle_pointer_motion_absolute(
 	struct libinput_device *libinput_device,
 	struct libinput_event_pointer *pointer_event)
@@ -112,7 +114,7 @@
 	uint32_t width, height;
 
 	if (!output)
-		return;
+		return false;
 
 	time = libinput_event_pointer_get_time(pointer_event);
 	width = device->output->current_mode->width;
@@ -127,9 +129,11 @@
 
 	weston_output_transform_coordinate(device->output, x, y, &x, &y);
 	notify_motion_absolute(device->seat, time, x, y);
+
+	return true;
 }
 
-static void
+static bool
 handle_pointer_button(struct libinput_device *libinput_device,
 		      struct libinput_event_pointer *pointer_event)
 {
@@ -145,21 +149,21 @@
 	     seat_button_count != 1) ||
 	    (button_state == LIBINPUT_BUTTON_STATE_RELEASED &&
 	     seat_button_count != 0))
-		return;
+		return false;
 
 	notify_button(device->seat,
 		      libinput_event_pointer_get_time(pointer_event),
 		      libinput_event_pointer_get_button(pointer_event),
 		      libinput_event_pointer_get_button_state(pointer_event));
+	return true;
 }
 
 static double
 normalize_scroll(struct libinput_event_pointer *pointer_event,
 		 enum libinput_pointer_axis axis)
 {
-	static int warned;
 	enum libinput_pointer_axis_source source;
-	double value;
+	double value = 0.0;
 
 	source = libinput_event_pointer_get_axis_source(pointer_event);
 	/* libinput < 0.8 sent wheel click events with value 10. Since 0.8
@@ -178,48 +182,101 @@
 		value = libinput_event_pointer_get_axis_value(pointer_event,
 							      axis);
 		break;
-	default:
-		value = 0;
-		if (warned < 5) {
-			weston_log("Unknown scroll source %d. Event discarded\n",
-				   source);
-			warned++;
-		}
-		break;
 	}
 
 	return value;
 }
 
-static void
+static int32_t
+get_axis_discrete(struct libinput_event_pointer *pointer_event,
+		  enum libinput_pointer_axis axis)
+{
+	enum libinput_pointer_axis_source source;
+
+	source = libinput_event_pointer_get_axis_source(pointer_event);
+
+	if (source != LIBINPUT_POINTER_AXIS_SOURCE_WHEEL)
+		return 0;
+
+	return libinput_event_pointer_get_axis_value_discrete(pointer_event,
+							      axis);
+}
+
+static bool
 handle_pointer_axis(struct libinput_device *libinput_device,
 		    struct libinput_event_pointer *pointer_event)
 {
+	static int warned;
 	struct evdev_device *device =
 		libinput_device_get_user_data(libinput_device);
-	double value;
+	double vert, horiz;
+	int32_t vert_discrete, horiz_discrete;
 	enum libinput_pointer_axis axis;
 	struct weston_pointer_axis_event weston_event;
+	enum libinput_pointer_axis_source source;
+	uint32_t wl_axis_source;
+	bool has_vert, has_horiz;
 
-	axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
-	if (libinput_event_pointer_has_axis(pointer_event, axis)) {
-		value = normalize_scroll(pointer_event, axis);
-		weston_event.value = wl_fixed_from_double(value);
+	has_vert = libinput_event_pointer_has_axis(pointer_event,
+				   LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+	has_horiz = libinput_event_pointer_has_axis(pointer_event,
+				   LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
+
+	if (!has_vert && !has_horiz)
+		return false;
+
+	source = libinput_event_pointer_get_axis_source(pointer_event);
+	switch (source) {
+	case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL:
+		wl_axis_source = WL_POINTER_AXIS_SOURCE_WHEEL;
+		break;
+	case LIBINPUT_POINTER_AXIS_SOURCE_FINGER:
+		wl_axis_source = WL_POINTER_AXIS_SOURCE_FINGER;
+		break;
+	case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS:
+		wl_axis_source = WL_POINTER_AXIS_SOURCE_CONTINUOUS;
+		break;
+	default:
+		if (warned < 5) {
+			weston_log("Unknown scroll source %d.\n", source);
+			warned++;
+		}
+		return false;
+	}
+
+	notify_axis_source(device->seat, wl_axis_source);
+
+	if (has_vert) {
+		axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
+		vert_discrete = get_axis_discrete(pointer_event, axis);
+		vert = normalize_scroll(pointer_event, axis);
+
 		weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL;
+		weston_event.value = wl_fixed_from_double(vert);
+		weston_event.discrete = vert_discrete;
+		weston_event.has_discrete = (vert_discrete != 0);
+
 		notify_axis(device->seat,
 			    libinput_event_pointer_get_time(pointer_event),
 			    &weston_event);
 	}
 
-	axis = LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL;
-	if (libinput_event_pointer_has_axis(pointer_event, axis)) {
-		value = normalize_scroll(pointer_event, axis);
-		weston_event.value = wl_fixed_from_double(value);
+	if (has_horiz) {
+		axis = LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL;
+		horiz_discrete = get_axis_discrete(pointer_event, axis);
+		horiz = normalize_scroll(pointer_event, axis);
+
 		weston_event.axis = WL_POINTER_AXIS_HORIZONTAL_SCROLL;
+		weston_event.value = wl_fixed_from_double(horiz);
+		weston_event.discrete = horiz_discrete;
+		weston_event.has_discrete = (horiz_discrete != 0);
+
 		notify_axis(device->seat,
 			    libinput_event_pointer_get_time(pointer_event),
 			    &weston_event);
 	}
+
+	return true;
 }
 
 static void
@@ -296,7 +353,10 @@
 {
 	struct libinput_device *libinput_device =
 		libinput_event_get_device(event);
+	struct evdev_device *device =
+		libinput_device_get_user_data(libinput_device);
 	int handled = 1;
+	bool need_frame = false;
 
 	switch (libinput_event_get_type(event)) {
 	case LIBINPUT_EVENT_KEYBOARD_KEY:
@@ -304,21 +364,22 @@
 				    libinput_event_get_keyboard_event(event));
 		break;
 	case LIBINPUT_EVENT_POINTER_MOTION:
-		handle_pointer_motion(libinput_device,
+		need_frame = handle_pointer_motion(libinput_device,
 				      libinput_event_get_pointer_event(event));
 		break;
 	case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
-		handle_pointer_motion_absolute(
-			libinput_device,
-			libinput_event_get_pointer_event(event));
+		need_frame = handle_pointer_motion_absolute(
+				libinput_device,
+				libinput_event_get_pointer_event(event));
 		break;
 	case LIBINPUT_EVENT_POINTER_BUTTON:
-		handle_pointer_button(libinput_device,
+		need_frame = handle_pointer_button(libinput_device,
 				      libinput_event_get_pointer_event(event));
 		break;
 	case LIBINPUT_EVENT_POINTER_AXIS:
-		handle_pointer_axis(libinput_device,
-				    libinput_event_get_pointer_event(event));
+		need_frame = handle_pointer_axis(
+				 libinput_device,
+				 libinput_event_get_pointer_event(event));
 		break;
 	case LIBINPUT_EVENT_TOUCH_DOWN:
 		handle_touch_down(libinput_device,
@@ -342,6 +403,9 @@
 			   libinput_event_get_type(event));
 	}
 
+	if (need_frame)
+		notify_pointer_frame(device->seat);
+
 	return handled;
 }
 
diff --git a/src/screen-share.c b/src/screen-share.c
index ab649e3..9b5154b 100644
--- a/src/screen-share.c
+++ b/src/screen-share.c
@@ -143,6 +143,7 @@
 	 * always receiving the input in the same coordinates as the output. */
 
 	notify_motion_absolute(&seat->base, time, x, y);
+	notify_pointer_frame(&seat->base);
 }
 
 static void
@@ -153,6 +154,7 @@
 	struct ss_seat *seat = data;
 
 	notify_button(&seat->base, time, button, state);
+	notify_pointer_frame(&seat->base);
 }
 
 static void
@@ -164,8 +166,10 @@
 
 	weston_event.axis = axis;
 	weston_event.value = value;
+	weston_event.has_discrete = false;
 
 	notify_axis(&seat->base, time, &weston_event);
+	notify_pointer_frame(&seat->base);
 }
 
 static const struct wl_pointer_listener ss_seat_pointer_listener = {