Trackball “gestures” and other mouse trickery

So I've had this idea for an AutoHotKey script that could turn motions from any pointing device (mice, trackballs, trackpads, etc.) into custom actions.

I guess you might call them gestures?


Aside: for purposes of this post, I'll often write "mouse" in context of system handling, when in reality it's anything that moves the cursor around - that includes "regular" mice, trackballs, trackpads, trackpoints, pens, touchscreens, and a variety of devices asserting that they can be a mouse (e.g. many programmable keyboards can also send mouse events).

Inspiration

On the right of my keyboard, I have a mouse that's my primary pointing device.

On the left of my keyboard, I have a Kensington Orbit trackball.


A diagram of Kensington Orbit trackball

Orbit could not fully replace a mouse for my uses, but at some point I noticed that it has a "track scroll" mode that you can bind to one or combination of buttons.

The said "track scroll" mode makes the ball act as a mouse wheel until one of the buttons is pressed again. And it's really nice - rolling a ball is a far more convenient way to scroll than most mouse wheels that I've used.

This also got me thinking - the driver maps horizontal motions to horizontal scrolling when "track scroll" is active, but there really isn't much software where horizontal scrolling is a significant part of workflow (mostly image editors, really).

What would it feel like if I could map the second axis to tab switching, arrow keys, volume control, or something else?

Research

As far as I can tell, you can't really "intercept" input from a specific mouse on Windows - at least not without writing a driver, which is a sort of trouble that I'd rather not get into.

However, a few years ago I already messed around with Raw Input API (and even made a GameMaker wrapper) and know how Windows handles input at lower level.

On Windows, inputs from connected devices kind of fight over the global state: mice offset the cursor, pens and touchscreens overwrite its position, and funny things may happen if you're pressing buttons on multiple devices at once.

Raw Input API lets you peek behind this curtain - you get events with per-device readings with coordinate changes and pressed/released buttons, even for changes that would not be observable (e.g. when the cursor is rubbing against edges of the screen).


My first idea was to undo movements from the chosen device(s) right in the Raw Input (WM_INPUT) handler.

This didn't work very well - Raw Input is a low-level API, but my C++ test program still could not rollback the cursor fast enough for it to not wobble back and forth.


Thus I settled for a workaround - when Raw Input reports mouse movement and conditions are satisfied (e.g. it's the desired mouse), I lock the mouse cursor in place and unlock it once enough time has passed without matching movements.

This means that you can't move the cursor (with another mouse) at the exact moment of scrolling, but with unlock delay of 30ms or less it's not much of an inconvenience.


Later I found another caveat - you can specify a "delta" for mouse scroll events yourself (the usual being -120 or 120), but a lot of software doesn't handle low-delta events very well, if at all.

So I changed the code to dispatch one "regular" scroll event once per N pixels of movement instead.

Implementation

With my shabby C++ test program now successfully scrolling the wheel when a trackball is rolled, it was finally time to rewrite it in something that's easier to customize without digging into WinAPI.

For Windows, I think that's currently AutoHotKey. AHKv1 has been a little weird with its layers of syntactic constructs piled on top of the old ones, but AHKv2 fixed most of those oddities.

With a little work (changing my WinAPI calls to DllCall) and some contemplation about what the API should look like, I finished the AHK version, wrote a little cheat sheet for it, and that's it.

I also updated my cheat sheet preprocessor to display "sticky" section names on the left:

Experiments

With the AHK version done, I could finally get to figuring out whether my alternate trackball use ideas were any good or not.

The results are as following:

Scrolling

I already mentioned this one, feels great on a trackball.

On a regular mouse, this feels convenient, but stranger.

Volume control

Also good, generally on par with rotary encoders.

Arrow keys

This one's interesting - rolling a ball to move the caret offers more control over caret speed, and this could potentially eliminate much of the need for the navigation block, but there are some challenges.

I was able to overcome accidental secondary-axis input by introducing options to consider one axis at a time and to temporarily disable movement from another axis after scrolling for long enough on one of them.

The default behaviour in text/code editors is that pressing Right while at the end of the line wraps over to the next line, which is already a little weird for code (as you probably have indentation there) and weirder if you're trying to brake in time with your rolling.

On a regular mouse, mapping movement to arrow keys seems mostly-redundant as you can usually just click the spot.

Conclusions

To me, this was an interesting thing to do between other tasks.

You can find the script and its examples on GitHub.

The cheat sheet is hosted on GitHub Pages.

Have fun!

Related posts:

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.