rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 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 | |
Zijie He | 813dbe3 | 2017-07-24 18:55:11 | [diff] [blame] | 7 | #include <string> |
sergeyu | 1417e013 | 2015-12-23 19:01:22 | [diff] [blame] | 8 | #include <utility> |
| 9 | |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 10 | #include "base/files/file_path.h" |
| 11 | #include "base/logging.h" |
| 12 | #include "base/native_library.h" |
Hans Wennborg | 22e28d6a | 2020-06-17 17:17:21 | [diff] [blame] | 13 | #include "base/notreached.h" |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 14 | #include "remoting/proto/event.pb.h" |
Zijie He | 813dbe3 | 2017-07-24 18:55:11 | [diff] [blame] | 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" |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 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. |
| 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 | |
Zijie He | 813dbe3 | 2017-07-24 18:55:11 | [diff] [blame] | 45 | void ConvertToPointerTouchInfoImpl( |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 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 | |
Zijie He | 813dbe3 | 2017-07-24 18:55:11 | [diff] [blame] | 87 | // The caller should set memset(0) the struct and set |
| 88 | // pointer_touch_info->pointerInfo.pointerFlags. |
| 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 | |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 108 | } // namespace |
| 109 | |
| 110 | TouchInjectorWinDelegate::~TouchInjectorWinDelegate() {} |
| 111 | |
| 112 | // static. |
dcheng | 0765c49 | 2016-04-06 22:41:53 | [diff] [blame] | 113 | std::unique_ptr<TouchInjectorWinDelegate> TouchInjectorWinDelegate::Create() { |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 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."; |
Lei Zhang | 9aeacf7 | 2021-04-07 19:02:01 | [diff] [blame] | 117 | return nullptr; |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 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."; |
Lei Zhang | 9aeacf7 | 2021-04-07 19:02:01 | [diff] [blame] | 125 | return nullptr; |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 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."; |
Lei Zhang | 9aeacf7 | 2021-04-07 19:02:01 | [diff] [blame] | 133 | return nullptr; |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 134 | } |
| 135 | |
dcheng | 0765c49 | 2016-04-06 22:41:53 | [diff] [blame] | 136 | return std::unique_ptr<TouchInjectorWinDelegate>(new TouchInjectorWinDelegate( |
Cliff Smolinsky | f395bef | 2019-04-12 23:45:44 | [diff] [blame] | 137 | library.release(), init_func, inject_touch_func)); |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 138 | } |
| 139 | |
| 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 | |
| 148 | BOOL TouchInjectorWinDelegate::InitializeTouchInjection(UINT32 max_count, |
| 149 | DWORD dw_mode) { |
| 150 | return initialize_touch_injection_func_(max_count, dw_mode); |
| 151 | } |
| 152 | |
| 153 | DWORD TouchInjectorWinDelegate::InjectTouchInput( |
| 154 | UINT32 count, |
| 155 | const POINTER_TOUCH_INFO* contacts) { |
| 156 | return inject_touch_input_func_(count, contacts); |
| 157 | } |
| 158 | |
Joe Downing | d7e58ca | 2018-08-01 20:55:21 | [diff] [blame] | 159 | TouchInjectorWin::TouchInjectorWin() = default; |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 160 | |
Joe Downing | d7e58ca | 2018-08-01 20:55:21 | [diff] [blame] | 161 | TouchInjectorWin::~TouchInjectorWin() = default; |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 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. |
| 166 | bool TouchInjectorWin::Init() { |
| 167 | if (!delegate_) |
Joe Downing | d7e58ca | 2018-08-01 20:55:21 | [diff] [blame] | 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_) |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 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 | |
| 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 | |
| 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 | |
| 219 | void TouchInjectorWin::SetInjectorDelegateForTest( |
dcheng | 0765c49 | 2016-04-06 22:41:53 | [diff] [blame] | 220 | std::unique_ptr<TouchInjectorWinDelegate> functions) { |
sergeyu | 1417e013 | 2015-12-23 19:01:22 | [diff] [blame] | 221 | delegate_ = std::move(functions); |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 222 | } |
| 223 | |
| 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 | |
davidben | b50f00c | 2015-12-01 00:01:50 | [diff] [blame] | 245 | if (delegate_->InjectTouchInput(touches.size(), touches.data()) == 0) { |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 246 | PLOG(ERROR) << "Failed to inject a touch start event."; |
| 247 | } |
| 248 | } |
| 249 | |
| 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); |
davidben | b50f00c | 2015-12-01 00:01:50 | [diff] [blame] | 265 | if (delegate_->InjectTouchInput(touches.size(), touches.data()) == 0) { |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 266 | PLOG(ERROR) << "Failed to inject a touch move event."; |
| 267 | } |
| 268 | } |
| 269 | |
| 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); |
davidben | b50f00c | 2015-12-01 00:01:50 | [diff] [blame] | 284 | if (delegate_->InjectTouchInput(touches.size(), touches.data()) == 0) { |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 285 | PLOG(ERROR) << "Failed to inject a touch end event."; |
| 286 | } |
| 287 | } |
| 288 | |
| 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); |
davidben | b50f00c | 2015-12-01 00:01:50 | [diff] [blame] | 304 | if (delegate_->InjectTouchInput(touches.size(), touches.data()) == 0) { |
rkuroiwa | 883da7e | 2015-03-25 05:22:12 | [diff] [blame] | 305 | PLOG(ERROR) << "Failed to inject a touch cancel event."; |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | } // namespace remoting |