1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
|
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/display/touch_calibrator_controller.h"
#include <memory>
#include "ash/display/touch_calibrator_view.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/host/ash_window_tree_host.h"
#include "ash/shell.h"
#include "ash/touch/ash_touch_transform_controller.h"
#include "base/functional/bind.h"
#include "base/ranges/algorithm.h"
#include "base/task/single_thread_task_runner.h"
#include "ui/aura/window_tree_host.h"
#include "ui/display/manager/touch_device_manager.h"
#include "ui/display/screen.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/event.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
void InitInternalTouchDeviceIds(std::set<int>& internal_touch_device_ids) {
internal_touch_device_ids.clear();
const std::vector<ui::TouchscreenDevice>& device_list =
ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices();
for (const auto& touchscreen_device : device_list) {
if (touchscreen_device.type == ui::InputDeviceType::INPUT_DEVICE_INTERNAL)
internal_touch_device_ids.insert(touchscreen_device.id);
}
}
// Returns a transform to undo any transformations that are applied to events
// originating from the touch device identified with |touch_device_id|. This
// transform converts the event's location to the raw touch location.
gfx::Transform CalculateEventTransformer(int touch_device_id) {
const display::DisplayManager* display_manager =
Shell::Get()->display_manager();
const std::vector<ui::TouchscreenDevice>& device_list =
ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices();
auto device_it = base::ranges::find(device_list, touch_device_id,
&ui::TouchscreenDevice::id);
DCHECK(device_it != device_list.end())
<< "Device id " << touch_device_id
<< " is invalid. No such device connected to system";
int64_t previous_display_id =
display_manager->touch_device_manager()->GetAssociatedDisplay(*device_it);
// If the touch device is not associated with any display. This may happen in
// tests when the test does not setup the |ui::TouchDeviceTransform| before
// generating a touch event.
if (previous_display_id == display::kInvalidDisplayId)
return gfx::Transform();
// Undo the event transformations that the previous display applied on the
// event location. We want to store the raw event location information.
gfx::Transform tm =
Shell::Get()
->window_tree_host_manager()
->GetAshWindowTreeHostForDisplayId(previous_display_id)
->AsWindowTreeHost()
->GetRootTransform();
return tm;
}
} // namespace
// Time interval after a touch event during which all other touch events are
// ignored during calibration.
const base::TimeDelta TouchCalibratorController::kTouchIntervalThreshold =
base::Milliseconds(200);
TouchCalibratorController::TouchCalibratorController()
: last_touch_timestamp_(base::Time::Now()) {}
TouchCalibratorController::~TouchCalibratorController() {
touch_calibrator_widgets_.clear();
StopCalibrationAndResetParams();
}
void TouchCalibratorController::OnDisplayConfigurationChanged() {
touch_calibrator_widgets_.clear();
StopCalibrationAndResetParams();
}
void TouchCalibratorController::StartCalibration(
const display::Display& target_display,
bool is_custom_calibration,
TouchCalibrationCallback opt_callback) {
state_ = is_custom_calibration ? CalibrationState::kCustomCalibration
: CalibrationState::kNativeCalibration;
if (opt_callback)
opt_callback_ = std::move(opt_callback);
target_display_ = target_display;
// Clear all touch calibrator views used in any previous calibration.
touch_calibrator_widgets_.clear();
// Set the touch device id as invalid so it can be set during calibration.
touch_device_id_ = ui::InputDevice::kInvalidId;
// Populate |internal_touch_device_ids_| with the ids of touch devices that
// are currently associated with the internal display and are of type
// |ui::InputDeviceType::INPUT_DEVICE_INTERNAL|.
InitInternalTouchDeviceIds(internal_touch_device_ids_);
// If this is a native touch calibration, then initialize the UX for it.
if (state_ == CalibrationState::kNativeCalibration) {
Shell::Get()->window_tree_host_manager()->AddObserver(this);
// Reset the calibration data.
touch_point_quad_.fill(std::make_pair(gfx::Point(0, 0), gfx::Point(0, 0)));
std::vector<display::Display> displays =
display::Screen::GetScreen()->GetAllDisplays();
for (const display::Display& display : displays) {
bool is_primary_view = display.id() == target_display_.id();
touch_calibrator_widgets_[display.id()] =
TouchCalibratorView::Create(display, is_primary_view);
}
}
Shell::Get()->touch_transformer_controller()->SetForCalibration(true);
// Add self as an event handler target.
Shell::Get()->AddPreTargetHandler(this);
}
void TouchCalibratorController::StopCalibrationAndResetParams() {
if (!IsCalibrating())
return;
Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
Shell::Get()->touch_transformer_controller()->SetForCalibration(false);
// Remove self as the event handler.
Shell::Get()->RemovePreTargetHandler(this);
// Transition all touch calibrator views to their final state for a graceful
// exit if this is touch calibration with native UX.
if (state_ == CalibrationState::kNativeCalibration) {
for (const auto& it : touch_calibrator_widgets_)
static_cast<TouchCalibratorView*>(it.second->GetContentsView())
->SkipToFinalState();
}
state_ = CalibrationState::kInactive;
if (opt_callback_) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(opt_callback_), false /* failure */));
opt_callback_.Reset();
}
}
void TouchCalibratorController::CompleteCalibration(
const CalibrationPointPairQuad& pairs,
const gfx::Size& display_size) {
bool did_find_touch_device = false;
const std::vector<ui::TouchscreenDevice>& device_list =
ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices();
ui::TouchscreenDevice target_device;
for (const auto& device : device_list) {
if (device.id == touch_device_id_) {
target_device = device;
did_find_touch_device = true;
break;
}
}
if (!did_find_touch_device) {
VLOG(1) << "No touch device with id: " << touch_device_id_ << " found to "
<< "complete touch calibration for display with id: "
<< target_display_.id() << ". Storing it as a fallback";
}
if (opt_callback_) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(opt_callback_), true /* success */));
opt_callback_.Reset();
}
StopCalibrationAndResetParams();
Shell::Get()->display_manager()->SetTouchCalibrationData(
target_display_.id(), pairs, display_size, target_device);
}
bool TouchCalibratorController::IsCalibrating() const {
return state_ != CalibrationState::kInactive;
}
// ui::EventHandler:
void TouchCalibratorController::OnKeyEvent(ui::KeyEvent* key) {
if (state_ != CalibrationState::kNativeCalibration)
return;
// Detect ESC key press.
if (key->type() == ui::ET_KEY_PRESSED && key->key_code() == ui::VKEY_ESCAPE)
StopCalibrationAndResetParams();
key->StopPropagation();
}
void TouchCalibratorController::OnTouchEvent(ui::TouchEvent* touch) {
if (!IsCalibrating())
return;
if (touch->type() != ui::ET_TOUCH_RELEASED)
return;
if (base::Time::Now() - last_touch_timestamp_ < kTouchIntervalThreshold)
return;
last_touch_timestamp_ = base::Time::Now();
// If the touch event originated from a touch device that is associated with
// the internal display, then ignore it.
if (internal_touch_device_ids_.count(touch->source_device_id()))
return;
if (touch_device_id_ == ui::InputDevice::kInvalidId) {
touch_device_id_ = touch->source_device_id();
event_transformer_ = CalculateEventTransformer(touch_device_id_);
}
// If this is a custom touch calibration, then everything else is managed
// by the application responsible for the custom calibration UX.
if (state_ == CalibrationState::kCustomCalibration)
return;
touch->StopPropagation();
TouchCalibratorView* target_screen_calibration_view =
static_cast<TouchCalibratorView*>(
touch_calibrator_widgets_[target_display_.id()]->GetContentsView());
// If this is the final state, then store all calibration data and stop
// calibration.
if (target_screen_calibration_view->state() ==
TouchCalibratorView::CALIBRATION_COMPLETE) {
gfx::RectF calibration_bounds =
Shell::Get()
->window_tree_host_manager()
->GetAshWindowTreeHostForDisplayId(target_display_.id())
->AsWindowTreeHost()
->GetRootTransform()
.MapRect(
gfx::RectF(target_screen_calibration_view->GetLocalBounds()));
CompleteCalibration(touch_point_quad_,
gfx::ToRoundedSize(calibration_bounds.size()));
return;
}
int state_index;
// Maps the state to an integer value. Assigns a non negative integral value
// for a state in which the user can interact with the the interface.
switch (target_screen_calibration_view->state()) {
case TouchCalibratorView::DISPLAY_POINT_1:
state_index = 0;
break;
case TouchCalibratorView::DISPLAY_POINT_2:
state_index = 1;
break;
case TouchCalibratorView::DISPLAY_POINT_3:
state_index = 2;
break;
case TouchCalibratorView::DISPLAY_POINT_4:
state_index = 3;
break;
default:
// Return early if the interface is in a state that does not allow user
// interaction.
return;
}
// Store touch point corresponding to its display point.
gfx::Point display_point;
if (target_screen_calibration_view->GetDisplayPointLocation(&display_point)) {
// If the screen has a root transform applied, the display point does not
// correctly map to the touch point. This is specially evident if the
// display is rotated or a device scale factor is applied. The display point
// needs to have the root transform applied as well to correctly pair it
// with the touch point.
display_point = Shell::Get()
->window_tree_host_manager()
->GetAshWindowTreeHostForDisplayId(target_display_.id())
->AsWindowTreeHost()
->GetRootTransform()
.MapPoint(display_point);
// Why do we need this? To understand this we need to know the life of an
// event location. The event location undergoes the following
// transformations along its path from the device to the event handler that
// is this class.
//
// Touch Device -> EventFactoryEvdev -> DrmWindowHost
// -> WindowEventDispatcher -> EventHandler(this)
//
// - The touch device dispatches the raw device event location. Lets assume
// this is (x, y).
// - The EventFactoryEvdev applies a touch transform that includes the
// calibration information as well as an offset of the native bounds
// of the display. This effectively converts the coordinates of the event
// from the raw device event location to the native screen coordinates.
// It gets the offset information from DrmWindowHost via the
// ManagedDisplayInfo class. If the offset of the PlatformWindow is (A,B)
// then the event location after this stage would be (x + A, y + B).
// - The DrmWindowHost removes the offset from the event location so that
// the location becomes relative to the platform window's origin. In
// Chrome OS it so happens that each display is its own platform window.
// So an offset equal to the display's origin in screen space is
// subtracted from the event location. This effectively undoes the
// previous step's transformation. Thus the event location after this
// step is (x, y) again.
// - WindowEventDispatcher applies an inverse root transform on the event
// location. This means that if the display is rotated or has a device
// scale factor, then those transformation are also applied to the event
// location. It effectively converts the coordinates from platform window
// coordinates to the aura's root window coordinates. The display in
// context here is the display that is associated with the touch device
// from which the event originated from.
//
// Up until the output of DrmWindowHost, everything is as expected. But
// WindowEventDispatcher applies an inverse root transform which modifies
// the raw event location that we wanted. Moreover, it modifies the raw
// event location using the root transform of the display that the touch
// device was previously associated with. To solve this, we need to undo the
// changes made to the event location by WindowEventDispatcher. This is what
// is achieved by |event_transformer_|.
gfx::PointF event_location_f =
event_transformer_.MapPoint(touch->location_f());
touch_point_quad_[state_index] =
std::make_pair(display_point, gfx::ToRoundedPoint(event_location_f));
} else {
// TODO(malaykeshav): Display some kind of error for the user.
NOTREACHED() << "Touch calibration failed. Could not retrieve location for"
" display point. Retry calibration.";
}
target_screen_calibration_view->AdvanceToNextState();
}
} // namespace ash
|