/* * Copyright © 2014-2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include "libinput-private.h" #include "timer.h" void libinput_timer_init(struct libinput_timer *timer, struct libinput *libinput, const char *timer_name, void (*timer_func)(uint64_t now, void *timer_func_data), void *timer_func_data) { timer->libinput = libinput; timer->timer_name = safe_strdup(timer_name); timer->timer_func = timer_func; timer->timer_func_data = timer_func_data; } void libinput_timer_destroy(struct libinput_timer *timer) { if (timer->link.prev != NULL && timer->link.prev != NULL && !list_empty(&timer->link)) { log_bug_libinput(timer->libinput, "timer: %s has not been cancelled\n", timer->timer_name); assert(!"timer not cancelled"); } free(timer->timer_name); } static void libinput_timer_arm_timer_fd(struct libinput *libinput) { int r; struct libinput_timer *timer; struct itimerspec its = { { 0, 0 }, { 0, 0 } }; uint64_t earliest_expire = UINT64_MAX; list_for_each(timer, &libinput->timer.list, link) { if (timer->expire < earliest_expire) earliest_expire = timer->expire; } if (earliest_expire != UINT64_MAX) { its.it_value.tv_sec = earliest_expire / ms2us(1000); its.it_value.tv_nsec = (earliest_expire % ms2us(1000)) * 1000; } r = timerfd_settime(libinput->timer.fd, TFD_TIMER_ABSTIME, &its, NULL); if (r) log_error(libinput, "timer: timerfd_settime error: %s\n", strerror(errno)); libinput->timer.next_expiry = earliest_expire; } void libinput_timer_set_flags(struct libinput_timer *timer, uint64_t expire, uint32_t flags) { #ifndef NDEBUG uint64_t now = libinput_now(timer->libinput); if (expire < now) { if ((flags & TIMER_FLAG_ALLOW_NEGATIVE) == 0) log_bug_client(timer->libinput, "timer %s: scheduled expiry is in the past (-%dms), your system is too slow\n", timer->timer_name, us2ms(now - expire)); } else if ((expire - now) > ms2us(5000)) { log_bug_libinput(timer->libinput, "timer %s: offset more than 5s, now %d expire %d\n", timer->timer_name, us2ms(now), us2ms(expire)); } #endif assert(expire); if (!timer->expire) list_insert(&timer->libinput->timer.list, &timer->link); timer->expire = expire; libinput_timer_arm_timer_fd(timer->libinput); } void libinput_timer_set(struct libinput_timer *timer, uint64_t expire) { libinput_timer_set_flags(timer, expire, TIMER_FLAG_NONE); } void libinput_timer_cancel(struct libinput_timer *timer) { if (!timer->expire) return; timer->expire = 0; list_remove(&timer->link); libinput_timer_arm_timer_fd(timer->libinput); } static void libinput_timer_handler(struct libinput *libinput , uint64_t now) { struct libinput_timer *timer; restart: list_for_each(timer, &libinput->timer.list, link) { if (timer->expire == 0) continue; if (timer->expire <= now) { /* Clear the timer before calling timer_func, as timer_func may re-arm it */ libinput_timer_cancel(timer); timer->timer_func(now, timer->timer_func_data); /* * Restart the loop. We can't use * list_for_each_safe() here because that only * allows removing one (our) timer per timer_func. * But the timer func may trigger another unrelated * timer to be cancelled and removed, causing a * segfault. */ goto restart; } } } static void libinput_timer_dispatch(void *data) { struct libinput *libinput = data; uint64_t now; uint64_t discard; int r; r = read(libinput->timer.fd, &discard, sizeof(discard)); if (r == -1 && errno != EAGAIN) log_bug_libinput(libinput, "timer: error %d reading from timerfd (%s)", errno, strerror(errno)); now = libinput_now(libinput); if (now == 0) return; libinput_timer_handler(libinput, now); } int libinput_timer_subsys_init(struct libinput *libinput) { libinput->timer.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); if (libinput->timer.fd < 0) return -1; list_init(&libinput->timer.list); libinput->timer.source = libinput_add_fd(libinput, libinput->timer.fd, libinput_timer_dispatch, libinput); if (!libinput->timer.source) { close(libinput->timer.fd); return -1; } return 0; } void libinput_timer_subsys_destroy(struct libinput *libinput) { #ifndef NDEBUG if (!list_empty(&libinput->timer.list)) { struct libinput_timer *t; list_for_each(t, &libinput->timer.list, link) { log_bug_libinput(libinput, "timer: %s still present on shutdown\n", t->timer_name); } } #endif /* All timer users should have destroyed their timers now */ assert(list_empty(&libinput->timer.list)); libinput_remove_source(libinput, libinput->timer.source); close(libinput->timer.fd); } /** * For a caller calling libinput_dispatch() only infrequently, we may have a * timer expiry *and* a later input event waiting in the pipe. We cannot * guarantee that we read the timer expiry first, so this hook exists to * flush any timers. * * Assume 'now' is the current time check if there is a current timer expiry * before this time. If so, trigger the timer func. */ void libinput_timer_flush(struct libinput *libinput, uint64_t now) { if (libinput->timer.next_expiry == 0 || libinput->timer.next_expiry > now) return; libinput_timer_handler(libinput, now); }