gecko-dev/widget/windows/WinMouseScrollHandler.cpp
thomasmo c3ba51407b Bug 1570124 - Enable mouse scrolling in FxR window r=masayuki
This change addresses two issues with vrhost sending WM_MOUSEWHEEL events:
- The point from the message had an incorrect coordinate origin. Documentation specifices that it should be screen, rather than window/client, origin. Since vrhost only knows about a position in the window, it translates the point before sending the message.
- Gecko ignores the point passed in to the window message and instead uses the point from GetMessagePos. As warnings indicate, this can be incorrect, as is exposed with vrhost. This change now uses this point from the message when available.

Differential Revision: https://phabricator.services.mozilla.com/D51322

--HG--
extra : moz-landing-system : lando
2019-11-06 20:38:45 +00:00

1705 lines
61 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/DebugOnly.h"
#include "mozilla/Logging.h"
#include "WinMouseScrollHandler.h"
#include "nsWindow.h"
#include "nsWindowDefs.h"
#include "KeyboardLayout.h"
#include "WinUtils.h"
#include "nsGkAtoms.h"
#include "nsIDOMWindowUtils.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/WheelEventBinding.h"
#include "mozilla/StaticPrefs_mousewheel.h"
#include <psapi.h>
namespace mozilla {
namespace widget {
LazyLogModule gMouseScrollLog("MouseScrollHandlerWidgets");
static const char* GetBoolName(bool aBool) { return aBool ? "TRUE" : "FALSE"; }
MouseScrollHandler* MouseScrollHandler::sInstance = nullptr;
bool MouseScrollHandler::Device::sFakeScrollableWindowNeeded = false;
bool MouseScrollHandler::Device::SynTP::sInitialized = false;
int32_t MouseScrollHandler::Device::SynTP::sMajorVersion = 0;
int32_t MouseScrollHandler::Device::SynTP::sMinorVersion = -1;
bool MouseScrollHandler::Device::Elantech::sUseSwipeHack = false;
bool MouseScrollHandler::Device::Elantech::sUsePinchHack = false;
DWORD MouseScrollHandler::Device::Elantech::sZoomUntil = 0;
bool MouseScrollHandler::Device::Apoint::sInitialized = false;
int32_t MouseScrollHandler::Device::Apoint::sMajorVersion = 0;
int32_t MouseScrollHandler::Device::Apoint::sMinorVersion = -1;
bool MouseScrollHandler::Device::SetPoint::sMightBeUsing = false;
// The duration until timeout of events transaction. The value is 1.5 sec,
// it's just a magic number, it was suggested by Logitech's engineer, see
// bug 605648 comment 90.
#define DEFAULT_TIMEOUT_DURATION 1500
/******************************************************************************
*
* MouseScrollHandler
*
******************************************************************************/
/* static */
POINTS
MouseScrollHandler::GetCurrentMessagePos() {
if (SynthesizingEvent::IsSynthesizing()) {
return sInstance->mSynthesizingEvent->GetCursorPoint();
}
DWORD pos = ::GetMessagePos();
return MAKEPOINTS(pos);
}
// Get rid of the GetMessagePos() API.
#define GetMessagePos()
/* static */
void MouseScrollHandler::Initialize() { Device::Init(); }
/* static */
void MouseScrollHandler::Shutdown() {
delete sInstance;
sInstance = nullptr;
}
/* static */
MouseScrollHandler* MouseScrollHandler::GetInstance() {
if (!sInstance) {
sInstance = new MouseScrollHandler();
}
return sInstance;
}
MouseScrollHandler::MouseScrollHandler()
: mIsWaitingInternalMessage(false), mSynthesizingEvent(nullptr) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll: Creating an instance, this=%p, sInstance=%p", this,
sInstance));
}
MouseScrollHandler::~MouseScrollHandler() {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll: Destroying an instance, this=%p, sInstance=%p", this,
sInstance));
delete mSynthesizingEvent;
}
/* static */
void MouseScrollHandler::MaybeLogKeyState() {
if (!MOZ_LOG_TEST(gMouseScrollLog, LogLevel::Debug)) {
return;
}
BYTE keyboardState[256];
if (::GetKeyboardState(keyboardState)) {
for (size_t i = 0; i < ArrayLength(keyboardState); i++) {
if (keyboardState[i]) {
MOZ_LOG(
gMouseScrollLog, LogLevel::Debug,
(" Current key state: keyboardState[0x%02X]=0x%02X (%s)", i,
keyboardState[i],
((keyboardState[i] & 0x81) == 0x81)
? "Pressed and Toggled"
: (keyboardState[i] & 0x80)
? "Pressed"
: (keyboardState[i] & 0x01) ? "Toggled" : "Unknown"));
}
}
} else {
MOZ_LOG(
gMouseScrollLog, LogLevel::Debug,
("MouseScroll::MaybeLogKeyState(): Failed to print current keyboard "
"state"));
}
}
/* static */
bool MouseScrollHandler::NeedsMessage(UINT aMsg) {
switch (aMsg) {
case WM_SETTINGCHANGE:
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
case WM_HSCROLL:
case WM_VSCROLL:
case MOZ_WM_MOUSEVWHEEL:
case MOZ_WM_MOUSEHWHEEL:
case MOZ_WM_HSCROLL:
case MOZ_WM_VSCROLL:
case WM_KEYDOWN:
case WM_KEYUP:
return true;
}
return false;
}
/* static */
bool MouseScrollHandler::ProcessMessage(nsWindowBase* aWidget, UINT msg,
WPARAM wParam, LPARAM lParam,
MSGResult& aResult) {
Device::Elantech::UpdateZoomUntil();
switch (msg) {
case WM_SETTINGCHANGE:
if (!sInstance) {
return false;
}
if (wParam == SPI_SETWHEELSCROLLLINES ||
wParam == SPI_SETWHEELSCROLLCHARS) {
sInstance->mSystemSettings.MarkDirty();
}
return false;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
GetInstance()->ProcessNativeMouseWheelMessage(aWidget, msg, wParam,
lParam);
sInstance->mSynthesizingEvent->NotifyNativeMessageHandlingFinished();
// We don't need to call next wndproc for WM_MOUSEWHEEL and
// WM_MOUSEHWHEEL. We should consume them always. If the messages
// would be handled by our window again, it caused making infinite
// message loop.
aResult.mConsumed = true;
aResult.mResult = (msg != WM_MOUSEHWHEEL);
return true;
case WM_HSCROLL:
case WM_VSCROLL:
aResult.mConsumed = GetInstance()->ProcessNativeScrollMessage(
aWidget, msg, wParam, lParam);
sInstance->mSynthesizingEvent->NotifyNativeMessageHandlingFinished();
aResult.mResult = 0;
return true;
case MOZ_WM_MOUSEVWHEEL:
case MOZ_WM_MOUSEHWHEEL:
GetInstance()->HandleMouseWheelMessage(aWidget, msg, wParam, lParam);
sInstance->mSynthesizingEvent->NotifyInternalMessageHandlingFinished();
// Doesn't need to call next wndproc for internal wheel message.
aResult.mConsumed = true;
return true;
case MOZ_WM_HSCROLL:
case MOZ_WM_VSCROLL:
GetInstance()->HandleScrollMessageAsMouseWheelMessage(aWidget, msg,
wParam, lParam);
sInstance->mSynthesizingEvent->NotifyInternalMessageHandlingFinished();
// Doesn't need to call next wndproc for internal scroll message.
aResult.mConsumed = true;
return true;
case WM_KEYDOWN:
case WM_KEYUP:
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessMessage(): aWidget=%p, "
"msg=%s(0x%04X), wParam=0x%02X, ::GetMessageTime()=%d",
aWidget,
msg == WM_KEYDOWN ? "WM_KEYDOWN"
: msg == WM_KEYUP ? "WM_KEYUP" : "Unknown",
msg, wParam, ::GetMessageTime()));
MaybeLogKeyState();
if (Device::Elantech::HandleKeyMessage(aWidget, msg, wParam, lParam)) {
aResult.mResult = 0;
aResult.mConsumed = true;
return true;
}
return false;
default:
return false;
}
}
/* static */
nsresult MouseScrollHandler::SynthesizeNativeMouseScrollEvent(
nsWindowBase* aWidget, const LayoutDeviceIntPoint& aPoint,
uint32_t aNativeMessage, int32_t aDelta, uint32_t aModifierFlags,
uint32_t aAdditionalFlags) {
bool useFocusedWindow = !(
aAdditionalFlags & nsIDOMWindowUtils::MOUSESCROLL_PREFER_WIDGET_AT_POINT);
POINT pt;
pt.x = aPoint.x;
pt.y = aPoint.y;
HWND target = useFocusedWindow ? ::WindowFromPoint(pt) : ::GetFocus();
NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
WPARAM wParam = 0;
LPARAM lParam = 0;
switch (aNativeMessage) {
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL: {
lParam = MAKELPARAM(pt.x, pt.y);
WORD mod = 0;
if (aModifierFlags & (nsIWidget::CTRL_L | nsIWidget::CTRL_R)) {
mod |= MK_CONTROL;
}
if (aModifierFlags & (nsIWidget::SHIFT_L | nsIWidget::SHIFT_R)) {
mod |= MK_SHIFT;
}
wParam = MAKEWPARAM(mod, aDelta);
break;
}
case WM_VSCROLL:
case WM_HSCROLL:
lParam = (aAdditionalFlags &
nsIDOMWindowUtils::MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL)
? reinterpret_cast<LPARAM>(target)
: 0;
wParam = aDelta;
break;
default:
return NS_ERROR_INVALID_ARG;
}
// Ensure to make the instance.
GetInstance();
BYTE kbdState[256];
memset(kbdState, 0, sizeof(kbdState));
AutoTArray<KeyPair, 10> keySequence;
WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags,
aNativeMessage);
for (uint32_t i = 0; i < keySequence.Length(); ++i) {
uint8_t key = keySequence[i].mGeneral;
uint8_t keySpecific = keySequence[i].mSpecific;
kbdState[key] = 0x81; // key is down and toggled on if appropriate
if (keySpecific) {
kbdState[keySpecific] = 0x81;
}
}
if (!sInstance->mSynthesizingEvent) {
sInstance->mSynthesizingEvent = new SynthesizingEvent();
}
POINTS pts;
pts.x = static_cast<SHORT>(pt.x);
pts.y = static_cast<SHORT>(pt.y);
return sInstance->mSynthesizingEvent->Synthesize(pts, target, aNativeMessage,
wParam, lParam, kbdState);
}
/* static */
void MouseScrollHandler::InitEvent(nsWindowBase* aWidget,
WidgetGUIEvent& aEvent, LPARAM* aPoint) {
NS_ENSURE_TRUE_VOID(aWidget);
// If a point is provided, use it; otherwise, get current message point or
// synthetic point
POINTS pointOnScreen;
if (aPoint != nullptr) {
pointOnScreen = MAKEPOINTS(*aPoint);
} else {
pointOnScreen = GetCurrentMessagePos();
}
// InitEvent expects the point to be in window coordinates, so translate the
// point from screen coordinates.
POINT pointOnWindow;
POINTSTOPOINT(pointOnWindow, pointOnScreen);
::ScreenToClient(aWidget->GetWindowHandle(), &pointOnWindow);
LayoutDeviceIntPoint point;
point.x = pointOnWindow.x;
point.y = pointOnWindow.y;
aWidget->InitEvent(aEvent, &point);
}
/* static */
ModifierKeyState MouseScrollHandler::GetModifierKeyState(UINT aMessage) {
ModifierKeyState result;
// Assume the Control key is down if the Elantech touchpad has sent the
// mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages. (See the comment in
// MouseScrollHandler::Device::Elantech::HandleKeyMessage().)
if ((aMessage == MOZ_WM_MOUSEVWHEEL || aMessage == WM_MOUSEWHEEL) &&
!result.IsControl() && Device::Elantech::IsZooming()) {
// XXX Do we need to unset MODIFIER_SHIFT, MODIFIER_ALT, MODIFIER_OS too?
// If one of them are true, the default action becomes not zooming.
result.Unset(MODIFIER_ALTGRAPH);
result.Set(MODIFIER_CONTROL);
}
return result;
}
POINT
MouseScrollHandler::ComputeMessagePos(UINT aMessage, WPARAM aWParam,
LPARAM aLParam) {
POINT point;
if (Device::SetPoint::IsGetMessagePosResponseValid(aMessage, aWParam,
aLParam)) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::ComputeMessagePos: Using ::GetCursorPos()"));
::GetCursorPos(&point);
} else {
POINTS pts = GetCurrentMessagePos();
point.x = pts.x;
point.y = pts.y;
}
return point;
}
void MouseScrollHandler::ProcessNativeMouseWheelMessage(nsWindowBase* aWidget,
UINT aMessage,
WPARAM aWParam,
LPARAM aLParam) {
if (SynthesizingEvent::IsSynthesizing()) {
mSynthesizingEvent->NativeMessageReceived(aWidget, aMessage, aWParam,
aLParam);
}
POINT point = ComputeMessagePos(aMessage, aWParam, aLParam);
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessNativeMouseWheelMessage: aWidget=%p, "
"aMessage=%s, wParam=0x%08X, lParam=0x%08X, point: { x=%d, y=%d }",
aWidget,
aMessage == WM_MOUSEWHEEL
? "WM_MOUSEWHEEL"
: aMessage == WM_MOUSEHWHEEL
? "WM_MOUSEHWHEEL"
: aMessage == WM_VSCROLL ? "WM_VSCROLL" : "WM_HSCROLL",
aWParam, aLParam, point.x, point.y));
MaybeLogKeyState();
HWND underCursorWnd = ::WindowFromPoint(point);
if (!underCursorWnd) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessNativeMouseWheelMessage: "
"No window is not found under the cursor"));
return;
}
if (Device::Elantech::IsPinchHackNeeded() &&
Device::Elantech::IsHelperWindow(underCursorWnd)) {
// The Elantech driver places a window right underneath the cursor
// when sending a WM_MOUSEWHEEL event to us as part of a pinch-to-zoom
// gesture. We detect that here, and search for our window that would
// be beneath the cursor if that window wasn't there.
underCursorWnd = WinUtils::FindOurWindowAtPoint(point);
if (!underCursorWnd) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessNativeMouseWheelMessage: "
"Our window is not found under the Elantech helper window"));
return;
}
}
// Handle most cases first. If the window under mouse cursor is our window
// except plugin window (MozillaWindowClass), we should handle the message
// on the window.
if (WinUtils::IsOurProcessWindow(underCursorWnd)) {
nsWindowBase* destWindow = WinUtils::GetNSWindowBasePtr(underCursorWnd);
if (!destWindow) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessNativeMouseWheelMessage: "
"Found window under the cursor isn't managed by nsWindow..."));
HWND wnd = ::GetParent(underCursorWnd);
for (; wnd; wnd = ::GetParent(wnd)) {
destWindow = WinUtils::GetNSWindowBasePtr(wnd);
if (destWindow) {
break;
}
}
if (!wnd) {
MOZ_LOG(
gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessNativeMouseWheelMessage: Our window which is "
"managed by nsWindow is not found under the cursor"));
return;
}
}
MOZ_ASSERT(destWindow, "destWindow must not be NULL");
// Some odd touchpad utils sets focus to window under the mouse cursor.
// this emulates the odd behavior for debug.
if (mUserPrefs.ShouldEmulateToMakeWindowUnderCursorForeground() &&
(aMessage == WM_MOUSEWHEEL || aMessage == WM_MOUSEHWHEEL) &&
::GetForegroundWindow() != destWindow->GetWindowHandle()) {
::SetForegroundWindow(destWindow->GetWindowHandle());
}
// If the found window is our plugin window, it means that the message
// has been handled by the plugin but not consumed. We should handle the
// message on its parent window. However, note that the DOM event may
// cause accessing the plugin. Therefore, we should unlock the plugin
// process by using PostMessage().
if (destWindow->IsPlugin()) {
destWindow = destWindow->GetParentWindowBase(false);
if (!destWindow) {
MOZ_LOG(
gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessNativeMouseWheelMessage: "
"Our window which is a parent of a plugin window is not found"));
return;
}
}
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, "
"Posting internal message to an nsWindow (%p)...",
destWindow));
mIsWaitingInternalMessage = true;
UINT internalMessage = WinUtils::GetInternalMessage(aMessage);
::PostMessage(destWindow->GetWindowHandle(), internalMessage, aWParam,
aLParam);
return;
}
// If the window under cursor is not in our process, it means:
// 1. The window may be a plugin window (GeckoPluginWindow or its descendant).
// 2. The window may be another application's window.
HWND pluginWnd = WinUtils::FindOurProcessWindow(underCursorWnd);
if (!pluginWnd) {
// If there is no plugin window in ancestors of the window under cursor,
// the window is for another applications (case 2).
// We don't need to handle this message.
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessNativeMouseWheelMessage: "
"Our window is not found under the cursor"));
return;
}
// If we're a plugin window (MozillaWindowClass) and cursor in this window,
// the message shouldn't go to plugin's wndproc again. So, we should handle
// it on parent window. However, note that the DOM event may cause accessing
// the plugin. Therefore, we should unlock the plugin process by using
// PostMessage().
if (aWidget->IsPlugin() && aWidget->GetWindowHandle() == pluginWnd) {
nsWindowBase* destWindow = aWidget->GetParentWindowBase(false);
if (!destWindow) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessNativeMouseWheelMessage: Our normal window "
"which "
"is a parent of this plugin window is not found"));
return;
}
MOZ_LOG(
gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, "
"Posting internal message to an nsWindow (%p) which is parent of this "
"plugin window...",
destWindow));
mIsWaitingInternalMessage = true;
UINT internalMessage = WinUtils::GetInternalMessage(aMessage);
::PostMessage(destWindow->GetWindowHandle(), internalMessage, aWParam,
aLParam);
return;
}
// If the window is a part of plugin, we should post the message to it.
MOZ_LOG(
gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, "
"Redirecting the message to a window which is a plugin child window"));
::PostMessage(underCursorWnd, aMessage, aWParam, aLParam);
}
bool MouseScrollHandler::ProcessNativeScrollMessage(nsWindowBase* aWidget,
UINT aMessage,
WPARAM aWParam,
LPARAM aLParam) {
if (aLParam || mUserPrefs.IsScrollMessageHandledAsWheelMessage()) {
// Scroll message generated by Thinkpad Trackpoint Driver or similar
// Treat as a mousewheel message and scroll appropriately
ProcessNativeMouseWheelMessage(aWidget, aMessage, aWParam, aLParam);
// Always consume the scroll message if we try to emulate mouse wheel
// action.
return true;
}
if (SynthesizingEvent::IsSynthesizing()) {
mSynthesizingEvent->NativeMessageReceived(aWidget, aMessage, aWParam,
aLParam);
}
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::ProcessNativeScrollMessage: aWidget=%p, "
"aMessage=%s, wParam=0x%08X, lParam=0x%08X",
aWidget, aMessage == WM_VSCROLL ? "WM_VSCROLL" : "WM_HSCROLL",
aWParam, aLParam));
// Scroll message generated by external application
WidgetContentCommandEvent commandEvent(true, eContentCommandScroll, aWidget);
commandEvent.mScroll.mIsHorizontal = (aMessage == WM_HSCROLL);
switch (LOWORD(aWParam)) {
case SB_LINEUP: // SB_LINELEFT
commandEvent.mScroll.mUnit =
WidgetContentCommandEvent::eCmdScrollUnit_Line;
commandEvent.mScroll.mAmount = -1;
break;
case SB_LINEDOWN: // SB_LINERIGHT
commandEvent.mScroll.mUnit =
WidgetContentCommandEvent::eCmdScrollUnit_Line;
commandEvent.mScroll.mAmount = 1;
break;
case SB_PAGEUP: // SB_PAGELEFT
commandEvent.mScroll.mUnit =
WidgetContentCommandEvent::eCmdScrollUnit_Page;
commandEvent.mScroll.mAmount = -1;
break;
case SB_PAGEDOWN: // SB_PAGERIGHT
commandEvent.mScroll.mUnit =
WidgetContentCommandEvent::eCmdScrollUnit_Page;
commandEvent.mScroll.mAmount = 1;
break;
case SB_TOP: // SB_LEFT
commandEvent.mScroll.mUnit =
WidgetContentCommandEvent::eCmdScrollUnit_Whole;
commandEvent.mScroll.mAmount = -1;
break;
case SB_BOTTOM: // SB_RIGHT
commandEvent.mScroll.mUnit =
WidgetContentCommandEvent::eCmdScrollUnit_Whole;
commandEvent.mScroll.mAmount = 1;
break;
default:
return false;
}
// XXX If this is a plugin window, we should dispatch the event from
// parent window.
aWidget->DispatchContentCommandEvent(&commandEvent);
return true;
}
void MouseScrollHandler::HandleMouseWheelMessage(nsWindowBase* aWidget,
UINT aMessage, WPARAM aWParam,
LPARAM aLParam) {
MOZ_ASSERT((aMessage == MOZ_WM_MOUSEVWHEEL || aMessage == MOZ_WM_MOUSEHWHEEL),
"HandleMouseWheelMessage must be called with "
"MOZ_WM_MOUSEVWHEEL or MOZ_WM_MOUSEHWHEEL");
MOZ_LOG(
gMouseScrollLog, LogLevel::Info,
("MouseScroll::HandleMouseWheelMessage: aWidget=%p, "
"aMessage=MOZ_WM_MOUSE%sWHEEL, aWParam=0x%08X, aLParam=0x%08X",
aWidget, aMessage == MOZ_WM_MOUSEVWHEEL ? "V" : "H", aWParam, aLParam));
mIsWaitingInternalMessage = false;
// If it's not allowed to cache system settings, we need to reset the cache
// before handling the mouse wheel message.
mSystemSettings.TrustedScrollSettingsDriver();
EventInfo eventInfo(aWidget, WinUtils::GetNativeMessage(aMessage), aWParam,
aLParam);
if (!eventInfo.CanDispatchWheelEvent()) {
MOZ_LOG(
gMouseScrollLog, LogLevel::Info,
("MouseScroll::HandleMouseWheelMessage: Cannot dispatch the events"));
mLastEventInfo.ResetTransaction();
return;
}
// Discard the remaining delta if current wheel message and last one are
// received by different window or to scroll different direction or
// different unit scroll. Furthermore, if the last event was too old.
if (!mLastEventInfo.CanContinueTransaction(eventInfo)) {
mLastEventInfo.ResetTransaction();
}
mLastEventInfo.RecordEvent(eventInfo);
ModifierKeyState modKeyState = GetModifierKeyState(aMessage);
// Grab the widget, it might be destroyed by a DOM event handler.
RefPtr<nsWindowBase> kungFuDethGrip(aWidget);
WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
if (mLastEventInfo.InitWheelEvent(aWidget, wheelEvent, modKeyState,
aLParam)) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::HandleMouseWheelMessage: dispatching "
"eWheel event"));
aWidget->DispatchWheelEvent(&wheelEvent);
if (aWidget->Destroyed()) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::HandleMouseWheelMessage: The window was destroyed "
"by eWheel event"));
mLastEventInfo.ResetTransaction();
return;
}
} else {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::HandleMouseWheelMessage: eWheel event is not "
"dispatched"));
}
}
void MouseScrollHandler::HandleScrollMessageAsMouseWheelMessage(
nsWindowBase* aWidget, UINT aMessage, WPARAM aWParam, LPARAM aLParam) {
MOZ_ASSERT((aMessage == MOZ_WM_VSCROLL || aMessage == MOZ_WM_HSCROLL),
"HandleScrollMessageAsMouseWheelMessage must be called with "
"MOZ_WM_VSCROLL or MOZ_WM_HSCROLL");
mIsWaitingInternalMessage = false;
ModifierKeyState modKeyState = GetModifierKeyState(aMessage);
WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
double& delta =
(aMessage == MOZ_WM_VSCROLL) ? wheelEvent.mDeltaY : wheelEvent.mDeltaX;
int32_t& lineOrPageDelta = (aMessage == MOZ_WM_VSCROLL)
? wheelEvent.mLineOrPageDeltaY
: wheelEvent.mLineOrPageDeltaX;
delta = 1.0;
lineOrPageDelta = 1;
switch (LOWORD(aWParam)) {
case SB_PAGEUP:
delta = -1.0;
lineOrPageDelta = -1;
case SB_PAGEDOWN:
wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_PAGE;
break;
case SB_LINEUP:
delta = -1.0;
lineOrPageDelta = -1;
case SB_LINEDOWN:
wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE;
break;
default:
return;
}
modKeyState.InitInputEvent(wheelEvent);
// Current mouse position may not be same as when the original message
// is received. However, this data is not available with the original
// message, which is why nullptr is passed in. We need to know the actual
// mouse cursor position when the original message was received.
InitEvent(aWidget, wheelEvent, nullptr);
MOZ_LOG(
gMouseScrollLog, LogLevel::Info,
("MouseScroll::HandleScrollMessageAsMouseWheelMessage: aWidget=%p, "
"aMessage=MOZ_WM_%sSCROLL, aWParam=0x%08X, aLParam=0x%08X, "
"wheelEvent { mRefPoint: { x: %d, y: %d }, mDeltaX: %f, mDeltaY: %f, "
"mLineOrPageDeltaX: %d, mLineOrPageDeltaY: %d, "
"isShift: %s, isControl: %s, isAlt: %s, isMeta: %s }",
aWidget, (aMessage == MOZ_WM_VSCROLL) ? "V" : "H", aWParam, aLParam,
wheelEvent.mRefPoint.x, wheelEvent.mRefPoint.y, wheelEvent.mDeltaX,
wheelEvent.mDeltaY, wheelEvent.mLineOrPageDeltaX,
wheelEvent.mLineOrPageDeltaY, GetBoolName(wheelEvent.IsShift()),
GetBoolName(wheelEvent.IsControl()), GetBoolName(wheelEvent.IsAlt()),
GetBoolName(wheelEvent.IsMeta())));
aWidget->DispatchWheelEvent(&wheelEvent);
}
/******************************************************************************
*
* EventInfo
*
******************************************************************************/
MouseScrollHandler::EventInfo::EventInfo(nsWindowBase* aWidget, UINT aMessage,
WPARAM aWParam, LPARAM aLParam) {
MOZ_ASSERT(
aMessage == WM_MOUSEWHEEL || aMessage == WM_MOUSEHWHEEL,
"EventInfo must be initialized with WM_MOUSEWHEEL or WM_MOUSEHWHEEL");
MouseScrollHandler::GetInstance()->mSystemSettings.Init();
mIsVertical = (aMessage == WM_MOUSEWHEEL);
mIsPage =
MouseScrollHandler::sInstance->mSystemSettings.IsPageScroll(mIsVertical);
mDelta = (short)HIWORD(aWParam);
mWnd = aWidget->GetWindowHandle();
mTimeStamp = TimeStamp::Now();
}
bool MouseScrollHandler::EventInfo::CanDispatchWheelEvent() const {
if (!GetScrollAmount()) {
// XXX I think that we should dispatch mouse wheel events even if the
// operation will not scroll because the wheel operation really happened
// and web application may want to handle the event for non-scroll action.
return false;
}
return (mDelta != 0);
}
int32_t MouseScrollHandler::EventInfo::GetScrollAmount() const {
if (mIsPage) {
return 1;
}
return MouseScrollHandler::sInstance->mSystemSettings.GetScrollAmount(
mIsVertical);
}
/******************************************************************************
*
* LastEventInfo
*
******************************************************************************/
bool MouseScrollHandler::LastEventInfo::CanContinueTransaction(
const EventInfo& aNewEvent) {
int32_t timeout = MouseScrollHandler::sInstance->mUserPrefs
.GetMouseScrollTransactionTimeout();
return !mWnd ||
(mWnd == aNewEvent.GetWindowHandle() &&
IsPositive() == aNewEvent.IsPositive() &&
mIsVertical == aNewEvent.IsVertical() &&
mIsPage == aNewEvent.IsPage() &&
(timeout < 0 || TimeStamp::Now() - mTimeStamp <=
TimeDuration::FromMilliseconds(timeout)));
}
void MouseScrollHandler::LastEventInfo::ResetTransaction() {
if (!mWnd) {
return;
}
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::LastEventInfo::ResetTransaction()"));
mWnd = nullptr;
mAccumulatedDelta = 0;
}
void MouseScrollHandler::LastEventInfo::RecordEvent(const EventInfo& aEvent) {
mWnd = aEvent.GetWindowHandle();
mDelta = aEvent.GetNativeDelta();
mIsVertical = aEvent.IsVertical();
mIsPage = aEvent.IsPage();
mTimeStamp = TimeStamp::Now();
}
/* static */
int32_t MouseScrollHandler::LastEventInfo::RoundDelta(double aDelta) {
return (aDelta >= 0) ? (int32_t)floor(aDelta) : (int32_t)ceil(aDelta);
}
bool MouseScrollHandler::LastEventInfo::InitWheelEvent(
nsWindowBase* aWidget, WidgetWheelEvent& aWheelEvent,
const ModifierKeyState& aModKeyState, LPARAM aLParam) {
MOZ_ASSERT(aWheelEvent.mMessage == eWheel);
if (StaticPrefs::mousewheel_ignore_cursor_position_in_lparam()) {
InitEvent(aWidget, aWheelEvent, nullptr);
} else {
InitEvent(aWidget, aWheelEvent, &aLParam);
}
aModKeyState.InitInputEvent(aWheelEvent);
// Our positive delta value means to bottom or right.
// But positive native delta value means to top or right.
// Use orienter for computing our delta value with native delta value.
int32_t orienter = mIsVertical ? -1 : 1;
aWheelEvent.mDeltaMode = mIsPage ? dom::WheelEvent_Binding::DOM_DELTA_PAGE
: dom::WheelEvent_Binding::DOM_DELTA_LINE;
double& delta = mIsVertical ? aWheelEvent.mDeltaY : aWheelEvent.mDeltaX;
int32_t& lineOrPageDelta = mIsVertical ? aWheelEvent.mLineOrPageDeltaY
: aWheelEvent.mLineOrPageDeltaX;
double nativeDeltaPerUnit =
mIsPage ? static_cast<double>(WHEEL_DELTA)
: static_cast<double>(WHEEL_DELTA) / GetScrollAmount();
delta = static_cast<double>(mDelta) * orienter / nativeDeltaPerUnit;
mAccumulatedDelta += mDelta;
lineOrPageDelta =
mAccumulatedDelta * orienter / RoundDelta(nativeDeltaPerUnit);
mAccumulatedDelta -=
lineOrPageDelta * orienter * RoundDelta(nativeDeltaPerUnit);
if (aWheelEvent.mDeltaMode != dom::WheelEvent_Binding::DOM_DELTA_LINE) {
// If the scroll delta mode isn't per line scroll, we shouldn't allow to
// override the system scroll speed setting.
aWheelEvent.mAllowToOverrideSystemScrollSpeed = false;
} else if (!MouseScrollHandler::sInstance->mSystemSettings
.IsOverridingSystemScrollSpeedAllowed()) {
// If the system settings are customized by either the user or
// the mouse utility, we shouldn't allow to override the system scroll
// speed setting.
aWheelEvent.mAllowToOverrideSystemScrollSpeed = false;
} else {
// For suppressing too fast scroll, we should ensure that the maximum
// overridden delta value should be less than overridden scroll speed
// with default scroll amount.
double defaultScrollAmount = mIsVertical
? SystemSettings::DefaultScrollLines()
: SystemSettings::DefaultScrollChars();
double maxDelta = WidgetWheelEvent::ComputeOverriddenDelta(
defaultScrollAmount, mIsVertical);
if (maxDelta != defaultScrollAmount) {
double overriddenDelta =
WidgetWheelEvent::ComputeOverriddenDelta(Abs(delta), mIsVertical);
if (overriddenDelta > maxDelta) {
// Suppress to fast scroll since overriding system scroll speed with
// current delta value causes too big delta value.
aWheelEvent.mAllowToOverrideSystemScrollSpeed = false;
}
}
}
MOZ_LOG(
gMouseScrollLog, LogLevel::Info,
("MouseScroll::LastEventInfo::InitWheelEvent: aWidget=%p, "
"aWheelEvent { mRefPoint: { x: %d, y: %d }, mDeltaX: %f, mDeltaY: %f, "
"mLineOrPageDeltaX: %d, mLineOrPageDeltaY: %d, "
"isShift: %s, isControl: %s, isAlt: %s, isMeta: %s, "
"mAllowToOverrideSystemScrollSpeed: %s }, "
"mAccumulatedDelta: %d",
aWidget, aWheelEvent.mRefPoint.x, aWheelEvent.mRefPoint.y,
aWheelEvent.mDeltaX, aWheelEvent.mDeltaY, aWheelEvent.mLineOrPageDeltaX,
aWheelEvent.mLineOrPageDeltaY, GetBoolName(aWheelEvent.IsShift()),
GetBoolName(aWheelEvent.IsControl()), GetBoolName(aWheelEvent.IsAlt()),
GetBoolName(aWheelEvent.IsMeta()),
GetBoolName(aWheelEvent.mAllowToOverrideSystemScrollSpeed),
mAccumulatedDelta));
return (delta != 0);
}
/******************************************************************************
*
* SystemSettings
*
******************************************************************************/
void MouseScrollHandler::SystemSettings::Init() {
if (mInitialized) {
return;
}
InitScrollLines();
InitScrollChars();
mInitialized = true;
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::SystemSettings::Init(): initialized, "
"mScrollLines=%d, mScrollChars=%d",
mScrollLines, mScrollChars));
}
bool MouseScrollHandler::SystemSettings::InitScrollLines() {
int32_t oldValue = mInitialized ? mScrollLines : 0;
mIsReliableScrollLines = false;
mScrollLines = MouseScrollHandler::sInstance->mUserPrefs
.GetOverriddenVerticalScrollAmout();
if (mScrollLines >= 0) {
// overridden by the pref.
mIsReliableScrollLines = true;
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::SystemSettings::InitScrollLines(): mScrollLines is "
"overridden by the pref: %d",
mScrollLines));
} else if (!::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &mScrollLines,
0)) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::SystemSettings::InitScrollLines(): "
"::SystemParametersInfo("
"SPI_GETWHEELSCROLLLINES) failed"));
mScrollLines = DefaultScrollLines();
}
if (mScrollLines > WHEEL_DELTA) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::SystemSettings::InitScrollLines(): the result of "
"::SystemParametersInfo(SPI_GETWHEELSCROLLLINES) is too large: %d",
mScrollLines));
// sScrollLines usually equals 3 or 0 (for no scrolling)
// However, if sScrollLines > WHEEL_DELTA, we assume that
// the mouse driver wants a page scroll. The docs state that
// sScrollLines should explicitly equal WHEEL_PAGESCROLL, but
// since some mouse drivers use an arbitrary large number instead,
// we have to handle that as well.
mScrollLines = WHEEL_PAGESCROLL;
}
return oldValue != mScrollLines;
}
bool MouseScrollHandler::SystemSettings::InitScrollChars() {
int32_t oldValue = mInitialized ? mScrollChars : 0;
mIsReliableScrollChars = false;
mScrollChars = MouseScrollHandler::sInstance->mUserPrefs
.GetOverriddenHorizontalScrollAmout();
if (mScrollChars >= 0) {
// overridden by the pref.
mIsReliableScrollChars = true;
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::SystemSettings::InitScrollChars(): mScrollChars is "
"overridden by the pref: %d",
mScrollChars));
} else if (!::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &mScrollChars,
0)) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::SystemSettings::InitScrollChars(): "
"::SystemParametersInfo("
"SPI_GETWHEELSCROLLCHARS) failed, this is unexpected on Vista or "
"later"));
// XXX Should we use DefaultScrollChars()?
mScrollChars = 1;
}
if (mScrollChars > WHEEL_DELTA) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::SystemSettings::InitScrollChars(): the result of "
"::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS) is too large: %d",
mScrollChars));
// See the comments for the case mScrollLines > WHEEL_DELTA.
mScrollChars = WHEEL_PAGESCROLL;
}
return oldValue != mScrollChars;
}
void MouseScrollHandler::SystemSettings::MarkDirty() {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScrollHandler::SystemSettings::MarkDirty(): "
"Marking SystemSettings dirty"));
mInitialized = false;
// When system settings are changed, we should reset current transaction.
MOZ_ASSERT(sInstance,
"Must not be called at initializing MouseScrollHandler");
MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction();
}
void MouseScrollHandler::SystemSettings::RefreshCache() {
bool isChanged = InitScrollLines();
isChanged = InitScrollChars() || isChanged;
if (!isChanged) {
return;
}
// If the scroll amount is changed, we should reset current transaction.
MOZ_ASSERT(sInstance,
"Must not be called at initializing MouseScrollHandler");
MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction();
}
void MouseScrollHandler::SystemSettings::TrustedScrollSettingsDriver() {
if (!mInitialized) {
return;
}
// if the cache is initialized with prefs, we don't need to refresh it.
if (mIsReliableScrollLines && mIsReliableScrollChars) {
return;
}
MouseScrollHandler::UserPrefs& userPrefs =
MouseScrollHandler::sInstance->mUserPrefs;
// If system settings cache is disabled, we should always refresh them.
if (!userPrefs.IsSystemSettingCacheEnabled()) {
RefreshCache();
return;
}
// If pref is set to as "always trust the cache", we shouldn't refresh them
// in any environments.
if (userPrefs.IsSystemSettingCacheForciblyEnabled()) {
return;
}
// If SynTP of Synaptics or Apoint of Alps is installed, it may hook
// ::SystemParametersInfo() and returns different value from system settings.
if (Device::SynTP::IsDriverInstalled() ||
Device::Apoint::IsDriverInstalled()) {
RefreshCache();
return;
}
// XXX We're not sure about other touchpad drivers...
}
bool MouseScrollHandler::SystemSettings::
IsOverridingSystemScrollSpeedAllowed() {
return mScrollLines == DefaultScrollLines() &&
mScrollChars == DefaultScrollChars();
}
/******************************************************************************
*
* UserPrefs
*
******************************************************************************/
MouseScrollHandler::UserPrefs::UserPrefs() : mInitialized(false) {
// We need to reset mouse wheel transaction when all of mousewheel related
// prefs are changed.
DebugOnly<nsresult> rv =
Preferences::RegisterPrefixCallback(OnChange, "mousewheel.", this);
MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to register callback for mousewheel.");
}
MouseScrollHandler::UserPrefs::~UserPrefs() {
DebugOnly<nsresult> rv =
Preferences::UnregisterPrefixCallback(OnChange, "mousewheel.", this);
MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to unregister callback for mousewheel.");
}
void MouseScrollHandler::UserPrefs::Init() {
if (mInitialized) {
return;
}
mInitialized = true;
mScrollMessageHandledAsWheelMessage =
Preferences::GetBool("mousewheel.emulate_at_wm_scroll", false);
mEnableSystemSettingCache =
Preferences::GetBool("mousewheel.system_settings_cache.enabled", true);
mForceEnableSystemSettingCache = Preferences::GetBool(
"mousewheel.system_settings_cache.force_enabled", false);
mEmulateToMakeWindowUnderCursorForeground = Preferences::GetBool(
"mousewheel.debug.make_window_under_cursor_foreground", false);
mOverriddenVerticalScrollAmount =
Preferences::GetInt("mousewheel.windows.vertical_amount_override", -1);
mOverriddenHorizontalScrollAmount =
Preferences::GetInt("mousewheel.windows.horizontal_amount_override", -1);
mMouseScrollTransactionTimeout = Preferences::GetInt(
"mousewheel.windows.transaction.timeout", DEFAULT_TIMEOUT_DURATION);
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::UserPrefs::Init(): initialized, "
"mScrollMessageHandledAsWheelMessage=%s, "
"mEnableSystemSettingCache=%s, "
"mForceEnableSystemSettingCache=%s, "
"mEmulateToMakeWindowUnderCursorForeground=%s, "
"mOverriddenVerticalScrollAmount=%d, "
"mOverriddenHorizontalScrollAmount=%d, "
"mMouseScrollTransactionTimeout=%d",
GetBoolName(mScrollMessageHandledAsWheelMessage),
GetBoolName(mEnableSystemSettingCache),
GetBoolName(mForceEnableSystemSettingCache),
GetBoolName(mEmulateToMakeWindowUnderCursorForeground),
mOverriddenVerticalScrollAmount, mOverriddenHorizontalScrollAmount,
mMouseScrollTransactionTimeout));
}
void MouseScrollHandler::UserPrefs::MarkDirty() {
MOZ_LOG(
gMouseScrollLog, LogLevel::Info,
("MouseScrollHandler::UserPrefs::MarkDirty(): Marking UserPrefs dirty"));
mInitialized = false;
// Some prefs might override system settings, so, we should mark them dirty.
MouseScrollHandler::sInstance->mSystemSettings.MarkDirty();
// When user prefs for mousewheel are changed, we should reset current
// transaction.
MOZ_ASSERT(sInstance,
"Must not be called at initializing MouseScrollHandler");
MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction();
}
/******************************************************************************
*
* Device
*
******************************************************************************/
/* static */
bool MouseScrollHandler::Device::GetWorkaroundPref(const char* aPrefName,
bool aValueIfAutomatic) {
if (!aPrefName) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::GetWorkaroundPref(): Failed, aPrefName is "
"NULL"));
return aValueIfAutomatic;
}
int32_t lHackValue = 0;
if (NS_FAILED(Preferences::GetInt(aPrefName, &lHackValue))) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::GetWorkaroundPref(): Preferences::GetInt() "
"failed,"
" aPrefName=\"%s\", aValueIfAutomatic=%s",
aPrefName, GetBoolName(aValueIfAutomatic)));
return aValueIfAutomatic;
}
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::GetWorkaroundPref(): Succeeded, "
"aPrefName=\"%s\", aValueIfAutomatic=%s, lHackValue=%d",
aPrefName, GetBoolName(aValueIfAutomatic), lHackValue));
switch (lHackValue) {
case 0: // disabled
return false;
case 1: // enabled
return true;
default: // -1: autodetect
return aValueIfAutomatic;
}
}
/* static */
void MouseScrollHandler::Device::Init() {
// FYI: Thinkpad's TrackPoint is Apoint of Alps and UltraNav is SynTP of
// Synaptics. So, those drivers' information should be initialized
// before calling methods of TrackPoint and UltraNav.
SynTP::Init();
Elantech::Init();
Apoint::Init();
sFakeScrollableWindowNeeded = GetWorkaroundPref(
"ui.trackpoint_hack.enabled", (TrackPoint::IsDriverInstalled() ||
UltraNav::IsObsoleteDriverInstalled()));
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::Init(): sFakeScrollableWindowNeeded=%s",
GetBoolName(sFakeScrollableWindowNeeded)));
}
/******************************************************************************
*
* Device::SynTP
*
******************************************************************************/
/* static */
void MouseScrollHandler::Device::SynTP::Init() {
if (sInitialized) {
return;
}
sInitialized = true;
sMajorVersion = 0;
sMinorVersion = -1;
wchar_t buf[40];
bool foundKey = WinUtils::GetRegistryKey(
HKEY_LOCAL_MACHINE, L"Software\\Synaptics\\SynTP\\Install",
L"DriverVersion", buf, sizeof buf);
if (!foundKey) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::SynTP::Init(): "
"SynTP driver is not found"));
return;
}
sMajorVersion = wcstol(buf, nullptr, 10);
sMinorVersion = 0;
wchar_t* p = wcschr(buf, L'.');
if (p) {
sMinorVersion = wcstol(p + 1, nullptr, 10);
}
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::SynTP::Init(): "
"found driver version = %d.%d",
sMajorVersion, sMinorVersion));
}
/******************************************************************************
*
* Device::Elantech
*
******************************************************************************/
/* static */
void MouseScrollHandler::Device::Elantech::Init() {
int32_t version = GetDriverMajorVersion();
bool needsHack = Device::GetWorkaroundPref(
"ui.elantech_gesture_hacks.enabled", version != 0);
sUseSwipeHack = needsHack && version <= 7;
sUsePinchHack = needsHack && version <= 8;
MOZ_LOG(
gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::Elantech::Init(): version=%d, sUseSwipeHack=%s, "
"sUsePinchHack=%s",
version, GetBoolName(sUseSwipeHack), GetBoolName(sUsePinchHack)));
}
/* static */
int32_t MouseScrollHandler::Device::Elantech::GetDriverMajorVersion() {
wchar_t buf[40];
// The driver version is found in one of these two registry keys.
bool foundKey = WinUtils::GetRegistryKey(HKEY_CURRENT_USER,
L"Software\\Elantech\\MainOption",
L"DriverVersion", buf, sizeof buf);
if (!foundKey) {
foundKey =
WinUtils::GetRegistryKey(HKEY_CURRENT_USER, L"Software\\Elantech",
L"DriverVersion", buf, sizeof buf);
}
if (!foundKey) {
return 0;
}
// Assume that the major version number can be found just after a space
// or at the start of the string.
for (wchar_t* p = buf; *p; p++) {
if (*p >= L'0' && *p <= L'9' && (p == buf || *(p - 1) == L' ')) {
return wcstol(p, nullptr, 10);
}
}
return 0;
}
/* static */
bool MouseScrollHandler::Device::Elantech::IsHelperWindow(HWND aWnd) {
// The helper window cannot be distinguished based on its window class, so we
// need to check if it is owned by the helper process, ETDCtrl.exe.
const wchar_t* filenameSuffix = L"\\etdctrl.exe";
const int filenameSuffixLength = 12;
DWORD pid;
::GetWindowThreadProcessId(aWnd, &pid);
HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!hProcess) {
return false;
}
bool result = false;
wchar_t path[256] = {L'\0'};
if (::GetProcessImageFileNameW(hProcess, path, ArrayLength(path))) {
int pathLength = lstrlenW(path);
if (pathLength >= filenameSuffixLength) {
if (lstrcmpiW(path + pathLength - filenameSuffixLength, filenameSuffix) ==
0) {
result = true;
}
}
}
::CloseHandle(hProcess);
return result;
}
/* static */
bool MouseScrollHandler::Device::Elantech::HandleKeyMessage(
nsWindowBase* aWidget, UINT aMsg, WPARAM aWParam, LPARAM aLParam) {
// 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 ScanCode = 00
// WM_KEYDOWN virtual_key = VK_NEXT ScanCode = 00
// WM_KEYUP virtual_key = VK_NEXT ScanCode = 00
// WM_KEYUP virtual_key = 0xCC or 0xFF ScanCode = 00
//
// Whether 0xCC or 0xFF is sent is suspected to depend on the driver
// version. 7.0.4.12_14Jul09_WHQL, 7.0.5.10, and 7.0.6.0 generate 0xCC.
// 7.0.4.3 from Asus on EeePC generates 0xFF.
//
// On some hardware, IS_VK_DOWN(0xFF) returns true even when Elantech
// messages are not involved, meaning that alone is not enough to
// distinguish the gesture from a regular Page Up or Page Down key press.
// The ScanCode is therefore also tested to detect the gesture.
// We then pretend that we should dispatch "Go Forward" command. Similarly
// for VK_PRIOR and "Go Back" command.
if (sUseSwipeHack && (aWParam == VK_NEXT || aWParam == VK_PRIOR) &&
WinUtils::GetScanCode(aLParam) == 0 &&
(IS_VK_DOWN(0xFF) || IS_VK_DOWN(0xCC))) {
if (aMsg == WM_KEYDOWN) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::Elantech::HandleKeyMessage(): Dispatching "
"%s command event",
aWParam == VK_NEXT ? "Forward" : "Back"));
WidgetCommandEvent appCommandEvent(
true, (aWParam == VK_NEXT) ? nsGkAtoms::Forward : nsGkAtoms::Back,
aWidget);
// In this scenario, the coordinate of the event isn't supplied, so pass
// nullptr as an argument to indicate using the coordinate from the last
// available window message.
InitEvent(aWidget, appCommandEvent, nullptr);
aWidget->DispatchWindowEvent(&appCommandEvent);
} else {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::Elantech::HandleKeyMessage(): Consumed"));
}
return true; // consume the message (doesn't need to dispatch key events)
}
// Version 8 of the Elantech touchpad driver sends these messages for
// zoom gestures:
//
// WM_KEYDOWN virtual_key = 0xCC time = 10
// WM_KEYDOWN virtual_key = VK_CONTROL time = 10
// WM_MOUSEWHEEL time = ::GetTickCount()
// WM_KEYUP virtual_key = VK_CONTROL time = 10
// WM_KEYUP virtual_key = 0xCC time = 10
//
// The result of this is that we process all of the WM_KEYDOWN/WM_KEYUP
// messages first because their timestamps make them appear to have
// been sent before the WM_MOUSEWHEEL message. To work around this,
// we store the current time when we process the WM_KEYUP message and
// assume that any WM_MOUSEWHEEL message with a timestamp before that
// time is one that should be processed as if the Control key was down.
if (sUsePinchHack && aMsg == WM_KEYUP && aWParam == VK_CONTROL &&
::GetMessageTime() == 10) {
// We look only at the bottom 31 bits of the system tick count since
// GetMessageTime returns a LONG, which is signed, so we want values
// that are more easily comparable.
sZoomUntil = ::GetTickCount() & 0x7FFFFFFF;
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::Elantech::HandleKeyMessage(): sZoomUntil=%d",
sZoomUntil));
}
return false;
}
/* static */
void MouseScrollHandler::Device::Elantech::UpdateZoomUntil() {
if (!sZoomUntil) {
return;
}
// For the Elantech Touchpad Zoom Gesture Hack, we should check that the
// system time (32-bit milliseconds) hasn't wrapped around. Otherwise we
// might get into the situation where wheel events for the next 50 days of
// system uptime are assumed to be Ctrl+Wheel events. (It is unlikely that
// we would get into that state, because the system would already need to be
// up for 50 days and the Control key message would need to be processed just
// before the system time overflow and the wheel message just after.)
//
// We also take the chance to reset sZoomUntil if we simply have passed that
// time.
LONG msgTime = ::GetMessageTime();
if ((sZoomUntil >= 0x3fffffffu && DWORD(msgTime) < 0x40000000u) ||
(sZoomUntil < DWORD(msgTime))) {
sZoomUntil = 0;
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::Elantech::UpdateZoomUntil(): "
"sZoomUntil was reset"));
}
}
/* static */
bool MouseScrollHandler::Device::Elantech::IsZooming() {
// Assume the Control key is down if the Elantech touchpad has sent the
// mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages. (See the comment in
// OnKeyUp.)
return (sZoomUntil && static_cast<DWORD>(::GetMessageTime()) < sZoomUntil);
}
/******************************************************************************
*
* Device::Apoint
*
******************************************************************************/
/* static */
void MouseScrollHandler::Device::Apoint::Init() {
if (sInitialized) {
return;
}
sInitialized = true;
sMajorVersion = 0;
sMinorVersion = -1;
wchar_t buf[40];
bool foundKey =
WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, L"Software\\Alps\\Apoint",
L"ProductVer", buf, sizeof buf);
if (!foundKey) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::Apoint::Init(): "
"Apoint driver is not found"));
return;
}
sMajorVersion = wcstol(buf, nullptr, 10);
sMinorVersion = 0;
wchar_t* p = wcschr(buf, L'.');
if (p) {
sMinorVersion = wcstol(p + 1, nullptr, 10);
}
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::Apoint::Init(): "
"found driver version = %d.%d",
sMajorVersion, sMinorVersion));
}
/******************************************************************************
*
* Device::TrackPoint
*
******************************************************************************/
/* static */
bool MouseScrollHandler::Device::TrackPoint::IsDriverInstalled() {
if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
L"Software\\Lenovo\\TrackPoint")) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::TrackPoint::IsDriverInstalled(): "
"Lenovo's TrackPoint driver is found"));
return true;
}
if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
L"Software\\Alps\\Apoint\\TrackPoint")) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::TrackPoint::IsDriverInstalled(): "
"Alps's TrackPoint driver is found"));
}
return false;
}
/******************************************************************************
*
* Device::UltraNav
*
******************************************************************************/
/* static */
bool MouseScrollHandler::Device::UltraNav::IsObsoleteDriverInstalled() {
if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
L"Software\\Lenovo\\UltraNav")) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
"Lenovo's UltraNav driver is found"));
return true;
}
bool installed = false;
if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
L"Software\\Synaptics\\SynTPEnh\\UltraNavUSB")) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
"Synaptics's UltraNav (USB) driver is found"));
installed = true;
} else if (WinUtils::HasRegistryKey(
HKEY_CURRENT_USER,
L"Software\\Synaptics\\SynTPEnh\\UltraNavPS2")) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
"Synaptics's UltraNav (PS/2) driver is found"));
installed = true;
}
if (!installed) {
return false;
}
int32_t majorVersion = Device::SynTP::GetDriverMajorVersion();
if (!majorVersion) {
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
"Failed to get UltraNav driver version"));
return false;
}
int32_t minorVersion = Device::SynTP::GetDriverMinorVersion();
return majorVersion < 15 || (majorVersion == 15 && minorVersion == 0);
}
/******************************************************************************
*
* Device::SetPoint
*
******************************************************************************/
/* static */
bool MouseScrollHandler::Device::SetPoint::IsGetMessagePosResponseValid(
UINT aMessage, WPARAM aWParam, LPARAM aLParam) {
if (aMessage != WM_MOUSEHWHEEL) {
return false;
}
POINTS pts = MouseScrollHandler::GetCurrentMessagePos();
LPARAM messagePos = MAKELPARAM(pts.x, pts.y);
// XXX We should check whether SetPoint is installed or not by registry.
// SetPoint, Logitech (Logicool) mouse driver, (confirmed with 4.82.11 and
// MX-1100) always sets 0 to the lParam of WM_MOUSEHWHEEL. The driver SENDs
// one message at first time, this time, ::GetMessagePos() works fine.
// Then, we will return 0 (0 means we process it) to the message. Then, the
// driver will POST the same messages continuously during the wheel tilted.
// But ::GetMessagePos() API always returns (0, 0) for them, even if the
// actual mouse cursor isn't 0,0. Therefore, we cannot trust the result of
// ::GetMessagePos API if the sender is SetPoint.
if (!sMightBeUsing && !aLParam && aLParam != messagePos &&
::InSendMessage()) {
sMightBeUsing = true;
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::SetPoint::IsGetMessagePosResponseValid(): "
"Might using SetPoint"));
} else if (sMightBeUsing && aLParam != 0 && ::InSendMessage()) {
// The user has changed the mouse from Logitech's to another one (e.g.,
// the user has changed to the touchpad of the notebook.
sMightBeUsing = false;
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScroll::Device::SetPoint::IsGetMessagePosResponseValid(): "
"Might stop using SetPoint"));
}
return (sMightBeUsing && !aLParam && !messagePos);
}
/******************************************************************************
*
* SynthesizingEvent
*
******************************************************************************/
/* static */
bool MouseScrollHandler::SynthesizingEvent::IsSynthesizing() {
return MouseScrollHandler::sInstance &&
MouseScrollHandler::sInstance->mSynthesizingEvent &&
MouseScrollHandler::sInstance->mSynthesizingEvent->mStatus !=
NOT_SYNTHESIZING;
}
nsresult MouseScrollHandler::SynthesizingEvent::Synthesize(
const POINTS& aCursorPoint, HWND aWnd, UINT aMessage, WPARAM aWParam,
LPARAM aLParam, const BYTE (&aKeyStates)[256]) {
MOZ_LOG(
gMouseScrollLog, LogLevel::Info,
("MouseScrollHandler::SynthesizingEvent::Synthesize(): aCursorPoint: { "
"x: %d, y: %d }, aWnd=0x%X, aMessage=0x%04X, aWParam=0x%08X, "
"aLParam=0x%08X, IsSynthesized()=%s, mStatus=%s",
aCursorPoint.x, aCursorPoint.y, aWnd, aMessage, aWParam, aLParam,
GetBoolName(IsSynthesizing()), GetStatusName()));
if (IsSynthesizing()) {
return NS_ERROR_NOT_AVAILABLE;
}
::GetKeyboardState(mOriginalKeyState);
// Note that we cannot use ::SetCursorPos() because it works asynchronously.
// We should SEND the message for reducing the possibility of receiving
// unexpected message which were not sent from here.
mCursorPoint = aCursorPoint;
mWnd = aWnd;
mMessage = aMessage;
mWParam = aWParam;
mLParam = aLParam;
memcpy(mKeyState, aKeyStates, sizeof(mKeyState));
::SetKeyboardState(mKeyState);
mStatus = SENDING_MESSAGE;
// Don't assume that aWnd is always managed by nsWindow. It might be
// a plugin window.
::SendMessage(aWnd, aMessage, aWParam, aLParam);
return NS_OK;
}
void MouseScrollHandler::SynthesizingEvent::NativeMessageReceived(
nsWindowBase* aWidget, UINT aMessage, WPARAM aWParam, LPARAM aLParam) {
if (mStatus == SENDING_MESSAGE && mMessage == aMessage &&
mWParam == aWParam && mLParam == aLParam) {
mStatus = NATIVE_MESSAGE_RECEIVED;
if (aWidget && aWidget->GetWindowHandle() == mWnd) {
return;
}
// If the target window is not ours and received window is our plugin
// window, it comes from child window of the plugin.
if (aWidget && aWidget->IsPlugin() && !WinUtils::GetNSWindowBasePtr(mWnd)) {
return;
}
// Otherwise, the message may not be sent by us.
}
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScrollHandler::SynthesizingEvent::NativeMessageReceived(): "
"aWidget=%p, aWidget->GetWindowHandle()=0x%X, mWnd=0x%X, "
"aMessage=0x%04X, aWParam=0x%08X, aLParam=0x%08X, mStatus=%s",
aWidget, aWidget ? aWidget->GetWindowHandle() : 0, mWnd, aMessage,
aWParam, aLParam, GetStatusName()));
// We failed to receive our sent message, we failed to do the job.
Finish();
return;
}
void MouseScrollHandler::SynthesizingEvent::
NotifyNativeMessageHandlingFinished() {
if (!IsSynthesizing()) {
return;
}
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScrollHandler::SynthesizingEvent::"
"NotifyNativeMessageHandlingFinished(): IsWaitingInternalMessage=%s",
GetBoolName(MouseScrollHandler::IsWaitingInternalMessage())));
if (MouseScrollHandler::IsWaitingInternalMessage()) {
mStatus = INTERNAL_MESSAGE_POSTED;
return;
}
// If the native message handler didn't post our internal message,
// we our job is finished.
// TODO: When we post the message to plugin window, there is remaning job.
Finish();
}
void MouseScrollHandler::SynthesizingEvent::
NotifyInternalMessageHandlingFinished() {
if (!IsSynthesizing()) {
return;
}
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScrollHandler::SynthesizingEvent::"
"NotifyInternalMessageHandlingFinished()"));
Finish();
}
void MouseScrollHandler::SynthesizingEvent::Finish() {
if (!IsSynthesizing()) {
return;
}
MOZ_LOG(gMouseScrollLog, LogLevel::Info,
("MouseScrollHandler::SynthesizingEvent::Finish()"));
// Restore the original key state.
::SetKeyboardState(mOriginalKeyState);
mStatus = NOT_SYNTHESIZING;
}
} // namespace widget
} // namespace mozilla