1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 * nsWinGesture - Touch input handling for tablet displays.
11 #include "nsWinGesture.h"
12 #include "nsUXThemeData.h"
13 #include "nsIDOMSimpleGestureEvent.h"
14 #include "nsIDOMWheelEvent.h"
15 #include "mozilla/Constants.h"
16 #include "mozilla/MouseEvents.h"
17 #include "mozilla/Preferences.h"
18 #include "mozilla/TouchEvents.h"
20 using namespace mozilla
;
21 using namespace mozilla::widget
;
24 extern PRLogModuleInfo
* gWindowsLog
;
27 const wchar_t nsWinGesture::kGestureLibraryName
[] = L
"user32.dll";
28 HMODULE
nsWinGesture::sLibraryHandle
= nullptr;
29 nsWinGesture::GetGestureInfoPtr
nsWinGesture::getGestureInfo
= nullptr;
30 nsWinGesture::CloseGestureInfoHandlePtr
nsWinGesture::closeGestureInfoHandle
= nullptr;
31 nsWinGesture::GetGestureExtraArgsPtr
nsWinGesture::getGestureExtraArgs
= nullptr;
32 nsWinGesture::SetGestureConfigPtr
nsWinGesture::setGestureConfig
= nullptr;
33 nsWinGesture::GetGestureConfigPtr
nsWinGesture::getGestureConfig
= nullptr;
34 nsWinGesture::BeginPanningFeedbackPtr
nsWinGesture::beginPanningFeedback
= nullptr;
35 nsWinGesture::EndPanningFeedbackPtr
nsWinGesture::endPanningFeedback
= nullptr;
36 nsWinGesture::UpdatePanningFeedbackPtr
nsWinGesture::updatePanningFeedback
= nullptr;
38 nsWinGesture::RegisterTouchWindowPtr
nsWinGesture::registerTouchWindow
= nullptr;
39 nsWinGesture::UnregisterTouchWindowPtr
nsWinGesture::unregisterTouchWindow
= nullptr;
40 nsWinGesture::GetTouchInputInfoPtr
nsWinGesture::getTouchInputInfo
= nullptr;
41 nsWinGesture::CloseTouchInputHandlePtr
nsWinGesture::closeTouchInputHandle
= nullptr;
43 static bool gEnableSingleFingerPanEvents
= false;
45 nsWinGesture::nsWinGesture() :
47 mFeedbackActive(false),
48 mXAxisFeedback(false),
49 mYAxisFeedback(false),
50 mPanInertiaActive(false)
53 mPixelScrollOverflow
= 0;
56 /* Load and shutdown */
58 bool nsWinGesture::InitLibrary()
62 } else if (sLibraryHandle
) {
66 sLibraryHandle
= ::LoadLibraryW(kGestureLibraryName
);
67 HMODULE hTheme
= nsUXThemeData::GetThemeDLL();
71 getGestureInfo
= (GetGestureInfoPtr
)GetProcAddress(sLibraryHandle
, "GetGestureInfo");
72 closeGestureInfoHandle
= (CloseGestureInfoHandlePtr
)GetProcAddress(sLibraryHandle
, "CloseGestureInfoHandle");
73 getGestureExtraArgs
= (GetGestureExtraArgsPtr
)GetProcAddress(sLibraryHandle
, "GetGestureExtraArgs");
74 setGestureConfig
= (SetGestureConfigPtr
)GetProcAddress(sLibraryHandle
, "SetGestureConfig");
75 getGestureConfig
= (GetGestureConfigPtr
)GetProcAddress(sLibraryHandle
, "GetGestureConfig");
76 registerTouchWindow
= (RegisterTouchWindowPtr
)GetProcAddress(sLibraryHandle
, "RegisterTouchWindow");
77 unregisterTouchWindow
= (UnregisterTouchWindowPtr
)GetProcAddress(sLibraryHandle
, "UnregisterTouchWindow");
78 getTouchInputInfo
= (GetTouchInputInfoPtr
)GetProcAddress(sLibraryHandle
, "GetTouchInputInfo");
79 closeTouchInputHandle
= (CloseTouchInputHandlePtr
)GetProcAddress(sLibraryHandle
, "CloseTouchInputHandle");
82 if (!getGestureInfo
|| !closeGestureInfoHandle
|| !getGestureExtraArgs
||
83 !setGestureConfig
|| !getGestureConfig
) {
84 getGestureInfo
= nullptr;
85 closeGestureInfoHandle
= nullptr;
86 getGestureExtraArgs
= nullptr;
87 setGestureConfig
= nullptr;
88 getGestureConfig
= nullptr;
92 if (!registerTouchWindow
|| !unregisterTouchWindow
|| !getTouchInputInfo
|| !closeTouchInputHandle
) {
93 registerTouchWindow
= nullptr;
94 unregisterTouchWindow
= nullptr;
95 getTouchInputInfo
= nullptr;
96 closeTouchInputHandle
= nullptr;
99 // panning feedback interfaces
101 beginPanningFeedback
= (BeginPanningFeedbackPtr
)GetProcAddress(hTheme
, "BeginPanningFeedback");
102 endPanningFeedback
= (EndPanningFeedbackPtr
)GetProcAddress(hTheme
, "EndPanningFeedback");
103 updatePanningFeedback
= (UpdatePanningFeedbackPtr
)GetProcAddress(hTheme
, "UpdatePanningFeedback");
106 if (!beginPanningFeedback
|| !endPanningFeedback
|| !updatePanningFeedback
) {
107 beginPanningFeedback
= nullptr;
108 endPanningFeedback
= nullptr;
109 updatePanningFeedback
= nullptr;
112 // Check to see if we want single finger gesture input. Only do this once
113 // for the app so we don't have to look it up on every window create.
114 gEnableSingleFingerPanEvents
=
115 Preferences::GetBool("gestures.enable_single_finger_input", false);
122 bool nsWinGesture::SetWinGestureSupport(HWND hWnd
,
123 WidgetGestureNotifyEvent::ePanDirection aDirection
)
128 GESTURECONFIG config
[GCOUNT
];
130 memset(&config
, 0, sizeof(config
));
132 config
[0].dwID
= GID_ZOOM
;
133 config
[0].dwWant
= GC_ZOOM
;
134 config
[0].dwBlock
= 0;
136 config
[1].dwID
= GID_ROTATE
;
137 config
[1].dwWant
= GC_ROTATE
;
138 config
[1].dwBlock
= 0;
140 config
[2].dwID
= GID_PAN
;
141 config
[2].dwWant
= GC_PAN
|GC_PAN_WITH_INERTIA
|
143 config
[2].dwBlock
= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY
|
144 GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY
;
146 if (gEnableSingleFingerPanEvents
) {
148 if (aDirection
== WidgetGestureNotifyEvent::ePanVertical
||
149 aDirection
== WidgetGestureNotifyEvent::ePanBoth
)
151 config
[2].dwWant
|= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY
;
152 config
[2].dwBlock
-= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY
;
155 if (aDirection
== WidgetGestureNotifyEvent::ePanHorizontal
||
156 aDirection
== WidgetGestureNotifyEvent::ePanBoth
)
158 config
[2].dwWant
|= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY
;
159 config
[2].dwBlock
-= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY
;
164 config
[3].dwWant
= GC_TWOFINGERTAP
;
165 config
[3].dwID
= GID_TWOFINGERTAP
;
166 config
[3].dwBlock
= 0;
168 config
[4].dwWant
= GC_PRESSANDTAP
;
169 config
[4].dwID
= GID_PRESSANDTAP
;
170 config
[4].dwBlock
= 0;
172 return SetGestureConfig(hWnd
, GCOUNT
, (PGESTURECONFIG
)&config
);
177 bool nsWinGesture::IsAvailable()
179 return getGestureInfo
!= nullptr;
182 bool nsWinGesture::RegisterTouchWindow(HWND hWnd
)
184 if (!registerTouchWindow
)
187 return registerTouchWindow(hWnd
, TWF_WANTPALM
);
190 bool nsWinGesture::UnregisterTouchWindow(HWND hWnd
)
192 if (!unregisterTouchWindow
)
195 return unregisterTouchWindow(hWnd
);
198 bool nsWinGesture::GetTouchInputInfo(HTOUCHINPUT hTouchInput
, uint32_t cInputs
, PTOUCHINPUT pInputs
)
200 if (!getTouchInputInfo
)
203 return getTouchInputInfo(hTouchInput
, cInputs
, pInputs
, sizeof(TOUCHINPUT
));
206 bool nsWinGesture::CloseTouchInputHandle(HTOUCHINPUT hTouchInput
)
208 if (!closeTouchInputHandle
)
211 return closeTouchInputHandle(hTouchInput
);
214 bool nsWinGesture::GetGestureInfo(HGESTUREINFO hGestureInfo
, PGESTUREINFO pGestureInfo
)
216 if (!getGestureInfo
|| !hGestureInfo
|| !pGestureInfo
)
219 ZeroMemory(pGestureInfo
, sizeof(GESTUREINFO
));
220 pGestureInfo
->cbSize
= sizeof(GESTUREINFO
);
222 return getGestureInfo(hGestureInfo
, pGestureInfo
);
225 bool nsWinGesture::CloseGestureInfoHandle(HGESTUREINFO hGestureInfo
)
227 if (!getGestureInfo
|| !hGestureInfo
)
230 return closeGestureInfoHandle(hGestureInfo
);
233 bool nsWinGesture::GetGestureExtraArgs(HGESTUREINFO hGestureInfo
, UINT cbExtraArgs
, PBYTE pExtraArgs
)
235 if (!getGestureInfo
|| !hGestureInfo
|| !pExtraArgs
)
238 return getGestureExtraArgs(hGestureInfo
, cbExtraArgs
, pExtraArgs
);
241 bool nsWinGesture::SetGestureConfig(HWND hWnd
, UINT cIDs
, PGESTURECONFIG pGestureConfig
)
243 if (!getGestureInfo
|| !pGestureConfig
)
246 return setGestureConfig(hWnd
, 0, cIDs
, pGestureConfig
, sizeof(GESTURECONFIG
));
249 bool nsWinGesture::GetGestureConfig(HWND hWnd
, DWORD dwFlags
, PUINT pcIDs
, PGESTURECONFIG pGestureConfig
)
251 if (!getGestureInfo
|| !pGestureConfig
)
254 return getGestureConfig(hWnd
, 0, dwFlags
, pcIDs
, pGestureConfig
, sizeof(GESTURECONFIG
));
257 bool nsWinGesture::BeginPanningFeedback(HWND hWnd
)
259 if (!beginPanningFeedback
)
262 return beginPanningFeedback(hWnd
);
265 bool nsWinGesture::EndPanningFeedback(HWND hWnd
)
267 if (!beginPanningFeedback
)
270 return endPanningFeedback(hWnd
, TRUE
);
273 bool nsWinGesture::UpdatePanningFeedback(HWND hWnd
, LONG offsetX
, LONG offsetY
, BOOL fInInertia
)
275 if (!beginPanningFeedback
)
278 return updatePanningFeedback(hWnd
, offsetX
, offsetY
, fInInertia
);
281 bool nsWinGesture::IsPanEvent(LPARAM lParam
)
285 ZeroMemory(&gi
,sizeof(GESTUREINFO
));
286 gi
.cbSize
= sizeof(GESTUREINFO
);
288 BOOL result
= GetGestureInfo((HGESTUREINFO
)lParam
, &gi
);
292 if (gi
.dwID
== GID_PAN
)
298 /* Gesture event processing */
301 nsWinGesture::ProcessGestureMessage(HWND hWnd
, WPARAM wParam
, LPARAM lParam
,
302 WidgetSimpleGestureEvent
& evt
)
306 ZeroMemory(&gi
,sizeof(GESTUREINFO
));
307 gi
.cbSize
= sizeof(GESTUREINFO
);
309 BOOL result
= GetGestureInfo((HGESTUREINFO
)lParam
, &gi
);
313 // The coordinates of this event
315 coord
= gi
.ptsLocation
;
316 coord
.ScreenToClient(hWnd
);
318 evt
.refPoint
.x
= coord
.x
;
319 evt
.refPoint
.y
= coord
.y
;
321 // Multiple gesture can occur at the same time so gesture state
322 // info can't be shared.
327 // These should always fall through to DefWndProc
333 if (gi
.dwFlags
& GF_BEGIN
) {
334 // Send a zoom start event
336 // The low 32 bits are the distance in pixels.
337 mZoomIntermediate
= (float)gi
.ullArguments
;
339 evt
.message
= NS_SIMPLE_GESTURE_MAGNIFY_START
;
342 else if (gi
.dwFlags
& GF_END
) {
343 // Send a zoom end event, the delta is the change
345 evt
.message
= NS_SIMPLE_GESTURE_MAGNIFY
;
346 // (positive for a "zoom in")
347 evt
.delta
= -1.0 * (mZoomIntermediate
- (float)gi
.ullArguments
);
348 mZoomIntermediate
= (float)gi
.ullArguments
;
351 // Send a zoom intermediate event, the delta is the change
353 evt
.message
= NS_SIMPLE_GESTURE_MAGNIFY_UPDATE
;
354 // (positive for a "zoom in")
355 evt
.delta
= -1.0 * (mZoomIntermediate
- (float)gi
.ullArguments
);
356 mZoomIntermediate
= (float)gi
.ullArguments
;
363 // Send a rotate start event
364 double radians
= 0.0;
366 // On GF_BEGIN, ullArguments contains the absolute rotation at the
367 // start of the gesture. In later events it contains the offset from
369 if (gi
.ullArguments
!= 0)
370 radians
= GID_ROTATE_ANGLE_FROM_ARGUMENT(gi
.ullArguments
);
372 double degrees
= -1 * radians
* (180/M_PI
);
374 if (gi
.dwFlags
& GF_BEGIN
) {
375 // At some point we should pass the initial angle in
376 // along with delta. It's useful.
377 degrees
= mRotateIntermediate
= 0.0;
381 evt
.delta
= degrees
- mRotateIntermediate
;
382 mRotateIntermediate
= degrees
;
385 evt
.direction
= nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE
;
386 else if (evt
.delta
< 0)
387 evt
.direction
= nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE
;
389 if (gi
.dwFlags
& GF_BEGIN
)
390 evt
.message
= NS_SIMPLE_GESTURE_ROTATE_START
;
391 else if (gi
.dwFlags
& GF_END
)
392 evt
.message
= NS_SIMPLE_GESTURE_ROTATE
;
394 evt
.message
= NS_SIMPLE_GESTURE_ROTATE_UPDATE
;
398 case GID_TWOFINGERTAP
:
400 // Normally maps to "restore" from whatever you may have recently changed. A simple
402 evt
.message
= NS_SIMPLE_GESTURE_TAP
;
407 case GID_PRESSANDTAP
:
409 // Two finger right click. Defaults to right click if it falls through.
410 evt
.message
= NS_SIMPLE_GESTURE_PRESSTAP
;
420 nsWinGesture::ProcessPanMessage(HWND hWnd
, WPARAM wParam
, LPARAM lParam
)
424 ZeroMemory(&gi
,sizeof(GESTUREINFO
));
425 gi
.cbSize
= sizeof(GESTUREINFO
);
427 BOOL result
= GetGestureInfo((HGESTUREINFO
)lParam
, &gi
);
431 // The coordinates of this event
433 coord
= mPanRefPoint
= gi
.ptsLocation
;
434 // We want screen coordinates in our local offsets as client coordinates will change
435 // when feedback is taking place. Gui events though require client coordinates.
436 mPanRefPoint
.ScreenToClient(hWnd
);
442 // These should always fall through to DefWndProc
446 // Setup pixel scroll events for both axis
449 if (gi
.dwFlags
& GF_BEGIN
) {
450 mPanIntermediate
= coord
;
451 mPixelScrollDelta
= 0;
453 mPanInertiaActive
= false;
458 int32_t deltaX
= mPanIntermediate
.x
- coord
.x
;
459 int32_t deltaY
= mPanIntermediate
.y
- coord
.y
;
460 PR_LOG(gWindowsLog
, PR_LOG_ALWAYS
,
461 ("coordX=%d coordY=%d deltaX=%d deltaY=%d x:%d y:%d\n", coord
.x
,
462 coord
.y
, deltaX
, deltaY
, mXAxisFeedback
, mYAxisFeedback
));
465 mPixelScrollDelta
.x
= mPanIntermediate
.x
- coord
.x
;
466 mPixelScrollDelta
.y
= mPanIntermediate
.y
- coord
.y
;
467 mPanIntermediate
= coord
;
469 if (gi
.dwFlags
& GF_INERTIA
)
470 mPanInertiaActive
= true;
472 if (gi
.dwFlags
& GF_END
) {
474 mPanInertiaActive
= false;
475 PanFeedbackFinalize(hWnd
, true);
484 inline bool TestTransition(int32_t a
, int32_t b
)
486 // If a is zero, overflow is zero, implying the cursor has moved back to the start position.
487 // If b is zero, cached overscroll is zero, implying feedback just begun.
488 if (a
== 0 || b
== 0) return true;
489 // Test for different signs.
490 return (a
< 0) == (b
< 0);
494 nsWinGesture::UpdatePanFeedbackX(HWND hWnd
, int32_t scrollOverflow
, bool& endFeedback
)
496 // If scroll overflow was returned indicating we panned past the bounds of
497 // the scrollable view port, start feeback.
498 if (scrollOverflow
!= 0) {
499 if (!mFeedbackActive
) {
500 BeginPanningFeedback(hWnd
);
501 mFeedbackActive
= true;
504 mXAxisFeedback
= true;
508 if (mXAxisFeedback
) {
509 int32_t newOverflow
= mPixelScrollOverflow
.x
- mPixelScrollDelta
.x
;
511 // Detect a reverse transition past the starting drag point. This tells us the user
512 // has panned all the way back so we can stop providing feedback for this axis.
513 if (!TestTransition(newOverflow
, mPixelScrollOverflow
.x
) || newOverflow
== 0)
516 // Cache the total over scroll in pixels.
517 mPixelScrollOverflow
.x
= newOverflow
;
523 nsWinGesture::UpdatePanFeedbackY(HWND hWnd
, int32_t scrollOverflow
, bool& endFeedback
)
525 // If scroll overflow was returned indicating we panned past the bounds of
526 // the scrollable view port, start feeback.
527 if (scrollOverflow
!= 0) {
528 if (!mFeedbackActive
) {
529 BeginPanningFeedback(hWnd
);
530 mFeedbackActive
= true;
533 mYAxisFeedback
= true;
537 if (mYAxisFeedback
) {
538 int32_t newOverflow
= mPixelScrollOverflow
.y
- mPixelScrollDelta
.y
;
540 // Detect a reverse transition past the starting drag point. This tells us the user
541 // has panned all the way back so we can stop providing feedback for this axis.
542 if (!TestTransition(newOverflow
, mPixelScrollOverflow
.y
) || newOverflow
== 0)
545 // Cache the total over scroll in pixels.
546 mPixelScrollOverflow
.y
= newOverflow
;
552 nsWinGesture::PanFeedbackFinalize(HWND hWnd
, bool endFeedback
)
554 if (!mFeedbackActive
)
558 mFeedbackActive
= false;
559 mXAxisFeedback
= false;
560 mYAxisFeedback
= false;
561 mPixelScrollOverflow
= 0;
562 EndPanningFeedback(hWnd
);
566 UpdatePanningFeedback(hWnd
, mPixelScrollOverflow
.x
, mPixelScrollOverflow
.y
, mPanInertiaActive
);
570 nsWinGesture::PanDeltaToPixelScroll(WidgetWheelEvent
& aWheelEvent
)
572 aWheelEvent
.deltaX
= aWheelEvent
.deltaY
= aWheelEvent
.deltaZ
= 0.0;
573 aWheelEvent
.lineOrPageDeltaX
= aWheelEvent
.lineOrPageDeltaY
= 0;
575 aWheelEvent
.refPoint
.x
= mPanRefPoint
.x
;
576 aWheelEvent
.refPoint
.y
= mPanRefPoint
.y
;
577 aWheelEvent
.deltaMode
= nsIDOMWheelEvent::DOM_DELTA_PIXEL
;
578 aWheelEvent
.scrollType
= WidgetWheelEvent::SCROLL_SYNCHRONOUSLY
;
579 aWheelEvent
.mIsNoLineOrPageDelta
= true;
581 aWheelEvent
.overflowDeltaX
= 0.0;
582 aWheelEvent
.overflowDeltaY
= 0.0;
584 // Don't scroll the view if we are currently at a bounds, or, if we are
585 // panning back from a max feedback position. This keeps the original drag point
587 if (!mXAxisFeedback
) {
588 aWheelEvent
.deltaX
= mPixelScrollDelta
.x
;
590 if (!mYAxisFeedback
) {
591 aWheelEvent
.deltaY
= mPixelScrollDelta
.y
;
594 return (aWheelEvent
.deltaX
!= 0 || aWheelEvent
.deltaY
!= 0);