1 /* -*- Mode: C++; tab-width: 4; 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/. */
5
6 #include "nsWindowBase.h"
7
8 #include "mozilla/MiscEvents.h"
9 #include "mozilla/PresShell.h"
10 #include "mozilla/StaticPrefs_apz.h"
11 #include "KeyboardLayout.h"
12 #include "WinUtils.h"
13 #include "npapi.h"
14
15 using namespace mozilla;
16 using namespace mozilla::widget;
17
18 static const wchar_t kUser32LibName[] = L"user32.dll";
19 bool nsWindowBase::sTouchInjectInitialized = false;
20 InjectTouchInputPtr nsWindowBase::sInjectTouchFuncPtr;
21
22 // static
InitTouchInjection()23 bool nsWindowBase::InitTouchInjection() {
24 if (!sTouchInjectInitialized) {
25 // Initialize touch injection on the first call
26 HMODULE hMod = LoadLibraryW(kUser32LibName);
27 if (!hMod) {
28 return false;
29 }
30
31 InitializeTouchInjectionPtr func =
32 (InitializeTouchInjectionPtr)GetProcAddress(hMod,
33 "InitializeTouchInjection");
34 if (!func) {
35 WinUtils::Log("InitializeTouchInjection not available.");
36 return false;
37 }
38
39 if (!func(TOUCH_INJECT_MAX_POINTS, TOUCH_FEEDBACK_DEFAULT)) {
40 WinUtils::Log("InitializeTouchInjection failure. GetLastError=%d",
41 GetLastError());
42 return false;
43 }
44
45 sInjectTouchFuncPtr =
46 (InjectTouchInputPtr)GetProcAddress(hMod, "InjectTouchInput");
47 if (!sInjectTouchFuncPtr) {
48 WinUtils::Log("InjectTouchInput not available.");
49 return false;
50 }
51 sTouchInjectInitialized = true;
52 }
53 return true;
54 }
55
InjectTouchPoint(uint32_t aId,LayoutDeviceIntPoint & aPoint,POINTER_FLAGS aFlags,uint32_t aPressure,uint32_t aOrientation)56 bool nsWindowBase::InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint,
57 POINTER_FLAGS aFlags, uint32_t aPressure,
58 uint32_t aOrientation) {
59 if (aId > TOUCH_INJECT_MAX_POINTS) {
60 WinUtils::Log("Pointer ID exceeds maximum. See TOUCH_INJECT_MAX_POINTS.");
61 return false;
62 }
63
64 POINTER_TOUCH_INFO info{};
65
66 info.touchFlags = TOUCH_FLAG_NONE;
67 info.touchMask =
68 TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
69 info.pressure = aPressure;
70 info.orientation = aOrientation;
71
72 info.pointerInfo.pointerFlags = aFlags;
73 info.pointerInfo.pointerType = PT_TOUCH;
74 info.pointerInfo.pointerId = aId;
75 info.pointerInfo.ptPixelLocation.x = aPoint.x;
76 info.pointerInfo.ptPixelLocation.y = aPoint.y;
77
78 info.rcContact.top = info.pointerInfo.ptPixelLocation.y - 2;
79 info.rcContact.bottom = info.pointerInfo.ptPixelLocation.y + 2;
80 info.rcContact.left = info.pointerInfo.ptPixelLocation.x - 2;
81 info.rcContact.right = info.pointerInfo.ptPixelLocation.x + 2;
82
83 for (int i = 0; i < 3; i++) {
84 if (sInjectTouchFuncPtr(1, &info)) {
85 break;
86 }
87 DWORD error = GetLastError();
88 if (error == ERROR_NOT_READY && i < 2) {
89 // We sent it too quickly after the previous injection (see bug 1535140
90 // comment 10). On the first loop iteration we just yield (via Sleep(0))
91 // and try again. If it happens again on the second loop iteration we
92 // explicitly Sleep(1) and try again. If that doesn't work either we just
93 // error out.
94 ::Sleep(i);
95 continue;
96 }
97 WinUtils::Log("InjectTouchInput failure. GetLastError=%d", error);
98 return false;
99 }
100 return true;
101 }
102
ChangedDPI()103 void nsWindowBase::ChangedDPI() {
104 if (mWidgetListener) {
105 if (PresShell* presShell = mWidgetListener->GetPresShell()) {
106 presShell->BackingScaleFactorChanged();
107 }
108 }
109 }
110
PointerStateToFlag(nsWindowBase::TouchPointerState aPointerState,bool isUpdate)111 static Result<POINTER_FLAGS, nsresult> PointerStateToFlag(
112 nsWindowBase::TouchPointerState aPointerState, bool isUpdate) {
113 bool hover = aPointerState & nsWindowBase::TOUCH_HOVER;
114 bool contact = aPointerState & nsWindowBase::TOUCH_CONTACT;
115 bool remove = aPointerState & nsWindowBase::TOUCH_REMOVE;
116 bool cancel = aPointerState & nsWindowBase::TOUCH_CANCEL;
117
118 POINTER_FLAGS flags;
119 if (isUpdate) {
120 // We know about this pointer, send an update
121 flags = POINTER_FLAG_UPDATE;
122 if (hover) {
123 flags |= POINTER_FLAG_INRANGE;
124 } else if (contact) {
125 flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE;
126 } else if (remove) {
127 flags = POINTER_FLAG_UP;
128 }
129
130 if (cancel) {
131 flags |= POINTER_FLAG_CANCELED;
132 }
133 } else {
134 // Missing init state, error out
135 if (remove || cancel) {
136 return Err(NS_ERROR_INVALID_ARG);
137 }
138
139 // Create a new pointer
140 flags = POINTER_FLAG_INRANGE;
141 if (contact) {
142 flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN;
143 }
144 }
145 return flags;
146 }
147
SynthesizeNativeTouchPoint(uint32_t aPointerId,nsIWidget::TouchPointerState aPointerState,LayoutDeviceIntPoint aPoint,double aPointerPressure,uint32_t aPointerOrientation,nsIObserver * aObserver)148 nsresult nsWindowBase::SynthesizeNativeTouchPoint(
149 uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
150 LayoutDeviceIntPoint aPoint, double aPointerPressure,
151 uint32_t aPointerOrientation, nsIObserver* aObserver) {
152 AutoObserverNotifier notifier(aObserver, "touchpoint");
153
154 if (StaticPrefs::apz_test_fails_with_native_injection() ||
155 !InitTouchInjection()) {
156 // If we don't have touch injection from the OS, or if we are running a test
157 // that cannot properly inject events to satisfy the OS requirements (see
158 // bug 1313170) we can just fake it and synthesize the events from here.
159 MOZ_ASSERT(NS_IsMainThread());
160 if (aPointerState == TOUCH_HOVER) {
161 return NS_ERROR_UNEXPECTED;
162 }
163
164 if (!mSynthesizedTouchInput) {
165 mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
166 }
167
168 WidgetEventTime time = CurrentMessageWidgetEventTime();
169 LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
170 MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
171 mSynthesizedTouchInput.get(), time.mTime, time.mTimeStamp, aPointerId,
172 aPointerState, pointInWindow, aPointerPressure, aPointerOrientation);
173 DispatchTouchInput(inputToDispatch);
174 return NS_OK;
175 }
176
177 // win api expects a value from 0 to 1024. aPointerPressure is a value
178 // from 0.0 to 1.0.
179 uint32_t pressure = (uint32_t)ceil(aPointerPressure * 1024);
180
181 // If we already know about this pointer id get it's record
182 return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
183 POINTER_FLAGS flags;
184 // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle
185 auto result = PointerStateToFlag(aPointerState, !!entry);
186 if (result.isOk()) {
187 flags = result.unwrap();
188 } else {
189 return result.unwrapErr();
190 }
191
192 if (!entry) {
193 entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
194 PointerInfo::PointerType::TOUCH));
195 } else {
196 if (entry.Data()->mType != PointerInfo::PointerType::TOUCH) {
197 return NS_ERROR_UNEXPECTED;
198 }
199 if (aPointerState & TOUCH_REMOVE) {
200 // Remove the pointer from our tracking list. This is UniquePtr wrapped,
201 // so shouldn't leak.
202 entry.Remove();
203 }
204 }
205
206 return !InjectTouchPoint(aPointerId, aPoint, flags, pressure,
207 aPointerOrientation)
208 ? NS_ERROR_UNEXPECTED
209 : NS_OK;
210 });
211 }
212
ClearNativeTouchSequence(nsIObserver * aObserver)213 nsresult nsWindowBase::ClearNativeTouchSequence(nsIObserver* aObserver) {
214 AutoObserverNotifier notifier(aObserver, "cleartouch");
215 if (!sTouchInjectInitialized) {
216 return NS_OK;
217 }
218
219 // cancel all input points
220 for (auto iter = mActivePointers.Iter(); !iter.Done(); iter.Next()) {
221 auto* info = iter.UserData();
222 if (info->mType != PointerInfo::PointerType::TOUCH) {
223 continue;
224 }
225 InjectTouchPoint(info->mPointerId, info->mPosition, POINTER_FLAG_CANCELED);
226 iter.Remove();
227 }
228
229 nsBaseWidget::ClearNativeTouchSequence(nullptr);
230
231 return NS_OK;
232 }
233
234 #if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
235 static CreateSyntheticPointerDevicePtr CreateSyntheticPointerDevice;
236 static DestroySyntheticPointerDevicePtr DestroySyntheticPointerDevice;
237 static InjectSyntheticPointerInputPtr InjectSyntheticPointerInput;
238 #endif
239 static HSYNTHETICPOINTERDEVICE sSyntheticPenDevice;
240
InitPenInjection()241 static bool InitPenInjection() {
242 if (sSyntheticPenDevice) {
243 return true;
244 }
245 #if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
246 HMODULE hMod = LoadLibraryW(kUser32LibName);
247 if (!hMod) {
248 return false;
249 }
250 CreateSyntheticPointerDevice =
251 (CreateSyntheticPointerDevicePtr)GetProcAddress(
252 hMod, "CreateSyntheticPointerDevice");
253 if (!CreateSyntheticPointerDevice) {
254 WinUtils::Log("CreateSyntheticPointerDevice not available.");
255 return false;
256 }
257 DestroySyntheticPointerDevice =
258 (DestroySyntheticPointerDevicePtr)GetProcAddress(
259 hMod, "DestroySyntheticPointerDevice");
260 if (!DestroySyntheticPointerDevice) {
261 WinUtils::Log("DestroySyntheticPointerDevice not available.");
262 return false;
263 }
264 InjectSyntheticPointerInput = (InjectSyntheticPointerInputPtr)GetProcAddress(
265 hMod, "InjectSyntheticPointerInput");
266 if (!InjectSyntheticPointerInput) {
267 WinUtils::Log("InjectSyntheticPointerInput not available.");
268 return false;
269 }
270 #endif
271 sSyntheticPenDevice =
272 CreateSyntheticPointerDevice(PT_PEN, 1, POINTER_FEEDBACK_DEFAULT);
273 return !!sSyntheticPenDevice;
274 }
275
SynthesizeNativePenInput(uint32_t aPointerId,nsIWidget::TouchPointerState aPointerState,LayoutDeviceIntPoint aPoint,double aPressure,uint32_t aRotation,int32_t aTiltX,int32_t aTiltY,nsIObserver * aObserver)276 nsresult nsWindowBase::SynthesizeNativePenInput(
277 uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
278 LayoutDeviceIntPoint aPoint, double aPressure, uint32_t aRotation,
279 int32_t aTiltX, int32_t aTiltY, nsIObserver* aObserver) {
280 AutoObserverNotifier notifier(aObserver, "peninput");
281 if (!InitPenInjection()) {
282 return NS_ERROR_UNEXPECTED;
283 }
284
285 // win api expects a value from 0 to 1024. aPointerPressure is a value
286 // from 0.0 to 1.0.
287 uint32_t pressure = (uint32_t)ceil(aPressure * 1024);
288
289 // If we already know about this pointer id get it's record
290 return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
291 POINTER_FLAGS flags;
292 // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle
293 auto result = PointerStateToFlag(aPointerState, !!entry);
294 if (result.isOk()) {
295 flags = result.unwrap();
296 } else {
297 return result.unwrapErr();
298 }
299
300 if (!entry) {
301 entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
302 PointerInfo::PointerType::PEN));
303 } else {
304 if (entry.Data()->mType != PointerInfo::PointerType::PEN) {
305 return NS_ERROR_UNEXPECTED;
306 }
307 if (aPointerState & TOUCH_REMOVE) {
308 // Remove the pointer from our tracking list. This is UniquePtr wrapped,
309 // so shouldn't leak.
310 entry.Remove();
311 }
312 }
313
314 POINTER_TYPE_INFO info{};
315
316 info.type = PT_PEN;
317 info.penInfo.pointerInfo.pointerType = PT_PEN;
318 info.penInfo.pointerInfo.pointerFlags = flags;
319 info.penInfo.pointerInfo.pointerId = aPointerId;
320 info.penInfo.pointerInfo.ptPixelLocation.x = aPoint.x;
321 info.penInfo.pointerInfo.ptPixelLocation.y = aPoint.y;
322
323 info.penInfo.penFlags = PEN_FLAG_NONE;
324 info.penInfo.penMask = PEN_MASK_PRESSURE | PEN_MASK_ROTATION |
325 PEN_MASK_TILT_X | PEN_MASK_TILT_Y;
326 info.penInfo.pressure = pressure;
327 info.penInfo.rotation = aRotation;
328 info.penInfo.tiltX = aTiltX;
329 info.penInfo.tiltY = aTiltY;
330
331 return InjectSyntheticPointerInput(sSyntheticPenDevice, &info, 1)
332 ? NS_OK
333 : NS_ERROR_UNEXPECTED;
334 });
335 };
336
HandleAppCommandMsg(const MSG & aAppCommandMsg,LRESULT * aRetValue)337 bool nsWindowBase::HandleAppCommandMsg(const MSG& aAppCommandMsg,
338 LRESULT* aRetValue) {
339 ModifierKeyState modKeyState;
340 NativeKey nativeKey(this, aAppCommandMsg, modKeyState);
341 bool consumed = nativeKey.HandleAppCommandMessage();
342 *aRetValue = consumed ? 1 : 0;
343 return consumed;
344 }
345