1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "remoting/host/touch_injector_win.h"
6 
7 #include <string>
8 #include <utility>
9 
10 #include "base/files/file_path.h"
11 #include "base/logging.h"
12 #include "base/native_library.h"
13 #include "base/notreached.h"
14 #include "remoting/proto/event.pb.h"
15 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
16 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
17 #include "third_party/webrtc/modules/desktop_capture/win/screen_capture_utils.h"
18 
19 namespace remoting {
20 
21 using protocol::TouchEvent;
22 using protocol::TouchEventPoint;
23 
24 namespace {
25 
26 typedef BOOL(NTAPI* InitializeTouchInjectionFunction)(UINT32, DWORD);
27 typedef BOOL(NTAPI* InjectTouchInputFunction)(UINT32,
28                                               const POINTER_TOUCH_INFO*);
29 const uint32_t kMaxSimultaneousTouchCount = 10;
30 
31 // This is used to reinject all points that have not changed as "move"ed points,
32 // even if they have not actually moved.
33 // This is required for multi-touch to work, e.g. pinching and zooming gestures
34 // (handled by apps) won't work without reinjecting the points, even though the
35 // user moved only one finger and held the other finger in place.
AppendMapValuesToVector(std::map<uint32_t,POINTER_TOUCH_INFO> * touches_in_contact,std::vector<POINTER_TOUCH_INFO> * output_vector)36 void AppendMapValuesToVector(
37     std::map<uint32_t, POINTER_TOUCH_INFO>* touches_in_contact,
38     std::vector<POINTER_TOUCH_INFO>* output_vector) {
39   for (auto& id_and_pointer_touch_info : *touches_in_contact) {
40     POINTER_TOUCH_INFO& pointer_touch_info = id_and_pointer_touch_info.second;
41     output_vector->push_back(pointer_touch_info);
42   }
43 }
44 
ConvertToPointerTouchInfoImpl(const TouchEventPoint & touch_point,POINTER_TOUCH_INFO * pointer_touch_info)45 void ConvertToPointerTouchInfoImpl(
46     const TouchEventPoint& touch_point,
47     POINTER_TOUCH_INFO* pointer_touch_info) {
48   pointer_touch_info->touchMask =
49       TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION;
50   pointer_touch_info->touchFlags = TOUCH_FLAG_NONE;
51 
52   // Although radius_{x,y} can be undefined (i.e. has_radius_{x,y} == false),
53   // the default value (0.0) will set the area correctly.
54   // MSDN mentions that if the digitizer does not detect the size of the touch
55   // point, rcContact should be set to 0 by 0 rectangle centered at the
56   // coordinate.
57   pointer_touch_info->rcContact.left =
58       touch_point.x() - touch_point.radius_x();
59   pointer_touch_info->rcContact.top = touch_point.y() - touch_point.radius_y();
60   pointer_touch_info->rcContact.right =
61       touch_point.x() + touch_point.radius_x();
62   pointer_touch_info->rcContact.bottom =
63       touch_point.y() + touch_point.radius_y();
64 
65   pointer_touch_info->orientation = touch_point.angle();
66 
67   if (touch_point.has_pressure()) {
68     pointer_touch_info->touchMask |= TOUCH_MASK_PRESSURE;
69     const float kMinimumPressure = 0.0;
70     const float kMaximumPressure = 1.0;
71     const float clamped_touch_point_pressure =
72         std::max(kMinimumPressure,
73                  std::min(kMaximumPressure, touch_point.pressure()));
74 
75     const int kWindowsMaxTouchPressure = 1024;  // Defined in MSDN.
76     const int pressure =
77         clamped_touch_point_pressure * kWindowsMaxTouchPressure;
78     pointer_touch_info->pressure = pressure;
79   }
80 
81   pointer_touch_info->pointerInfo.pointerType = PT_TOUCH;
82   pointer_touch_info->pointerInfo.pointerId = touch_point.id();
83   pointer_touch_info->pointerInfo.ptPixelLocation.x = touch_point.x();
84   pointer_touch_info->pointerInfo.ptPixelLocation.y = touch_point.y();
85 }
86 
87 // The caller should set memset(0) the struct and set
88 // pointer_touch_info->pointerInfo.pointerFlags.
ConvertToPointerTouchInfo(const TouchEventPoint & touch_point,POINTER_TOUCH_INFO * pointer_touch_info)89 void ConvertToPointerTouchInfo(
90     const TouchEventPoint& touch_point,
91     POINTER_TOUCH_INFO* pointer_touch_info) {
92   // TODO(zijiehe): Use GetFullscreenTopLeft() once
93   // https://chromium-review.googlesource.com/c/581951/ is submitted.
94   webrtc::DesktopVector top_left = webrtc::GetScreenRect(
95       webrtc::kFullDesktopScreenId, std::wstring()).top_left();
96   if (top_left.is_zero()) {
97     ConvertToPointerTouchInfoImpl(touch_point, pointer_touch_info);
98     return;
99   }
100 
101   TouchEventPoint point(touch_point);
102   point.set_x(point.x() + top_left.x());
103   point.set_y(point.y() + top_left.y());
104 
105   ConvertToPointerTouchInfoImpl(point, pointer_touch_info);
106 }
107 
108 }  // namespace
109 
~TouchInjectorWinDelegate()110 TouchInjectorWinDelegate::~TouchInjectorWinDelegate() {}
111 
112 // static.
Create()113 std::unique_ptr<TouchInjectorWinDelegate> TouchInjectorWinDelegate::Create() {
114   base::ScopedNativeLibrary library(base::FilePath(L"User32.dll"));
115   if (!library.is_valid()) {
116     PLOG(INFO) << "Failed to get library module for touch injection functions.";
117     return std::unique_ptr<TouchInjectorWinDelegate>();
118   }
119 
120   InitializeTouchInjectionFunction init_func =
121       reinterpret_cast<InitializeTouchInjectionFunction>(
122           library.GetFunctionPointer("InitializeTouchInjection"));
123   if (!init_func) {
124     PLOG(INFO) << "Failed to get InitializeTouchInjection function handle.";
125     return std::unique_ptr<TouchInjectorWinDelegate>();
126   }
127 
128   InjectTouchInputFunction inject_touch_func =
129       reinterpret_cast<InjectTouchInputFunction>(
130           library.GetFunctionPointer("InjectTouchInput"));
131   if (!inject_touch_func) {
132     PLOG(INFO) << "Failed to get InjectTouchInput.";
133     return std::unique_ptr<TouchInjectorWinDelegate>();
134   }
135 
136   return std::unique_ptr<TouchInjectorWinDelegate>(new TouchInjectorWinDelegate(
137       library.release(), init_func, inject_touch_func));
138 }
139 
TouchInjectorWinDelegate(base::NativeLibrary library,InitializeTouchInjectionFunction initialize_touch_injection_func,InjectTouchInputFunction inject_touch_input_func)140 TouchInjectorWinDelegate::TouchInjectorWinDelegate(
141     base::NativeLibrary library,
142     InitializeTouchInjectionFunction initialize_touch_injection_func,
143     InjectTouchInputFunction inject_touch_input_func)
144     : library_module_(library),
145       initialize_touch_injection_func_(initialize_touch_injection_func),
146       inject_touch_input_func_(inject_touch_input_func) {}
147 
InitializeTouchInjection(UINT32 max_count,DWORD dw_mode)148 BOOL TouchInjectorWinDelegate::InitializeTouchInjection(UINT32 max_count,
149                                                         DWORD dw_mode) {
150   return initialize_touch_injection_func_(max_count, dw_mode);
151 }
152 
InjectTouchInput(UINT32 count,const POINTER_TOUCH_INFO * contacts)153 DWORD TouchInjectorWinDelegate::InjectTouchInput(
154     UINT32 count,
155     const POINTER_TOUCH_INFO* contacts) {
156   return inject_touch_input_func_(count, contacts);
157 }
158 
159 TouchInjectorWin::TouchInjectorWin() = default;
160 
161 TouchInjectorWin::~TouchInjectorWin() = default;
162 
163 // Note that TouchInjectorWinDelegate::Create() is not called in this method
164 // so that a mock delegate can be injected in tests and set expectations on the
165 // mock and return value of this method.
Init()166 bool TouchInjectorWin::Init() {
167   if (!delegate_)
168     delegate_ = TouchInjectorWinDelegate::Create();
169 
170   // If initializing the delegate failed above, then the platform likely doesn't
171   // support touch (or the libraries failed to load for some reason).
172   if (!delegate_)
173     return false;
174 
175   if (!delegate_->InitializeTouchInjection(
176           kMaxSimultaneousTouchCount, TOUCH_FEEDBACK_DEFAULT)) {
177     // delagate_ is reset here so that the function that need the delegate
178     // can check if it is null.
179     delegate_.reset();
180     PLOG(INFO) << "Failed to initialize touch injection.";
181     return false;
182   }
183 
184   return true;
185 }
186 
Deinitialize()187 void TouchInjectorWin::Deinitialize() {
188   touches_in_contact_.clear();
189   // Same reason as TouchInjectorWin::Init(). For injecting mock delegates for
190   // tests, a new delegate is created here.
191   delegate_ = TouchInjectorWinDelegate::Create();
192 }
193 
InjectTouchEvent(const TouchEvent & event)194 void TouchInjectorWin::InjectTouchEvent(const TouchEvent& event) {
195   if (!delegate_) {
196     VLOG(3) << "Touch injection functions are not initialized.";
197     return;
198   }
199 
200   switch (event.event_type()) {
201     case TouchEvent::TOUCH_POINT_START:
202       AddNewTouchPoints(event);
203       break;
204     case TouchEvent::TOUCH_POINT_MOVE:
205       MoveTouchPoints(event);
206       break;
207     case TouchEvent::TOUCH_POINT_END:
208       EndTouchPoints(event);
209       break;
210     case TouchEvent::TOUCH_POINT_CANCEL:
211       CancelTouchPoints(event);
212       break;
213     default:
214       NOTREACHED();
215       return;
216   }
217 }
218 
SetInjectorDelegateForTest(std::unique_ptr<TouchInjectorWinDelegate> functions)219 void TouchInjectorWin::SetInjectorDelegateForTest(
220     std::unique_ptr<TouchInjectorWinDelegate> functions) {
221   delegate_ = std::move(functions);
222 }
223 
AddNewTouchPoints(const TouchEvent & event)224 void TouchInjectorWin::AddNewTouchPoints(const TouchEvent& event) {
225   DCHECK_EQ(event.event_type(), TouchEvent::TOUCH_POINT_START);
226 
227   std::vector<POINTER_TOUCH_INFO> touches;
228   // Must inject already touching points as move events.
229   AppendMapValuesToVector(&touches_in_contact_, &touches);
230 
231   for (const TouchEventPoint& touch_point : event.touch_points()) {
232     POINTER_TOUCH_INFO pointer_touch_info;
233     memset(&pointer_touch_info, 0, sizeof(pointer_touch_info));
234     pointer_touch_info.pointerInfo.pointerFlags =
235         POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN;
236     ConvertToPointerTouchInfo(touch_point, &pointer_touch_info);
237     touches.push_back(pointer_touch_info);
238 
239     // All points in the map should be a move point.
240     pointer_touch_info.pointerInfo.pointerFlags =
241         POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT | POINTER_FLAG_UPDATE;
242     touches_in_contact_[touch_point.id()] = pointer_touch_info;
243   }
244 
245   if (delegate_->InjectTouchInput(touches.size(), touches.data()) == 0) {
246     PLOG(ERROR) << "Failed to inject a touch start event.";
247   }
248 }
249 
MoveTouchPoints(const TouchEvent & event)250 void TouchInjectorWin::MoveTouchPoints(const TouchEvent& event) {
251   DCHECK_EQ(event.event_type(), TouchEvent::TOUCH_POINT_MOVE);
252 
253   for (const TouchEventPoint& touch_point : event.touch_points()) {
254     POINTER_TOUCH_INFO* pointer_touch_info =
255         &touches_in_contact_[touch_point.id()];
256     memset(pointer_touch_info, 0, sizeof(*pointer_touch_info));
257     pointer_touch_info->pointerInfo.pointerFlags =
258         POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT | POINTER_FLAG_UPDATE;
259     ConvertToPointerTouchInfo(touch_point, pointer_touch_info);
260   }
261 
262   std::vector<POINTER_TOUCH_INFO> touches;
263   // Must inject already touching points as move events.
264   AppendMapValuesToVector(&touches_in_contact_, &touches);
265   if (delegate_->InjectTouchInput(touches.size(), touches.data()) == 0) {
266     PLOG(ERROR) << "Failed to inject a touch move event.";
267   }
268 }
269 
EndTouchPoints(const TouchEvent & event)270 void TouchInjectorWin::EndTouchPoints(const TouchEvent& event) {
271   DCHECK_EQ(event.event_type(), TouchEvent::TOUCH_POINT_END);
272 
273   std::vector<POINTER_TOUCH_INFO> touches;
274   for (const TouchEventPoint& touch_point : event.touch_points()) {
275     POINTER_TOUCH_INFO pointer_touch_info =
276         touches_in_contact_[touch_point.id()];
277     pointer_touch_info.pointerInfo.pointerFlags = POINTER_FLAG_UP;
278 
279     touches_in_contact_.erase(touch_point.id());
280     touches.push_back(pointer_touch_info);
281   }
282 
283   AppendMapValuesToVector(&touches_in_contact_, &touches);
284   if (delegate_->InjectTouchInput(touches.size(), touches.data()) == 0) {
285     PLOG(ERROR) << "Failed to inject a touch end event.";
286   }
287 }
288 
CancelTouchPoints(const TouchEvent & event)289 void TouchInjectorWin::CancelTouchPoints(const TouchEvent& event) {
290   DCHECK_EQ(event.event_type(), TouchEvent::TOUCH_POINT_CANCEL);
291 
292   std::vector<POINTER_TOUCH_INFO> touches;
293   for (const TouchEventPoint& touch_point : event.touch_points()) {
294     POINTER_TOUCH_INFO pointer_touch_info =
295         touches_in_contact_[touch_point.id()];
296     pointer_touch_info.pointerInfo.pointerFlags =
297         POINTER_FLAG_UP | POINTER_FLAG_CANCELED;
298 
299     touches_in_contact_.erase(touch_point.id());
300     touches.push_back(pointer_touch_info);
301   }
302 
303   AppendMapValuesToVector(&touches_in_contact_, &touches);
304   if (delegate_->InjectTouchInput(touches.size(), touches.data()) == 0) {
305     PLOG(ERROR) << "Failed to inject a touch cancel event.";
306   }
307 }
308 
309 }  // namespace remoting
310