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 <derekf@osg.samsung.com>
This commit is contained in:
Derek Foreman 2015-04-22 17:23:35 -05:00 committed by Bryce Harrington
parent 6feb0f9f55
commit 118a429504

View file

@ -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)
i = 0;
else
i = wl_cursor_frame(cursor, time - input->cursor_anim_start);
input->cursor_anim_current = time;
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 (time == 0 || input->cursor_anim_start == 0) {
duration = 0;
i = 0;
} else
i = wl_cursor_frame_and_duration(
cursor,
time - input->cursor_anim_start,
&duration);
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);
}