From 118a429504055dab3062304ec0dadc60a2919ce5 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 22 Apr 2015 17:23:35 -0500 Subject: [PATCH] window: Use wl_cursor_frame_and_duration() for mouse cursor updates 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 --- clients/window.c | 123 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 114 insertions(+), 9 deletions(-) diff --git a/clients/window.c b/clients/window.c index 8e43e181..e97fe855 100644 --- a/clients/window.c +++ b/clients/window.c @@ -319,6 +319,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; @@ -2640,6 +2645,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) { @@ -2652,6 +2681,7 @@ input_remove_pointer_focus(struct input *input) input->pointer_focus = NULL; input->current_cursor = CURSOR_UNSET; + cancel_pointer_image_update(input); } static void @@ -3540,6 +3570,43 @@ input_set_pointer_special(struct input *input) return false; } +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) @@ -3547,11 +3614,16 @@ pointer_surface_frame_callback(void *data, struct wl_callback *callback, 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) @@ -3571,21 +3643,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 }; @@ -5169,7 +5268,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, @@ -5211,6 +5315,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); }