From 8ef569ee5980d80c49dbfa8945dc7902d2947c2e Mon Sep 17 00:00:00 2001 From: Alexander Barker Date: Mon, 29 May 2023 11:46:57 -0700 Subject: [PATCH] Rotation value is always zero for mouse wheel events generated by a trackpad on Windows. Fixes #139 --- demo/demo_hook.c | 16 ++++---- demo/demo_hook_async.c | 10 +++-- demo/demo_post.c | 4 +- include/uiohook.h | 7 ++-- src/darwin/dispatch_event.c | 80 +++++++++++++++++++----------------- src/darwin/post_event.c | 2 +- src/windows/dispatch_event.c | 18 ++++++++ src/windows/input_helper.h | 5 +-- src/windows/post_event.c | 2 +- src/x11/dispatch_event.c | 45 ++++++++------------ 10 files changed, 99 insertions(+), 90 deletions(-) diff --git a/demo/demo_hook.c b/demo/demo_hook.c index ad028ad6..128ebf68 100644 --- a/demo/demo_hook.c +++ b/demo/demo_hook.c @@ -61,7 +61,7 @@ void dispatch_proc(uiohook_event * const event, void *user_data) { size_t length = snprintf(buffer, sizeof(buffer), "id=%i,when=%" PRIu64 ",mask=0x%X", event->type, event->time, event->mask); - + switch (event->type) { case EVENT_KEY_PRESSED: // If the escape key is pressed, naturally terminate the program. @@ -107,24 +107,26 @@ void dispatch_proc(uiohook_event * const event, void *user_data) { case EVENT_MOUSE_CLICKED: case EVENT_MOUSE_MOVED: case EVENT_MOUSE_DRAGGED: - snprintf(buffer + length, sizeof(buffer) - length, + snprintf(buffer + length, sizeof(buffer) - length, ",x=%i,y=%i,button=%i,clicks=%i", event->data.mouse.x, event->data.mouse.y, event->data.mouse.button, event->data.mouse.clicks); break; case EVENT_MOUSE_WHEEL: - snprintf(buffer + length, sizeof(buffer) - length, - ",type=%i,amount=%i,rotation=%i", - event->data.wheel.type, event->data.wheel.amount, - event->data.wheel.rotation); + snprintf(buffer + length, sizeof(buffer) - length, + ",type=%u,rotation=%i,delta=%u,direction=%u", + event->data.wheel.type, + event->data.wheel.rotation, + event->data.wheel.delta, + event->data.wheel.direction); break; default: break; } - fprintf(stdout, "%s\n", buffer); + fprintf(stdout, "%s\n", buffer); } int main() { diff --git a/demo/demo_hook_async.c b/demo/demo_hook_async.c index d491cbfe..9a8b402c 100644 --- a/demo/demo_hook_async.c +++ b/demo/demo_hook_async.c @@ -182,10 +182,12 @@ void dispatch_proc(uiohook_event * const event, void *user_data) { break; case EVENT_MOUSE_WHEEL: - snprintf(buffer + length, sizeof(buffer) - length, - ",type=%i,amount=%i,rotation=%i", - event->data.wheel.type, event->data.wheel.amount, - event->data.wheel.rotation); + snprintf(buffer + length, sizeof(buffer) - length, + ",type=%u,rotation=%i,delta=%u,direction=%u", + event->data.wheel.type, + event->data.wheel.rotation, + event->data.wheel.delta, + event->data.wheel.direction); break; default: diff --git a/demo/demo_post.c b/demo/demo_post.c index 153433c8..173400ac 100644 --- a/demo/demo_post.c +++ b/demo/demo_post.c @@ -133,8 +133,8 @@ int main() { event->data.wheel.x = 675; event->data.wheel.y = 675; - event->data.wheel.amount = 3; - event->data.wheel.rotation = 1; + event->data.wheel.rotation = 300; + event->data.wheel.delta = 100; hook_post_event(event); //*/ diff --git a/include/uiohook.h b/include/uiohook.h index 89071b16..ccd90027 100644 --- a/include/uiohook.h +++ b/include/uiohook.h @@ -106,12 +106,11 @@ typedef struct _mouse_event_data { mouse_clicked_event_data; typedef struct _mouse_wheel_event_data { - uint16_t clicks; int16_t x; int16_t y; uint8_t type; - uint16_t amount; int16_t rotation; + uint16_t delta; uint8_t direction; } mouse_wheel_event_data; @@ -465,8 +464,8 @@ typedef void (*dispatcher_t)(uiohook_event * const, void *); #define MOUSE_BUTTON4 4 // Extra Mouse Button #define MOUSE_BUTTON5 5 // Extra Mouse Button -#define WHEEL_UNIT_SCROLL 1 -#define WHEEL_BLOCK_SCROLL 2 +#define WHEEL_UNIT_SCROLL 1 // Scroll by line +#define WHEEL_BLOCK_SCROLL 2 // Scroll by page #define WHEEL_VERTICAL_DIRECTION 3 #define WHEEL_HORIZONTAL_DIRECTION 4 diff --git a/src/darwin/dispatch_event.c b/src/darwin/dispatch_event.c index 278f6356..3851d35c 100644 --- a/src/darwin/dispatch_event.c +++ b/src/darwin/dispatch_event.c @@ -589,11 +589,10 @@ bool dispatch_mouse_wheel(uint64_t timestamp, CGEventRef event_ref) { bool consumed = false; // Reset the click count and previous button. - click_count = 1; + click_count = 0; click_button = MOUSE_NOBUTTON; // Check to see what axis was rotated, we only care about axis 1 for vertical rotation. - // TODO Implement horizontal scrolling by examining axis 2. // NOTE kCGScrollWheelEventDeltaAxis3 is currently unused. if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0 || CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) != 0) { @@ -606,55 +605,60 @@ bool dispatch_mouse_wheel(uint64_t timestamp, CGEventRef event_ref) { uio_event.type = EVENT_MOUSE_WHEEL; uio_event.mask = get_modifiers(); - uio_event.data.wheel.clicks = click_count; uio_event.data.wheel.x = event_point.x; uio_event.data.wheel.y = event_point.y; - // TODO Figure out if kCGScrollWheelEventDeltaAxis2 causes mouse events with zero rotation. - if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventIsContinuous) == 0) { - // Scrolling data is line-based. - uio_event.data.wheel.type = WHEEL_BLOCK_SCROLL; - } else { - // Scrolling data is pixel-based. - uio_event.data.wheel.type = WHEEL_UNIT_SCROLL; - } - - // TODO The result of kCGScrollWheelEventIsContinuous may effect this value. - // Calculate the amount based on the Point Delta / Event Delta. Integer sign should always be homogeneous resulting in a positive result. - // NOTE kCGScrollWheelEventFixedPtDeltaAxis1 a floating point value (+0.1/-0.1) that takes acceleration into account. - // NOTE kCGScrollWheelEventPointDeltaAxis1 will not build on OS X < 10.5 - - if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0) { - uio_event.data.wheel.amount = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventPointDeltaAxis1) / CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1); + uio_event.data.wheel.delta = 0; + uio_event.data.wheel.rotation = 0; - // Scrolling data uses a fixed-point 16.16 signed integer format (Ex: 1.0 = 0x00010000). - uio_event.data.wheel.rotation = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) * -1; + /* This function returns the scale of pixels per line in the specified event source. For example, if the + * scale in the event source is 10.5 pixels per line, this function would return 10.5. Every scrolling event + * can be interpreted to be scrolling by pixel or by line. By default, the scale is about ten pixels per + * line. You can alter the scale with the function CGEventSourceSetPixelsPerLine. + * See: https://gist.github.com/svoisen/5215826 */ + CGEventSourceRef source = CGEventCreateSourceFromEvent(event_ref); + double ppl = CGEventSourceGetPixelsPerLine(source); - } else if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) != 0) { - uio_event.data.wheel.amount = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventPointDeltaAxis2) / CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2); + if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventIsContinuous) != 0) { + // continuous device (trackpad) + ppl *= 1; + uio_event.data.wheel.type = WHEEL_BLOCK_SCROLL; - // Scrolling data uses a fixed-point 16.16 signed integer format (Ex: 1.0 = 0x00010000). - uio_event.data.wheel.rotation = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) * -1; + if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0) { + uio_event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION; + uio_event.data.wheel.rotation = (int16_t) (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventPointDeltaAxis1) * ppl * 1); + } else if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) != 0) { + uio_event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION; + uio_event.data.wheel.rotation = (int16_t) (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventPointDeltaAxis2) * ppl * 1); + } } else { - //Fail Silently if a 3rd axis gets added without changing this section of code. - uio_event.data.wheel.amount = 0; - uio_event.data.wheel.rotation = 0; + // non-continuous device (wheel mice) + ppl *= 10; + uio_event.data.wheel.type = WHEEL_UNIT_SCROLL; + + if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0) { + uio_event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION; + uio_event.data.wheel.rotation = (int16_t) (CGEventGetDoubleValueField(event_ref, kCGScrollWheelEventFixedPtDeltaAxis1) * ppl * 10); + } else if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) != 0) { + uio_event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION; + uio_event.data.wheel.rotation = (int16_t) (CGEventGetDoubleValueField(event_ref, kCGScrollWheelEventFixedPtDeltaAxis2) * ppl * 10); + } } + uio_event.data.wheel.delta = (uint16_t) ppl; - if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0) { - // Wheel Rotated Up or Down. - uio_event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION; - } else { // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight - // Wheel Rotated Left or Right. - uio_event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION; + if (source) { + CFRelease(source); } - logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n", - __FUNCTION__, __LINE__, uio_event.data.wheel.type, - uio_event.data.wheel.amount * uio_event.data.wheel.rotation, + logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse wheel %i / %u of type %u in the %u direction at %u, %u.\n", + __FUNCTION__, __LINE__, + uio_event.data.wheel.rotation, + uio_event.data.wheel.delta, + uio_event.data.wheel.type, uio_event.data.wheel.direction, - uio_event.data.wheel.x, uio_event.data.wheel.y); + uio_event.data.wheel.x, + uio_event.data.wheel.y); // Fire mouse wheel event. dispatch_event(&uio_event); diff --git a/src/darwin/post_event.c b/src/darwin/post_event.c index aaddc0d9..65f02b2f 100644 --- a/src/darwin/post_event.c +++ b/src/darwin/post_event.c @@ -241,7 +241,7 @@ static int post_mouse_wheel_event(uiohook_event * const event, CGEventSourceRef kCGScrollEventUnitLine, // TODO Currently only support 1 wheel axis. (CGWheelCount) 1, // 1 for Y-only, 2 for Y-X, 3 for Y-X-Z - event->data.wheel.amount * event->data.wheel.rotation + event->data.wheel.rotation // TODO Is this value correct? Do we need PPL? ); if (cg_event == NULL) { diff --git a/src/windows/dispatch_event.c b/src/windows/dispatch_event.c index 8de379c3..c2385732 100644 --- a/src/windows/dispatch_event.c +++ b/src/windows/dispatch_event.c @@ -44,6 +44,24 @@ UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc, void *user_d dispatch_data = user_data; } +#ifdef USE_EPOCH_TIME +static uint64_t get_unix_timestamp() { + FILETIME system_time; + + // Get the local system time in UTC. + GetSystemTimeAsFileTime(&system_time); + + // Convert the local system time to a Unix epoch in MS. + // milliseconds = 100-nanoseconds / 10000 + uint64_t timestamp = (((uint64_t) system_time.dwHighDateTime << 32) | system_time.dwLowDateTime) / 10000; + + // Convert Windows epoch to Unix epoch. (1970 - 1601 in milliseconds) + timestamp -= 11644473600000; + + return timestamp; +} +#endif + // Send out an event if a dispatcher was set. static void dispatch_event(uiohook_event *const event) { if (dispatch != NULL) { diff --git a/src/windows/input_helper.h b/src/windows/input_helper.h index 9b4899b9..c52f3e4c 100644 --- a/src/windows/input_helper.h +++ b/src/windows/input_helper.h @@ -143,10 +143,7 @@ extern void unset_modifier_mask(uint16_t mask); /* Get the current native modifier mask state. */ extern uint16_t get_modifiers(); -/* Help track how much rotation should be applied to a scroll wheel event. */ -extern int16_t get_scroll_wheel_rotation(DWORD data, uint8_t direction); - -// Initialize the locale list and wow64 pointer size. +/* Initialize the locale list and wow64 pointer size. */ extern int load_input_helper(); /* Cleanup the initialized locales. */ diff --git a/src/windows/post_event.c b/src/windows/post_event.c index 4fad5340..437d9b72 100644 --- a/src/windows/post_event.c +++ b/src/windows/post_event.c @@ -167,7 +167,7 @@ static int map_mouse_event(uiohook_event * const event, INPUT * const input) { input->mi.dwFlags = MOUSEEVENTF_WHEEL; // type, amount and rotation? - input->mi.mouseData = event->data.wheel.amount * event->data.wheel.rotation * WHEEL_DELTA; + input->mi.mouseData = event->data.wheel.rotation; break; case EVENT_MOUSE_DRAGGED: diff --git a/src/x11/dispatch_event.c b/src/x11/dispatch_event.c index b1b709e8..c98ad3dd 100644 --- a/src/x11/dispatch_event.c +++ b/src/x11/dispatch_event.c @@ -217,16 +217,9 @@ static bool dispatch_mouse_wheel_rotated(XButtonEvent * const x_event) { bool consumed = false; // Reset the click count and previous button. - click.count = 1; + click.count = 0; click.button = MOUSE_NOBUTTON; - /* Scroll wheel release events. - * Scroll type: WHEEL_UNIT_SCROLL - * Scroll amount: 3 unit increments per notch - * Units to scroll: 3 unit increments - * Vertical unit increment: 15 pixels - */ - // Populate mouse wheel event. uio_event.time = x_event->serial; uio_event.reserved = 0x00; @@ -234,7 +227,6 @@ static bool dispatch_mouse_wheel_rotated(XButtonEvent * const x_event) { uio_event.type = EVENT_MOUSE_WHEEL; uio_event.mask = get_modifiers(); - uio_event.data.wheel.clicks = click.count; uio_event.data.wheel.x = x_event->x_root; uio_event.data.wheel.y = x_event->y_root; @@ -251,26 +243,21 @@ static bool dispatch_mouse_wheel_rotated(XButtonEvent * const x_event) { } #endif - /* X11 does not have an API call for acquiring the mouse scroll type. This - * maybe part of the XInput2 (XI2) extention but I will wont know until it - * is available on my platform. For the time being we will just use the - * unit scroll value. - */ + /* X11 does not have an API call for acquiring the mouse scroll type. This maybe part of the XInput2 (XI2) + * extension but I will wont know until it is available on my platform. For the time being we will just use the + * unit scroll value. */ uio_event.data.wheel.type = WHEEL_UNIT_SCROLL; - /* Some scroll wheel properties are available via the new XInput2 (XI2) - * extension. Unfortunately the extension is not available on my - * development platform at this time. For the time being we will just - * use the Windows default value of 3. - */ - uio_event.data.wheel.amount = 3; - - if (x_event->button == WheelUp || x_event->button == WheelLeft) { + /* Some scroll wheel properties are available via the new XInput2 (XI2) extension. Unfortunately the extension is + * not available on my development platform at this time. For the time being we will just use the Windows default + * value of 3. */ + uio_event.data.wheel.delta = 100; + if (x_event->button == WheelDown || x_event->button == WheelLeft) { // Wheel Rotated Up and Away. - uio_event.data.wheel.rotation = -1; - } else { // event.button == WheelDown || event.button == WheelRight + uio_event.data.wheel.rotation = -3 * uio_event.data.wheel.delta; + } else { // event.button == WheelUp || event.button == WheelRight // Wheel Rotated Down and Towards. - uio_event.data.wheel.rotation = 1; + uio_event.data.wheel.rotation = 3 * uio_event.data.wheel.delta; } if (x_event->button == WheelUp || x_event->button == WheelDown) { @@ -281,10 +268,10 @@ static bool dispatch_mouse_wheel_rotated(XButtonEvent * const x_event) { uio_event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION; } - logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n", - __FUNCTION__, __LINE__, uio_event.data.wheel.type, - uio_event.data.wheel.amount * uio_event.data.wheel.rotation, - uio_event.data.wheel.direction, + logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse wheel %i / %u of type %u in the %u direction at %u, %u.\n", + __FUNCTION__, __LINE__, + uio_event.data.wheel.rotation, uio_event.data.wheel.delta, + uio_event.data.wheel.type, uio_event.data.wheel.direction, uio_event.data.wheel.x, uio_event.data.wheel.y); // Fire mouse wheel event.