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 <der...@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 _______________________________________________ wayland-devel mailing list wayland-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/wayland-devel