Skip to content

Commit

Permalink
Bug 139: Rotation value is always zero for mouse wheel events generat…
Browse files Browse the repository at this point in the history
…ed by a trackpad on Windows
  • Loading branch information
kwhat committed Nov 26, 2023
1 parent b859ac8 commit f9a346c
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 140 deletions.
16 changes: 9 additions & 7 deletions demo/demo_hook.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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() {
Expand Down
10 changes: 6 additions & 4 deletions demo/demo_hook_async.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions demo/demo_post.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
//*/

Expand Down
7 changes: 3 additions & 4 deletions include/uiohook.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down
80 changes: 42 additions & 38 deletions src/darwin/dispatch_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/darwin/post_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
55 changes: 34 additions & 21 deletions src/windows/dispatch_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down
28 changes: 0 additions & 28 deletions src/windows/input_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down
7 changes: 2 additions & 5 deletions src/windows/input_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion src/windows/post_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading

0 comments on commit f9a346c

Please sign in to comment.