Below is an expression I wrote to give me advanced mouse control from any keyboard. Figured it may be something others are interested in as well. The basics are as follows:
Arrow Keyscontrol cursor movementArrow Keys+Left Shiftcontrol scrollingLeft Control,Left Alt, andLeft GUIcontrol left, middle and right clicksSpacealso triggers left-clickZandXadjusts speed acrosss five user-configurable levels
It’s that last point that brought this from ‘novel’ to ‘practical’ for me. Having the ability to easily speed up or slow down the cursor to one of five levels using only two keys, Z and X, depending on which you press, and in which order allows me to easily navigate across multiple screens but still with pixel-level precision. This is because all five speeds are user-configurable to match your preferences, and there are separate speeds for moving vs. scrolling, again based on your preferences.
Speed Control FTW!
The way the speed works is as follows:
- Neither
XnorZpressed → Normal speed - Press
X→ Faster - Press
Xfollowed byZ(holding both) → Fastest - Press
Z→ Slower - Press
Zfollowed by X (holding both) → Slowest
Configuration
Configuring this requires a few steps. First, copy the expression below into any expression box. If all of them are full, you can simply paste this at the end of any of them as they all execute together, in order (as if they were all combined.)
Note: This uses registers R1 thru R9. If you are using registers in your other expressions, make sure to adjust if there are any conflicts.
Mappings
Next, you add the following mappings. It’s best to put them on their own layer which you can activate with another key. I personally use right-alt for mouse mode. You can trigger the layer via normal (momentary) or sticky (toggle) based on your preferences. (I personally prefer holding right-alt for mouse mode.)
Register 6→Cursor XRegister 7→Cursor YRegister 8→H scrollRegister 9→V scrollLeft Control→Left ButtonLeft Alt→Middle ButtonLeft GUI→Right ButtonSpace→Left Button
Finally, you must uncheck ‘Unmapped inputs passthrough on layer’ for your mouse layer (I’m using layer 3 here as an example.) This stops the z, x, left-shift and arrow keys from mapping through as they’re being used inside the expression.
Note: You can’t simply map them to ‘Nothing’ as that also prevents the expression engine from parsing them. Hopefully this will change in the future so you can use mouse functions on layers with other active items.
Mouse Control Expression - The Magic Sauce:
Finally, here’s the expression in question. I’ve added comments throughout so you can hopefully follow along and understand how it works.
Again, you can put it in its own expression box, or add it to the end of another. They’re all evaluated together regardless.
/**************************************
* *
* Calculated Registers *
* *
* R1: Mouse Move/Scroll Speed Mode *
* R2: H-direction multiplier *
* R3: V-direction multiplier *
* R4: Mouse Move Speed *
* R5: Mouse Scroll Speed *
* R6: Mouse H Move Amount *
* R7: Mouse V Move Amount *
* R8: Mouse H Scroll Amount *
* R9: Mouse V Scroll Amount *
* *
**************************************/
/*************************************
* R1: Mouse Move/Scroll Speed Mode *
*************************************
Mode 1 (Slowest) : Z and X both pressed (Z pressed first)
Mode 2 (Slower) : Z pressed
Mode 3 (Normal) : Neither Pressed
Mode 4 (Faster) : X Pressed
Mode 5 (Fastest) : Z and X both pressed (X pressed first)
The logic is:
We make a simple bit-mask of the Z and X keys
0 is nothing pressed
1 is Z key pressed
2 is X key pressed
3 is both Z and X pressed
We then check the value of the bitmask to determine the speed mode.
For mask 0, 1 and 2, we can map those directly to modes 3 (Normal), 2 (Slower) or 4 (Faster) respectively
For mask 3 (i.e. both Z and X are pressed), a little more work is needed to determine if the
mode is 1 (Slowest) or 5 (Fastest). By reading the prior pressed states for each of the keys,
we can determine which key was pressed first.
If X wasn't pressed before, then it was just pressed, meaning Z was pressed first, so the mode is 1 (Slowest)
If Z wasn't pressed before, then it was just pressed, meaning X was pressed first, so the mode is 5 (Fastest).
If both X and Z were pressed before (i.e. both checks above were false), then there was no change for this
cycle so we simply return the currently-stored speed mode as it was already calculated on a prior iteration.
The trick to returning the appropriate value is to perform all checks, one after the other, returning either
the matched value, or zero for each, then keeping a running total by adding the results together. Since only
one check will pass, that check will return the corresponding mode whereas all the other checks will return zero.
This means at the end, the total from adding them all together is the mode we're interested in, acting like
a 'switch' statement in other languages.
****************/
/* Read the Z and X keys as a bit-mask */
0x0007001d input_state_binary /* Bit 0 - Z Pressed */
0x0007001b input_state_binary 2 mul /* Bit 1 - X Pressed (use '2 mul' to shift bit over) */
add /* Add values together to create the bitmask */
/* Dup it three times so we can read it four times */
dup dup dup
/* Calculate Mouse Speed Mode */
0 eq 3 mul swap /* Mode is 0 so put 'Normal' on the stack */
1 eq 2 mul add swap /* Mode is 1 so put 'Slower' on the stack and add with the prior result */
2 eq 4 mul add swap /* Mode is 2 so put 'Faster' on the stack and add with the prior result */
3 eq
0x0007001d prev_input_state_binary not /* 1 if 'Z' not pressed? */
5 /* Fastest mode */
0x0007001b prev_input_state_binary not /* 1 if 'X' not pressed? */
1 /* Slowest Mode */
1 recall /* Speed mode from prior cycle */
ifte /* If 'X' was not pressed last time, put 'Slowest' on the stack. Otherwise put the last mode on the stack */
ifte /* If 'Z' was not pressed last time, put 'Fastest' on the stack. Otherwise put the result of the calculation right before this */
mul add /* Mode is 3 so put 'Slowest/Fastest/Prior' on the stack based on the above and add with the prior result */
/* The value left on the stack here is the speed mode */
dup 0x1 monitor /* Trick to monitor for debugging */
1 store
/********************************
* R2: H-direction multiplier *
********************************
Simple value determining direction
We read the left and right key values, negating the result for the left key, then add them together.
This returns the following multiplier
-1 : Left key pressed
0 : Either both, or neither keys are pressed
+1 : Right key pressed
We then multiply this by the speed calculation to get actual horizontal moving or scrolling speed
****************/
0x00070050 input_state_binary -1 mul /* Left Arrow (negated) */
0x0007004f input_state_binary /* Right Arrow */
add
dup 0x2 monitor /* Trick to monitor for debugging */
2 store
/********************************
* R3: V-direction multiplier *
********************************
Simple value determining direction
We read the up and down key values, negating the result for the up key, then add them together.
This returns the following multiplier
-1 : Up Pressed
0 : Either both, or neither keys are pressed
+1 : Down Pressed
We then multiply this by the speed calculation to get actual vertical moving or scrolling speed
****************/
0x00070052 input_state_binary -1 mul /* Up Arrow (negated) */
0x00070051 input_state_binary /* Down Arrow */
add
dup 0x3 monitor /* Trick to monitor for debugging */
3 store
/************************
* R4: Mouse Move Speed *
************************/
1 recall dup dup dup dup
1 eq 0.1 mul swap /* Slowest */
2 eq 1.0 mul add swap /* Slow */
3 eq 4.0 mul add swap /* Normal */
4 eq 8.0 mul add swap /* Fast */
5 eq 16.0 mul add /* Fastest */
dup 0x4 monitor /* Trick to monitor for debugging */
4 store
/**************************
* R5: Mouse Scroll Speed *
**************************/
1 recall dup dup dup dup
1 eq 0.008 mul swap /* Slowest */
2 eq 0.010 mul add swap /* Slow */
3 eq 0.016 mul add swap /* Normal */
4 eq 0.032 mul add swap /* Fast */
5 eq 1.000 mul add /* Fastest */
dup 0x5 monitor /* Trick to monitor for debugging */
5 store
/***************************
* R6: Mouse H Move Amount *
***************************/
2 recall /* H-Direction Multiplier */
4 recall /* Mouse Speed Multiplier */
mul
0x000700e1 input_state_binary not /* No Left Shift */
mul
dup 0x6 monitor /* Trick to monitor for debugging */
6 store
/***************************
* R7: Mouse V Move Amount *
***************************/
3 recall /* V-Direction Multiplier */
4 recall /* Mouse Speed Multiplier */
mul
0x000700e1 input_state_binary not /* No Left Shift */
mul
dup 0x7 monitor /* Trick to monitor for debugging */
7 store
/*****************************
* R8: Mouse H Scroll Amount *
*****************************/
2 recall /* H-Direction Multiplier */
5 recall /* Mouse Scroll Multiplier */
mul
0x000700e1 input_state_binary /* Scroll Requires Left Shift */
mul
dup 0x8 monitor /* Trick to monitor for debugging */
8 store
/*****************************
* R9: Mouse V Scroll Amount *
*****************************/
3 recall /* V-Direction Multiplier */
5 recall /* Mouse Scroll Multiplier */
mul
0x000700e1 input_state_binary /* Scroll Requires Left Shift */
mul
dup 0x9 monitor /* Trick to monitor for debugging */
9 store
