SDL: wayland: Add high-resolution event timestamp support

From 8cafde5ecc402f62a3fa1efe9c94b4f12ae6f4f4 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Sun, 4 Dec 2022 11:45:19 -0500
Subject: [PATCH] wayland: Add high-resolution event timestamp support

Add the protocol for high-resolution timestamp events and subscribe to them if available.

Event timestamps are now handled natively in nanoseconds as much as possible to avoid error-prone conversions.

Variables have been appended with _ms or _ns where appropriate, to avoid ambiguity.
---
 src/video/wayland/SDL_waylandevents.c         | 126 +++++++++++----
 src/video/wayland/SDL_waylandevents_c.h       |  27 ++--
 src/video/wayland/SDL_waylandtouch.c          |  11 +-
 src/video/wayland/SDL_waylandvideo.c          |  11 ++
 src/video/wayland/SDL_waylandvideo.h          |   1 +
 .../input-timestamps-unstable-v1.xml          | 145 ++++++++++++++++++
 6 files changed, 278 insertions(+), 43 deletions(-)
 create mode 100644 wayland-protocols/input-timestamps-unstable-v1.xml

diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index f8a3781b7fe4..a92fd6ea9555 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -39,6 +39,7 @@
 #include "text-input-unstable-v3-client-protocol.h"
 #include "tablet-unstable-v2-client-protocol.h"
 #include "primary-selection-unstable-v1-client-protocol.h"
+#include "input-timestamps-unstable-v1-client-protocol.h"
 
 #ifdef HAVE_LIBDECOR_H
 #include <libdecor.h>
@@ -173,11 +174,10 @@ static struct wl_surface *touch_surface(SDL_TouchID id)
     return NULL;
 }
 
-Uint64 Wayland_GetEventTimestamp(Uint32 wayland_timestamp)
+Uint64 Wayland_GetEventTimestamp(Uint64 nsTimestamp)
 {
-    static Uint32 last;
+    static Uint64 last;
     static Uint64 timestamp_offset;
-    Uint64 nsTimestamp = SDL_MS_TO_NS(wayland_timestamp);
     const Uint64 now = SDL_GetTicksNS();
 
     if (!nsTimestamp) {
@@ -203,19 +203,71 @@ Uint64 Wayland_GetEventTimestamp(Uint32 wayland_timestamp)
     return nsTimestamp;
 }
 
+static void Wayland_input_timestamp_listener(void *data, struct zwp_input_timestamps_v1 *zwp_input_timestamps_v1,
+                                             uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec)
+{
+    *((Uint64 *)data) = ((((Uint64)tv_sec_hi << 32) | (Uint64)tv_sec_lo) * SDL_NS_PER_SECOND) + tv_nsec;
+}
+
+static const struct zwp_input_timestamps_v1_listener timestamp_listener = {
+    Wayland_input_timestamp_listener
+};
+
+static Uint64 Wayland_GetKeyboardTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms)
+{
+    return Wayland_GetEventTimestamp(input->keyboard_timestamp_ns ? input->keyboard_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms));
+}
+
+static Uint64 Wayland_GetKeyboardTimestampRaw(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms)
+{
+    return input->keyboard_timestamp_ns ? input->keyboard_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms);
+}
+
+static Uint64 Wayland_GetPointerTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms)
+{
+    return Wayland_GetEventTimestamp(input->pointer_timestamp_ns ? input->pointer_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms));
+}
+
+Uint64 Wayland_GetTouchTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms)
+{
+    return Wayland_GetEventTimestamp(input->touch_timestamp_ns ? input->touch_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms));
+}
+
+void Wayland_RegisterTimestampListeners(struct SDL_WaylandInput *input)
+{
+    SDL_VideoData *viddata = input->display;
+
+    if (viddata->input_timestamps_manager) {
+        if (input->keyboard && !input->keyboard_timestamps) {
+            input->keyboard_timestamps = zwp_input_timestamps_manager_v1_get_keyboard_timestamps(viddata->input_timestamps_manager, input->keyboard);
+            zwp_input_timestamps_v1_add_listener(input->keyboard_timestamps, &timestamp_listener, &input->keyboard_timestamp_ns);
+        }
+
+        if (input->pointer && !input->pointer_timestamps) {
+            input->pointer_timestamps = zwp_input_timestamps_manager_v1_get_pointer_timestamps(viddata->input_timestamps_manager, input->pointer);
+            zwp_input_timestamps_v1_add_listener(input->pointer_timestamps, &timestamp_listener, &input->pointer_timestamp_ns);
+        }
+
+        if (input->touch && !input->touch_timestamps) {
+            input->touch_timestamps = zwp_input_timestamps_manager_v1_get_touch_timestamps(viddata->input_timestamps_manager, input->touch);
+            zwp_input_timestamps_v1_add_listener(input->touch_timestamps, &timestamp_listener, &input->touch_timestamp_ns);
+        }
+    }
+}
+
 /* Returns SDL_TRUE if a key repeat event was due */
-static SDL_bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, uint32_t elapsed)
+static SDL_bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, Uint64 elapsed)
 {
     SDL_bool ret = SDL_FALSE;
-    while ((elapsed - repeat_info->next_repeat_ms) < 0x80000000U) {
+    while (elapsed >= repeat_info->next_repeat_ns) {
         if (repeat_info->scancode != SDL_SCANCODE_UNKNOWN) {
-            const Uint32 timestamp = repeat_info->wl_press_time + repeat_info->next_repeat_ms;
+            const Uint64 timestamp = repeat_info->wl_press_time_ns + repeat_info->next_repeat_ns;
             SDL_SendKeyboardKey(Wayland_GetEventTimestamp(timestamp), SDL_PRESSED, repeat_info->scancode);
         }
         if (repeat_info->text[0]) {
             SDL_SendKeyboardText(repeat_info->text);
         }
-        repeat_info->next_repeat_ms += 1000 / repeat_info->repeat_rate;
+        repeat_info->next_repeat_ns += SDL_NS_PER_SECOND / (Uint64)repeat_info->repeat_rate;
         ret = SDL_TRUE;
     }
     return ret;
@@ -229,7 +281,7 @@ static void keyboard_repeat_clear(SDL_WaylandKeyboardRepeat *repeat_info)
     repeat_info->is_key_down = SDL_FALSE;
 }
 
-static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, uint32_t key, uint32_t wl_press_time,
+static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, uint32_t key, Uint64 wl_press_time_ns,
                                 uint32_t scancode, SDL_bool has_text, char text[8])
 {
     if (!repeat_info->is_initialized || !repeat_info->repeat_rate) {
@@ -237,9 +289,9 @@ static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, uint32_t
     }
     repeat_info->is_key_down = SDL_TRUE;
     repeat_info->key = key;
-    repeat_info->wl_press_time = wl_press_time;
-    repeat_info->sdl_press_time = SDL_GetTicks();
-    repeat_info->next_repeat_ms = repeat_info->repeat_delay;
+    repeat_info->wl_press_time_ns = wl_press_time_ns;
+    repeat_info->sdl_press_time_ns = SDL_GetTicksNS();
+    repeat_info->next_repeat_ns = SDL_MS_TO_NS(repeat_info->repeat_delay_ms);
     repeat_info->scancode = scancode;
     if (has_text) {
         SDL_memcpy(repeat_info->text, text, 8);
@@ -317,16 +369,16 @@ int Wayland_WaitEventTimeout(_THIS, Sint64 timeoutNS)
 
     /* If key repeat is active, we'll need to cap our maximum wait time to handle repeats */
     if (input && keyboard_repeat_is_set(&input->keyboard_repeat)) {
-        uint32_t elapsed = (uint32_t)(SDL_GetTicks() - input->keyboard_repeat.sdl_press_time);
+        const Uint64 elapsed = SDL_GetTicksNS() - input->keyboard_repeat.sdl_press_time_ns;
         if (keyboard_repeat_handle(&input->keyboard_repeat, elapsed)) {
             /* A repeat key event was already due */
             return 1;
         } else {
-            uint32_t next_repeat_wait_time = (input->keyboard_repeat.next_repeat_ms - elapsed) + 1;
+            const Uint64 next_repeat_wait_time = (input->keyboard_repeat.next_repeat_ns - elapsed) + 1;
             if (timeoutNS >= 0) {
-                timeoutNS = SDL_min(timeoutNS, SDL_MS_TO_NS(next_repeat_wait_time));
+                timeoutNS = SDL_min(timeoutNS, next_repeat_wait_time);
             } else {
-                timeoutNS = SDL_MS_TO_NS(next_repeat_wait_time);
+                timeoutNS = next_repeat_wait_time;
             }
             key_repeat_active = SDL_TRUE;
         }
@@ -347,7 +399,7 @@ int Wayland_WaitEventTimeout(_THIS, Sint64 timeoutNS)
 
             /* If key repeat is active, we might have woken up to generate a key event */
             if (key_repeat_active) {
-                uint32_t elapsed = (uint32_t)(SDL_GetTicks() - input->keyboard_repeat.sdl_press_time);
+                const Uint64 elapsed = SDL_GetTicksNS() - input->keyboard_repeat.sdl_press_time_ns;
                 if (keyboard_repeat_handle(&input->keyboard_repeat, elapsed)) {
                     return 1;
                 }
@@ -406,7 +458,7 @@ void Wayland_PumpEvents(_THIS)
     err = WAYLAND_wl_display_dispatch_pending(d->display);
 
     if (input && keyboard_repeat_is_set(&input->keyboard_repeat)) {
-        uint32_t elapsed = (uint32_t)(SDL_GetTicks() - input->keyboard_repeat.sdl_press_time);
+        const Uint64 elapsed = SDL_GetTicksNS() - input->keyboard_repeat.sdl_press_time_ns;
         keyboard_repeat_handle(&input->keyboard_repeat, elapsed);
     }
 
@@ -440,7 +492,7 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
         const float sy_f = (float)wl_fixed_to_double(sy_w);
         const int sx = (int)SDL_floorf(sx_f * window->pointer_scale_x);
         const int sy = (int)SDL_floorf(sy_f * window->pointer_scale_y);
-        SDL_SendMouseMotion(Wayland_GetEventTimestamp(time), window->sdlwindow, 0, 0, sx, sy);
+        SDL_SendMouseMotion(Wayland_GetPointerTimestamp(input, time), window->sdlwindow, 0, 0, sx, sy);
     }
 }
 
@@ -634,7 +686,7 @@ static void pointer_handle_button_common(struct SDL_WaylandInput *input, uint32_
         Wayland_data_device_set_serial(input->data_device, serial);
         Wayland_primary_selection_device_set_serial(input->primary_selection_device, serial);
 
-        SDL_SendMouseButton(Wayland_GetEventTimestamp(time), window->sdlwindow, 0,
+        SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, time), window->sdlwindow, 0,
                             state ? SDL_PRESSED : SDL_RELEASED, sdl_button);
     }
 }
@@ -671,7 +723,7 @@ static void pointer_handle_axis_common_v1(struct SDL_WaylandInput *input,
         x /= WAYLAND_WHEEL_AXIS_UNIT;
         y /= WAYLAND_WHEEL_AXIS_UNIT;
 
-        SDL_SendMouseWheel(Wayland_GetEventTimestamp(time), window->sdlwindow, 0, x, y, SDL_MOUSEWHEEL_NORMAL);
+        SDL_SendMouseWheel(Wayland_GetPointerTimestamp(input, time), window->sdlwindow, 0, x, y, SDL_MOUSEWHEEL_NORMAL);
     }
 }
 
@@ -754,7 +806,7 @@ static void pointer_handle_axis(void *data, struct wl_pointer *pointer,
     struct SDL_WaylandInput *input = data;
 
     if (wl_seat_get_version(input->seat) >= 5) {
-        input->pointer_curr_axis_info.timestamp = time;
+        input->pointer_curr_axis_info.timestamp_ns = Wayland_GetPointerTimestamp(input, time);
         pointer_handle_axis_common(input, AXIS_EVENT_CONTINUOUS, axis, value);
     } else {
         pointer_handle_axis_common_v1(input, time, axis, value);
@@ -801,7 +853,7 @@ static void pointer_handle_frame(void *data, struct wl_pointer *pointer)
     SDL_memset(&input->pointer_curr_axis_info, 0, sizeof input->pointer_curr_axis_info);
 
     if (x != 0.0f || y != 0.0f) {
-        SDL_SendMouseWheel(Wayland_GetEventTimestamp(input->pointer_curr_axis_info.timestamp),
+        SDL_SendMouseWheel(input->pointer_curr_axis_info.timestamp_ns,
                            window->sdlwindow, 0, x, y, SDL_MOUSEWHEEL_NORMAL);
     }
 }
@@ -851,6 +903,7 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
                                uint32_t timestamp, struct wl_surface *surface,
                                int id, wl_fixed_t fx, wl_fixed_t fy)
 {
+    struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
     SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(surface);
     const double dblx = wl_fixed_to_double(fx) * window_data->pointer_scale_x;
     const double dbly = wl_fixed_to_double(fy) * window_data->pointer_scale_y;
@@ -859,13 +912,14 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
 
     touch_add(id, x, y, surface);
 
-    SDL_SendTouch(Wayland_GetEventTimestamp(timestamp), (SDL_TouchID)(intptr_t)touch,
+    SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch,
                   (SDL_FingerID)id, window_data->sdlwindow, SDL_TRUE, x, y, 1.0f);
 }
 
 static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial,
                              uint32_t timestamp, int id)
 {
+    struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
     float x = 0, y = 0;
     struct wl_surface *surface = NULL;
     SDL_Window *window = NULL;
@@ -877,13 +931,14 @@ static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial
         window = window_data->sdlwindow;
     }
 
-    SDL_SendTouch(Wayland_GetEventTimestamp(timestamp), (SDL_TouchID)(intptr_t)touch,
+    SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch,
                   (SDL_FingerID)id, window, SDL_FALSE, x, y, 0.0f);
 }
 
 static void touch_handler_motion(void *data, struct wl_touch *touch, uint32_t timestamp,
                                  int id, wl_fixed_t fx, wl_fixed_t fy)
 {
+    struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
     SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(touch_surface(id));
     const double dblx = wl_fixed_to_double(fx) * window_data->pointer_scale_x;
     const double dbly = wl_fixed_to_double(fy) * window_data->pointer_scale_y;
@@ -891,7 +946,7 @@ static void touch_handler_motion(void *data, struct wl_touch *touch, uint32_t ti
     const float y = dbly / window_data->sdlwindow->h;
 
     touch_update(id, x, y);
-    SDL_SendTouchMotion(Wayland_GetEventTimestamp(timestamp), (SDL_TouchID)(intptr_t)touch,
+    SDL_SendTouchMotion(Wayland_GetPointerTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch,
                         (SDL_FingerID)id, window_data->sdlwindow, x, y, 1.0f);
 }
 
@@ -1237,6 +1292,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
     char text[8];
     SDL_bool has_text = SDL_FALSE;
     SDL_bool handled_by_ime = SDL_FALSE;
+    const Uint64 timestamp_raw_ns = Wayland_GetKeyboardTimestampRaw(input, time);
 
     if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
         has_text = keyboard_input_get_text(text, input, key, SDL_PRESSED, &handled_by_ime);
@@ -1247,7 +1303,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
              * Using SDL_GetTicks would be wrong, as it would report when the release event is processed,
              * which may be off if the application hasn't pumped events for a while.
              */
-            keyboard_repeat_handle(&input->keyboard_repeat, time - input->keyboard_repeat.wl_press_time);
+            keyboard_repeat_handle(&input->keyboard_repeat, timestamp_raw_ns - input->keyboard_repeat.wl_press_time_ns);
             keyboard_repeat_clear(&input->keyboard_repeat);
         }
         keyboard_input_get_text(text, input, key, SDL_RELEASED, &handled_by_ime);
@@ -1255,7 +1311,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
 
     if (!handled_by_ime) {
         scancode = Wayland_get_scancode_from_key(input, key + 8);
-        SDL_SendKeyboardKey(Wayland_GetEventTimestamp(time), state == WL_KEYBOARD_KEY_STATE_PRESSED ? SDL_PRESSED : SDL_RELEASED, scancode);
+        SDL_SendKeyboardKey(Wayland_GetKeyboardTimestamp(input, time), state == WL_KEYBOARD_KEY_STATE_PRESSED ? SDL_PRESSED : SDL_RELEASED, scancode);
     }
 
     if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
@@ -1267,7 +1323,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
             }
         }
         if (input->xkb.keymap && WAYLAND_xkb_keymap_key_repeats(input->xkb.keymap, key + 8)) {
-            keyboard_repeat_set(&input->keyboard_repeat, key, time, scancode, has_text, text);
+            keyboard_repeat_set(&input->keyboard_repeat, key, timestamp_raw_ns, scancode, has_text, text);
         }
     }
 }
@@ -1325,7 +1381,7 @@ static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keybo
 {
     struct SDL_WaylandInput *input = data;
     input->keyboard_repeat.repeat_rate = SDL_clamp(rate, 0, 1000);
-    input->keyboard_repeat.repeat_delay = delay;
+    input->keyboard_repeat.repeat_delay_ms = delay;
     input->keyboard_repeat.is_initialized = SDL_TRUE;
 }
 
@@ -1377,6 +1433,8 @@ static void seat_handle_capabilities(void *data, struct wl_seat *seat,
         wl_keyboard_destroy(input->keyboard);
         input->keyboard = NULL;
     }
+
+    Wayland_RegisterTimestampListeners(input);
 }
 
 static void seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name)
@@ -2396,6 +2454,16 @@ void Wayland_display_destroy_input(SDL_VideoData *d)
         return;
     }
 
+    if (input->keyboard_timestamps) {
+        zwp_input_timestamps_v1_destroy(input->keyboard_timestamps);
+    }
+    if (input->pointer_timestamps) {
+        zwp_input_timestamps_v1_destroy(input->pointer_timestamps);
+    }
+    if (input->touch_timestamps) {
+        zwp_input_timestamps_v1_destroy(input->touch_timestamps);
+    }
+
     if (input->data_device != NULL) {
         Wayland_data_device_clear_selection(input->data_device);
         if (input->data_device->selection_offer != NULL) {
diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h
index 904bb56fbdb5..fee4c854bf54 100644
--- a/src/video/wayland/SDL_waylandevents_c.h
+++ b/src/video/wayland/SDL_waylandevents_c.h
@@ -68,16 +68,15 @@ struct SDL_WaylandTabletInput
 
 typedef struct
 {
-    // repeat_rate in range of [1, 1000]
-    int32_t repeat_rate;
-    int32_t repeat_delay;
+    int32_t repeat_rate;      /* Repeat rate in range of [1, 1000] character(s) per second */
+    int32_t repeat_delay_ms;  /* Time to first repeat event in milliseconds */
     SDL_bool is_initialized;
 
     SDL_bool is_key_down;
     uint32_t key;
-    uint32_t wl_press_time;  // Key press time as reported by the Wayland API
-    Uint64 sdl_press_time;   // Key press time expressed in SDL ticks
-    uint32_t next_repeat_ms;
+    Uint64 wl_press_time_ns;   /* Key press time as reported by the Wayland API */
+    Uint64 sdl_press_time_ns;  /* Key press time expressed in SDL ticks */
+    Uint64 next_repeat_ns;     /* Next repeat event in nanoseconds */
     uint32_t scancode;
     char text[8];
 } SDL_WaylandKeyboardRepeat;
@@ -93,10 +92,18 @@ struct SDL_WaylandInput
     SDL_WaylandPrimarySelectionDevice *primary_selection_device;
     SDL_WaylandTextInput *text_input;
     struct zwp_relative_pointer_v1 *relative_pointer;
+    struct zwp_input_timestamps_v1 *keyboard_timestamps;
+    struct zwp_input_timestamps_v1 *pointer_timestamps;
+    struct zwp_input_timestamps_v1 *touch_timestamps;
     SDL_WindowData *pointer_focus;
     SDL_WindowData *keyboard_focus;
     uint32_t pointer_enter_serial;
 
+    /* High-resolution event timestamps */
+    Uint64 keyboard_timestamp_ns;
+    Uint64 pointer_timestamp_ns;
+    Uint64 touch_timestamp_ns;
+
     /* Last motion location */
     wl_fixed_t sx_w;
     wl_fixed_t sy_w;
@@ -134,8 +141,8 @@ struct SDL_WaylandInput
         enum SDL_WaylandAxisEvent y_axis_type;
         float y;
 
-        /* Event timestamp in milliseconds */
-        Uint32 timestamp;
+        /* Event timestamp in nanoseconds */
+        Uint64 timestamp_ns;
     } pointer_curr_axis_info;
 
     SDL_WaylandKeyboardRepeat keyboard_repeat;
@@ -149,7 +156,7 @@ struct SDL_WaylandInput
     SDL_bool keyboard_is_virtual;
 };
 
-extern Uint64 Wayland_GetEventTimestamp(Uint32 wayland_timestamp);
+extern Uint64 Wayland_GetTouchTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms);
 
 extern void Wayland_PumpEvents(_THIS);
 extern void Wayland_SendWakeupEvent(_THIS, SDL_Window *window);
@@ -180,6 +187,8 @@ extern int Wayland_input_ungrab_keyboard(SDL_Window *window);
 extern void Wayland_input_add_tablet(struct SDL_WaylandInput *input, struct SDL_WaylandTabletManager *tablet_manager);
 extern void Wayland_input_destroy_tablet(struct SDL_WaylandInput *input);
 
+extern void Wayland_RegisterTimestampListeners(struct SDL_WaylandInput *input);
+
 #endif /* SDL_waylandevents_h_ */
 
 /* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/video/wayland/SDL_waylandtouch.c b/src/video/wayland/SDL_waylandtouch.c
index 7f946ed1a80b..17d17baf94b4 100644
--- a/src/video/wayland/SDL_waylandtouch.c
+++ b/src/video/wayland/SDL_waylandtouch.c
@@ -71,6 +71,8 @@ static void touch_handle_touch(void *data,
      * (src/compositor/wayland_wrapper/qwltouch.cpp)
      **/
 
+    SDL_VideoData *viddata = (SDL_VideoData *)data;
+
     float FIXED_TO_FLOAT = 1. / 10000.;
     float xf = FIXED_TO_FLOAT * normalized_x;
     float yf = FIXED_TO_FLOAT * normalized_y;
@@ -105,13 +107,12 @@ static void touch_handle_touch(void *data,
     switch (touchState) {
     case QtWaylandTouchPointPressed:
     case QtWaylandTouchPointReleased:
-        SDL_SendTouch(Wayland_GetEventTimestamp(time), deviceId, (SDL_FingerID)id, window,
-                      (touchState == QtWaylandTouchPointPressed) ? SDL_TRUE : SDL_FALSE,
-                      xf, yf, pressuref);
+        SDL_SendTouch(Wayland_GetTouchTimestamp(viddata->input, time), deviceId, (SDL_FingerID)id,
+                      window, (touchState == QtWaylandTouchPointPressed) ? SDL_TRUE : SDL_FALSE, xf, yf, pressuref);
         break;
     case QtWaylandTouchPointMoved:
-        SDL_SendTouchMotion(Wayland_GetEventTimestamp(time), deviceId, (SDL_FingerID)id, window,
-                            xf, yf, pressuref);
+        SDL_SendTouchMotion(Wayland_GetTouchTimestamp(viddata->input, time), deviceId, (SDL_FingerID)id,
+                            window, xf, yf, pressuref);
         break;
     default:
         /* Should not happen */
diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index 47b909cd808b..870a7f17f5b9 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -53,6 +53,7 @@
 #include "viewporter-client-protocol.h"
 #include "primary-selection-unstable-v1-client-protocol.h"
 #include "fractional-scale-v1-client-protocol.h"
+#include "input-timestamps-unstable-v1-client-protocol.h"
 
 #ifdef HAVE_LIBDECOR_H
 #include <libdecor.h>
@@ -849,6 +850,11 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
         d->viewporter = wl_registry_bind(d->registry, id, &wp_viewporter_interface, 1);
     } else if (SDL_strcmp(interface, "wp_fractional_scale_manager_v1") == 0) {
         d->fractional_scale_manager = wl_registry_bind(d->registry, id, &wp_fractional_scale_manager_v1_interface, 1);
+    } else if (SDL_strcmp(interface, "zwp_input_timestamps_manager_v1") == 0) {
+        d->input_timestamps_manager = wl_registry_bind(d->registry, id, &zwp_input_timestamps_manager_v1_interface, 1);
+        if (d->input) {
+            Wayland_RegisterTimestampListeners(d->input);
+        }
 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
     } else if (SDL_strcmp(interface, "qt_touch_extension") == 0) {
         Wayland_touch_create(d, id);
@@ -1099,6 +1105,11 @@ static void Wayland_VideoCleanup(_THIS)
         data->fractional_scale_manager = NULL;
     }
 
+    if (data->input_timestamps_manager) {
+        zwp_input_timestamps_manager_v1_destroy(data->input_timestamps_manager);
+        data->input_timestamps_manager = NULL;
+    }
+
     if (data->compositor) {
         wl_compositor_destroy(data->compositor);
         data->compositor = NULL;
diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h
index b8aed7ae93d7..cae968b8a7ef 100644
--- a/src/video/wayland/SDL_waylandvideo.h
+++ b/src/video/wayland/SDL_waylandvideo.h
@@ -79,6 +79,7 @@ typedef struct
     struct zxdg_output_manager_v1 *xdg_output_manager;
     struct wp_viewporter *viewporter;
     struct wp_fractional_scale_manager_v1 *fractional_scale_manager;
+    struct zwp_input_timestamps_manager_v1 *input_timestamps_manager;
 
     EGLDisplay edpy;
     EGLContext context;
diff --git a/wayland-protocols/input-timestamps-unstable-v1.xml b/wayland-protocols/input-timestamps-unstable-v1.xml
new file mode 100644
index 000000000000..7c5e08280af5
--- /dev/null
+++ b/wayland-protocols/input-timestamps-unstable-v1.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="input_timestamps_unstable_v1">
+
+  <copyright>
+    Copyright © 2017 Collabora, Ltd.
+
+    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.
+  </copyright>
+
+  <description summary="High-resolution timestamps for input events">
+    This protocol specifies a way for a client to request and receive
+    high-resolution timestamps for input events.
+
+    Warning! The protocol described in this file is experimental and
+    backward incompatible changes may be made. Backward compatible changes
+    may be added together with the corresponding interface version bump.
+    Backward incompatible changes are done by bumping the version number in
+    the protocol and interface names and resetting the interface version.
+    Once the protocol is to be declared stable, the 'z' prefix and the
+    version number in the protocol and interface names are removed and the
+    interface version number is reset.
+  </description>
+
+  <interface name="zwp_input_timestamps_manager_v1" version="1">
+    <description summary="context object for high-resolution input timestamps">
+      A global interface used for requesting high-resolution timestamps
+      for input events.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the input timestamps manager object">
+        Informs the server that the client will no longer be using this
+        protocol object. Existing objects created by this object are not
+        affected.
+      </description>
+    </request>
+
+    <request name="get_keyboard_timestamps">
+      <description summary="subscribe to high-resolution keyboard timestamp events">
+        Creates a new input timestamps object that represents a subscription
+        to high-resolution timestamp events for all wl_keyboard events that
+        carry a timestamp.
+
+        If the associated wl_keyboard object is invalidated, either through
+        client action (e.g. release) or server-side changes, the input
+        timestamps object becomes inert and the client should destroy it
+        by calling zwp_input_timestamps_v1.destroy.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_input_timestamps_v1"/>
+      <arg name="keyboard" type="object" interface="wl_keyboard"
+           summary="the wl_keyboard object for which to get timestamp events"/>
+    </request>
+
+    <request name="get_pointer_timestamps">
+      <description summary="subscribe to high-resolution pointer timestamp events">
+        Creates a new input timestamps object that represents a subscription
+        to high-resolution timestamp events for all wl_pointer events that
+        carry a timestamp.
+
+        If the associated wl_pointer object is invalidated, either through
+        client action (e.g. release) or server-side changes, the input
+        timestamps object becomes inert and the client should destroy it
+        by calling zwp_input_timestamps_v1.destroy.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_input_timestamps_v1"/>
+      <arg name="pointer" type="object" interface="wl_pointer"
+           summary="the wl_pointer object for which to get timestamp events"/>
+    </request>
+
+    <request name="get_touch_timestamps">
+      <description summary="subscribe to high-resolution touch timestamp events">
+        Creates a new input timestamps object that represents a subscription
+        to high-resolution timestamp events for all wl_touch events that
+        carry a timestamp.
+
+        If the associated wl_touch object becomes invalid, either through
+        client action (e.g. release) or server-side changes, the input
+        timestamps object becomes inert and the client should destroy it
+        by calling zwp_input_timestamps_v1.destroy.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_input_timestamps_v1"/>
+      <arg name="touch" type="object" interface="wl_touch"
+           summary="the wl_touch object for which to get timestamp events"/>
+    </request>
+  </interface>
+
+  <interface name="zwp_input_timestamps_v1" version="1">
+    <description summary="context object for input timestamps">
+      Provides high-resolution timestamp events for a set of subscribed input
+      events. The set of subscribed input events is determined by the
+      zwp_input_timestamps_manager_v1 request used to create this object.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the input timestamps object">
+        Informs the server that the client will no longer be using this
+        protocol object. After the server processes the request, no more
+        timestamp events will be emitted.
+      </description>
+    </request>
+
+    <event name="timestamp">
+      <description summary="high-resolution timestamp event">
+        The timestamp event is associated with the first subsequent input event
+        carrying a timestamp which belongs to the set of input events this
+        object is subscribed to.
+
+        The timestamp provided by this event is a high-resolution version of
+        the timestamp argument of the associated input event. The provided
+        timestamp is in the same clock domain and is at least as accurate as
+        the associated input event timestamp.
+
+        The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
+        each component being an unsigned 32-bit value. Whole seconds are in
+        tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
+        and the additional fractional part in tv_nsec as nanoseconds. Hence,
+        for valid timestamps tv_nsec must be in [0, 999999999].
+      </description>
+      <arg name="tv_sec_hi" type="uint"
+           summary="high 32 bits of the seconds part of the timestamp"/>
+      <arg

(Patch may be truncated, please check the link at the top of this post.)