AutoHotInterception/RollMouse.ahk
2021-05-27 19:35:32 +01:00

189 lines
5.3 KiB
AutoHotkey

#SingleInstance force
#Persistent
#include Lib\AutoHotInterception.ahk
OutputDebug, DBGVIEWCLEAR
rm := new RollMouse(11)
return
class RollMouse {
; User configurable items
; The speed at which you must move the mouse to be able to trigger a roll
MoveThreshold := {x: 4, y: 4}
; Good value for my mouse with FPS games: 4
; Good value for my laptop trackpad: 3
; The speed at which to move the mouse, can be decimals (eg 0.5)
; X and Y do not need to be equal
; Good value for my mouse with FPS games: x:2, y: 1 (don't need vertical roll so much)
;~ MoveFactor := {x: 1, y: 1}
MoveFactor := {x: 0.5, y: 0.25}
; Good value for my laptop trackpad: 0.2
; How fast (in ms) to send moves when rolling.
; High values for this will cause rolls to appear jerky instead of smooth
; if you halved this, double MoveFactor to get the same move amount, but at a faster frequency.
RollFreq := 1
; How long to wait after each move to decide whether a roll has taken place.
TimeOutRate := 50
; The amount that we are currently rolling by
LastMove := {x: 0, y: 0}
; The number of previous moves stored - used to calculate vector of a roll
; Higher numbers = greater fidelity, but more CPU
MOVE_BUFFER_SIZE := 5
; Non user-configurable items
STATE_UNDER_THRESH := 1
STATE_OVER_THRESH := 2
STATE_ROLLING := 3
StateNames := ["UNDER THRESHOLD", "OVER THRESHOLD", "ROLLING"]
State := 1
TimeOutFunc := 0
History := {} ; Movement history. The most recent item is first (Index 1), and old (high index) items get pruned off the end
; Was an option in old RollMouse
Friction := 0
__New(mouseId){
this.TimeOutFunc := this.DoRoll.Bind(this)
this.AHI := new AutoHotInterception()
this.mouseId := mouseId
this.AHI.SubscribeMouseMove(this.mouseId, false, this.MouseMove.Bind(this))
}
MouseMove(x, y){
static axes := {x: 1, y: 2}
;~ ToolTip % x ", " y
moved := {x: 0, y: 0}
for axis, index in axes {
obj := {}
obj.delta_move := %axis%
obj.abs_delta_move := abs(obj.delta_move)
obj.sgn_move := (obj.abs_delta_move = obj.delta_move) ? 1 : -1
if (obj.abs_delta_move >= this.MoveThreshold[axis]){
moved[axis] := 1
}
this.UpdateHistory(axis, obj)
}
if (moved.x || moved.y){
; A move over the threshold was detected.
this.ChangeState(this.STATE_OVER_THRESH)
} else {
this.ChangeState(this.STATE_UNDER_THRESH)
}
}
UpdateHistory(axis, obj){
this.History[axis].InsertAt(1, obj)
; Enforce max number of entries
max := this.History[axis].Length()
if (max > (this.MOVE_BUFFER_SIZE - 1)){
this.History[axis].RemoveAt(max, max - this.MOVE_BUFFER_SIZE)
}
}
DoRoll(){
static axes := {x: 1, y: 2}
;s := ""
if (this.State != this.STATE_ROLLING){
; If roll has just started, calculate roll vector from movement history
this.LastMove := {x: 0, y: 0}
for axis in axes {
;s .= axis ": "
trend := 0
if (this.History[axis].Length() < this.MOVE_BUFFER_SIZE){
; ignore gestures that are too short
continue
}
Loop % this.History[axis].Length() {
if (A_Index != 1){
; Calculate the trend of the history.
trend += (this.History[axis][A_Index].delta_move - this.History[axis][A_Index-1].delta_move)
}
this.LastMove[axis] += this.History[axis][A_Index].delta_move
s .= this.History[axis][A_Index].delta_move ","
}
;s .= "(" trend ")`n"
/*
Disabled, as seems to break mouse trackpads.
Also seems to stop MoveFactor being applied to both axes?
if (sgn(trend) != sgn(this.History[axis][1].delta_move)){
; downward trend of move speed detected - this is probably a normal stop of the mouse, not a lift
continue
}
*/
this.LastMove[axis] := round(this.LastMove[axis] * this.MoveFactor[axis])
}
}
if (this.LastMove.x = 0 && this.LastMove.y = 0){
return
}
this.ChangeState(this.STATE_ROLLING)
this.Debug("ROLL DETECTED: " s "Rolling x: " this.LastMove.x ", y: " this.LastMove.y)
fn := this.MoveFunc
while (this.State == this.STATE_ROLLING){
; Send output
DllCall("user32.dll\mouse_event", "UInt", 0x0001, "UInt", this.LastMove.x, "UInt", this.LastMove.y, "UInt", 0, "UPtr", 0)
if (this.Friction){
this.LastMove.x := this.ApplyFriction(this.LastMove.x, this.Friction)
this.LastMove.y := this.ApplyFriction(this.LastMove.y, this.Friction)
if (this.LastMove.x == 0 && this.LastMove.y == 0){
this.State := this.STATE_UNDER_THRESH
break
}
}
; Wait for a bit (allow real mouse movement to be detected, which will turn off roll)
Sleep % this.RollFreq
}
}
ChangeState(newstate){
fn := this.TimeOutFunc
if (this.State != newstate){
this.Debug("Changing State to : " this.StateNames[newstate])
this.State := newstate
}
; DO NOT return if this.State == newstate!
; We need to reset the timer!
if (this.State = this.STATE_UNDER_THRESH){
; Kill the timer
SetTimer % fn, Off
; Clear the history
this.InitHistory()
} else if (this.State = this.STATE_OVER_THRESH){
; Mouse is moving fast - start timer to detect sudden stop in messages (mouse was lifted in motion)
SetTimer % fn, % this.TimeOutRate
}
/* else if (this.State = this.STATE_ROLLING){
;this.LastMove := {x: 0, y: 0}
}
*/
}
InitHistory(){
this.History := {x: [], y: []}
}
Debug(text){
OutputDebug % "AHK| " text
}
}
^Esc::
ExitApp