blob: d1f4894f55049dec620fa6c1b6578c4a43621913 [file] [log] [blame]
rkuroiwa883da7e2015-03-25 05:22:121// 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
Zijie He813dbe32017-07-24 18:55:117#include <string>
sergeyu1417e0132015-12-23 19:01:228#include <utility>
9
rkuroiwa883da7e2015-03-25 05:22:1210#include "base/files/file_path.h"
11#include "base/logging.h"
12#include "base/native_library.h"
Hans Wennborg22e28d6a2020-06-17 17:17:2113#include "base/notreached.h"
rkuroiwa883da7e2015-03-25 05:22:1214#include "remoting/proto/event.pb.h"
Zijie He813dbe32017-07-24 18:55:1115#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"
rkuroiwa883da7e2015-03-25 05:22:1218
19namespace remoting {
20
21using protocol::TouchEvent;
22using protocol::TouchEventPoint;
23
24namespace {
25
26typedef BOOL(NTAPI* InitializeTouchInjectionFunction)(UINT32, DWORD);
27typedef BOOL(NTAPI* InjectTouchInputFunction)(UINT32,
28 const POINTER_TOUCH_INFO*);
29const 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.
36void 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
Zijie He813dbe32017-07-24 18:55:1145void ConvertToPointerTouchInfoImpl(
rkuroiwa883da7e2015-03-25 05:22:1246 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
Zijie He813dbe32017-07-24 18:55:1187// The caller should set memset(0) the struct and set
88// pointer_touch_info->pointerInfo.pointerFlags.
89void 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
rkuroiwa883da7e2015-03-25 05:22:12108} // namespace
109
110TouchInjectorWinDelegate::~TouchInjectorWinDelegate() {}
111
112// static.
dcheng0765c492016-04-06 22:41:53113std::unique_ptr<TouchInjectorWinDelegate> TouchInjectorWinDelegate::Create() {
rkuroiwa883da7e2015-03-25 05:22:12114 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.";
Lei Zhang9aeacf72021-04-07 19:02:01117 return nullptr;
rkuroiwa883da7e2015-03-25 05:22:12118 }
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.";
Lei Zhang9aeacf72021-04-07 19:02:01125 return nullptr;
rkuroiwa883da7e2015-03-25 05:22:12126 }
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.";
Lei Zhang9aeacf72021-04-07 19:02:01133 return nullptr;
rkuroiwa883da7e2015-03-25 05:22:12134 }
135
dcheng0765c492016-04-06 22:41:53136 return std::unique_ptr<TouchInjectorWinDelegate>(new TouchInjectorWinDelegate(
Cliff Smolinskyf395bef2019-04-12 23:45:44137 library.release(), init_func, inject_touch_func));
rkuroiwa883da7e2015-03-25 05:22:12138}
139
140TouchInjectorWinDelegate::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
148BOOL TouchInjectorWinDelegate::InitializeTouchInjection(UINT32 max_count,
149 DWORD dw_mode) {
150 return initialize_touch_injection_func_(max_count, dw_mode);
151}
152
153DWORD TouchInjectorWinDelegate::InjectTouchInput(
154 UINT32 count,
155 const POINTER_TOUCH_INFO* contacts) {
156 return inject_touch_input_func_(count, contacts);
157}
158
Joe Downingd7e58ca2018-08-01 20:55:21159TouchInjectorWin::TouchInjectorWin() = default;
rkuroiwa883da7e2015-03-25 05:22:12160
Joe Downingd7e58ca2018-08-01 20:55:21161TouchInjectorWin::~TouchInjectorWin() = default;
rkuroiwa883da7e2015-03-25 05:22:12162
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.
166bool TouchInjectorWin::Init() {
167 if (!delegate_)
Joe Downingd7e58ca2018-08-01 20:55:21168 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_)
rkuroiwa883da7e2015-03-25 05:22:12173 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
187void 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
194void 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
219void TouchInjectorWin::SetInjectorDelegateForTest(
dcheng0765c492016-04-06 22:41:53220 std::unique_ptr<TouchInjectorWinDelegate> functions) {
sergeyu1417e0132015-12-23 19:01:22221 delegate_ = std::move(functions);
rkuroiwa883da7e2015-03-25 05:22:12222}
223
224void 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
davidbenb50f00c2015-12-01 00:01:50245 if (delegate_->InjectTouchInput(touches.size(), touches.data()) == 0) {
rkuroiwa883da7e2015-03-25 05:22:12246 PLOG(ERROR) << "Failed to inject a touch start event.";
247 }
248}
249
250void 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);
davidbenb50f00c2015-12-01 00:01:50265 if (delegate_->InjectTouchInput(touches.size(), touches.data()) == 0) {
rkuroiwa883da7e2015-03-25 05:22:12266 PLOG(ERROR) << "Failed to inject a touch move event.";
267 }
268}
269
270void 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);
davidbenb50f00c2015-12-01 00:01:50284 if (delegate_->InjectTouchInput(touches.size(), touches.data()) == 0) {
rkuroiwa883da7e2015-03-25 05:22:12285 PLOG(ERROR) << "Failed to inject a touch end event.";
286 }
287}
288
289void 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);
davidbenb50f00c2015-12-01 00:01:50304 if (delegate_->InjectTouchInput(touches.size(), touches.data()) == 0) {
rkuroiwa883da7e2015-03-25 05:22:12305 PLOG(ERROR) << "Failed to inject a touch cancel event.";
306 }
307}
308
309} // namespace remoting