blob: cc62fd688711153e2930fe06550c219e4cc01173 [file] [log] [blame]
// 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 "content/browser/renderer_host/direct_manipulation_helper_win.h"
#include <objbase.h>
#include <cmath>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/win/window_event_target.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/geometry/rect.h"
namespace content {
bool LoggingEnabled() {
static bool logging_enabled =
base::FeatureList::IsEnabled(features::kPrecisionTouchpadLogging);
return logging_enabled;
}
// TODO(crbug.com/914914) This is added for help us getting debug log on
// machine with scrolling issue on Windows Precision Touchpad. We will remove it
// after Windows Precision Touchpad scrolling issue fixed.
void DebugLogging(const std::string& s, HRESULT hr) {
if (!LoggingEnabled())
return;
LOG(ERROR) << "Windows PTP: " << s << " " << hr;
}
// static
std::unique_ptr<DirectManipulationHelper>
DirectManipulationHelper::CreateInstance(HWND window,
ui::WindowEventTarget* event_target) {
if (!::IsWindow(window))
return nullptr;
if (!base::FeatureList::IsEnabled(features::kPrecisionTouchpad))
return nullptr;
// DM_POINTERHITTEST supported since Win10.
if (base::win::GetVersion() < base::win::Version::WIN10)
return nullptr;
std::unique_ptr<DirectManipulationHelper> instance =
base::WrapUnique(new DirectManipulationHelper());
instance->window_ = window;
if (instance->Initialize(event_target))
return instance;
return nullptr;
}
// static
std::unique_ptr<DirectManipulationHelper>
DirectManipulationHelper::CreateInstanceForTesting(
ui::WindowEventTarget* event_target,
Microsoft::WRL::ComPtr<IDirectManipulationViewport> viewport) {
if (!base::FeatureList::IsEnabled(features::kPrecisionTouchpad))
return nullptr;
// DM_POINTERHITTEST supported since Win10.
if (base::win::GetVersion() < base::win::Version::WIN10)
return nullptr;
std::unique_ptr<DirectManipulationHelper> instance =
base::WrapUnique(new DirectManipulationHelper());
instance->event_handler_ =
Microsoft::WRL::Make<DirectManipulationEventHandler>(instance.get());
instance->event_handler_->SetWindowEventTarget(event_target);
instance->viewport_ = viewport;
return instance;
}
DirectManipulationHelper::~DirectManipulationHelper() {
if (viewport_)
viewport_->Abandon();
}
DirectManipulationHelper::DirectManipulationHelper() {}
bool DirectManipulationHelper::Initialize(ui::WindowEventTarget* event_target) {
// IDirectManipulationUpdateManager is the first COM object created by the
// application to retrieve other objects in the Direct Manipulation API.
// It also serves to activate and deactivate Direct Manipulation functionality
// on a per-HWND basis.
HRESULT hr =
::CoCreateInstance(CLSID_DirectManipulationManager, nullptr,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&manager_));
if (!SUCCEEDED(hr)) {
DebugLogging("DirectManipulationManager create failed.", hr);
return false;
}
// Since we want to use fake viewport, we need UpdateManager to tell a fake
// fake render frame.
hr = manager_->GetUpdateManager(IID_PPV_ARGS(&update_manager_));
if (!SUCCEEDED(hr)) {
DebugLogging("Get UpdateManager failed.", hr);
return false;
}
hr = manager_->CreateViewport(nullptr, window_, IID_PPV_ARGS(&viewport_));
if (!SUCCEEDED(hr)) {
DebugLogging("Viewport create failed.", hr);
return false;
}
DIRECTMANIPULATION_CONFIGURATION configuration =
DIRECTMANIPULATION_CONFIGURATION_INTERACTION |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA |
DIRECTMANIPULATION_CONFIGURATION_RAILS_X |
DIRECTMANIPULATION_CONFIGURATION_RAILS_Y |
DIRECTMANIPULATION_CONFIGURATION_SCALING;
hr = viewport_->ActivateConfiguration(configuration);
if (!SUCCEEDED(hr)) {
DebugLogging("Viewport set ActivateConfiguration failed.", hr);
return false;
}
// Since we are using fake viewport and only want to use Direct Manipulation
// for touchpad, we need to use MANUALUPDATE option.
hr = viewport_->SetViewportOptions(
DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE);
if (!SUCCEEDED(hr)) {
DebugLogging("Viewport set ViewportOptions failed.", hr);
return false;
}
event_handler_ = Microsoft::WRL::Make<DirectManipulationEventHandler>(this);
event_handler_->SetWindowEventTarget(event_target);
// We got Direct Manipulation transform from
// IDirectManipulationViewportEventHandler.
hr = viewport_->AddEventHandler(window_, event_handler_.Get(),
&view_port_handler_cookie_);
if (!SUCCEEDED(hr)) {
DebugLogging("Viewport add EventHandler failed.", hr);
return false;
}
// Set default rect for viewport before activate.
viewport_size_in_pixels_ = {1000, 1000};
RECT rect = gfx::Rect(viewport_size_in_pixels_).ToRECT();
hr = viewport_->SetViewportRect(&rect);
if (!SUCCEEDED(hr)) {
DebugLogging("Viewport set rect failed.", hr);
return false;
}
hr = manager_->Activate(window_);
if (!SUCCEEDED(hr)) {
DebugLogging("DirectManipulationManager activate failed.", hr);
return false;
}
hr = viewport_->Enable();
if (!SUCCEEDED(hr)) {
DebugLogging("Viewport enable failed.", hr);
return false;
}
hr = update_manager_->Update(nullptr);
if (!SUCCEEDED(hr)) {
DebugLogging("UpdateManager update failed.", hr);
return false;
}
DebugLogging("DirectManipulation initialization complete", S_OK);
return true;
}
void DirectManipulationHelper::Activate() {
HRESULT hr = viewport_->Stop();
if (!SUCCEEDED(hr)) {
DebugLogging("Viewport stop failed.", hr);
return;
}
hr = manager_->Activate(window_);
if (!SUCCEEDED(hr))
DebugLogging("DirectManipulationManager activate failed.", hr);
}
void DirectManipulationHelper::Deactivate() {
HRESULT hr = viewport_->Stop();
if (!SUCCEEDED(hr)) {
DebugLogging("Viewport stop failed.", hr);
return;
}
hr = manager_->Deactivate(window_);
if (!SUCCEEDED(hr))
DebugLogging("DirectManipulationManager deactivate failed.", hr);
}
void DirectManipulationHelper::SetSizeInPixels(
const gfx::Size& size_in_pixels) {
if (viewport_size_in_pixels_ == size_in_pixels)
return;
HRESULT hr = viewport_->Stop();
if (!SUCCEEDED(hr)) {
DebugLogging("Viewport stop failed.", hr);
return;
}
viewport_size_in_pixels_ = size_in_pixels;
RECT rect = gfx::Rect(viewport_size_in_pixels_).ToRECT();
hr = viewport_->SetViewportRect(&rect);
if (!SUCCEEDED(hr))
DebugLogging("Viewport set rect failed.", hr);
}
bool DirectManipulationHelper::OnPointerHitTest(
WPARAM w_param,
ui::WindowEventTarget* event_target) {
// Update the device scale factor.
event_handler_->SetDeviceScaleFactor(
display::win::ScreenWin::GetScaleFactorForHWND(window_));
// Only DM_POINTERHITTEST can be the first message of input sequence of
// touchpad input.
// TODO(chaopeng) Check if Windows API changes:
// For WM_POINTER, the pointer type will show the event from mouse.
// For WM_POINTERACTIVATE, the pointer id will be different with the following
// message.
event_handler_->SetWindowEventTarget(event_target);
using GetPointerTypeFn = BOOL(WINAPI*)(UINT32, POINTER_INPUT_TYPE*);
UINT32 pointer_id = GET_POINTERID_WPARAM(w_param);
POINTER_INPUT_TYPE pointer_type;
static const auto get_pointer_type = reinterpret_cast<GetPointerTypeFn>(
base::win::GetUser32FunctionPointer("GetPointerType"));
if (get_pointer_type && get_pointer_type(pointer_id, &pointer_type) &&
pointer_type == PT_TOUCHPAD && event_target) {
HRESULT hr = viewport_->SetContact(pointer_id);
if (!SUCCEEDED(hr)) {
DebugLogging("Viewport set contact failed.", hr);
return false;
}
// Request begin frame for fake viewport.
need_poll_events_ = true;
}
return need_poll_events_;
}
HRESULT DirectManipulationHelper::Reset(bool need_poll_events) {
// By zooming the primary content to a rect that match the viewport rect, we
// reset the content's transform to identity.
HRESULT hr = viewport_->ZoomToRect(
static_cast<float>(0), static_cast<float>(0),
static_cast<float>(viewport_size_in_pixels_.width()),
static_cast<float>(viewport_size_in_pixels_.height()), FALSE);
if (!SUCCEEDED(hr)) {
DebugLogging("Viewport zoom to rect failed.", hr);
return hr;
}
need_poll_events_ = need_poll_events;
return S_OK;
}
bool DirectManipulationHelper::PollForNextEvent() {
// Simulate 1 frame in update_manager_.
HRESULT hr = update_manager_->Update(nullptr);
if (!SUCCEEDED(hr))
DebugLogging("UpdateManager update failed.", hr);
return need_poll_events_;
}
void DirectManipulationHelper::SetDeviceScaleFactorForTesting(float factor) {
event_handler_->SetDeviceScaleFactor(factor);
}
} // namespace content