#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