/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "nsWindowBase.h" #include "mozilla/MiscEvents.h" #include "mozilla/PresShell.h" #include "mozilla/StaticPrefs_apz.h" #include "KeyboardLayout.h" #include "WinUtils.h" #include "npapi.h" using namespace mozilla; using namespace mozilla::widget; static const wchar_t kUser32LibName[] = L"user32.dll"; bool nsWindowBase::sTouchInjectInitialized = false; InjectTouchInputPtr nsWindowBase::sInjectTouchFuncPtr; // static bool nsWindowBase::InitTouchInjection() { if (!sTouchInjectInitialized) { // Initialize touch injection on the first call HMODULE hMod = LoadLibraryW(kUser32LibName); if (!hMod) { return false; } InitializeTouchInjectionPtr func = (InitializeTouchInjectionPtr)GetProcAddress(hMod, "InitializeTouchInjection"); if (!func) { WinUtils::Log("InitializeTouchInjection not available."); return false; } if (!func(TOUCH_INJECT_MAX_POINTS, TOUCH_FEEDBACK_DEFAULT)) { WinUtils::Log("InitializeTouchInjection failure. GetLastError=%d", GetLastError()); return false; } sInjectTouchFuncPtr = (InjectTouchInputPtr)GetProcAddress(hMod, "InjectTouchInput"); if (!sInjectTouchFuncPtr) { WinUtils::Log("InjectTouchInput not available."); return false; } sTouchInjectInitialized = true; } return true; } bool nsWindowBase::InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint, POINTER_FLAGS aFlags, uint32_t aPressure, uint32_t aOrientation) { if (aId > TOUCH_INJECT_MAX_POINTS) { WinUtils::Log("Pointer ID exceeds maximum. See TOUCH_INJECT_MAX_POINTS."); return false; } POINTER_TOUCH_INFO info{}; info.touchFlags = TOUCH_FLAG_NONE; info.touchMask = TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE; info.pressure = aPressure; info.orientation = aOrientation; info.pointerInfo.pointerFlags = aFlags; info.pointerInfo.pointerType = PT_TOUCH; info.pointerInfo.pointerId = aId; info.pointerInfo.ptPixelLocation.x = aPoint.x; info.pointerInfo.ptPixelLocation.y = aPoint.y; info.rcContact.top = info.pointerInfo.ptPixelLocation.y - 2; info.rcContact.bottom = info.pointerInfo.ptPixelLocation.y + 2; info.rcContact.left = info.pointerInfo.ptPixelLocation.x - 2; info.rcContact.right = info.pointerInfo.ptPixelLocation.x + 2; for (int i = 0; i < 3; i++) { if (sInjectTouchFuncPtr(1, &info)) { break; } DWORD error = GetLastError(); if (error == ERROR_NOT_READY && i < 2) { // We sent it too quickly after the previous injection (see bug 1535140 // comment 10). On the first loop iteration we just yield (via Sleep(0)) // and try again. If it happens again on the second loop iteration we // explicitly Sleep(1) and try again. If that doesn't work either we just // error out. ::Sleep(i); continue; } WinUtils::Log("InjectTouchInput failure. GetLastError=%d", error); return false; } return true; } void nsWindowBase::ChangedDPI() { if (mWidgetListener) { if (PresShell* presShell = mWidgetListener->GetPresShell()) { presShell->BackingScaleFactorChanged(); } } } static Result PointerStateToFlag( nsWindowBase::TouchPointerState aPointerState, bool isUpdate) { bool hover = aPointerState & nsWindowBase::TOUCH_HOVER; bool contact = aPointerState & nsWindowBase::TOUCH_CONTACT; bool remove = aPointerState & nsWindowBase::TOUCH_REMOVE; bool cancel = aPointerState & nsWindowBase::TOUCH_CANCEL; POINTER_FLAGS flags; if (isUpdate) { // We know about this pointer, send an update flags = POINTER_FLAG_UPDATE; if (hover) { flags |= POINTER_FLAG_INRANGE; } else if (contact) { flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE; } else if (remove) { flags = POINTER_FLAG_UP; } if (cancel) { flags |= POINTER_FLAG_CANCELED; } } else { // Missing init state, error out if (remove || cancel) { return Err(NS_ERROR_INVALID_ARG); } // Create a new pointer flags = POINTER_FLAG_INRANGE; if (contact) { flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN; } } return flags; } nsresult nsWindowBase::SynthesizeNativeTouchPoint( uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState, LayoutDeviceIntPoint aPoint, double aPointerPressure, uint32_t aPointerOrientation, nsIObserver* aObserver) { AutoObserverNotifier notifier(aObserver, "touchpoint"); if (StaticPrefs::apz_test_fails_with_native_injection() || !InitTouchInjection()) { // If we don't have touch injection from the OS, or if we are running a test // that cannot properly inject events to satisfy the OS requirements (see // bug 1313170) we can just fake it and synthesize the events from here. MOZ_ASSERT(NS_IsMainThread()); if (aPointerState == TOUCH_HOVER) { return NS_ERROR_UNEXPECTED; } if (!mSynthesizedTouchInput) { mSynthesizedTouchInput = MakeUnique(); } WidgetEventTime time = CurrentMessageWidgetEventTime(); LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset(); MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState( mSynthesizedTouchInput.get(), time.mTime, time.mTimeStamp, aPointerId, aPointerState, pointInWindow, aPointerPressure, aPointerOrientation); DispatchTouchInput(inputToDispatch); return NS_OK; } // win api expects a value from 0 to 1024. aPointerPressure is a value // from 0.0 to 1.0. uint32_t pressure = (uint32_t)ceil(aPointerPressure * 1024); // If we already know about this pointer id get it's record return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) { POINTER_FLAGS flags; // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle auto result = PointerStateToFlag(aPointerState, !!entry); if (result.isOk()) { flags = result.unwrap(); } else { return result.unwrapErr(); } if (!entry) { entry.Insert(MakeUnique(aPointerId, aPoint, PointerInfo::PointerType::TOUCH)); } else { if (entry.Data()->mType != PointerInfo::PointerType::TOUCH) { return NS_ERROR_UNEXPECTED; } if (aPointerState & TOUCH_REMOVE) { // Remove the pointer from our tracking list. This is UniquePtr wrapped, // so shouldn't leak. entry.Remove(); } } return !InjectTouchPoint(aPointerId, aPoint, flags, pressure, aPointerOrientation) ? NS_ERROR_UNEXPECTED : NS_OK; }); } nsresult nsWindowBase::ClearNativeTouchSequence(nsIObserver* aObserver) { AutoObserverNotifier notifier(aObserver, "cleartouch"); if (!sTouchInjectInitialized) { return NS_OK; } // cancel all input points for (auto iter = mActivePointers.Iter(); !iter.Done(); iter.Next()) { auto* info = iter.UserData(); if (info->mType != PointerInfo::PointerType::TOUCH) { continue; } InjectTouchPoint(info->mPointerId, info->mPosition, POINTER_FLAG_CANCELED); iter.Remove(); } nsBaseWidget::ClearNativeTouchSequence(nullptr); return NS_OK; } #if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5) static CreateSyntheticPointerDevicePtr CreateSyntheticPointerDevice; static DestroySyntheticPointerDevicePtr DestroySyntheticPointerDevice; static InjectSyntheticPointerInputPtr InjectSyntheticPointerInput; #endif static HSYNTHETICPOINTERDEVICE sSyntheticPenDevice; static bool InitPenInjection() { if (sSyntheticPenDevice) { return true; } #if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5) HMODULE hMod = LoadLibraryW(kUser32LibName); if (!hMod) { return false; } CreateSyntheticPointerDevice = (CreateSyntheticPointerDevicePtr)GetProcAddress( hMod, "CreateSyntheticPointerDevice"); if (!CreateSyntheticPointerDevice) { WinUtils::Log("CreateSyntheticPointerDevice not available."); return false; } DestroySyntheticPointerDevice = (DestroySyntheticPointerDevicePtr)GetProcAddress( hMod, "DestroySyntheticPointerDevice"); if (!DestroySyntheticPointerDevice) { WinUtils::Log("DestroySyntheticPointerDevice not available."); return false; } InjectSyntheticPointerInput = (InjectSyntheticPointerInputPtr)GetProcAddress( hMod, "InjectSyntheticPointerInput"); if (!InjectSyntheticPointerInput) { WinUtils::Log("InjectSyntheticPointerInput not available."); return false; } #endif sSyntheticPenDevice = CreateSyntheticPointerDevice(PT_PEN, 1, POINTER_FEEDBACK_DEFAULT); return !!sSyntheticPenDevice; } nsresult nsWindowBase::SynthesizeNativePenInput( uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState, LayoutDeviceIntPoint aPoint, double aPressure, uint32_t aRotation, int32_t aTiltX, int32_t aTiltY, nsIObserver* aObserver) { AutoObserverNotifier notifier(aObserver, "peninput"); if (!InitPenInjection()) { return NS_ERROR_UNEXPECTED; } // win api expects a value from 0 to 1024. aPointerPressure is a value // from 0.0 to 1.0. uint32_t pressure = (uint32_t)ceil(aPressure * 1024); // If we already know about this pointer id get it's record return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) { POINTER_FLAGS flags; // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle auto result = PointerStateToFlag(aPointerState, !!entry); if (result.isOk()) { flags = result.unwrap(); } else { return result.unwrapErr(); } if (!entry) { entry.Insert(MakeUnique(aPointerId, aPoint, PointerInfo::PointerType::PEN)); } else { if (entry.Data()->mType != PointerInfo::PointerType::PEN) { return NS_ERROR_UNEXPECTED; } if (aPointerState & TOUCH_REMOVE) { // Remove the pointer from our tracking list. This is UniquePtr wrapped, // so shouldn't leak. entry.Remove(); } } POINTER_TYPE_INFO info{}; info.type = PT_PEN; info.penInfo.pointerInfo.pointerType = PT_PEN; info.penInfo.pointerInfo.pointerFlags = flags; info.penInfo.pointerInfo.pointerId = aPointerId; info.penInfo.pointerInfo.ptPixelLocation.x = aPoint.x; info.penInfo.pointerInfo.ptPixelLocation.y = aPoint.y; info.penInfo.penFlags = PEN_FLAG_NONE; info.penInfo.penMask = PEN_MASK_PRESSURE | PEN_MASK_ROTATION | PEN_MASK_TILT_X | PEN_MASK_TILT_Y; info.penInfo.pressure = pressure; info.penInfo.rotation = aRotation; info.penInfo.tiltX = aTiltX; info.penInfo.tiltY = aTiltY; return InjectSyntheticPointerInput(sSyntheticPenDevice, &info, 1) ? NS_OK : NS_ERROR_UNEXPECTED; }); }; bool nsWindowBase::HandleAppCommandMsg(const MSG& aAppCommandMsg, LRESULT* aRetValue) { ModifierKeyState modKeyState; NativeKey nativeKey(this, aAppCommandMsg, modKeyState); bool consumed = nativeKey.HandleAppCommandMessage(); *aRetValue = consumed ? 1 : 0; return consumed; }