1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef mozilla_widget_WinMouseScrollHandler_h__
8 #define mozilla_widget_WinMouseScrollHandler_h__
9 
10 #include "nscore.h"
11 #include "nsDebug.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/EventForwards.h"
14 #include "mozilla/TimeStamp.h"
15 #include "Units.h"
16 #include <windows.h>
17 #include "nsPoint.h"
18 
19 class nsWindowBase;
20 
21 namespace mozilla {
22 namespace widget {
23 
24 class ModifierKeyState;
25 
26 struct MSGResult;
27 
28 class MouseScrollHandler {
29  public:
30   static MouseScrollHandler* GetInstance();
31 
32   static void Initialize();
33   static void Shutdown();
34 
35   static bool NeedsMessage(UINT aMsg);
36   static bool ProcessMessage(nsWindowBase* aWidget, UINT msg, WPARAM wParam,
37                              LPARAM lParam, MSGResult& aResult);
38 
39   /**
40    * See nsIWidget::SynthesizeNativeMouseScrollEvent() for the detail about
41    * this method.
42    */
43   static nsresult SynthesizeNativeMouseScrollEvent(
44       nsWindowBase* aWidget, const LayoutDeviceIntPoint& aPoint,
45       uint32_t aNativeMessage, int32_t aDelta, uint32_t aModifierFlags,
46       uint32_t aAdditionalFlags);
47 
48   /**
49    * IsWaitingInternalMessage() returns true if MouseScrollHandler posted
50    * an internal message for a native mouse wheel message and has not
51    * received it. Otherwise, false.
52    */
IsWaitingInternalMessage()53   static bool IsWaitingInternalMessage() {
54     return sInstance && sInstance->mIsWaitingInternalMessage;
55   }
56 
57  private:
58   MouseScrollHandler();
59   ~MouseScrollHandler();
60 
61   bool mIsWaitingInternalMessage;
62 
63   static void MaybeLogKeyState();
64 
65   static MouseScrollHandler* sInstance;
66 
67   /**
68    * InitEvent() initializes the aEvent.  If aPoint is null, the result of
69    * GetCurrentMessagePos() will be used.
70    */
71   static void InitEvent(nsWindowBase* aWidget, WidgetGUIEvent& aEvent,
72                         LayoutDeviceIntPoint* aPoint = nullptr);
73 
74   /**
75    * GetModifierKeyState() returns current modifier key state.
76    * Note that some devices need some hack for the modifier key state.
77    * This method does it automatically.
78    *
79    * @param aMessage    Handling message.
80    */
81   static ModifierKeyState GetModifierKeyState(UINT aMessage);
82 
83   /**
84    * MozGetMessagePos() returns the mouse cursor position when GetMessage()
85    * was called last time.  However, if we're sending a native message,
86    * this returns the specified cursor position by
87    * SynthesizeNativeMouseScrollEvent().
88    */
89   static POINTS GetCurrentMessagePos();
90 
91   /**
92    * ProcessNativeMouseWheelMessage() processes WM_MOUSEWHEEL and
93    * WM_MOUSEHWHEEL.  Additionally, processes WM_VSCROLL and WM_HSCROLL if they
94    * should be processed as mouse wheel message.
95    * This method posts MOZ_WM_MOUSEVWHEEL, MOZ_WM_MOUSEHWHEEL,
96    * MOZ_WM_VSCROLL or MOZ_WM_HSCROLL if we need to dispatch mouse scroll
97    * events.  That avoids deadlock with plugin process.
98    *
99    * @param aWidget     A window which receives the message.
100    * @param aMessage    WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL or
101    *                    WM_HSCROLL.
102    * @param aWParam     The wParam value of the message.
103    * @param aLParam     The lParam value of the message.
104    */
105   void ProcessNativeMouseWheelMessage(nsWindowBase* aWidget, UINT aMessage,
106                                       WPARAM aWParam, LPARAM aLParam);
107 
108   /**
109    * ProcessNativeScrollMessage() processes WM_VSCROLL and WM_HSCROLL.
110    * This method just call ProcessMouseWheelMessage() if the message should be
111    * processed as mouse wheel message.  Otherwise, dispatches a content
112    * command event.
113    *
114    * @param aWidget     A window which receives the message.
115    * @param aMessage    WM_VSCROLL or WM_HSCROLL.
116    * @param aWParam     The wParam value of the message.
117    * @param aLParam     The lParam value of the message.
118    * @return            TRUE if the message is processed.  Otherwise, FALSE.
119    */
120   bool ProcessNativeScrollMessage(nsWindowBase* aWidget, UINT aMessage,
121                                   WPARAM aWParam, LPARAM aLParam);
122 
123   /**
124    * HandleMouseWheelMessage() processes MOZ_WM_MOUSEVWHEEL and
125    * MOZ_WM_MOUSEHWHEEL which are posted when one of our windows received
126    * WM_MOUSEWHEEL or WM_MOUSEHWHEEL for avoiding deadlock with OOPP.
127    *
128    * @param aWidget     A window which receives the wheel message.
129    * @param aMessage    MOZ_WM_MOUSEWHEEL or MOZ_WM_MOUSEHWHEEL.
130    * @param aWParam     The wParam value of the original message.
131    * @param aLParam     The lParam value of the original message.
132    */
133   void HandleMouseWheelMessage(nsWindowBase* aWidget, UINT aMessage,
134                                WPARAM aWParam, LPARAM aLParam);
135 
136   /**
137    * HandleScrollMessageAsMouseWheelMessage() processes the MOZ_WM_VSCROLL and
138    * MOZ_WM_HSCROLL which are posted when one of mouse windows received
139    * WM_VSCROLL or WM_HSCROLL and user wants them to emulate mouse wheel
140    * message's behavior.
141    *
142    * @param aWidget     A window which receives the scroll message.
143    * @param aMessage    MOZ_WM_VSCROLL or MOZ_WM_HSCROLL.
144    * @param aWParam     The wParam value of the original message.
145    * @param aLParam     The lParam value of the original message.
146    */
147   void HandleScrollMessageAsMouseWheelMessage(nsWindowBase* aWidget,
148                                               UINT aMessage, WPARAM aWParam,
149                                               LPARAM aLParam);
150 
151   /**
152    * ComputeMessagePos() computes the cursor position when the message was
153    * added to the queue.
154    *
155    * @param aMessage    Handling message.
156    * @param aWParam     Handling message's wParam.
157    * @param aLParam     Handling message's lParam.
158    * @return            Mouse cursor position when the message is added to
159    *                    the queue or current cursor position if the result of
160    *                    ::GetMessagePos() is broken.
161    */
162   POINT ComputeMessagePos(UINT aMessage, WPARAM aWParam, LPARAM aLParam);
163 
164   class EventInfo {
165    public:
166     /**
167      * @param aWidget   An nsWindow which is handling the event.
168      * @param aMessage  Must be WM_MOUSEWHEEL or WM_MOUSEHWHEEL.
169      */
170     EventInfo(nsWindowBase* aWidget, UINT aMessage, WPARAM aWParam,
171               LPARAM aLParam);
172 
173     bool CanDispatchWheelEvent() const;
174 
GetNativeDelta()175     int32_t GetNativeDelta() const { return mDelta; }
GetWindowHandle()176     HWND GetWindowHandle() const { return mWnd; }
GetTimeStamp()177     const TimeStamp& GetTimeStamp() const { return mTimeStamp; }
IsVertical()178     bool IsVertical() const { return mIsVertical; }
IsPositive()179     bool IsPositive() const { return (mDelta > 0); }
IsPage()180     bool IsPage() const { return mIsPage; }
181 
182     /**
183      * @return          Number of lines or pages scrolled per WHEEL_DELTA.
184      */
185     int32_t GetScrollAmount() const;
186 
187    protected:
EventInfo()188     EventInfo()
189         : mIsVertical(false), mIsPage(false), mDelta(0), mWnd(nullptr) {}
190 
191     // TRUE if event is for vertical scroll.  Otherwise, FALSE.
192     bool mIsVertical;
193     // TRUE if event scrolls per page, otherwise, FALSE.
194     bool mIsPage;
195     // The native delta value.
196     int32_t mDelta;
197     // The window handle which is handling the event.
198     HWND mWnd;
199     // Timestamp of the event.
200     TimeStamp mTimeStamp;
201   };
202 
203   class LastEventInfo : public EventInfo {
204    public:
LastEventInfo()205     LastEventInfo() : EventInfo(), mAccumulatedDelta(0) {}
206 
207     /**
208      * CanContinueTransaction() checks whether the new event can continue the
209      * last transaction or not.  Note that if there is no transaction, this
210      * returns true.
211      */
212     bool CanContinueTransaction(const EventInfo& aNewEvent);
213 
214     /**
215      * ResetTransaction() resets the transaction, i.e., the instance forgets
216      * the last event information.
217      */
218     void ResetTransaction();
219 
220     /**
221      * RecordEvent() saves the information of new event.
222      */
223     void RecordEvent(const EventInfo& aEvent);
224 
225     /**
226      * InitWheelEvent() initializes NS_WHEEL_WHEEL event and
227      * recomputes the remaning detla for the event.
228      * This must be called only once during handling a message and after
229      * RecordEvent() is called.
230      *
231      * @param aWidget           A window which will dispatch the event.
232      * @param aWheelEvent       An NS_WHEEL_WHEEL event, this will be
233      *                          initialized.
234      * @param aModKeyState      Current modifier key state.
235      * @return                  TRUE if the event is ready to dispatch.
236      *                          Otherwise, FALSE.
237      */
238     bool InitWheelEvent(nsWindowBase* aWidget, WidgetWheelEvent& aWheelEvent,
239                         const ModifierKeyState& aModKeyState);
240 
241    private:
242     static int32_t RoundDelta(double aDelta);
243 
244     int32_t mAccumulatedDelta;
245   };
246 
247   LastEventInfo mLastEventInfo;
248 
249   class SystemSettings {
250    public:
SystemSettings()251     SystemSettings() : mInitialized(false) {}
252 
253     void Init();
254     void MarkDirty();
255     void NotifyUserPrefsMayOverrideSystemSettings();
256 
257     // On some environments, SystemParametersInfo() may be hooked by touchpad
258     // utility or something.  In such case, when user changes active pointing
259     // device to another one, the result of SystemParametersInfo() may be
260     // changed without WM_SETTINGCHANGE message.  For avoiding this trouble,
261     // we need to modify cache of system settings at every wheel message
262     // handling if we meet known device whose utility may hook the API.
263     void TrustedScrollSettingsDriver();
264 
265     // Returns true if the system scroll may be overridden for faster scroll.
266     // Otherwise, false.  For example, if the user maybe uses an expensive
267     // mouse which supports acceleration of scroll speed, faster scroll makes
268     // the user inconvenient.
269     bool IsOverridingSystemScrollSpeedAllowed();
270 
GetScrollAmount(bool aForVertical)271     int32_t GetScrollAmount(bool aForVertical) const {
272       MOZ_ASSERT(mInitialized, "SystemSettings must be initialized");
273       return aForVertical ? mScrollLines : mScrollChars;
274     }
275 
IsPageScroll(bool aForVertical)276     bool IsPageScroll(bool aForVertical) const {
277       MOZ_ASSERT(mInitialized, "SystemSettings must be initialized");
278       return aForVertical ? (uint32_t(mScrollLines) == WHEEL_PAGESCROLL)
279                           : (uint32_t(mScrollChars) == WHEEL_PAGESCROLL);
280     }
281 
282     // The default vertical and horizontal scrolling speed is 3, this is defined
283     // on the document of SystemParametersInfo in MSDN.
DefaultScrollLines()284     static int32_t DefaultScrollLines() { return 3; }
DefaultScrollChars()285     static int32_t DefaultScrollChars() { return 3; }
286 
287    private:
288     bool mInitialized;
289     // The result of SystemParametersInfo() may not be reliable since it may
290     // be hooked.  So, if the values are initialized with prefs, we can trust
291     // the value.  Following mIsReliableScroll* are set true when mScroll* are
292     // initialized with prefs.
293     bool mIsReliableScrollLines;
294     bool mIsReliableScrollChars;
295 
296     int32_t mScrollLines;
297     int32_t mScrollChars;
298 
299     // Returns true if cached value is changed.
300     bool InitScrollLines();
301     bool InitScrollChars();
302 
303     void RefreshCache();
304   };
305 
306   SystemSettings mSystemSettings;
307 
308   class UserPrefs {
309    public:
310     UserPrefs();
311     ~UserPrefs();
312 
313     void MarkDirty();
314 
IsScrollMessageHandledAsWheelMessage()315     bool IsScrollMessageHandledAsWheelMessage() {
316       Init();
317       return mScrollMessageHandledAsWheelMessage;
318     }
319 
IsSystemSettingCacheEnabled()320     bool IsSystemSettingCacheEnabled() {
321       Init();
322       return mEnableSystemSettingCache;
323     }
324 
IsSystemSettingCacheForciblyEnabled()325     bool IsSystemSettingCacheForciblyEnabled() {
326       Init();
327       return mForceEnableSystemSettingCache;
328     }
329 
ShouldEmulateToMakeWindowUnderCursorForeground()330     bool ShouldEmulateToMakeWindowUnderCursorForeground() {
331       Init();
332       return mEmulateToMakeWindowUnderCursorForeground;
333     }
334 
GetOverriddenVerticalScrollAmout()335     int32_t GetOverriddenVerticalScrollAmout() {
336       Init();
337       return mOverriddenVerticalScrollAmount;
338     }
339 
GetOverriddenHorizontalScrollAmout()340     int32_t GetOverriddenHorizontalScrollAmout() {
341       Init();
342       return mOverriddenHorizontalScrollAmount;
343     }
344 
GetMouseScrollTransactionTimeout()345     int32_t GetMouseScrollTransactionTimeout() {
346       Init();
347       return mMouseScrollTransactionTimeout;
348     }
349 
350    private:
351     void Init();
352 
OnChange(const char * aPrefName,void * aClosure)353     static void OnChange(const char* aPrefName, void* aClosure) {
354       static_cast<UserPrefs*>(aClosure)->MarkDirty();
355     }
356 
357     bool mInitialized;
358     bool mScrollMessageHandledAsWheelMessage;
359     bool mEnableSystemSettingCache;
360     bool mForceEnableSystemSettingCache;
361     bool mEmulateToMakeWindowUnderCursorForeground;
362     int32_t mOverriddenVerticalScrollAmount;
363     int32_t mOverriddenHorizontalScrollAmount;
364     int32_t mMouseScrollTransactionTimeout;
365   };
366 
367   UserPrefs mUserPrefs;
368 
369   class SynthesizingEvent {
370    public:
SynthesizingEvent()371     SynthesizingEvent()
372         : mWnd(nullptr),
373           mMessage(0),
374           mWParam(0),
375           mLParam(0),
376           mStatus(NOT_SYNTHESIZING) {}
377 
~SynthesizingEvent()378     ~SynthesizingEvent() {}
379 
380     static bool IsSynthesizing();
381 
382     nsresult Synthesize(const POINTS& aCursorPoint, HWND aWnd, UINT aMessage,
383                         WPARAM aWParam, LPARAM aLParam,
384                         const BYTE (&aKeyStates)[256]);
385 
386     void NativeMessageReceived(nsWindowBase* aWidget, UINT aMessage,
387                                WPARAM aWParam, LPARAM aLParam);
388 
389     void NotifyNativeMessageHandlingFinished();
390     void NotifyInternalMessageHandlingFinished();
391 
GetCursorPoint()392     const POINTS& GetCursorPoint() const { return mCursorPoint; }
393 
394    private:
395     POINTS mCursorPoint;
396     HWND mWnd;
397     UINT mMessage;
398     WPARAM mWParam;
399     LPARAM mLParam;
400     BYTE mKeyState[256];
401     BYTE mOriginalKeyState[256];
402 
403     enum Status {
404       NOT_SYNTHESIZING,
405       SENDING_MESSAGE,
406       NATIVE_MESSAGE_RECEIVED,
407       INTERNAL_MESSAGE_POSTED,
408     };
409     Status mStatus;
410 
GetStatusName()411     const char* GetStatusName() {
412       switch (mStatus) {
413         case NOT_SYNTHESIZING:
414           return "NOT_SYNTHESIZING";
415         case SENDING_MESSAGE:
416           return "SENDING_MESSAGE";
417         case NATIVE_MESSAGE_RECEIVED:
418           return "NATIVE_MESSAGE_RECEIVED";
419         case INTERNAL_MESSAGE_POSTED:
420           return "INTERNAL_MESSAGE_POSTED";
421         default:
422           return "Unknown";
423       }
424     }
425 
426     void Finish();
427   };  // SynthesizingEvent
428 
429   SynthesizingEvent* mSynthesizingEvent;
430 
431  public:
432   class Device {
433    public:
434     // SynTP is a touchpad driver of Synaptics.
435     class SynTP {
436      public:
IsDriverInstalled()437       static bool IsDriverInstalled() { return sMajorVersion != 0; }
438       /**
439        * GetDriverMajorVersion() returns the installed driver's major version.
440        * If SynTP driver isn't installed, this returns 0.
441        */
GetDriverMajorVersion()442       static int32_t GetDriverMajorVersion() { return sMajorVersion; }
443       /**
444        * GetDriverMinorVersion() returns the installed driver's minor version.
445        * If SynTP driver isn't installed, this returns -1.
446        */
GetDriverMinorVersion()447       static int32_t GetDriverMinorVersion() { return sMinorVersion; }
448 
449       static void Init();
450 
451      private:
452       static bool sInitialized;
453       static int32_t sMajorVersion;
454       static int32_t sMinorVersion;
455     };
456 
457     class Elantech {
458      public:
459       /**
460        * GetDriverMajorVersion() returns the installed driver's major version.
461        * If Elantech's driver was installed, returns 0.
462        */
463       static int32_t GetDriverMajorVersion();
464 
465       /**
466        * IsHelperWindow() checks whether aWnd is a helper window of Elantech's
467        * touchpad.  Returns TRUE if so.  Otherwise, FALSE.
468        */
469       static bool IsHelperWindow(HWND aWnd);
470 
471       /**
472        * Key message handler for Elantech's hack.  Returns TRUE if the message
473        * is consumed by this handler.  Otherwise, FALSE.
474        */
475       static bool HandleKeyMessage(nsWindowBase* aWidget, UINT aMsg,
476                                    WPARAM aWParam, LPARAM aLParam);
477 
478       static void UpdateZoomUntil();
479       static bool IsZooming();
480 
481       static void Init();
482 
IsPinchHackNeeded()483       static bool IsPinchHackNeeded() { return sUsePinchHack; }
484 
485      private:
486       // Whether to enable the Elantech swipe gesture hack.
487       static bool sUseSwipeHack;
488       // Whether to enable the Elantech pinch-to-zoom gesture hack.
489       static bool sUsePinchHack;
490       static DWORD sZoomUntil;
491     };  // class Elantech
492 
493     // Apoint is a touchpad driver of Alps.
494     class Apoint {
495      public:
IsDriverInstalled()496       static bool IsDriverInstalled() { return sMajorVersion != 0; }
497       /**
498        * GetDriverMajorVersion() returns the installed driver's major version.
499        * If Apoint driver isn't installed, this returns 0.
500        */
GetDriverMajorVersion()501       static int32_t GetDriverMajorVersion() { return sMajorVersion; }
502       /**
503        * GetDriverMinorVersion() returns the installed driver's minor version.
504        * If Apoint driver isn't installed, this returns -1.
505        */
GetDriverMinorVersion()506       static int32_t GetDriverMinorVersion() { return sMinorVersion; }
507 
508       static void Init();
509 
510      private:
511       static bool sInitialized;
512       static int32_t sMajorVersion;
513       static int32_t sMinorVersion;
514     };
515 
516     class TrackPoint {
517      public:
518       /**
519        * IsDriverInstalled() returns TRUE if TrackPoint's driver is installed.
520        * Otherwise, returns FALSE.
521        */
522       static bool IsDriverInstalled();
523     };  // class TrackPoint
524 
525     class UltraNav {
526      public:
527       /**
528        * IsObsoleteDriverInstalled() checks whether obsoleted UltraNav
529        * is installed on the environment.
530        * Returns TRUE if it was installed.  Otherwise, FALSE.
531        */
532       static bool IsObsoleteDriverInstalled();
533     };  // class UltraNav
534 
535     class SetPoint {
536      public:
537       /**
538        * SetPoint, Logitech's mouse driver, may report wrong cursor position
539        * for WM_MOUSEHWHEEL message.  See comment in the implementation for
540        * the detail.
541        */
542       static bool IsGetMessagePosResponseValid(UINT aMessage, WPARAM aWParam,
543                                                LPARAM aLParam);
544 
545      private:
546       static bool sMightBeUsing;
547     };
548 
549     static void Init();
550 
IsFakeScrollableWindowNeeded()551     static bool IsFakeScrollableWindowNeeded() {
552       return sFakeScrollableWindowNeeded;
553     }
554 
555    private:
556     /**
557      * Gets the bool value of aPrefName used to enable or disable an input
558      * workaround (like the Trackpoint hack).  The pref can take values 0 (for
559      * disabled), 1 (for enabled) or -1 (to automatically detect whether to
560      * enable the workaround).
561      *
562      * @param aPrefName The name of the pref.
563      * @param aValueIfAutomatic Whether the given input workaround should be
564      *                          enabled by default.
565      */
566     static bool GetWorkaroundPref(const char* aPrefName,
567                                   bool aValueIfAutomatic);
568 
569     static bool sFakeScrollableWindowNeeded;
570   };  // class Device
571 };
572 
573 }  // namespace widget
574 }  // namespace mozilla
575 
576 #endif  // mozilla_widget_WinMouseScrollHandler_h__
577