Orbital Mouse
Pascal Getreuer, 2023-12-31 (updated 2025-01-11)
Overview
QMK has a Mouse Keys feature that controls the mouse using keyboard keys. You can then avoid switching hands between keyboard and mouse, in theory a substantial ergonomic advantage. In reality, this interface is pretty awkward.
This post describes Orbital Mouse, an alternative keyboard-based mouse scheme. I find it is easier to control than QMK’s Mouse Keys and good enough to be useful. To be clear, it still pales in comparison to real mouse or trackball hardware.
What is Orbital Mouse
Orbital Mouse is a userspace library that replaces QMK Mouse Keys. The pointer moves according to a heading direction. Two keys move forward and backward along that direction while another two keys steer.
Figures have been enlarged and annotated in blue for sake of visualizing the control scheme. In real use, sadly, there are no annotations.
Orbital Mouse is essentially tank controls, like the player movement in earlier Resident Evil and Tomb Raiders games, but no monsters chasing your cursor.
When steering, the cursor rotates (orbits!) around a circle to indicate change in heading direction. This rotation effect is additionally useful as a means of fine-scale control, good for hitting small targets like menu items and toolbar buttons.
Add Orbital Mouse to your keymap
Step 1: Use the Orbital Mouse keycodes in your layout in keymap.c. A description of the keycodes is in the next section. A suggested right-handed layout is:
, OM_BTNS, OM_U , OM_DBLS, _______,
OM_W_U , OM_L , OM_D , OM_R , OM_SLOW,
OM_W_D , OM_HLDS, OM_SEL1, OM_SEL2, OM_SEL3, OM_RELS
Step 2: Include the orbital_mouse header by adding near the top of keymap.c:
#include "features/orbital_mouse.h"`
Step 3: Still in keymap.c, handle Orbital Mouse by
defining (or adding to) process_record_user()
and
housekeeping_task_user()
as
bool process_record_user(uint16_t keycode, keyrecord_t* record) {
if (!process_orbital_mouse(keycode, record)) { return false; }
// Your macros ...
return true;
}
void housekeeping_task_user(void) {
();
orbital_mouse_task
// Other tasks ...
}
Step 4: In your rules.mk
file, add
SRC += features/orbital_mouse.c
MOUSE_ENABLE = yes
Optionally, if Mouse Keys is enabled, you may disable it with
“MOUSEKEYS_ENABLE = no
” to save some firmware space.
Step 5: In the directory containing keymap.c, create
a features
subdirectory and copy orbital_mouse.h
and orbital_mouse.c
there.
Keycodes
Orbital Mouse is controlled with the following keycodes.
Keycode | Description |
---|---|
OM_U |
Move forward. |
OM_D |
Move backward. |
OM_L |
Steer left (counter-clockwise). |
OM_R |
Steer right (clockwise). |
OM_SLOW |
Slow mode. Movement is slower while held. |
OM_BTN n |
Press mouse button n, for n = 1, …, 8. |
OM_W_U |
Mouse wheel up. |
OM_W_D |
Mouse wheel down. |
OM_W_L |
Mouse wheel left. |
OM_W_R |
Mouse wheel right. |
Of course, you don’t have to use all of them in your layout, just use what is useful. There are many ways these keys could be arranged in a layout. I leave it to you to explore.
Slow mode
Keycode OM_SLOW
momentarily activates slow mode, aka
“sniping.” This key is useful for fine adjustments. While held,
forward/backward movement and turning are slower.
Mouse wheel controls
Keycodes OM_W_U
, OM_W_D
,
OM_W_L
, OM_W_R
scroll the mouse wheel. Unlike
cursor movement, mouse wheel navigation follows usual Cartesian
up/down/left/right controls.
Selected mouse button
While keycodes OM_BTN1
… OM_BTN8
may be
used to click mouse buttons 1 through 8, the following keycodes may be
preferred for more detailed control.
Keycode | Description |
---|---|
OM_SEL n |
Select mouse button n, for n = 1, …, 8. |
OM_BTNS |
Press the selected mouse button. |
OM_DBLS |
Double click the selected mouse button. |
OM_HLDS |
Hold the selected mouse button. |
OM_RELS |
Release the selected mouse button. |
These keycodes work in terms of setting and using a “selected” mouse
button. Initially, mouse button 1 is selected. Keycodes
OM_SEL1
… OM_SEL8
set the selected mouse
button. Keycode OM_BTNS
and its companions
OM_HLDS
, OM_RELS
, OM_DBLS
make
use of the selected mouse button.
OM_HLDS
and OM_RELS
are useful when a mouse
button must be held for an extended duration, like click and drag
inputs. Tapping OM_HLDS
holds down the selected mouse
button. The button is held until OM_RELS
is tapped to
release it.
Configuration
Speed curve
Mouse keys must facilitate both large motions across the screen as well as precise fine motions for selecting menu items and such. An idea from the X Windows System is that holding a mouse key should begin moving the cursor at a slow speed and accelerate to faster speed. A “speed curve” defines how this speed changes as a function of time.
ORBITAL_MOUSE_SPEED_CURVE
defines the speed curve as a
table of 16 values, representing speed in pixels per 16-ms interval. The
nth table entry is the movement speed after holding the key for
0.256n seconds. The entries are piecewise linearly interpolated
at times between these points. Table entries are uint8_t
values in the range 0–255, larger values meaning faster movement.
The default curve is a bilevel scheme, beginning at a lower speed of 24, for fine-scale motions, then transitions after a second to a higher speed of 66, for large-scale motions:
#define ORBITAL_MOUSE_SPEED_CURVE \
{24, 24, 24, 32, 58, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66}
// | | | | |
// t = 0.000 1.024 2.048 3.072 3.840 s
Define ORBITAL_MOUSE_SPEED_CURVE
in your config.h to use
a different speed curve. You can represent just about any speed curve
you like in this manner.
In QMK Mouse Keys, the default Accelerated mode is based on X Window System MouseKeysAccel. Here is a family of curves following that design:
\[ s(t) = s_0 + (s_T - s_0) (t / T)^p. \]
#define ORBITAL_MOUSE_SPEED_CURVE \
{24, 28, 36, 47, 59, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66}
// | | | | |
// t = 0.000 1.024 2.048 3.072 3.840 s
Time (s)
Options
Orbital Mouse can be further customized by tuning the following options in config.h.
#define ORBITAL_MOUSE_RADIUS 36
#define ORBITAL_MOUSE_SLOW_MOVE_FACTOR 0.333
#define ORBITAL_MOUSE_SLOW_TURN_FACTOR 0.5
#define ORBITAL_MOUSE_WHEEL_SPEED 0.2
#define ORBITAL_MOUSE_DBL_DELAY_MS 50
ORBITAL_MOUSE_RADIUS
is the radius in pixels of the
circle that the cursor rotates or “orbits” around when steering. Must be
in [0, 63]. Default is 36
.
ORBITAL_MOUSE_SLOW_MOVE_FACTOR
is the multiplicative
factor applied to forward/backward movement speed when holding the
OM_SLOW
key. This factor stacks on top of
ORBITAL_MOUSE_SPEED_CURVE
. Must be in [0, 1]. Default is
0.333
.
ORBITAL_MOUSE_SLOW_TURN_FACTOR
is the multiplicative
factor applied to turning speed when holding the OM_SLOW
key. Must be in [0, 1]. Default is 0.5
.
ORBITAL_MOUSE_WHEEL_SPEED
is the mouse wheel speed in
units per 16-ms interval, specified as a double value. Must be in [0,
3.99]. Default is 0.2
.
ORBITAL_MOUSE_DBL_DELAY_MS
is the delay in ms between
clicks with OM_DBLS
double clicking. If this is too low,
the computer might debounce the double click. Default is
50
.
Functions
set_orbital_mouse_speed_curve(speed_curve)
sets the speed curve at run time, wherespeed_curve
points to auint8_t
array of 16 values. This enables dynamically switching between multiple speed curves. Calling this function withNULL
resets to the speed curve defined byORBITAL_MOUSE_SPEED_CURVE
.get_orbital_mouse_angle()
gets the heading direction as a value in the range 0–63. Value 0 represents up, and values increase in counter-clockwise direction.set_orbital_mouse_angle(angle)
sets the heading direction. The function wraps the givenangle
to the 0–63 range.
Explanation
If you are interested in the technical details, here are some notes on how Orbital Mouse is implemented.
While Orbital Mouse controls are actively being used, it runs a task function once every 16 ms. This function updates the state, moving and turning according to which keys are currently held, then sends a mouse report to the host.
Details:
The mouse report is created by filling a
report_mouse_t
struct, then passing this struct tohost_mouse_send()
.Orbital Mouse needs 29 keycodes. While keycodes for userspace features are conventionally allocated in the user-defined keycode range, that range is limited. It would be unreasonable to allocate Orbital Mouse’s keys there. Being a Mouse Keys replacement, we repurpose the Mouse Keys keycodes (
KC_MS_U
,KC_BTN1
, etc.) for the analogous functions in Orbital Mouse. We also repurpose the block of keycodesUC(0x41)
toUC(0x4a)
. These keycode represent Unicode input of ASCII characters, which seems unlikely to be missed.The Orbital Mouse implementation uses no floating point arithmetic at run time. Instead, fixed point arithmetic is used to represent fractional values. The internal functions
scaled_sin()
andscaled_cos()
evaluate sine and cosine for a desired phase and amplitude, returning the result as a Q6.8 value.