From b5ea5e47f93527254f4557e1fca3b47fad1151c4 Mon Sep 17 00:00:00 2001 From: Alexander Barker Date: Mon, 29 May 2023 11:46:57 -0700 Subject: [PATCH] Bug 139: Rotation value is always zero for mouse wheel events generated by a trackpad on Windows --- 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 | 55 +++++++++++++++---------- src/windows/input_helper.c | 28 ------------- src/windows/input_helper.h | 7 +--- src/windows/post_event.c | 2 +- src/x11/dispatch_event.c | 45 ++++++++------------ 11 files changed, 116 insertions(+), 140 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 7fe7506e..cc492907 100644 --- a/src/darwin/dispatch_event.c +++ b/src/darwin/dispatch_event.c @@ -592,11 +592,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) { @@ -609,55 +608,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 3b9e88ee..8240a98a 100644 --- a/src/windows/dispatch_event.c +++ b/src/windows/dispatch_event.c @@ -46,17 +46,19 @@ UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc, void *user_d #ifdef USE_EPOCH_TIME static uint64_t get_unix_timestamp() { - // Get the local system time in UTC. - GetSystemTimeAsFileTime(&system_time); + FILETIME 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; + // Get the local system time in UTC. + GetSystemTimeAsFileTime(&system_time); - // Convert Windows epoch to Unix epoch. (1970 - 1601 in milliseconds) + // 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; + return timestamp; } #endif @@ -420,7 +422,7 @@ bool dispatch_mouse_wheel(MSLLHOOKSTRUCT *mshook, uint8_t direction) { // Track the number of clicks. // Reset the click count and previous button. - click_count = 1; + click_count = 0; click_button = MOUSE_NOBUTTON; // Populate mouse wheel event. @@ -430,35 +432,46 @@ bool dispatch_mouse_wheel(MSLLHOOKSTRUCT *mshook, uint8_t direction) { uio_event.type = EVENT_MOUSE_WHEEL; uio_event.mask = get_modifiers(); - uio_event.data.wheel.clicks = click_count; uio_event.data.wheel.x = (int16_t) mshook->pt.x; uio_event.data.wheel.y = (int16_t) mshook->pt.y; - uio_event.data.wheel.rotation = get_scroll_wheel_rotation(mshook->mouseData, direction); - - UINT uiAction = SPI_GETWHEELSCROLLCHARS; - if (direction == WHEEL_VERTICAL_DIRECTION) { - uiAction = SPI_GETWHEELSCROLLLINES; + /* Delta GET_WHEEL_DELTA_WPARAM(mshook->mouseData) + * A positive value indicates that the wheel was rotated + * forward, away from the user; a negative value indicates that + * the wheel was rotated backward, toward the user. One wheel + * click is defined as WHEEL_DELTA, which is 120. */ + uio_event.data.wheel.rotation = (int16_t) GET_WHEEL_DELTA_WPARAM(mshook->mouseData); + uio_event.data.wheel.delta = WHEEL_DELTA; + + UINT uiAction = SPI_GETWHEELSCROLLLINES; + if (direction == WHEEL_HORIZONTAL_DIRECTION) { + uiAction = SPI_GETWHEELSCROLLCHARS; } UINT wheel_amount = 3; if (SystemParametersInfo(uiAction, 0, &wheel_amount, 0)) { if (wheel_amount == WHEEL_PAGESCROLL) { + /* If this number is WHEEL_PAGESCROLL, a wheel roll should be interpreted as clicking once in the page + * down or page up regions of the scroll bar. */ + uio_event.data.wheel.type = WHEEL_BLOCK_SCROLL; - uio_event.data.wheel.amount = 1; + uio_event.data.wheel.rotation *= 1; } else { + /* If this number is 0, no scrolling should occur. + * If the number of lines to scroll is greater than the number of lines viewable, the scroll operation + * should also be interpreted as a page down or page up operation. */ + uio_event.data.wheel.type = WHEEL_UNIT_SCROLL; - uio_event.data.wheel.amount = (uint16_t) wheel_amount; + uio_event.data.wheel.rotation *= wheel_amount; } // Set the direction based on what event was received. uio_event.data.wheel.direction = direction; - - logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n", + + 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.type, - uio_event.data.wheel.amount * uio_event.data.wheel.rotation, - uio_event.data.wheel.direction, + 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. diff --git a/src/windows/input_helper.c b/src/windows/input_helper.c index c2225f84..2a8e6ae5 100644 --- a/src/windows/input_helper.c +++ b/src/windows/input_helper.c @@ -364,31 +364,6 @@ uint16_t get_modifiers() { return modifier_mask; } -/* Track the amount of vertical and horizontal rotation between "clicks." - * This is between mouse wheel delta. */ -static int16_t v_rotation, h_rotation; - -int16_t get_scroll_wheel_rotation(DWORD data, uint8_t direction) { - int16_t value; - - /* Delta GET_WHEEL_DELTA_WPARAM(mshook->mouseData) - * A positive value indicates that the wheel was rotated - * forward, away from the user; a negative value indicates that - * the wheel was rotated backward, toward the user. One wheel - * click is defined as WHEEL_DELTA, which is 120. */ - if (direction == WHEEL_VERTICAL_DIRECTION) { - v_rotation += (int16_t) GET_WHEEL_DELTA_WPARAM(data); - // Vertical direction needs to be inverted on Windows to conform with other platforms. - value = (int16_t) v_rotation / (-1 * WHEEL_DELTA); - v_rotation %= WHEEL_DELTA; - } else { - h_rotation += (int16_t) GET_WHEEL_DELTA_WPARAM(data); - value = (int16_t) h_rotation / WHEEL_DELTA; - h_rotation %= WHEEL_DELTA; - } - - return value; -} /*********************************************************************** * The following code is based on code provided by Marc-André Moreau @@ -883,9 +858,6 @@ int load_input_helper() { } #endif - v_rotation = 0; - h_rotation = 0; - int count = refresh_locale_list(); logger(LOG_LEVEL_DEBUG, "%s [%u]: refresh_locale_list() found %i locale(s).\n", diff --git a/src/windows/input_helper.h b/src/windows/input_helper.h index 1a4273c8..13d371cd 100644 --- a/src/windows/input_helper.h +++ b/src/windows/input_helper.h @@ -143,13 +143,10 @@ 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. +/* Cleanup the initialized locales. */ extern int unload_input_helper(); #endif diff --git a/src/windows/post_event.c b/src/windows/post_event.c index 0426e2ee..19fb874a 100644 --- a/src/windows/post_event.c +++ b/src/windows/post_event.c @@ -185,7 +185,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 3b49de93..f1550893 100644 --- a/src/x11/dispatch_event.c +++ b/src/x11/dispatch_event.c @@ -208,16 +208,9 @@ void dispatch_key_release(XKeyReleasedEvent * const x_event) { static void dispatch_mouse_wheel_rotated(XButtonEvent * const x_event) { // 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; @@ -225,7 +218,6 @@ static void 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; @@ -242,26 +234,21 @@ static void 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) { @@ -272,10 +259,10 @@ static void 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.