Index: remoting/host/touch_injector_win.cc |
diff --git a/remoting/host/touch_injector_win.cc b/remoting/host/touch_injector_win.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3eaadf476ef1cefdb88dbcfe27acb7cd12552aec |
--- /dev/null |
+++ b/remoting/host/touch_injector_win.cc |
@@ -0,0 +1,285 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "remoting/host/touch_injector_win.h" |
+ |
+#include "base/files/file_path.h" |
+#include "base/logging.h" |
+#include "base/native_library.h" |
+#include "base/stl_util.h" |
+#include "remoting/proto/event.pb.h" |
+ |
+namespace remoting { |
+ |
+using protocol::TouchEvent; |
+using protocol::TouchEventPoint; |
+ |
+namespace { |
+ |
+typedef BOOL(NTAPI* InitializeTouchInjectionFunction)(UINT32, DWORD); |
+typedef BOOL(NTAPI* InjectTouchInputFunction)(UINT32, |
+ const POINTER_TOUCH_INFO*); |
+const uint32_t kMaxSimultaneousTouchCount = 10; |
+ |
+// This is used to reinject all points that have not changed as "move"ed points, |
+// even if they have not actually moved. |
+// This is required for multi-touch to work, e.g. pinching and zooming gestures |
+// (handled by apps) won't work without reinjecting the points, even though the |
+// user moved only one finger and held the other finger in place. |
+void AppendMapValuesToVector( |
+ std::map<uint32_t, POINTER_TOUCH_INFO>* touches_in_contact, |
+ std::vector<POINTER_TOUCH_INFO>* output_vector) { |
+ for (auto& id_and_pointer_touch_info : *touches_in_contact) { |
+ POINTER_TOUCH_INFO& pointer_touch_info = id_and_pointer_touch_info.second; |
+ output_vector->push_back(pointer_touch_info); |
+ } |
+} |
+ |
+// The caller should set memset(0) the struct and set |
+// pointer_touch_info->pointerInfo.pointerFlags. |
+void ConvertToPointerTouchInfo( |
+ const TouchEventPoint& touch_point, |
+ POINTER_TOUCH_INFO* pointer_touch_info) { |
+ pointer_touch_info->touchMask = |
+ TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION; |
+ pointer_touch_info->touchFlags = TOUCH_FLAG_NONE; |
+ |
+ // Although radius_{x,y} can be undefined (i.e. has_radius_{x,y} == false), |
+ // the default value (0.0) will set the area correctly. |
+ // MSDN mentions that if the digitizer does not detect the size of the touch |
+ // point, rcContact should be set to 0 by 0 rectangle centered at the |
+ // coordinate. |
+ pointer_touch_info->rcContact.left = |
+ touch_point.x() - touch_point.radius_x(); |
+ pointer_touch_info->rcContact.top = touch_point.y() - touch_point.radius_y(); |
+ pointer_touch_info->rcContact.right = |
+ touch_point.x() + touch_point.radius_x(); |
+ pointer_touch_info->rcContact.bottom = |
+ touch_point.y() + touch_point.radius_y(); |
+ |
+ pointer_touch_info->orientation = touch_point.angle(); |
+ |
+ if (touch_point.has_pressure()) { |
+ pointer_touch_info->touchMask |= TOUCH_MASK_PRESSURE; |
+ const float kMinimumPressure = 0.0; |
+ const float kMaximumPressure = 1.0; |
+ const float clamped_touch_point_pressure = |
+ std::max(kMinimumPressure, |
+ std::min(kMaximumPressure, touch_point.pressure())); |
+ |
+ const int kWindowsMaxTouchPressure = 1024; // Defined in MSDN. |
+ const int pressure = |
+ clamped_touch_point_pressure * kWindowsMaxTouchPressure; |
+ pointer_touch_info->pressure = pressure; |
+ } |
+ |
+ pointer_touch_info->pointerInfo.pointerType = PT_TOUCH; |
+ pointer_touch_info->pointerInfo.pointerId = touch_point.id(); |
+ pointer_touch_info->pointerInfo.ptPixelLocation.x = touch_point.x(); |
+ pointer_touch_info->pointerInfo.ptPixelLocation.y = touch_point.y(); |
+} |
+ |
+} // namespace |
+ |
+TouchInjectorWinDelegate::~TouchInjectorWinDelegate() {} |
+ |
+// static. |
+scoped_ptr<TouchInjectorWinDelegate> TouchInjectorWinDelegate::Create() { |
+ base::ScopedNativeLibrary library(base::FilePath(L"User32.dll")); |
+ if (!library.is_valid()) { |
+ PLOG(INFO) << "Failed to get library module for touch injection functions."; |
+ return scoped_ptr<TouchInjectorWinDelegate>(); |
+ } |
+ |
+ InitializeTouchInjectionFunction init_func = |
+ reinterpret_cast<InitializeTouchInjectionFunction>( |
+ library.GetFunctionPointer("InitializeTouchInjection")); |
+ if (!init_func) { |
+ PLOG(INFO) << "Failed to get InitializeTouchInjection function handle."; |
+ return scoped_ptr<TouchInjectorWinDelegate>(); |
+ } |
+ |
+ InjectTouchInputFunction inject_touch_func = |
+ reinterpret_cast<InjectTouchInputFunction>( |
+ library.GetFunctionPointer("InjectTouchInput")); |
+ if (!inject_touch_func) { |
+ PLOG(INFO) << "Failed to get InjectTouchInput."; |
+ return scoped_ptr<TouchInjectorWinDelegate>(); |
+ } |
+ |
+ return scoped_ptr<TouchInjectorWinDelegate>( |
+ new TouchInjectorWinDelegate( |
+ library.Release(), init_func, inject_touch_func)); |
+} |
+ |
+TouchInjectorWinDelegate::TouchInjectorWinDelegate( |
+ base::NativeLibrary library, |
+ InitializeTouchInjectionFunction initialize_touch_injection_func, |
+ InjectTouchInputFunction inject_touch_input_func) |
+ : library_module_(library), |
+ initialize_touch_injection_func_(initialize_touch_injection_func), |
+ inject_touch_input_func_(inject_touch_input_func) {} |
+ |
+BOOL TouchInjectorWinDelegate::InitializeTouchInjection(UINT32 max_count, |
+ DWORD dw_mode) { |
+ return initialize_touch_injection_func_(max_count, dw_mode); |
+} |
+ |
+DWORD TouchInjectorWinDelegate::InjectTouchInput( |
+ UINT32 count, |
+ const POINTER_TOUCH_INFO* contacts) { |
+ return inject_touch_input_func_(count, contacts); |
+} |
+ |
+TouchInjectorWin::TouchInjectorWin() |
+ : delegate_(TouchInjectorWinDelegate::Create()) {} |
+ |
+TouchInjectorWin::~TouchInjectorWin() {} |
+ |
+// Note that TouchInjectorWinDelegate::Create() is not called in this method |
+// so that a mock delegate can be injected in tests and set expectations on the |
+// mock and return value of this method. |
+bool TouchInjectorWin::Init() { |
+ if (!delegate_) |
+ return false; |
+ |
+ if (!delegate_->InitializeTouchInjection( |
+ kMaxSimultaneousTouchCount, TOUCH_FEEDBACK_DEFAULT)) { |
+ // delagate_ is reset here so that the function that need the delegate |
+ // can check if it is null. |
+ delegate_.reset(); |
+ PLOG(INFO) << "Failed to initialize touch injection."; |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+void TouchInjectorWin::Deinitialize() { |
+ touches_in_contact_.clear(); |
+ // Same reason as TouchInjectorWin::Init(). For injecting mock delegates for |
+ // tests, a new delegate is created here. |
+ delegate_ = TouchInjectorWinDelegate::Create(); |
+} |
+ |
+void TouchInjectorWin::InjectTouchEvent(const TouchEvent& event) { |
+ if (!delegate_) { |
+ VLOG(3) << "Touch injection functions are not initialized."; |
+ return; |
+ } |
+ |
+ switch (event.event_type()) { |
+ case TouchEvent::TOUCH_POINT_START: |
+ AddNewTouchPoints(event); |
+ break; |
+ case TouchEvent::TOUCH_POINT_MOVE: |
+ MoveTouchPoints(event); |
+ break; |
+ case TouchEvent::TOUCH_POINT_END: |
+ EndTouchPoints(event); |
+ break; |
+ case TouchEvent::TOUCH_POINT_CANCEL: |
+ CancelTouchPoints(event); |
+ break; |
+ default: |
+ NOTREACHED(); |
+ return; |
+ } |
+} |
+ |
+void TouchInjectorWin::SetInjectorDelegateForTest( |
+ scoped_ptr<TouchInjectorWinDelegate> functions) { |
+ delegate_ = functions.Pass(); |
+} |
+ |
+void TouchInjectorWin::AddNewTouchPoints(const TouchEvent& event) { |
+ DCHECK_EQ(event.event_type(), TouchEvent::TOUCH_POINT_START); |
+ |
+ std::vector<POINTER_TOUCH_INFO> touches; |
+ // Must inject already touching points as move events. |
+ AppendMapValuesToVector(&touches_in_contact_, &touches); |
+ |
+ for (const TouchEventPoint& touch_point : event.touch_points()) { |
+ POINTER_TOUCH_INFO pointer_touch_info; |
+ memset(&pointer_touch_info, 0, sizeof(pointer_touch_info)); |
+ pointer_touch_info.pointerInfo.pointerFlags = |
+ POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN; |
+ ConvertToPointerTouchInfo(touch_point, &pointer_touch_info); |
+ touches.push_back(pointer_touch_info); |
+ |
+ // All points in the map should be a move point. |
+ pointer_touch_info.pointerInfo.pointerFlags = |
+ POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT | POINTER_FLAG_UPDATE; |
+ touches_in_contact_[touch_point.id()] = pointer_touch_info; |
+ } |
+ |
+ if (delegate_->InjectTouchInput(touches.size(), |
+ vector_as_array(&touches)) == 0) { |
+ PLOG(ERROR) << "Failed to inject a touch start event."; |
+ } |
+} |
+ |
+void TouchInjectorWin::MoveTouchPoints(const TouchEvent& event) { |
+ DCHECK_EQ(event.event_type(), TouchEvent::TOUCH_POINT_MOVE); |
+ |
+ for (const TouchEventPoint& touch_point : event.touch_points()) { |
+ POINTER_TOUCH_INFO* pointer_touch_info = |
+ &touches_in_contact_[touch_point.id()]; |
+ memset(pointer_touch_info, 0, sizeof(*pointer_touch_info)); |
+ pointer_touch_info->pointerInfo.pointerFlags = |
+ POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT | POINTER_FLAG_UPDATE; |
+ ConvertToPointerTouchInfo(touch_point, pointer_touch_info); |
+ } |
+ |
+ std::vector<POINTER_TOUCH_INFO> touches; |
+ // Must inject already touching points as move events. |
+ AppendMapValuesToVector(&touches_in_contact_, &touches); |
+ if (delegate_->InjectTouchInput(touches.size(), |
+ vector_as_array(&touches)) == 0) { |
+ PLOG(ERROR) << "Failed to inject a touch move event."; |
+ } |
+} |
+ |
+void TouchInjectorWin::EndTouchPoints(const TouchEvent& event) { |
+ DCHECK_EQ(event.event_type(), TouchEvent::TOUCH_POINT_END); |
+ |
+ std::vector<POINTER_TOUCH_INFO> touches; |
+ for (const TouchEventPoint& touch_point : event.touch_points()) { |
+ POINTER_TOUCH_INFO pointer_touch_info = |
+ touches_in_contact_[touch_point.id()]; |
+ pointer_touch_info.pointerInfo.pointerFlags = POINTER_FLAG_UP; |
+ |
+ touches_in_contact_.erase(touch_point.id()); |
+ touches.push_back(pointer_touch_info); |
+ } |
+ |
+ AppendMapValuesToVector(&touches_in_contact_, &touches); |
+ if (delegate_->InjectTouchInput(touches.size(), |
+ vector_as_array(&touches)) == 0) { |
+ PLOG(ERROR) << "Failed to inject a touch end event."; |
+ } |
+} |
+ |
+void TouchInjectorWin::CancelTouchPoints(const TouchEvent& event) { |
+ DCHECK_EQ(event.event_type(), TouchEvent::TOUCH_POINT_CANCEL); |
+ |
+ std::vector<POINTER_TOUCH_INFO> touches; |
+ for (const TouchEventPoint& touch_point : event.touch_points()) { |
+ POINTER_TOUCH_INFO pointer_touch_info = |
+ touches_in_contact_[touch_point.id()]; |
+ pointer_touch_info.pointerInfo.pointerFlags = |
+ POINTER_FLAG_UP | POINTER_FLAG_CANCELED; |
+ |
+ touches_in_contact_.erase(touch_point.id()); |
+ touches.push_back(pointer_touch_info); |
+ } |
+ |
+ AppendMapValuesToVector(&touches_in_contact_, &touches); |
+ if (delegate_->InjectTouchInput(touches.size(), |
+ vector_as_array(&touches)) == 0) { |
+ PLOG(ERROR) << "Failed to inject a touch cancel event."; |
+ } |
+} |
+ |
+} // namespace remoting |