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

Reply via email to