[PATCH v2 weston] Support axis source, axis discrete, axis frame and axis stop events

Peter Hutterer peter.hutterer at who-t.net
Sun Nov 15 23:51:50 PST 2015


On Mon, Nov 16, 2015 at 03:38:13PM +0800, Jonas Ã…dahl wrote:
> Hey,
> 
> I took a look at how the currently internal, but to be made external,
> API looks like, and I wonder if it really should be this low level.
> 
> For the unaccelerated/accelerated/relative/absolute pointer events, I
> made an event struct that is passed around and input.c takes that one
> and emits the correct wl_pointer events, and I wonder that, now that
> axis events are as or even more complex, whether we should do so for
> axis events now. In other words, introducing a
> weston_pointer_axis_event struct that contains the relevant data and
> then change weston_pointer_send_axis() to take such an event?
> 
> Everywhere we send button, motion, enter, leave etc we would then just
> use helpers implemented in input.c that emit frame events accordingly.

yeah, that works for me, and something along those lines is needed for
tablet events anyway (not happy with the current code) so I guess that
approach would make it more consistent then.

Cheers,
   Peter
> 
> 
> Jonas
> 
> On Mon, Nov 16, 2015 at 02:50:19PM +1000, Peter Hutterer wrote:
> > Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
> > ---
> > This one goes with v6 of the protocol, see
> > http://patchwork.freedesktop.org/patch/64871/
> > 
> > Changes to v1:
> > - revamped for wl_pointer.frame (instead of axis frame)
> > - added the hooks for the tests
> > - rebased on master, added the new pointer axis grab handling bits
> > - re-did the pointer event handling so the frame is only sent when we're
> >   sending at least one pointer event to the client
> > 
> >  clients/eventdemo.c               |  57 ++++++++++++++-
> >  clients/window.c                  |  97 +++++++++++++++++++++++++-
> >  clients/window.h                  |  31 +++++++++
> >  desktop-shell/exposay.c           |  19 +++++
> >  desktop-shell/shell.c             |  51 ++++++++++++++
> >  ivi-shell/hmi-controller.c        |  23 ++++++
> >  src/compositor-wayland.c          |  39 +++++++++++
> >  src/compositor-x11.c              |  29 ++++++--
> >  src/compositor.h                  |  24 +++++++
> >  src/data-device.c                 |  19 +++++
> >  src/input.c                       | 120 ++++++++++++++++++++++++++++++--
> >  src/libinput-device.c             | 142 +++++++++++++++++++++++++++++---------
> >  tests/weston-test-client-helper.c |  32 +++++++++
> >  13 files changed, 639 insertions(+), 44 deletions(-)
> > 
> > diff --git a/clients/eventdemo.c b/clients/eventdemo.c
> > index bdad6fd..e323aa5 100644
> > --- a/clients/eventdemo.c
> > +++ b/clients/eventdemo.c
> > @@ -259,6 +259,54 @@ axis_handler(struct widget *widget, struct input *input, uint32_t time,
> >  	       wl_fixed_to_double(value));
> >  }
> >  
> > +static void
> > +pointer_frame_handler(struct widget *widget, struct input *input, void *data)
> > +{
> > +	printf("pointer frame\n");
> > +}
> > +
> > +static void
> > +axis_source_handler(struct widget *widget, struct input *input,
> > +		    uint32_t source, void *data)
> > +{
> > +	const char *axis_source;
> > +
> > +	switch (source) {
> > +	case WL_POINTER_AXIS_SOURCE_WHEEL:
> > +		axis_source = "wheel";
> > +		break;
> > +	case WL_POINTER_AXIS_SOURCE_FINGER:
> > +		axis_source = "finger";
> > +		break;
> > +	case WL_POINTER_AXIS_SOURCE_CONTINUOUS:
> > +		axis_source = "continuous";
> > +		break;
> > +	default:
> > +		axis_source = "<invalid source value>";
> > +		break;
> > +	}
> > +
> > +	printf("axis source: %s\n", axis_source);
> > +}
> > +
> > +static void
> > +axis_stop_handler(struct widget *widget, struct input *input,
> > +		  uint32_t time, uint32_t axis,
> > +		  void *data)
> > +{
> > +	printf("axis stop time: %d, axis: %s\n",
> > +	       time,
> > +	       axis == WL_POINTER_AXIS_VERTICAL_SCROLL ? "vertical" :
> > +							 "horizontal");
> > +}
> > +
> > +static void
> > +axis_discrete_handler(struct widget *widget, struct input *input,
> > +		      uint32_t axis, int32_t discrete, void *data)
> > +{
> > +	printf("axis discrete axis: %d value: %d\n", axis, discrete);
> > +}
> > +
> >  /**
> >   * \brief CALLBACK function, Waylands informs about pointer motion
> >   * \param widget widget
> > @@ -347,8 +395,15 @@ eventdemo_create(struct display *d)
> >  	/* Set the callback motion handler for the window */
> >  	widget_set_motion_handler(e->widget, motion_handler);
> >  
> > +	/* Set the callback pointer frame handler for the window */
> > +	widget_set_pointer_frame_handler(e->widget, pointer_frame_handler);
> > +
> >  	/* Set the callback axis handler for the window */
> > -	widget_set_axis_handler(e->widget, axis_handler);
> > +	widget_set_axis_handlers(e->widget,
> > +				 axis_handler,
> > +				 axis_source_handler,
> > +				 axis_stop_handler,
> > +				 axis_discrete_handler);
> >  
> >  	/* Initial drawing of the window */
> >  	window_schedule_resize(e->window, width, height);
> > diff --git a/clients/window.c b/clients/window.c
> > index 47628de..5197e94 100644
> > --- a/clients/window.c
> > +++ b/clients/window.c
> > @@ -287,6 +287,10 @@ struct widget {
> >  	widget_touch_frame_handler_t touch_frame_handler;
> >  	widget_touch_cancel_handler_t touch_cancel_handler;
> >  	widget_axis_handler_t axis_handler;
> > +	widget_pointer_frame_handler_t pointer_frame_handler;
> > +	widget_axis_source_handler_t axis_source_handler;
> > +	widget_axis_stop_handler_t axis_stop_handler;
> > +	widget_axis_discrete_handler_t axis_discrete_handler;
> >  	void *user_data;
> >  	int opaque;
> >  	int tooltip_count;
> > @@ -1935,6 +1939,26 @@ widget_set_axis_handler(struct widget *widget,
> >  	widget->axis_handler = handler;
> >  }
> >  
> > +void
> > +widget_set_pointer_frame_handler(struct widget *widget,
> > +				 widget_pointer_frame_handler_t handler)
> > +{
> > +	widget->pointer_frame_handler = handler;
> > +}
> > +
> > +void
> > +widget_set_axis_handlers(struct widget *widget,
> > +			widget_axis_handler_t axis_handler,
> > +			widget_axis_source_handler_t axis_source_handler,
> > +			widget_axis_stop_handler_t axis_stop_handler,
> > +			widget_axis_discrete_handler_t axis_discrete_handler)
> > +{
> > +	widget->axis_handler = axis_handler;
> > +	widget->axis_source_handler = axis_source_handler;
> > +	widget->axis_stop_handler = axis_stop_handler;
> > +	widget->axis_discrete_handler = axis_discrete_handler;
> > +}
> > +
> >  static void
> >  window_schedule_redraw_task(struct window *window);
> >  
> > @@ -2819,12 +2843,83 @@ pointer_handle_axis(void *data, struct wl_pointer *pointer,
> >  					widget->user_data);
> >  }
> >  
> > +static void
> > +pointer_handle_frame(void *data, struct wl_pointer *pointer)
> > +{
> > +	struct input *input = data;
> > +	struct widget *widget;
> > +
> > +	widget = input->focus_widget;
> > +	if (input->grab)
> > +		widget = input->grab;
> > +	if (widget && widget->pointer_frame_handler)
> > +		(*widget->pointer_frame_handler)(widget,
> > +						 input,
> > +						 widget->user_data);
> > +}
> > +
> > +static void
> > +pointer_handle_axis_source(void *data, struct wl_pointer *pointer,
> > +			   uint32_t source)
> > +{
> > +	struct input *input = data;
> > +	struct widget *widget;
> > +
> > +	widget = input->focus_widget;
> > +	if (input->grab)
> > +		widget = input->grab;
> > +	if (widget && widget->axis_source_handler)
> > +		(*widget->axis_source_handler)(widget,
> > +					       input,
> > +					       source,
> > +					       widget->user_data);
> > +}
> > +
> > +static void
> > +pointer_handle_axis_stop(void *data, struct wl_pointer *pointer,
> > +			 uint32_t time, uint32_t axis)
> > +{
> > +	struct input *input = data;
> > +	struct widget *widget;
> > +
> > +	widget = input->focus_widget;
> > +	if (input->grab)
> > +		widget = input->grab;
> > +	if (widget && widget->axis_stop_handler)
> > +		(*widget->axis_stop_handler)(widget,
> > +					     input, time,
> > +					     axis,
> > +					     widget->user_data);
> > +}
> > +
> > +static void
> > +pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer,
> > +			     uint32_t axis, int32_t discrete)
> > +{
> > +	struct input *input = data;
> > +	struct widget *widget;
> > +
> > +	widget = input->focus_widget;
> > +	if (input->grab)
> > +		widget = input->grab;
> > +	if (widget && widget->axis_discrete_handler)
> > +		(*widget->axis_discrete_handler)(widget,
> > +						 input,
> > +						 axis,
> > +						 discrete,
> > +						 widget->user_data);
> > +}
> > +
> >  static const struct wl_pointer_listener pointer_listener = {
> >  	pointer_handle_enter,
> >  	pointer_handle_leave,
> >  	pointer_handle_motion,
> >  	pointer_handle_button,
> >  	pointer_handle_axis,
> > +	pointer_handle_frame,
> > +	pointer_handle_axis_source,
> > +	pointer_handle_axis_stop,
> > +	pointer_handle_axis_discrete,
> >  };
> >  
> >  static void
> > @@ -5224,7 +5319,7 @@ static void
> >  display_add_input(struct display *d, uint32_t id, int display_seat_version)
> >  {
> >  	struct input *input;
> > -	int seat_version = MIN(display_seat_version, 4);
> > +	int seat_version = MIN(display_seat_version, 5);
> >  
> >  	input = xzalloc(sizeof *input);
> >  	input->display = d;
> > diff --git a/clients/window.h b/clients/window.h
> > index b61a62a..17bcd20 100644
> > --- a/clients/window.h
> > +++ b/clients/window.h
> > @@ -267,6 +267,27 @@ typedef void (*widget_axis_handler_t)(struct widget *widget,
> >  				      wl_fixed_t value,
> >  				      void *data);
> >  
> > +typedef void (*widget_pointer_frame_handler_t)(struct widget *widget,
> > +					       struct input *input,
> > +					       void *data);
> > +
> > +typedef void (*widget_axis_source_handler_t)(struct widget *widget,
> > +					     struct input *input,
> > +					     uint32_t source,
> > +					     void *data);
> > +
> > +typedef void (*widget_axis_stop_handler_t)(struct widget *widget,
> > +					   struct input *input,
> > +					   uint32_t time,
> > +					   uint32_t axis,
> > +					   void *data);
> > +
> > +typedef void (*widget_axis_discrete_handler_t)(struct widget *widget,
> > +					       struct input *input,
> > +					       uint32_t axis,
> > +					       int32_t discrete,
> > +					       void *data);
> > +
> >  struct window *
> >  window_create(struct display *display);
> >  struct window *
> > @@ -520,6 +541,16 @@ void
> >  widget_set_axis_handler(struct widget *widget,
> >  			widget_axis_handler_t handler);
> >  void
> > +widget_set_pointer_frame_handler(struct widget *widget,
> > +				 widget_pointer_frame_handler_t handler);
> > +void
> > +widget_set_axis_handlers(struct widget *widget,
> > +			widget_axis_handler_t axis_handler,
> > +			widget_axis_source_handler_t axis_source_handler,
> > +			widget_axis_stop_handler_t axis_stop_handler,
> > +			widget_axis_discrete_handler_t axis_discrete_handler);
> > +
> > +void
> >  widget_schedule_redraw(struct widget *widget);
> >  void
> >  widget_set_use_cairo(struct widget *widget, int use_cairo);
> > diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c
> > index eb4070e..1bb6ebc 100644
> > --- a/desktop-shell/exposay.c
> > +++ b/desktop-shell/exposay.c
> > @@ -392,6 +392,22 @@ exposay_axis(struct weston_pointer_grab *grab,
> >  }
> >  
> >  static void
> > +exposay_axis_discrete(struct weston_pointer_grab *grab,
> > +		      uint32_t axis, wl_fixed_t value)
> > +{
> > +}
> > +
> > +static void
> > +exposay_axis_source(struct weston_pointer_grab *grab, uint32_t source)
> > +{
> > +}
> > +
> > +static void
> > +exposay_frame(struct weston_pointer_grab *grab)
> > +{
> > +}
> > +
> > +static void
> >  exposay_pointer_grab_cancel(struct weston_pointer_grab *grab)
> >  {
> >  	struct desktop_shell *shell =
> > @@ -405,6 +421,9 @@ static const struct weston_pointer_grab_interface exposay_ptr_grab = {
> >  	exposay_motion,
> >  	exposay_button,
> >  	exposay_axis,
> > +	exposay_axis_discrete,
> > +	exposay_axis_source,
> > +	exposay_frame,
> >  	exposay_pointer_grab_cancel,
> >  };
> >  
> > diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c
> > index 9968307..9075039 100644
> > --- a/desktop-shell/shell.c
> > +++ b/desktop-shell/shell.c
> > @@ -1707,6 +1707,23 @@ noop_grab_axis(struct weston_pointer_grab *grab,
> >  }
> >  
> >  static void
> > +noop_grab_axis_discrete(struct weston_pointer_grab *grab,
> > +			uint32_t axis, wl_fixed_t value)
> > +{
> > +}
> > +
> > +static void
> > +noop_grab_axis_source(struct weston_pointer_grab *grab,
> > +		      uint32_t source)
> > +{
> > +}
> > +
> > +static void
> > +noop_grab_frame(struct weston_pointer_grab *grab)
> > +{
> > +}
> > +
> > +static void
> >  constrain_position(struct weston_move_grab *move, int *cx, int *cy)
> >  {
> >  	struct shell_surface *shsurf = move->base.shsurf;
> > @@ -1789,6 +1806,9 @@ static const struct weston_pointer_grab_interface move_grab_interface = {
> >  	move_grab_motion,
> >  	move_grab_button,
> >  	noop_grab_axis,
> > +	noop_grab_axis_discrete,
> > +	noop_grab_axis_source,
> > +	noop_grab_frame,
> >  	move_grab_cancel,
> >  };
> >  
> > @@ -1953,6 +1973,9 @@ static const struct weston_pointer_grab_interface resize_grab_interface = {
> >  	resize_grab_motion,
> >  	resize_grab_button,
> >  	noop_grab_axis,
> > +	noop_grab_axis_discrete,
> > +	noop_grab_axis_source,
> > +	noop_grab_frame,
> >  	resize_grab_cancel,
> >  };
> >  
> > @@ -2118,6 +2141,9 @@ static const struct weston_pointer_grab_interface busy_cursor_grab_interface = {
> >  	busy_cursor_grab_motion,
> >  	busy_cursor_grab_button,
> >  	noop_grab_axis,
> > +	noop_grab_axis_discrete,
> > +	noop_grab_axis_source,
> > +	noop_grab_frame,
> >  	busy_cursor_grab_cancel,
> >  };
> >  
> > @@ -3307,6 +3333,25 @@ popup_grab_axis(struct weston_pointer_grab *grab,
> >  }
> >  
> >  static void
> > +popup_grab_axis_discrete(struct weston_pointer_grab *grab,
> > +			 uint32_t axis, int32_t value)
> > +{
> > +	weston_pointer_send_axis_discrete(grab->pointer, axis, value);
> > +}
> > +
> > +static void
> > +popup_grab_axis_source(struct weston_pointer_grab *grab, uint32_t source)
> > +{
> > +	weston_pointer_send_axis_source(grab->pointer, source);
> > +}
> > +
> > +static void
> > +popup_grab_frame(struct weston_pointer_grab *grab)
> > +{
> > +	weston_pointer_send_frame(grab->pointer);
> > +}
> > +
> > +static void
> >  popup_grab_cancel(struct weston_pointer_grab *grab)
> >  {
> >  	popup_grab_end(grab->pointer);
> > @@ -3317,6 +3362,9 @@ static const struct weston_pointer_grab_interface popup_grab_interface = {
> >  	popup_grab_motion,
> >  	popup_grab_button,
> >  	popup_grab_axis,
> > +	popup_grab_axis_discrete,
> > +	popup_grab_axis_source,
> > +	popup_grab_frame,
> >  	popup_grab_cancel,
> >  };
> >  
> > @@ -5018,6 +5066,9 @@ static const struct weston_pointer_grab_interface rotate_grab_interface = {
> >  	rotate_grab_motion,
> >  	rotate_grab_button,
> >  	noop_grab_axis,
> > +	noop_grab_axis_discrete,
> > +	noop_grab_axis_source,
> > +	noop_grab_frame,
> >  	rotate_grab_cancel,
> >  };
> >  
> > diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c
> > index 633ea19..433caac 100644
> > --- a/ivi-shell/hmi-controller.c
> > +++ b/ivi-shell/hmi-controller.c
> > @@ -1330,6 +1330,26 @@ pointer_default_grab_axis(struct weston_pointer_grab *grab,
> >  }
> >  
> >  static void
> > +pointer_default_grab_axis_discrete(struct weston_pointer_grab *grab,
> > +				   uint32_t axis, int32_t value)
> > +{
> > +	weston_pointer_send_axis_discrete(grab->pointer, axis, value);
> > +}
> > +
> > +static void
> > +pointer_default_grab_axis_source(struct weston_pointer_grab *grab,
> > +				 uint32_t source)
> > +{
> > +	weston_pointer_send_axis_source(grab->pointer, source);
> > +}
> > +
> > +static void
> > +pointer_default_grab_frame(struct weston_pointer_grab *grab)
> > +{
> > +	weston_pointer_send_frame(grab->pointer);
> > +}
> > +
> > +static void
> >  move_grab_update(struct move_grab *move, wl_fixed_t pointer[2])
> >  {
> >  	struct timespec timestamp = {0};
> > @@ -1474,6 +1494,9 @@ static const struct weston_pointer_grab_interface pointer_move_grab_workspace_in
> >  	pointer_move_grab_motion,
> >  	pointer_move_workspace_grab_button,
> >  	pointer_default_grab_axis,
> > +	pointer_default_grab_axis_discrete,
> > +	pointer_default_grab_axis_source,
> > +	pointer_default_grab_frame,
> >  	pointer_move_workspace_grab_cancel
> >  };
> >  
> > diff --git a/src/compositor-wayland.c b/src/compositor-wayland.c
> > index a819867..f2c5251 100644
> > --- a/src/compositor-wayland.c
> > +++ b/src/compositor-wayland.c
> > @@ -1429,12 +1429,51 @@ input_handle_axis(void *data, struct wl_pointer *pointer,
> >  	notify_axis(&input->base, time, axis, value);
> >  }
> >  
> > +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;
> > +
> > +	notify_axis(&input->base, time, axis, 0);
> > +}
> > +
> > +static void
> > +input_handle_axis_discrete(void *data, struct wl_pointer *pointer,
> > +			   uint32_t axis, int32_t discrete)
> > +{
> > +	struct wayland_input *input = data;
> > +
> > +	notify_axis_discrete(&input->base, axis, discrete);
> > +}
> > +
> >  static const struct wl_pointer_listener pointer_listener = {
> >  	input_handle_pointer_enter,
> >  	input_handle_pointer_leave,
> >  	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
> > diff --git a/src/compositor-x11.c b/src/compositor-x11.c
> > index ccb7867..b399a35 100644
> > --- a/src/compositor-x11.c
> > +++ b/src/compositor-x11.c
> > @@ -1082,32 +1082,48 @@ x11_backend_deliver_button_event(struct x11_backend *b,
> >  	case 4:
> >  		/* Axis are measured in pixels, but the xcb events are discrete
> >  		 * steps. Therefore move the axis by some pixels every step. */
> > -		if (state)
> > +		if (state) {
> > +			notify_axis_discrete(&b->core_seat,
> > +					     WL_POINTER_AXIS_VERTICAL_SCROLL,
> > +					     -1);
> >  			notify_axis(&b->core_seat,
> >  				    weston_compositor_get_time(),
> >  				    WL_POINTER_AXIS_VERTICAL_SCROLL,
> >  				    -DEFAULT_AXIS_STEP_DISTANCE);
> > +		}
> >  		return;
> >  	case 5:
> > -		if (state)
> > +		if (state) {
> > +			notify_axis_discrete(&b->core_seat,
> > +					     WL_POINTER_AXIS_VERTICAL_SCROLL,
> > +					     1);
> >  			notify_axis(&b->core_seat,
> >  				    weston_compositor_get_time(),
> >  				    WL_POINTER_AXIS_VERTICAL_SCROLL,
> >  				    DEFAULT_AXIS_STEP_DISTANCE);
> > +		}
> >  		return;
> >  	case 6:
> > -		if (state)
> > +		if (state) {
> > +			notify_axis_discrete(&b->core_seat,
> > +					     WL_POINTER_AXIS_HORIZONTAL_SCROLL,
> > +					     -1);
> >  			notify_axis(&b->core_seat,
> >  				    weston_compositor_get_time(),
> >  				    WL_POINTER_AXIS_HORIZONTAL_SCROLL,
> >  				    -DEFAULT_AXIS_STEP_DISTANCE);
> > +		}
> >  		return;
> >  	case 7:
> > -		if (state)
> > +		if (state) {
> > +			notify_axis_discrete(&b->core_seat,
> > +					     WL_POINTER_AXIS_HORIZONTAL_SCROLL,
> > +					     1);
> >  			notify_axis(&b->core_seat,
> >  				    weston_compositor_get_time(),
> >  				    WL_POINTER_AXIS_HORIZONTAL_SCROLL,
> >  				    DEFAULT_AXIS_STEP_DISTANCE);
> > +		}
> >  		return;
> >  	default:
> >  		button = button_event->detail + BTN_SIDE - 8;
> > @@ -1118,6 +1134,7 @@ x11_backend_deliver_button_event(struct x11_backend *b,
> >  		      weston_compositor_get_time(), button,
> >  		      state ? WL_POINTER_BUTTON_STATE_PRESSED :
> >  			      WL_POINTER_BUTTON_STATE_RELEASED);
> > +	notify_pointer_frame(&b->core_seat);
> >  }
> >  
> >  static void
> > @@ -1142,6 +1159,7 @@ x11_backend_deliver_motion_event(struct x11_backend *b,
> >  
> >  	notify_motion(&b->core_seat, weston_compositor_get_time(),
> >  		      x - b->prev_x, y - b->prev_y);
> > +	notify_pointer_frame(&b->core_seat);
> >  
> >  	b->prev_x = x;
> >  	b->prev_y = y;
> > @@ -1169,6 +1187,7 @@ x11_backend_deliver_enter_event(struct x11_backend *b,
> >  					   wl_fixed_from_int(enter_notify->event_y), &x, &y);
> >  
> >  	notify_pointer_focus(&b->core_seat, &output->base, x, y);
> > +	notify_pointer_frame(&b->core_seat);
> >  
> >  	b->prev_x = x;
> >  	b->prev_y = y;
> > @@ -1322,6 +1341,7 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data)
> >  
> >  		case XCB_ENTER_NOTIFY:
> >  			x11_backend_deliver_enter_event(b, event);
> > +			notify_pointer_frame(&b->core_seat);
> >  			break;
> >  
> >  		case XCB_LEAVE_NOTIFY:
> > @@ -1331,6 +1351,7 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data)
> >  			if (!b->has_xkb)
> >  				update_xkb_state_from_core(b, enter_notify->state);
> >  			notify_pointer_focus(&b->core_seat, NULL, 0, 0);
> > +			notify_pointer_frame(&b->core_seat);
> >  			break;
> >  
> >  		case XCB_CLIENT_MESSAGE:
> > diff --git a/src/compositor.h b/src/compositor.h
> > index f3e0075..e9c6daf 100644
> > --- a/src/compositor.h
> > +++ b/src/compositor.h
> > @@ -249,6 +249,10 @@ struct weston_pointer_grab_interface {
> >  		       uint32_t time, uint32_t button, uint32_t state);
> >  	void (*axis)(struct weston_pointer_grab *grab,
> >  		     uint32_t time, uint32_t axis, wl_fixed_t value);
> > +	void (*axis_discrete)(struct weston_pointer_grab *grab,
> > +			      uint32_t axis, int32_t value);
> > +	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);
> >  };
> >  
> > @@ -374,6 +378,15 @@ void
> >  weston_pointer_send_axis(struct weston_pointer *pointer,
> >  			 uint32_t time, uint32_t axis, wl_fixed_t value);
> >  void
> > +weston_pointer_send_axis_discrete(struct weston_pointer *pointer,
> > +				  uint32_t axis, wl_fixed_t value);
> > +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);
> > @@ -1112,6 +1125,17 @@ notify_button(struct weston_seat *seat, uint32_t time, int32_t button,
> >  void
> >  notify_axis(struct weston_seat *seat, uint32_t time, uint32_t axis,
> >  	    wl_fixed_t value);
> > +
> > +void
> > +notify_axis_discrete(struct weston_seat *seat, uint32_t axis,
> > +		     int32_t discrete);
> > +
> > +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,
> > diff --git a/src/data-device.c b/src/data-device.c
> > index 66c4c7a..bee6599 100644
> > --- a/src/data-device.c
> > +++ b/src/data-device.c
> > @@ -417,6 +417,22 @@ drag_grab_axis(struct weston_pointer_grab *grab,
> >  }
> >  
> >  static void
> > +drag_grab_axis_discrete(struct weston_pointer_grab *grab,
> > +			uint32_t axis, int32_t value)
> > +{
> > +}
> > +
> > +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 +449,9 @@ static const struct weston_pointer_grab_interface pointer_drag_grab_interface =
> >  	drag_grab_motion,
> >  	drag_grab_button,
> >  	drag_grab_axis,
> > +	drag_grab_axis_discrete,
> > +	drag_grab_axis_source,
> > +	drag_grab_frame,
> >  	drag_grab_cancel,
> >  };
> >  
> > diff --git a/src/input.c b/src/input.c
> > index 09d12de..b75be56 100644
> > --- a/src/input.c
> > +++ b/src/input.c
> > @@ -241,7 +241,57 @@ weston_pointer_send_axis(struct weston_pointer *pointer,
> >  
> >  	resource_list = &pointer->focus_resource_list;
> >  	wl_resource_for_each(resource, resource_list)
> > -		wl_pointer_send_axis(resource, time, axis, value);
> > +		if (value)
> > +			wl_pointer_send_axis(resource, time, axis, value);
> > +		else if (wl_resource_get_version(resource) >=
> > +			 WL_POINTER_AXIS_STOP_SINCE_VERSION)
> > +			wl_pointer_send_axis_stop(resource, time, axis);
> > +}
> > +
> > +WL_EXPORT void
> > +weston_pointer_send_axis_discrete(struct weston_pointer *pointer,
> > +				  uint32_t axis, wl_fixed_t value)
> > +{
> > +	struct wl_resource *resource;
> > +	struct wl_list *resource_list;
> > +
> > +	resource_list = &pointer->focus_resource_list;
> > +	wl_resource_for_each(resource, resource_list) {
> > +		if (wl_resource_get_version(resource) >=
> > +		    WL_POINTER_AXIS_DISCRETE_SINCE_VERSION) {
> > +			wl_pointer_send_axis_discrete(resource, axis, value);
> > +		}
> > +	}
> > +}
> > +
> > +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_resource_list;
> > +	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);
> > +		}
> > +	}
> > +}
> > +
> > +WL_EXPORT void
> > +weston_pointer_send_frame(struct weston_pointer *pointer)
> > +{
> > +	struct wl_resource *resource;
> > +	struct wl_list *resource_list;
> > +
> > +	resource_list = &pointer->focus_resource_list;
> > +	wl_resource_for_each(resource, resource_list) {
> > +		if (wl_resource_get_version(resource) >=
> > +		    WL_POINTER_FRAME_SINCE_VERSION) {
> > +			wl_pointer_send_frame(resource);
> > +		}
> > +	}
> >  }
> >  
> >  static void
> > @@ -252,6 +302,26 @@ default_grab_pointer_axis(struct weston_pointer_grab *grab,
> >  }
> >  
> >  static void
> > +default_grab_pointer_axis_discrete(struct weston_pointer_grab *grab,
> > +				   uint32_t axis, wl_fixed_t value)
> > +{
> > +	weston_pointer_send_axis_discrete(grab->pointer, axis, value);
> > +}
> > +
> > +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)
> >  {
> >  }
> > @@ -262,6 +332,9 @@ static const struct weston_pointer_grab_interface
> >  	default_grab_pointer_motion,
> >  	default_grab_pointer_button,
> >  	default_grab_pointer_axis,
> > +	default_grab_pointer_axis_discrete,
> > +	default_grab_pointer_axis_source,
> > +	default_grab_pointer_frame,
> >  	default_grab_pointer_cancel,
> >  };
> >  
> > @@ -707,6 +780,7 @@ weston_pointer_set_focus(struct weston_pointer *pointer,
> >  		wl_resource_for_each(resource, focus_resource_list) {
> >  			wl_pointer_send_leave(resource, serial,
> >  					      pointer->focus->surface->resource);
> > +			wl_pointer_send_frame(resource);
> >  		}
> >  
> >  		move_resources(&pointer->resource_list, focus_resource_list);
> > @@ -733,6 +807,7 @@ weston_pointer_set_focus(struct weston_pointer *pointer,
> >  					      serial,
> >  					      view->surface->resource,
> >  					      sx, sy);
> > +			wl_pointer_send_frame(resource);
> >  		}
> >  
> >  		pointer->focus_serial = serial;
> > @@ -1118,9 +1193,6 @@ notify_axis(struct weston_seat *seat, uint32_t time, uint32_t axis,
> >  
> >  	weston_compositor_wake(compositor);
> >  
> > -	if (!value)
> > -		return;
> > -
> >  	if (weston_compositor_run_axis_binding(compositor, pointer,
> >  					       time, axis, value))
> >  		return;
> > @@ -1128,6 +1200,41 @@ notify_axis(struct weston_seat *seat, uint32_t time, uint32_t axis,
> >  	pointer->grab->interface->axis(pointer->grab, time, axis, value);
> >  }
> >  
> > +WL_EXPORT void
> > +notify_axis_discrete(struct weston_seat *seat, uint32_t axis,
> > +		     int32_t discrete)
> > +{
> > +	struct weston_compositor *compositor = seat->compositor;
> > +	struct weston_pointer *pointer = weston_seat_get_pointer(seat);
> > +
> > +	weston_compositor_wake(compositor);
> > +
> > +	pointer->grab->interface->axis_discrete(pointer->grab, axis,
> > +						discrete);
> > +}
> > +
> > +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)
> > @@ -1809,6 +1916,7 @@ seat_get_pointer(struct wl_client *client, struct wl_resource *resource,
> >  				      pointer->focus_serial,
> >  				      pointer->focus->surface->resource,
> >  				      sx, sy);
> > +		wl_pointer_send_frame(cr);
> >  	}
> >  }
> >  
> > @@ -1984,7 +2092,7 @@ bind_seat(struct wl_client *client, void *data, uint32_t version, uint32_t id)
> >  	enum wl_seat_capability caps = 0;
> >  
> >  	resource = wl_resource_create(client,
> > -				      &wl_seat_interface, MIN(version, 4), id);
> > +				      &wl_seat_interface, MIN(version, 5), id);
> >  	wl_list_insert(&seat->base_resource_list, wl_resource_get_link(resource));
> >  	wl_resource_set_implementation(resource, &seat_interface, data,
> >  				       unbind_resource);
> > @@ -2387,7 +2495,7 @@ weston_seat_init(struct weston_seat *seat, struct weston_compositor *ec,
> >  	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 69dcbf8..ef76825 100644
> > --- a/src/libinput-device.c
> > +++ b/src/libinput-device.c
> > @@ -80,7 +80,7 @@ handle_keyboard_key(struct libinput_device *libinput_device,
> >  		   STATE_UPDATE_AUTOMATIC);
> >  }
> >  
> > -static void
> > +static bool
> >  handle_pointer_motion(struct libinput_device *libinput_device,
> >  		      struct libinput_event_pointer *pointer_event)
> >  {
> > @@ -94,9 +94,11 @@ handle_pointer_motion(struct libinput_device *libinput_device,
> >  		      libinput_event_pointer_get_time(pointer_event),
> >  		      dx,
> >  		      dy);
> > +
> > +	return true;
> >  }
> >  
> > -static void
> > +static bool
> >  handle_pointer_motion_absolute(
> >  	struct libinput_device *libinput_device,
> >  	struct libinput_event_pointer *pointer_event)
> > @@ -109,7 +111,7 @@ handle_pointer_motion_absolute(
> >  	uint32_t width, height;
> >  
> >  	if (!output)
> > -		return;
> > +		return false;
> >  
> >  	time = libinput_event_pointer_get_time(pointer_event);
> >  	width = device->output->current_mode->width;
> > @@ -124,9 +126,11 @@ handle_pointer_motion_absolute(
> >  
> >  	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)
> >  {
> > @@ -142,21 +146,21 @@ handle_pointer_button(struct libinput_device *libinput_device,
> >  	     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
> > @@ -175,45 +179,112 @@ normalize_scroll(struct libinput_event_pointer *pointer_event,
> >  		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.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;
> > +	enum libinput_pointer_axis_source source;
> > +	uint32_t wl_axis_source;
> > +	bool has_vert, has_horiz;
> > +
> > +	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;
> > +	}
> >  
> >  	axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
> > +	vert = 0;
> > +	vert_discrete = 0;
> >  	if (libinput_event_pointer_has_axis(pointer_event, axis)) {
> > -		value = normalize_scroll(pointer_event, axis);
> > -		notify_axis(device->seat,
> > -			    libinput_event_pointer_get_time(pointer_event),
> > -			    WL_POINTER_AXIS_VERTICAL_SCROLL,
> > -			    wl_fixed_from_double(value));
> > +		vert_discrete = get_axis_discrete(pointer_event, axis);
> > +		vert = normalize_scroll(pointer_event, axis);
> >  	}
> >  
> >  	axis = LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL;
> > +	horiz = 0;
> > +	horiz_discrete = 0;
> >  	if (libinput_event_pointer_has_axis(pointer_event, axis)) {
> > -		value = normalize_scroll(pointer_event, axis);
> > +		horiz_discrete = get_axis_discrete(pointer_event, axis);
> > +		horiz = normalize_scroll(pointer_event, axis);
> > +	}
> > +
> > +	if (vert == 0.0 && vert_discrete == 0 &&
> > +	    horiz == 0.0 && horiz_discrete == 0)
> > +		return false;
> > +
> > +	notify_axis_source(device->seat, wl_axis_source);
> > +
> > +	/* if we have a discrete value, we must send an normal axis
> > +	 * event, even if the axis value is 0 */
> > +	if (vert_discrete != 0)
> > +		notify_axis_discrete(device->seat,
> > +				     WL_POINTER_AXIS_VERTICAL_SCROLL,
> > +				     vert_discrete);
> > +	if (vert != 0.0 || vert_discrete != 0)
> > +		notify_axis(device->seat,
> > +			    libinput_event_pointer_get_time(pointer_event),
> > +			    WL_POINTER_AXIS_VERTICAL_SCROLL,
> > +			    wl_fixed_from_double(vert));
> > +
> > +	if (horiz_discrete != 0)
> > +		notify_axis_discrete(device->seat,
> > +				     WL_POINTER_AXIS_HORIZONTAL_SCROLL,
> > +				     horiz_discrete);
> > +	if (horiz != 0 || horiz_discrete != 0)
> >  		notify_axis(device->seat,
> >  			    libinput_event_pointer_get_time(pointer_event),
> >  			    WL_POINTER_AXIS_HORIZONTAL_SCROLL,
> > -			    wl_fixed_from_double(value));
> > -	}
> > +			    wl_fixed_from_double(horiz));
> > +
> > +	return true;
> >  }
> >  
> >  static void
> > @@ -290,7 +361,10 @@ evdev_device_process_event(struct libinput_event *event)
> >  {
> >  	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:
> > @@ -298,21 +372,22 @@ evdev_device_process_event(struct libinput_event *event)
> >  				    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,
> > @@ -336,6 +411,9 @@ evdev_device_process_event(struct libinput_event *event)
> >  			   libinput_event_get_type(event));
> >  	}
> >  
> > +	if (need_frame)
> > +		notify_pointer_frame(device->seat);
> > +
> >  	return handled;
> >  }
> >  
> > diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c
> > index 16786d9..97c4395 100644
> > --- a/tests/weston-test-client-helper.c
> > +++ b/tests/weston-test-client-helper.c
> > @@ -197,12 +197,44 @@ pointer_handle_axis(void *data, struct wl_pointer *wl_pointer,
> >  		axis, wl_fixed_to_double(value));
> >  }
> >  
> > +static void
> > +pointer_handle_frame(void *data, struct wl_pointer *wl_pointer)
> > +{
> > +	fprintf(stderr, "test-client: got pointer frame\n");
> > +}
> > +
> > +static void
> > +pointer_handle_axis_source(void *data, struct wl_pointer *wl_pointer,
> > +			     uint32_t source)
> > +{
> > +	fprintf(stderr, "test-client: got pointer axis source %u\n", source);
> > +}
> > +
> > +static void
> > +pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer,
> > +			 uint32_t time, uint32_t axis)
> > +{
> > +	fprintf(stderr, "test-client: got pointer axis stop\n");
> > +}
> > +
> > +static void
> > +pointer_handle_axis_discrete(void *data, struct wl_pointer *wl_pointer,
> > +			     uint32_t axis, int32_t value)
> > +{
> > +	fprintf(stderr, "test-client: got pointer axis discrete %u %d\n",
> > +		axis, value);
> > +}
> > +
> >  static const struct wl_pointer_listener pointer_listener = {
> >  	pointer_handle_enter,
> >  	pointer_handle_leave,
> >  	pointer_handle_motion,
> >  	pointer_handle_button,
> >  	pointer_handle_axis,
> > +	pointer_handle_frame,
> > +	pointer_handle_axis_source,
> > +	pointer_handle_axis_stop,
> > +	pointer_handle_axis_discrete,
> >  };
> >  
> >  static void
> > -- 
> > 2.4.3
> > 
> > _______________________________________________
> > wayland-devel mailing list
> > wayland-devel at lists.freedesktop.org
> > http://lists.freedesktop.org/mailman/listinfo/wayland-devel


More information about the wayland-devel mailing list