Bug 593372 - Part 1: Add a hack for the Elantech touchpad so that three-finger swipe left/right performs a browser back/forward action instead of page up/down r=jmathies a=blocking-betaN
authorCameron McCormack <cam@mcc.id.au>, Ehsan Akhgari <ehsan@mozilla.com>
Tue, 25 Jan 2011 10:38:41 +1300
changeset 62477 720d17e280230b308d75901f40bbff1e939cbcd5
parent 62476 3510c76c789f143101347d7d5e004a2dda650477
child 62478 ace573ee54218966898d9b9aae0cc8e1e5951869
push id1
push userroot
push dateTue, 10 Dec 2013 15:46:25 +0000
reviewersjmathies, blocking-betaN
bugs593372
milestone2.0b12pre
Bug 593372 - Part 1: Add a hack for the Elantech touchpad so that three-finger swipe left/right performs a browser back/forward action instead of page up/down r=jmathies a=blocking-betaN
modules/libpref/src/init/all.js
widget/src/windows/nsWindow.cpp
widget/src/windows/nsWindow.h
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -1809,16 +1809,21 @@ pref("mousewheel.emulate_at_wm_scroll", 
 // and 1 is on.  Set this to 1 if TrackPoint scrolling is not working.
 pref("ui.trackpoint_hack.enabled", -1);
 
 // Setting this to a non-empty string overrides the Win32 "window class" used
 // for "normal" windows. Setting this to MozillaUIWindowClass might make
 // some trackpad drivers behave better.
 pref("ui.window_class_override", "");
 
+// Enables or disables the Elantech gesture hacks.  -1 is autodetect, 0 is off,
+// and 1 is on.  Set this to 1 if three-finger swipe gestures do not cause
+// page back/forward actions, or if pinch-to-zoom does not work.
+pref("ui.elantech_gesture_hacks.enabled", -1);
+
 # WINNT
 #endif
 
 #ifdef XP_MACOSX
 // Mac specific preference defaults
 pref("browser.drag_out_of_frame_style", 1);
 pref("ui.key.saveLink.shift", false); // true = shift, false = meta
 
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -294,16 +294,18 @@ BYTE            nsWindow::sLastMouseButt
 
 // Trim heap on minimize. (initialized, but still true.)
 int             nsWindow::sTrimOnMinimize         = 2;
 
 // Default value for Trackpoint hack (used when the pref is set to -1).
 PRBool          nsWindow::sDefaultTrackPointHack  = PR_FALSE;
 // Default value for general window class (used when the pref is the empty string).
 const char*     nsWindow::sDefaultMainWindowClass = kClassNameGeneral;
+// Whether to enable the Elantech gesture hack.
+PRBool          nsWindow::sUseElantechGestureHacks = PR_FALSE;
 
 // If we're using D3D9, this will not be allowed during initial 5 seconds.
 bool            nsWindow::sAllowD3D9              = false;
 
 #ifdef ACCESSIBILITY
 BOOL            nsWindow::sIsAccessibilityOn      = FALSE;
 // Accessibility wm_getobject handler
 HINSTANCE       nsWindow::sAccLib                 = 0;
@@ -465,17 +467,17 @@ nsWindow::nsWindow() : nsBaseWidget()
 
 #if !defined(WINCE)
     if (SUCCEEDED(::OleInitialize(NULL)))
       sIsOleInitialized = TRUE;
     NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
 #endif
 
 #if !defined(WINCE)
-    InitInputHackDefaults();
+    InitInputWorkaroundPrefDefaults();
 #endif
 
     // Init titlebar button info for custom frames.
     nsUXThemeData::InitTitlebarInfo();
     // Init theme data
     nsUXThemeData::UpdateNativeThemeInfo();
 
     ForgetRedirectedKeyDownMessage();
@@ -6735,16 +6737,47 @@ UINT nsWindow::MapFromNativeToDOM(UINT a
 /* static */
 PRBool nsWindow::IsRedirectedKeyDownMessage(const MSG &aMsg)
 {
   return (aMsg.message == WM_KEYDOWN || aMsg.message == WM_SYSKEYDOWN) &&
          (sRedirectedKeyDown.message == aMsg.message &&
           GetScanCode(sRedirectedKeyDown.lParam) == GetScanCode(aMsg.lParam));
 }
 
+void
+nsWindow::PerformElantechSwipeGestureHack(UINT& aVirtualKeyCode,
+                                          nsModifierKeyState& aModKeyState)
+{
+  // The Elantech touchpad driver understands three-finger swipe left and
+  // right gestures, and translates them into Page Up and Page Down key
+  // events for most applications.  For Firefox 3.6, it instead sends
+  // Alt+Left and Alt+Right to trigger browser back/forward actions.  As
+  // with the Thinkpad Driver hack in nsWindow::Create, the change in
+  // HWND structure makes Firefox not trigger the driver's heuristics
+  // any longer.
+  //
+  // The Elantech driver actually sends these messages for a three-finger
+  // swipe right:
+  //
+  //   WM_KEYDOWN virtual_key = 0xCC or 0xFF (depending on driver version)
+  //   WM_KEYDOWN virtual_key = VK_NEXT
+  //   WM_KEYUP   virtual_key = VK_NEXT
+  //   WM_KEYUP   virtual_key = 0xCC or 0xFF
+  //
+  // so we use the 0xCC or 0xFF key modifier to detect whether the Page Down
+  // is due to the gesture rather than a regular Page Down keypress.  We then
+  // pretend that we were went an Alt+Right keystroke instead.  Similarly
+  // for VK_PRIOR and Alt+Left.
+  if ((aVirtualKeyCode == VK_NEXT || aVirtualKeyCode == VK_PRIOR) &&
+      (IS_VK_DOWN(0xFF) || IS_VK_DOWN(0xCC))) {
+    aModKeyState.mIsAltDown = true;
+    aVirtualKeyCode = aVirtualKeyCode == VK_NEXT ? VK_RIGHT : VK_LEFT;
+  }
+}
+
 /**
  * nsWindow::OnKeyDown peeks into the message queue and pulls out
  * WM_CHAR messages for processing. During testing we don't want to
  * mess with the real message queue. Instead we pass a
  * pseudo-WM_CHAR-message using this structure, and OnKeyDown will use
  * that as if it was in the message queue, and refrain from actually
  * looking at or touching the message queue.
  */
@@ -6755,16 +6788,20 @@ LRESULT nsWindow::OnKeyDown(const MSG &a
 {
   UINT virtualKeyCode =
     aMsg.wParam != VK_PROCESSKEY ? aMsg.wParam : ::ImmGetVirtualKey(mWnd);
 
 #ifndef WINCE
   gKbdLayout.OnKeyDown(virtualKeyCode);
 #endif
 
+  if (sUseElantechGestureHacks) {
+    PerformElantechSwipeGestureHack(virtualKeyCode, aModKeyState);
+  }
+
   // Use only DOMKeyCode for XP processing.
   // Use virtualKeyCode for gKbdLayout and native processing.
   UINT DOMKeyCode = nsIMM32Handler::IsComposingOn(this) ?
                       virtualKeyCode : MapFromNativeToDOM(virtualKeyCode);
 
 #ifdef DEBUG
   //printf("In OnKeyDown virt: %d\n", DOMKeyCode);
 #endif
@@ -7105,16 +7142,20 @@ LRESULT nsWindow::OnKeyDown(const MSG &a
 
 // OnKeyUp
 LRESULT nsWindow::OnKeyUp(const MSG &aMsg,
                           nsModifierKeyState &aModKeyState,
                           PRBool *aEventDispatched)
 {
   UINT virtualKeyCode = aMsg.wParam;
 
+  if (sUseElantechGestureHacks) {
+    PerformElantechSwipeGestureHack(virtualKeyCode, aModKeyState);
+  }
+
   PR_LOG(gWindowsLog, PR_LOG_ALWAYS,
          ("nsWindow::OnKeyUp VK=%d\n", virtualKeyCode));
 
   if (!nsIMM32Handler::IsComposingOn(this)) {
     virtualKeyCode = MapFromNativeToDOM(virtualKeyCode);
   }
 
   if (aEventDispatched)
@@ -8803,83 +8844,171 @@ void nsWindow::GetMainWindowClass(nsAStr
     if (NS_SUCCEEDED(rv) && !name.IsEmpty()) {
       aClass.AssignASCII(name.get());
       return;
     }
   }
   aClass.AssignASCII(sDefaultMainWindowClass);
 }
 
-PRBool nsWindow::UseTrackPointHack()
-{
+/**
+ * Gets the Boolean value of a pref used to enable or disable an input
+ * workaround (like the Trackpoint hack).  The pref can take values 0 (for
+ * disabled), 1 (for enabled) or -1 (to automatically detect whether to
+ * enable the workaround).
+ *
+ * @param aPrefName The name of the pref.
+ * @param aValueIfAutomatic Whether the given input workaround should be
+ *   enabled by default.
+ */
+PRBool nsWindow::GetInputWorkaroundPref(const char* aPrefName,
+                                        PRBool aValueIfAutomatic)
+{
+  if (!aPrefName) {
+    return aValueIfAutomatic;
+  }
+
   nsresult rv;
   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
   if (NS_SUCCEEDED(rv) && prefs) {
     PRInt32 lHackValue;
-    rv = prefs->GetIntPref("ui.trackpoint_hack.enabled", &lHackValue);
+    rv = prefs->GetIntPref(aPrefName, &lHackValue);
     if (NS_SUCCEEDED(rv)) {
       switch (lHackValue) {
         case 0: // disabled
           return PR_FALSE;
         case 1: // enabled
           return PR_TRUE;
         default: // -1: autodetect
           break;
       }
     }
   }
-  return sDefaultTrackPointHack;
+  return aValueIfAutomatic;
+}
+
+PRBool nsWindow::UseTrackPointHack()
+{
+  return GetInputWorkaroundPref("ui.trackpoint_hack.enabled",
+                                sDefaultTrackPointHack);
 }
 
 #if !defined(WINCE)
 static PRBool
-HasRegistryKey(HKEY aRoot, LPCWSTR aName)
+HasRegistryKey(HKEY aRoot, PRUnichar* aName)
 {
   HKEY key;
   LONG result = ::RegOpenKeyExW(aRoot, aName, 0, KEY_READ, &key);
   if (result != ERROR_SUCCESS)
     return PR_FALSE;
   ::RegCloseKey(key);
   return PR_TRUE;
 }
 
+/**
+ * Gets the value of a string-typed registry value.
+ *
+ * @param aRoot The registry root to search in.
+ * @param aKeyName The name of the registry key to open.
+ * @param aValueName The name of the registry value in the specified key whose
+ *   value is to be retrieved.  Can be null, to retrieve the key's unnamed/
+ *   default value.
+ * @param aBuffer The buffer into which to store the string value.  Can be null,
+ *   in which case the return value indicates just whether the value exists.
+ * @param aBufferLength The size of aBuffer, in bytes.
+ * @return Whether the value exists and is a string.
+ */
 static PRBool
-IsObsoleteSynapticsDriver()
-{
+GetRegistryKey(HKEY aRoot, PRUnichar* aKeyName, PRUnichar* aValueName, PRUnichar* aBuffer, DWORD aBufferLength)
+{
+  if (!aKeyName) {
+    return PR_FALSE;
+  }
+
   HKEY key;
-  LONG result = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE,
-      L"Software\\Synaptics\\SynTP\\Install", 0, KEY_READ, &key);
+  LONG result = ::RegOpenKeyExW(aRoot, aKeyName, NULL, KEY_READ, &key);
   if (result != ERROR_SUCCESS)
     return PR_FALSE;
   DWORD type;
-  PRUnichar buf[40];
-  DWORD buflen = sizeof(buf);
-  result = ::RegQueryValueExW(key, L"DriverVersion", NULL, &type, (BYTE*)buf, &buflen);
+  result = ::RegQueryValueExW(key, aValueName, NULL, &type, (BYTE*) aBuffer, &aBufferLength);
   ::RegCloseKey(key);
   if (result != ERROR_SUCCESS || type != REG_SZ)
     return PR_FALSE;
-  buf[NS_ARRAY_LENGTH(buf) - 1] = 0;
+  if (aBuffer)
+    aBuffer[aBufferLength / sizeof(*aBuffer) - 1] = 0;
+  return PR_TRUE;
+}
+
+static PRBool
+IsObsoleteSynapticsDriver()
+{
+  PRUnichar buf[40];
+  PRBool foundKey = GetRegistryKey(HKEY_LOCAL_MACHINE,
+                                   L"Software\\Synaptics\\SynTP\\Install",
+                                   L"DriverVersion",
+                                   buf,
+                                   sizeof buf);
+  if (!foundKey)
+    return PR_FALSE;
 
   int majorVersion = wcstol(buf, NULL, 10);
   return majorVersion < 15;
 }
 
-void nsWindow::InitInputHackDefaults()
+static PRBool
+IsObsoleteElantechDriver()
+{
+  PRUnichar buf[40];
+  // The driver version is found in one of these two registry keys.
+  PRBool foundKey = GetRegistryKey(HKEY_CURRENT_USER,
+                                   L"Software\\Elantech\\MainOption",
+                                   L"DriverVersion",
+                                   buf,
+                                   sizeof buf);
+  if (!foundKey)
+    foundKey = GetRegistryKey(HKEY_CURRENT_USER,
+                              L"Software\\Elantech",
+                              L"DriverVersion",
+                              buf,
+                              sizeof buf);
+
+  if (!foundKey)
+    return PR_FALSE;
+
+  // Assume that the major version number can be found just after a space
+  // or at the start of the string.
+  for (PRUnichar* p = buf; *p; p++) {
+    if (*p >= L'0' && *p <= L'9' && (p == buf || *(p - 1) == L' ')) {
+      int majorVersion = wcstol(p, NULL, 10);
+      // Version 7 needs the hack.
+      if (majorVersion == 7)
+        return PR_TRUE;
+    }
+  }
+
+  return PR_FALSE;
+}
+
+void nsWindow::InitInputWorkaroundPrefDefaults()
 {
   if (HasRegistryKey(HKEY_CURRENT_USER, L"Software\\Lenovo\\TrackPoint")) {
     sDefaultTrackPointHack = PR_TRUE;
   } else if (HasRegistryKey(HKEY_CURRENT_USER, L"Software\\Lenovo\\UltraNav")) {
     sDefaultTrackPointHack = PR_TRUE;
   } else if (HasRegistryKey(HKEY_CURRENT_USER, L"Software\\Alps\\Apoint\\TrackPoint")) {
     sDefaultTrackPointHack = PR_TRUE;
   } else if ((HasRegistryKey(HKEY_CURRENT_USER, L"Software\\Synaptics\\SynTPEnh\\UltraNavUSB") ||
               HasRegistryKey(HKEY_CURRENT_USER, L"Software\\Synaptics\\SynTPEnh\\UltraNavPS2")) &&
               IsObsoleteSynapticsDriver()) {
     sDefaultTrackPointHack = PR_TRUE;
   }
+
+  sUseElantechGestureHacks =
+    GetInputWorkaroundPref("ui.elantech_gesture_hacks.enabled",
+                           IsObsoleteElantechDriver());
 }
 #endif // #if !defined(WINCE)
 
 LPARAM nsWindow::lParamToScreen(LPARAM lParam)
 {
   POINT pt;
   pt.x = GET_X_LPARAM(lParam);
   pt.y = GET_Y_LPARAM(lParam);
--- a/widget/src/windows/nsWindow.h
+++ b/widget/src/windows/nsWindow.h
@@ -327,19 +327,21 @@ protected:
   virtual void            SubclassWindow(BOOL bState);
   PRBool                  CanTakeFocus();
   PRBool                  UpdateNonClientMargins(PRInt32 aSizeMode = -1, PRBool aReflowWindow = PR_TRUE);
   void                    UpdateGetWindowInfoCaptionStatus(PRBool aActiveCaption);
   void                    ResetLayout();
   void                    InvalidateNonClientRegion();
   HRGN                    ExcludeNonClientFromPaintRegion(HRGN aRegion);
 #if !defined(WINCE)
-  static void             InitInputHackDefaults();
+  static void             InitInputWorkaroundPrefDefaults();
 #endif
+  static PRBool           GetInputWorkaroundPref(const char* aPrefName, PRBool aValueIfAutomatic);
   static PRBool           UseTrackPointHack();
+  static void             PerformElantechSwipeGestureHack(UINT& aVirtualKeyCode, nsModifierKeyState& aModKeyState);
   static void             GetMainWindowClass(nsAString& aClass);
   PRBool                  HasGlass() const {
     return mTransparencyMode == eTransparencyGlass ||
            mTransparencyMode == eTransparencyBorderlessGlass;
   }
   PRBool                  IsOurProcessWindow(HWND aHWND);
   HWND                    FindOurProcessWindow(HWND aHWND);
 
@@ -535,16 +537,17 @@ protected:
   static imgIContainer* sCursorImgContainer;
   static PRBool         sSwitchKeyboardLayout;
   static PRBool         sJustGotDeactivate;
   static PRBool         sJustGotActivate;
   static PRBool         sIsInMouseCapture;
   static int            sTrimOnMinimize;
   static PRBool         sDefaultTrackPointHack;
   static const char*    sDefaultMainWindowClass;
+  static PRBool         sUseElantechGestureHacks;
   static bool           sAllowD3D9;
 #ifdef MOZ_IPC
   static PRUint32       sOOPPPluginFocusEvent;
 #endif
 
   // Non-client margin settings
   // Pre-calculated outward offset applied to default frames
   nsIntMargin           mNonClientOffset;