[PATCH v2 weston] window: Use wl_cursor_frame_and_duration() for mouse cursor updates

Derek Foreman derekf at osg.samsung.com
Wed Apr 22 15:23:35 PDT 2015


Some animated cursor sets use very long delays, but until now we'd use the
frame callback and update the cursor at the display framerate anyway.

Now we use a timerfd to drive cursor animation if the delay is longer
than 100ms, or the old method for short delays.

Signed-off-by: Derek Foreman <derekf at osg.samsung.com>
---
 clients/window.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 114 insertions(+), 9 deletions(-)

diff --git a/clients/window.c b/clients/window.c
index 81e007b..e044505 100644
--- a/clients/window.c
+++ b/clients/window.c
@@ -317,6 +317,11 @@ struct input {
 	int current_cursor;
 	uint32_t cursor_anim_start;
 	struct wl_callback *cursor_frame_cb;
+	uint32_t cursor_timer_start;
+	uint32_t cursor_anim_current;
+	int cursor_delay_fd;
+	bool cursor_timer_running;
+	struct task cursor_task;
 	struct wl_surface *pointer_surface;
 	uint32_t modifiers;
 	uint32_t pointer_enter_serial;
@@ -2644,6 +2649,30 @@ input_ungrab(struct input *input)
 }
 
 static void
+cursor_delay_timer_reset(struct input *input, uint32_t duration)
+{
+	struct itimerspec its;
+
+	if (!duration)
+		input->cursor_timer_running = false;
+	else
+		input->cursor_timer_running = true;
+
+	its.it_interval.tv_sec = 0;
+	its.it_interval.tv_nsec = 0;
+	its.it_value.tv_sec = duration / 1000;
+	its.it_value.tv_nsec = (duration % 1000) * 1000 * 1000;
+	if (timerfd_settime(input->cursor_delay_fd, 0, &its, NULL) < 0)
+		fprintf(stderr, "could not set cursor timerfd\n: %m");
+}
+
+static void cancel_pointer_image_update(struct input *input)
+{
+	if (input->cursor_timer_running)
+		cursor_delay_timer_reset(input, 0);
+}
+
+static void
 input_remove_pointer_focus(struct input *input)
 {
 	struct window *window = input->pointer_focus;
@@ -2655,6 +2684,7 @@ input_remove_pointer_focus(struct input *input)
 
 	input->pointer_focus = NULL;
 	input->current_cursor = CURSOR_UNSET;
+	cancel_pointer_image_update(input);
 }
 
 static void
@@ -3544,17 +3574,59 @@ input_set_pointer_special(struct input *input)
 }
 
 static void
+schedule_pointer_image_update(struct input *input,
+			      struct wl_cursor *cursor,
+			      uint32_t duration,
+			      bool force_frame)
+{
+	/* Some silly cursor sets have enormous pauses in them.  In these
+	 * cases it's better to use a timer even if it results in less
+	 * accurate presentation, since it will save us having to set the
+	 * same cursor image over and over again.
+	 *
+	 * This is really not the way we're supposed to time any kind of
+	 * animation, but we're pretending it's OK here because we don't
+	 * want animated cursors with long delays to needlessly hog CPU.
+	 *
+	 * We use force_frame to ensure we don't accumulate large timing
+	 * errors by running off the wrong clock.
+	 */
+	if (!force_frame && duration > 100) {
+		struct timespec tp;
+
+		clock_gettime(CLOCK_MONOTONIC, &tp);
+		input->cursor_timer_start = tp.tv_sec * 1000
+					  + tp.tv_nsec / 1000000;
+		cursor_delay_timer_reset(input, duration);
+		return;
+	}
+
+	/* for short durations we'll just spin on frame callbacks for
+	 * accurate timing - the way any kind of timing sensitive animation
+	 * should really be done. */
+	input->cursor_frame_cb = wl_surface_frame(input->pointer_surface);
+	wl_callback_add_listener(input->cursor_frame_cb,
+				 &pointer_surface_listener, input);
+
+}
+
+static void
 pointer_surface_frame_callback(void *data, struct wl_callback *callback,
 			       uint32_t time)
 {
 	struct input *input = data;
 	struct wl_cursor *cursor;
 	int i;
+	uint32_t duration;
+	bool force_frame = true;
+
+	cancel_pointer_image_update(input);
 
 	if (callback) {
 		assert(callback == input->cursor_frame_cb);
 		wl_callback_destroy(callback);
 		input->cursor_frame_cb = NULL;
+		force_frame = false;
 	}
 
 	if (!input->pointer)
@@ -3574,21 +3646,48 @@ pointer_surface_frame_callback(void *data, struct wl_callback *callback,
 	else if (input->cursor_anim_start == 0)
 		input->cursor_anim_start = time;
 
-	if (time == 0 || input->cursor_anim_start == 0)
+	input->cursor_anim_current = time;
+
+	if (time == 0 || input->cursor_anim_start == 0) {
+		duration = 0;
 		i = 0;
-	else
-		i = wl_cursor_frame(cursor, time - input->cursor_anim_start);
+	} else
+		i = wl_cursor_frame_and_duration(
+					cursor,
+					time - input->cursor_anim_start,
+					&duration);
 
-	if (cursor->image_count > 1) {
-		input->cursor_frame_cb =
-			wl_surface_frame(input->pointer_surface);
-		wl_callback_add_listener(input->cursor_frame_cb,
-					 &pointer_surface_listener, input);
-	}
+	if (cursor->image_count > 1)
+		schedule_pointer_image_update(input, cursor, duration,
+					      force_frame);
 
 	input_set_pointer_image_index(input, i);
 }
 
+static void
+cursor_timer_func(struct task *task, uint32_t events)
+{
+	struct input *input = container_of(task, struct input, cursor_task);
+	struct timespec tp;
+	struct wl_cursor *cursor;
+	uint32_t time;
+	uint64_t exp;
+
+	if (!input->cursor_timer_running)
+		return;
+
+	if (read(input->cursor_delay_fd, &exp, sizeof (uint64_t)) != sizeof (uint64_t))
+		return;
+
+	cursor = input->display->cursors[input->current_cursor];
+	if (!cursor)
+		return;
+
+	clock_gettime(CLOCK_MONOTONIC, &tp);
+	time = tp.tv_sec * 1000 + tp.tv_nsec / 1000000 - input->cursor_timer_start;
+	pointer_surface_frame_callback(input, NULL, input->cursor_anim_current + time);
+}
+
 static const struct wl_callback_listener pointer_surface_listener = {
 	pointer_surface_frame_callback
 };
@@ -5178,7 +5277,12 @@ display_add_input(struct display *d, uint32_t id)
 	}
 
 	input->pointer_surface = wl_compositor_create_surface(d->compositor);
+	input->cursor_task.run = cursor_timer_func;
 
+	input->cursor_delay_fd = timerfd_create(CLOCK_MONOTONIC,
+						TFD_CLOEXEC | TFD_NONBLOCK);
+	display_watch_fd(d, input->cursor_delay_fd, EPOLLIN,
+			 &input->cursor_task);
 	set_repeat_info(input, 40, 400);
 
 	input->repeat_timer_fd = timerfd_create(CLOCK_MONOTONIC,
@@ -5220,6 +5324,7 @@ input_destroy(struct input *input)
 	wl_list_remove(&input->link);
 	wl_seat_destroy(input->seat);
 	close(input->repeat_timer_fd);
+	close(input->cursor_delay_fd);
 	free(input);
 }
 
-- 
2.1.4



More information about the wayland-devel mailing list