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