1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=2 sw=2 et tw=78:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6  */
7 
8 #include "InkCollector.h"
9 
10 // Msinkaut_i.c and Msinkaut.h should both be included
11 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms695519.aspx
12 #include <msinkaut_i.c>
13 
14 StaticAutoPtr<InkCollector> InkCollector::sInkCollector;
15 
~InkCollector()16 InkCollector::~InkCollector() {
17   Shutdown();
18   MOZ_ASSERT(!mCookie && !mEnabled && !mComInitialized && !mMarshaller &&
19              !mInkCollector && !mConnectionPoint && !mInkCollectorEvent);
20 }
21 
Initialize()22 void InkCollector::Initialize() {
23   // Possibly, we can use mConnectionPoint for checking,
24   // But if errors exist (perhaps COM object is unavailable),
25   // Initialize() will be called more times.
26   static bool sInkCollectorCreated = false;
27   if (sInkCollectorCreated) {
28     return;
29   }
30   sInkCollectorCreated = true;
31 
32   // COM could get uninitialized due to previous initialization.
33   mComInitialized = SUCCEEDED(::CoInitialize(nullptr));
34 
35   // Set up instance of InkCollectorEvent.
36   mInkCollectorEvent = new InkCollectorEvent();
37 
38   // Set up a free threaded marshaler.
39   if (FAILED(::CoCreateFreeThreadedMarshaler(mInkCollectorEvent,
40                                              getter_AddRefs(mMarshaller)))) {
41     return;
42   }
43 
44   // Create the ink collector.
45   if (FAILED(::CoCreateInstance(CLSID_InkCollector, NULL, CLSCTX_INPROC_SERVER,
46                                 IID_IInkCollector,
47                                 getter_AddRefs(mInkCollector)))) {
48     return;
49   }
50 
51   // Set up connection between sink and InkCollector.
52   RefPtr<IConnectionPointContainer> connPointContainer;
53 
54   // Get the connection point container.
55   if (SUCCEEDED(mInkCollector->QueryInterface(
56           IID_IConnectionPointContainer, getter_AddRefs(connPointContainer)))) {
57     // Find the connection point for Ink Collector events.
58     if (SUCCEEDED(connPointContainer->FindConnectionPoint(
59             __uuidof(_IInkCollectorEvents),
60             getter_AddRefs(mConnectionPoint)))) {
61       // Hook up sink to connection point.
62       if (SUCCEEDED(mConnectionPoint->Advise(mInkCollectorEvent, &mCookie))) {
63         OnInitialize();
64       }
65     }
66   }
67 }
68 
Shutdown()69 void InkCollector::Shutdown() {
70   Enable(false);
71   if (mConnectionPoint) {
72     // Remove the connection of the sink to the Ink Collector.
73     mConnectionPoint->Unadvise(mCookie);
74     mCookie = 0;
75     mConnectionPoint = nullptr;
76   }
77   mInkCollector = nullptr;
78   mMarshaller = nullptr;
79   mInkCollectorEvent = nullptr;
80 
81   // Let uninitialization get handled in a place where it got inited.
82   if (mComInitialized) {
83     CoUninitialize();
84     mComInitialized = false;
85   }
86 }
87 
OnInitialize()88 void InkCollector::OnInitialize() {
89   // Suppress all events to do not allow performance decreasing.
90   // https://msdn.microsoft.com/en-us/library/ms820347.aspx
91   mInkCollector->SetEventInterest(InkCollectorEventInterest::ICEI_AllEvents,
92                                   VARIANT_FALSE);
93 
94   // Sets a value that indicates whether an object or control has interest in a
95   // specified event.
96   mInkCollector->SetEventInterest(
97       InkCollectorEventInterest::ICEI_CursorOutOfRange, VARIANT_TRUE);
98 
99   // If the MousePointer property is set to IMP_Custom and the MouseIcon
100   // property is NULL, Then the ink collector no longer handles mouse cursor
101   // settings.
102   // https://msdn.microsoft.com/en-us/library/windows/desktop/ms700686.aspx
103   mInkCollector->put_MouseIcon(nullptr);
104   mInkCollector->put_MousePointer(InkMousePointer::IMP_Custom);
105 
106   // This mode allows an ink collector to collect ink from any tablet attached
107   // to the Tablet PC. The Boolean value that indicates whether to use the mouse
108   // as an input device. If TRUE, the mouse is used for input.
109   // https://msdn.microsoft.com/en-us/library/ms820346.aspx
110   mInkCollector->SetAllTabletsMode(VARIANT_FALSE);
111 
112   // Sets the value that specifies whether ink is rendered as it is drawn.
113   // VARIANT_TRUE to render ink as it is drawn on the display.
114   // VARIANT_FALSE to not have the ink appear on the display as strokes are
115   // made.
116   // https://msdn.microsoft.com/en-us/library/windows/desktop/dd314598.aspx
117   mInkCollector->put_DynamicRendering(VARIANT_FALSE);
118 
119   // Set AutoRedraw to false to prevent repainting the ink when the window is
120   // invalidated.
121   mInkCollector->put_AutoRedraw(VARIANT_FALSE);
122 }
123 
124 // Sets a value that specifies whether the InkCollector object collects pen
125 // input. This property must be set to FALSE before setting or calling specific
126 // properties and methods of the object.
127 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms701721.aspx
Enable(bool aNewState)128 void InkCollector::Enable(bool aNewState) {
129   if (aNewState != mEnabled) {
130     if (mInkCollector) {
131       if (SUCCEEDED(mInkCollector->put_Enabled(aNewState ? VARIANT_TRUE
132                                                          : VARIANT_FALSE))) {
133         mEnabled = aNewState;
134       } else {
135         NS_WARNING("InkCollector did not change status successfully");
136       }
137     } else {
138       NS_WARNING("InkCollector should be exist");
139     }
140   }
141 }
142 
GetTarget()143 HWND InkCollector::GetTarget() { return mTargetWindow; }
144 
SetTarget(HWND aTargetWindow)145 void InkCollector::SetTarget(HWND aTargetWindow) {
146   NS_ASSERTION(aTargetWindow, "aTargetWindow should be exist");
147   if (aTargetWindow && (aTargetWindow != mTargetWindow)) {
148     Initialize();
149     if (mInkCollector) {
150       Enable(false);
151       if (SUCCEEDED(mInkCollector->put_hWnd((LONG_PTR)aTargetWindow))) {
152         mTargetWindow = aTargetWindow;
153       } else {
154         NS_WARNING("InkCollector did not change window property successfully");
155       }
156       Enable(true);
157     }
158   }
159 }
160 
ClearTarget()161 void InkCollector::ClearTarget() {
162   if (mTargetWindow && mInkCollector) {
163     Enable(false);
164     if (SUCCEEDED(mInkCollector->put_hWnd(0))) {
165       mTargetWindow = 0;
166     } else {
167       NS_WARNING("InkCollector did not clear window property successfully");
168     }
169   }
170 }
171 
GetPointerId()172 uint16_t InkCollector::GetPointerId() { return mPointerId; }
173 
SetPointerId(uint16_t aPointerId)174 void InkCollector::SetPointerId(uint16_t aPointerId) {
175   mPointerId = aPointerId;
176 }
177 
ClearPointerId()178 void InkCollector::ClearPointerId() { mPointerId = 0; }
179 
180 // The display and the digitizer have quite different properties.
181 // The display has CursorMustTouch, the mouse pointer alway touches the display
182 // surface. The digitizer lists Integrated and HardProximity. When the stylus is
183 // in the proximity of the tablet its movements are also detected. An external
184 // tablet will only list HardProximity.
IsHardProximityTablet(IInkTablet * aTablet) const185 bool InkCollectorEvent::IsHardProximityTablet(IInkTablet* aTablet) const {
186   if (aTablet) {
187     TabletHardwareCapabilities caps;
188     if (SUCCEEDED(aTablet->get_HardwareCapabilities(&caps))) {
189       return (TabletHardwareCapabilities::THWC_HardProximity & caps);
190     }
191   }
192   return false;
193 }
194 
QueryInterface(REFIID aRiid,void ** aObject)195 HRESULT __stdcall InkCollectorEvent::QueryInterface(REFIID aRiid,
196                                                     void** aObject) {
197   // Validate the input
198   if (!aObject) {
199     return E_POINTER;
200   }
201   HRESULT result = E_NOINTERFACE;
202   // This object supports IUnknown/IDispatch/IInkCollectorEvents
203   if ((IID_IUnknown == aRiid) || (IID_IDispatch == aRiid) ||
204       (DIID__IInkCollectorEvents == aRiid)) {
205     *aObject = this;
206     // AddRef should be called when we give info about interface
207     NS_ADDREF_THIS();
208     result = S_OK;
209   }
210   return result;
211 }
212 
Invoke(DISPID aDispIdMember,REFIID,LCID,WORD,DISPPARAMS * aDispParams,VARIANT *,EXCEPINFO *,UINT *)213 HRESULT InkCollectorEvent::Invoke(DISPID aDispIdMember, REFIID /*aRiid*/,
214                                   LCID /*aId*/, WORD /*wFlags*/,
215                                   DISPPARAMS* aDispParams,
216                                   VARIANT* /*aVarResult*/,
217                                   EXCEPINFO* /*aExcepInfo*/,
218                                   UINT* /*aArgErr*/) {
219   switch (aDispIdMember) {
220     case DISPID_ICECursorOutOfRange: {
221       if (aDispParams && aDispParams->cArgs) {
222         CursorOutOfRange(
223             static_cast<IInkCursor*>(aDispParams->rgvarg[0].pdispVal));
224       }
225       break;
226     }
227   }
228   return S_OK;
229 }
230 
CursorOutOfRange(IInkCursor * aCursor) const231 void InkCollectorEvent::CursorOutOfRange(IInkCursor* aCursor) const {
232   IInkTablet* curTablet = nullptr;
233   if (FAILED(aCursor->get_Tablet(&curTablet))) {
234     return;
235   }
236   // All events should be suppressed except
237   // from tablets with hard proximity.
238   if (!IsHardProximityTablet(curTablet)) {
239     return;
240   }
241   // Notify current target window.
242   if (HWND targetWindow = InkCollector::sInkCollector->GetTarget()) {
243     ::SendMessage(targetWindow, MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER, 0, 0);
244   }
245 }
246