Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Send mouse scrolls to window under mouse


  • Please log in to reply
142 replies to this topic
Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

How do I "see how many wheel events are buffered"?

Run the script and scroll on a large website, like this one. If you turn the wheel once quickly down, you see the window still scrolling after a second, or so. Exit the script and turn the wheel again: the time of the window still scrolling after you released the wheel is much shorter.

what does "120 << 16" do?

Shift left by two bytes.

Here is a more complete experimental script. I am not happy with its speed. Also, in MS Word, sometimes there is no scroll, only some flickering.
CoordMode Mouse, Screen
SetBatchLines -1
SetMouseDelay -1
Process Priority,,R

WheelTime  = 500
WheelDelta:= 120 << 17           ; doubled
WheelMax  := 5 * WheelDelta      ; to optimize
CntDelta  := 40 << 17            ; code

WheelDown::                      ; scroll window under mouse
WheelUp::
   Critical
   If (A_ThisHotKey <> A_PriorHotKey OR A_TimeSincePriorHotkey > WheelTime)
        WCnt = %WheelDelta%
   Else If (WCnt < WheelMax)
        WCnt+=  CntDelta
   MouseGetPos m_x, m_y
   If (m_x <> m_x0 OR m_y <> m_y0) {
      m_x0 = %m_x%
      m_y0 = %m_y%
      hw_m_target := DllCall("WindowFromPoint", "int",m_x, "int",m_y)
   }
   SendMessage 0x20A,((A_ThisHotKey="WheelUp")-.5)*WCnt,(m_y<<16)|m_x,,ahk_id %hw_m_target%
Return


Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Instead of buffering all the hotkeys with "critical", we can buffer only a certain number of them with the 2 lines below, added in front of the hotkey definitions (and removing critical)
#MaxThreadsBuffer On

#MaxThreadsPerHotkey 5
With this shorter memory there is no such a long scrolling period, after the whell was released.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
This version seems to work the best, so far. It has geometrically increasing acceleration. Instead of SendMessage, it uses PostMessage, which does not wait for a response. Sleep 0 is used to give the application time to scroll. This way there is no need for buffering.
CoordMode Mouse, Screen
SetBatchLines -1

WheelTime  = 500                 ; when to reset counting
WheelDelta:= 120 << 17           ; doubled, to optimize code

WheelDown::
WheelUp::
   If (A_ThisHotKey <> A_PriorHotKey OR A_TimeSincePriorHotkey > WheelTime)
        WCnt = %WheelDelta%
   Else WCnt+= WCnt>>2
   MouseGetPos m_x, m_y
   hw_m_target := DllCall("WindowFromPoint", "int",m_x, "int",m_y)
   PostMessage 0x20A,((A_ThisHotKey="WheelUp")-.5)*WCnt,(m_y<<16)|m_x,,ahk_id %hw_m_target%
   Sleep 0
Return

Question: "MouseGetPos m_x, m_y, hw_m_target" should get the window ID under the mouse pointer (so there were no need for the dll call WindowFromPoint), but most of the time it returns the wrong value (MS Word seems to work, but most other applications don't). Is it a bug in AHK?

Babis
  • Members
  • 69 posts
  • Last active: Aug 26 2015 09:31 AM
  • Joined: 08 Dec 2005
Very nice thanks :)

With the last script, in some progs like editplus, if I scroll hard, the scroll bar after reaches the end starts again from the beginning. Small bug maybe?

I love the acceleration.

evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
There's still the same problem with, if you scroll too fast, then you end up not scrolling as far as if you'd just scrolled slowly. It can probably be worked around with the acceleration / time since previous hotkey, but maybe Chris knows the exact cause of the wheel up/down hotkeys seemingly not receiving all events when scrolled very fast?

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
The answer may be that the system combines fast consecutive wheel turns into a single event with a larger-than-usual delta/movement parameter. If this is in fact what happens, AHK will only generate one WheelUp/Down hotkey event even when the delta is larger than usual.

I will investigate and try to come up with a solution. Thanks for pointing it out.

evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
Thanks for taking a look :) It can probably be worked around for a script like this (although it may be a bit ugly), but for other scripts that might need to keep a more accurate count it could be a big problem (at least worth mentioning in the manual if it can't be solved).

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Chris: do you know why MouseGetPos returns a window ID which is different from what WindowFromPoint does? (My question a few posts ago.) It looks like the later returns the ID of the control. Who knew that controls have that, too?

But, we don't really need WindowFromPoint. MouseGetPos returns the window ID and the name of the control under the mouse pointer. If we use them both in SendMessage, it works:
...
   MouseGetPos m_x, m_y, WinID, Ctrl
   PostMessage 0x20A,((A_ThisHotKey="WheelUp")-.5)*WCnt,(m_y<<16)|m_x,%Ctrl%,ahk_id %WinID%
...


Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004

do you know why MouseGetPos returns a window ID which is different from what WindowFromPoint does?

It's designed to report the parent window's unique ID, never the control's. This is because AHK is largely oriented toward parent windows when it comes to unique IDs. This philosophy may shift in the future since there is growing demand to operate upon controls individually.

By the way, the ahk_id technique can be used to directly operate on a control via PostMessage and other windowing commands. But you have to know its unique ID (HWND), either via the GetChildHWND() function (on the DllCall page) or some other function like the WindowFromPoint() you mentioned.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Thanks, Chris. The Control, WinID combination from MouseGetPos seems to work, though.

Here is yet another experimental version. It starts with setting up the acceleration profile, in this case a geometrically increasing sequence from 1 to 250, stored in the array S. (Be careful, not to use too large values. They could be interpreted as negative by some applications.)

The hotkeys start with determining the speed of the wheel. It fluctuates strongly, therefore the average speed of the last two wheel events are computed and stored in the variable Speed. For that, we need to buffer one hotkey (#MaxThreadsPerHotkey 2); otherwise the speed computation would be wrong at fast turning wheel, when an event could be lost. The tick counter sometimes does not change between the hotkey routine activations, so we add 10 ms to the time difference. It avoids divisions with 0, and also limits the speeds values to be between 1 and 31 (with the numerator 300).

The distance, how much we want to scroll is dependent on the speed of the wheel. Its value is taken from the array S, and accumulated in the variable WCnt.

If the last wheel event was sent to the application within 30 ms, we just return, and will send the next wheel event with a larger scroll value. This way we don't send too many messages.

If enough time has passed since the last scroll, look where the mouse pointer is, and send the corresponding control a WM_MOUSEWHEEL message with the accumulated scroll amount in WCnt. Its sign (scroll up or down) is determined from the current hotkey name.

Most applications don't need to know the mouse position, but some, like MS Internet Explorer do not scroll, if the absolute screen coordinates are not included in the message, as two 16-bit values.

Sometimes, when the wheel is turned too fast, some mice randomly generate wheel events in the wrong direction. To make the script more robust, we could check if after WheelUp events there were a few WheelDown events within a very short time (or the other way around). If there were, we should ignore them, but let's fix possible other problems first.
CoordMode Mouse, Screen
Loop 31
   S%A_index% := Round(.5*1.222**A_Index)*(120 << 17) ; doubled, to optimize code

#MaxThreadsPerHotkey 2
WheelDown::
WheelUp::
   Speed := 1 + 300//(A_TickCount-Tick0+10)
   Tick0 = %Tick%
   Tick  = %A_TickCount%
   WCnt  += S%Speed%
   If (Tick < WTick + 30)
      Return
   WTick = %Tick%
   MouseGetPos m_x, m_y, WinID, Ctrl
   PostMessage 0x20A,((A_ThisHotKey="WheelUp")-.5)*WCnt,(m_y<<16)|m_x,%Ctrl%,ahk_id %WinID%
   WCnt = 0
Return


Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
In today's v1.0.43.03, mouse wheel hotkeys (WheelDown/Up) report the number of wheel turns in A_EventInfo, which allows distinguishing between fast and slow wheel movement.

Please give this a try and let me know if it helps enough. Thanks.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
The A_EventInfo in mouse wheel hotkeys of AHK v1.0.43.03 works very well. Thanks, Chris!

The script below utilizes this new feature (this time with a linear acceleration profile). It is the simplest and the one closest in behavior to the original wheel handling of Windows, except, the control under the mouse pointer is scrolled, not the active one.
CoordMode Mouse, Screen
n = 20
Loop %n%
   S%A_index% := (4*A_Index-3)*(120 << 17)   ; Linear acceleration

#MaxThreadsPerHotkey 2                       ; Buffer one wheel event
WheelDown::
WheelUp::
   Events += A_EventInfo                     ; Accumulate events
   IfLess A_TimeSincePriorHotkey,20, Return  ; Not to send wheel events too often
   Speed := 1 + 16*Events//A_TimeSincePriorHotkey
   Events = 0                                ; Reset counting
   IfGreater Speed,%n%, SetEnv Speed,%n%     ; Max speed at S%n%
   MouseGetPos m_x, m_y, WinID, Ctrl
   PostMessage 0x20A,((A_ThisHotKey="WheelUp")-.5)*S%Speed%,(m_y<<16)|m_x,%Ctrl%,ahk_id %WinID%
Return
There are several parameters to control the acceleration:
- n = the number of different speed values we distinguish. 10 works reasonably well, too.
- The number 4 in setting up the acceleration profile (S%A_index% := (4*A_Index-3)...). A larger value increases the speedup at faster turning the wheel, but reduces the accuracy at low speeds. Subtract one less in the expression above, to have 1 scroll step at low speeds.
- The number 16 in calculating the speed of the wheel (Speed := 1 + 16*Events//A_TimeSincePriorHotkey). A larger value leads to higher speeds values computed, which reduces the speed-resolution.

With a simpler acceleration profile we could eliminate the S array all together.
CoordMode Mouse, Screen
WheelDown::
WheelUp::
   Events += A_EventInfo                     ; Accumulate events
   IfLess A_TimeSincePriorHotkey,20, Return  ; Not to send wheel events too often
   MouseGetPos m_x, m_y, WinID, Ctrl
   PostMessage 0x20A,((A_ThisHotKey="WheelUp")-.5)*Events*(120<<17),(m_y<<16)|m_x,%Ctrl%,ahk_id %WinID%
   Events = 0                                ; Reset counting
Return
Sending wheel events too often to the applications (like MSIE6) leads to lazy reactions. If you don't mind it, the script can be further simplified, and still works OK.
CoordMode Mouse, Screen
WheelDown::
WheelUp::
   MouseGetPos m_x, m_y, WinID, Ctrl
   PostMessage 0x20A,((A_ThisHotKey="WheelUp")-.5)*A_EventInfo*(120<<17),(m_y<<16)|m_x,%Ctrl%,ahk_id %WinID%
Return


evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
I've tried testing both the short and longer versions but have found that PSPad and the main window in Avant Browser are ignoring the PostMessage - is there another way to scroll a control under the mouse? (I think Katmouse has some extra code to do with a few specific apps but, must use another method for generic programs like these).

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
How about Shimanov's solution?
...

   MouseGetPos m_x, m_y 

   hw_m_target := DllCall("WindowFromPoint", "int",m_x, "int",m_y) 

   PostMessage 0x20A,((A_ThisHotKey="WheelUp")-.5)*A_EventInfo*(120<<17),(m_y<<16)|m_x,,ahk_id %hw_m_target% 

...


evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
Well that just scrolls the active control in the active window (like normal windows behaviour), not the control under the mouse - which is why I use KatMouse in the first place.