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