From cf514a707dd28fcb84d08ce0da3e2254bba9baf7 Mon Sep 17 00:00:00 2001 From: Maksim Lebedev Date: Fri, 17 Apr 2015 09:59:00 -0400 Subject: [PATCH] Bug 1016232 - Add detection when pen leave hover of digitizer. r=smaug, r=jimm --- widget/windows/InkCollector.cpp | 223 ++++++++++++++++++++++++++++++++ widget/windows/InkCollector.h | 71 ++++++++++ widget/windows/moz.build | 1 + widget/windows/nsWindow.cpp | 29 +++++ 4 files changed, 324 insertions(+) create mode 100644 widget/windows/InkCollector.cpp create mode 100644 widget/windows/InkCollector.h diff --git a/widget/windows/InkCollector.cpp b/widget/windows/InkCollector.cpp new file mode 100644 index 00000000000..c1c7c3c7d6e --- /dev/null +++ b/widget/windows/InkCollector.cpp @@ -0,0 +1,223 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +* vim: set ts=2 sw=2 et tw=78: +* 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 "InkCollector.h" + +// Msinkaut_i.c and Msinkaut.h should both be included +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms695519.aspx +#include + +StaticRefPtr InkCollector::sInkCollector; + +InkCollector::InkCollector() +{ +} + +InkCollector::~InkCollector() +{ + Shutdown(); + MOZ_ASSERT(!mRefCount); +} + +void InkCollector::Initialize() +{ + // Possibly, we can use mConnectionPoint for checking, + // But if errors exist (perhaps COM object is unavailable), + // Initialize() will be called more times. + static bool sInkCollectorCreated = false; + if (sInkCollectorCreated) { + return; + } + sInkCollectorCreated = true; + + // COM could get uninitialized due to previous initialization. + mComInitialized = SUCCEEDED(::CoInitialize(nullptr)); + + // Set up a free threaded marshaler. + if (FAILED(::CoCreateFreeThreadedMarshaler(this, getter_AddRefs(mMarshaller)))) { + return; + } + // Create the ink collector. + if (FAILED(::CoCreateInstance(CLSID_InkCollector, NULL, CLSCTX_INPROC_SERVER, + IID_IInkCollector, getter_AddRefs(mInkCollector)))) { + return; + } + NS_ADDREF(mInkCollector); + // Set up connection between sink and InkCollector. + nsRefPtr connPointContainer; + // Get the connection point container. + if (SUCCEEDED(mInkCollector->QueryInterface(IID_IConnectionPointContainer, + getter_AddRefs(connPointContainer)))) { + // Find the connection point for Ink Collector events. + if (SUCCEEDED(connPointContainer->FindConnectionPoint(__uuidof(_IInkCollectorEvents), + getter_AddRefs(mConnectionPoint)))) { + NS_ADDREF(mConnectionPoint); + // Hook up sink to connection point. + if (SUCCEEDED(mConnectionPoint->Advise(this, &mCookie))) { + OnInitialize(); + } + } + } +} + +void InkCollector::Shutdown() +{ + Enable(false); + if (mConnectionPoint) { + // Remove the connection of the sink to the Ink Collector. + mConnectionPoint->Unadvise(mCookie); + NS_RELEASE(mConnectionPoint); + } + NS_IF_RELEASE(mMarshaller); + NS_IF_RELEASE(mInkCollector); + + // Let uninitialization get handled in a place where it got inited. + if (mComInitialized) { + CoUninitialize(); + mComInitialized = false; + } +} + +void InkCollector::OnInitialize() +{ + // Suppress all events to do not allow performance decreasing. + // https://msdn.microsoft.com/en-us/library/ms820347.aspx + mInkCollector->SetEventInterest(InkCollectorEventInterest::ICEI_AllEvents, VARIANT_FALSE); + + // Sets a value that indicates whether an object or control has interest in a specified event. + mInkCollector->SetEventInterest(InkCollectorEventInterest::ICEI_CursorOutOfRange, VARIANT_TRUE); + + // If the MousePointer property is set to IMP_Custom and the MouseIcon property is NULL, + // Then the ink collector no longer handles mouse cursor settings. + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms700686.aspx + mInkCollector->put_MouseIcon(nullptr); + mInkCollector->put_MousePointer(InkMousePointer::IMP_Custom); + + // This mode allows an ink collector to collect ink from any tablet attached to the Tablet PC. + // The Boolean value that indicates whether to use the mouse as an input device. + // If TRUE, the mouse is used for input. + // https://msdn.microsoft.com/en-us/library/ms820346.aspx + mInkCollector->SetAllTabletsMode(VARIANT_FALSE); + + // Sets the value that specifies whether ink is rendered as it is drawn. + // VARIANT_TRUE to render ink as it is drawn on the display. + // VARIANT_FALSE to not have the ink appear on the display as strokes are made. + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd314598.aspx + mInkCollector->put_DynamicRendering(VARIANT_FALSE); +} + +// Sets a value that specifies whether the InkCollector object collects pen input. +// This property must be set to FALSE before setting or +// calling specific properties and methods of the object. +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms701721.aspx +void InkCollector::Enable(bool aNewState) +{ + if (aNewState != mEnabled) { + if (mInkCollector) { + if (S_OK == mInkCollector->put_Enabled(aNewState ? VARIANT_TRUE : VARIANT_FALSE)) { + mEnabled = aNewState; + } else { + NS_WARNING("InkCollector did not change status successfully"); + } + } else { + NS_WARNING("InkCollector should be exist"); + } + } +} + +void InkCollector::SetTarget(HWND aTargetWindow) +{ + NS_ASSERTION(aTargetWindow, "aTargetWindow should be exist"); + if (aTargetWindow && (aTargetWindow != mTargetWindow)) { + Initialize(); + if (mInkCollector) { + Enable(false); + if (S_OK == mInkCollector->put_hWnd((LONG_PTR)aTargetWindow)) { + mTargetWindow = aTargetWindow; + } else { + NS_WARNING("InkCollector did not change window property successfully"); + } + Enable(true); + } + } +} + +// The display and the digitizer have quite different properties. +// The display has CursorMustTouch, the mouse pointer alway touches the display surface. +// The digitizer lists Integrated and HardProximity. +// When the stylus is in the proximity of the tablet its movements are also detected. +// An external tablet will only list HardProximity. +bool InkCollector::IsHardProximityTablet(IInkTablet* aTablet) const +{ + if (aTablet) { + TabletHardwareCapabilities caps; + if (SUCCEEDED(aTablet->get_HardwareCapabilities(&caps))) { + return (TabletHardwareCapabilities::THWC_HardProximity & caps); + } + } + return false; +} + +HRESULT __stdcall InkCollector::QueryInterface(REFIID aRiid, void **aObject) +{ + // Validate the input + if (!aObject) { + return E_POINTER; + } + HRESULT result = E_NOINTERFACE; + // This object supports IUnknown/IDispatch/IInkCollectorEvents + if ((IID_IUnknown == aRiid) || + (IID_IDispatch == aRiid) || + (DIID__IInkCollectorEvents == aRiid)) { + *aObject = this; + // AddRef should be called when we give info about interface + NS_ADDREF_THIS(); + result = S_OK; + } else if (IID_IMarshal == aRiid) { + // Assert that the free threaded marshaller has been initialized. + // It is necessary to call Initialize() before invoking this method. + NS_ASSERTION(mMarshaller, "Free threaded marshaller is null!"); + // Use free threaded marshalling. + result = mMarshaller->QueryInterface(aRiid, aObject); + } + return result; +} + +HRESULT InkCollector::Invoke(DISPID aDispIdMember, REFIID /*aRiid*/, + LCID /*aId*/, WORD /*wFlags*/, + DISPPARAMS* aDispParams, VARIANT* /*aVarResult*/, + EXCEPINFO* /*aExcepInfo*/, UINT* /*aArgErr*/) +{ + switch (aDispIdMember) { + case DISPID_ICECursorOutOfRange: { + if (aDispParams && aDispParams->cArgs) { + CursorOutOfRange(static_cast(aDispParams->rgvarg[0].pdispVal)); + } + break; + } + }; + // Release should be called after all usage of this method. + NS_RELEASE_THIS(); + return S_OK; +} + +void InkCollector::CursorOutOfRange(IInkCursor* aCursor) const +{ + IInkTablet* curTablet = nullptr; + if (FAILED(aCursor->get_Tablet(&curTablet))) { + return; + } + // All events should be suppressed except + // from tablets with hard proximity. + if (!IsHardProximityTablet(curTablet)) { + return; + } + // Notify current target window. + if (mTargetWindow) { + ::PostMessage(mTargetWindow, MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER, 0, 0); + } +} diff --git a/widget/windows/InkCollector.h b/widget/windows/InkCollector.h new file mode 100644 index 00000000000..3efccf57c81 --- /dev/null +++ b/widget/windows/InkCollector.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +* vim: set ts=2 sw=2 et tw=78: +* 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/. +*/ + +#ifndef InkCollector_h__ +#define InkCollector_h__ + +#include +#include "StaticPtr.h" + +#define MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER WM_USER + 0x83 + +class InkCollector : public _IInkCollectorEvents +{ +public: + InkCollector(); + + void Shutdown(); + void SetTarget(HWND aTargetWindow); + + static StaticRefPtr sInkCollector; + friend StaticRefPtr; + +protected: + // IUnknown + virtual ULONG STDMETHODCALLTYPE AddRef() { return ++mRefCount; } + virtual ULONG STDMETHODCALLTYPE Release() + { + MOZ_ASSERT(mRefCount); + if (!--mRefCount) { + delete this; + return 0; + } + return mRefCount; + } + HRESULT __stdcall QueryInterface(REFIID aRiid, void **aObject); + + // IDispatch + STDMETHOD(GetTypeInfoCount)(UINT* aInfo) { return E_NOTIMPL; } + STDMETHOD(GetTypeInfo)(UINT aInfo, LCID aId, ITypeInfo** aTInfo) { return E_NOTIMPL; } + STDMETHOD(GetIDsOfNames)(REFIID aRiid, LPOLESTR* aStrNames, UINT aNames, + LCID aId, DISPID* aDispId) { return E_NOTIMPL; } + STDMETHOD(Invoke)(DISPID aDispIdMember, REFIID aRiid, + LCID aId, WORD wFlags, + DISPPARAMS* aDispParams, VARIANT* aVarResult, + EXCEPINFO* aExcepInfo, UINT* aArgErr); + + // InkCollector + virtual ~InkCollector(); + void Initialize(); + void OnInitialize(); + void Enable(bool aNewState); + void CursorOutOfRange(IInkCursor* aCursor) const; + bool IsHardProximityTablet(IInkTablet* aTablet) const; + +private: + nsRefPtr mMarshaller; + nsRefPtr mInkCollector; + nsRefPtr mConnectionPoint; + + uint32_t mRefCount = 0; + DWORD mCookie = 0; + HWND mTargetWindow = 0; + bool mComInitialized = false; + bool mEnabled = false; +}; + +#endif // nsInkCollector_h_ diff --git a/widget/windows/moz.build b/widget/windows/moz.build index fddee5f0a51..aa8bf3225f9 100644 --- a/widget/windows/moz.build +++ b/widget/windows/moz.build @@ -20,6 +20,7 @@ UNIFIED_SOURCES += [ 'AudioSession.cpp', 'GfxInfo.cpp', 'IEnumFE.cpp', + 'InkCollector.cpp', 'JumpListItem.cpp', 'KeyboardLayout.cpp', 'nsAppShell.cpp', diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp index 45967e38d93..cdfd9be9e26 100644 --- a/widget/windows/nsWindow.cpp +++ b/widget/windows/nsWindow.cpp @@ -175,6 +175,8 @@ #include +#include "InkCollector.h" + #if !defined(SM_CONVERTIBLESLATEMODE) #define SM_CONVERTIBLESLATEMODE 0x2003 #endif @@ -395,6 +397,7 @@ nsWindow::nsWindow() : nsWindowBase() // Init theme data nsUXThemeData::UpdateNativeThemeInfo(); RedirectedKeyDownMessageManager::Forget(); + InkCollector::sInkCollector = new InkCollector(); Preferences::AddBoolVarCache(&gIsPointerEventsEnabled, "dom.w3c_pointer_events.enabled", @@ -428,6 +431,8 @@ nsWindow::~nsWindow() // Global shutdown if (sInstanceCount == 0) { + InkCollector::sInkCollector->Shutdown(); + InkCollector::sInkCollector = nullptr; IMEHandler::Terminate(); NS_IF_RELEASE(sCursorImgContainer); if (sIsOleInitialized) { @@ -650,6 +655,7 @@ nsWindow::Create(nsIWidget *aParent, !nsUXThemeData::sTitlebarInfoPopulatedAero)) { nsUXThemeData::UpdateTitlebarInfo(mWnd); } + return NS_OK; } @@ -3774,6 +3780,20 @@ bool nsWindow::DispatchMouseEvent(uint32_t aEventType, WPARAM wParam, return result; } + // Checking for NS_MOUSE_MOVE prevents a largest waterfall of unused initializations. + if (NS_MOUSE_MOVE != aEventType + // Since it is unclear whether a user will use the digitizer, + // Postpone initialization until first PEN message will be found. + && nsIDOMMouseEvent::MOZ_SOURCE_PEN == aInputSource + // Messages should be only at topLevel window. + && nsWindowType::eWindowType_toplevel == mWindowType + // Currently this scheme is used only when pointer events is enabled. + && gfxPrefs::PointerEventsEnabled() + // NS_MOUSE_EXIT is received, when InkCollector has been already initialized. + && NS_MOUSE_EXIT != aEventType) { + InkCollector::sInkCollector->SetTarget(mWnd); + } + switch (aEventType) { case NS_MOUSE_BUTTON_DOWN: CaptureMouse(true); @@ -4895,6 +4915,15 @@ nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam, } break; + case MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER: + { + LPARAM pos = lParamToClient(::GetMessagePos()); + DispatchMouseEvent(NS_MOUSE_EXIT, wParam, pos, false, + WidgetMouseEvent::eLeftButton, + nsIDOMMouseEvent::MOZ_SOURCE_PEN); + } + break; + case WM_CONTEXTMENU: { // if the context menu is brought up from the keyboard, |lParam|