From 02418cca1bcf86ebd4cdec162d81c1ea10c7bed7 Mon Sep 17 00:00:00 2001 From: "miletus@chromium.org" Date: Fri, 9 May 2014 17:55:41 +0000 Subject: Move touch CTM from X into Chrome Currently we compute the touch CTM in OutputConfigurator and push that into X. This CL makes computing the touch CTM in DisplayController, and pushing it into WindowTreeHostX11. This moves the functionality of touch CTM from X into Chrome. Basically, when there is output configuration change, we compute the TouchCTM for each touch device, and push the TouchCTM into the WindowTreeHostX11 that is associated with the touchscreen. Then when X events reaching root window, we use the CTM to map the events coordinate in framebuffer space into the root window's coordinate space. BUG=351019, chrome-os-partner:25788 TEST=tested on Pixel/Clapper with external touch/non-touch displays on both extended/mirror mode. Touch events are correctly mapped to chrome window or discarded if it is from blank region from letterboxing/pillarboxing mirror mode. Review URL: https://codereview.chromium.org/191223007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@269371 0039d316-1c4b-4281-b951-d872f2087c98 --- ash/touch/touch_transformer_controller.cc | 225 +++++++++++++++++++++ ash/touch/touch_transformer_controller.h | 55 +++++ ash/touch/touch_transformer_controller_unittest.cc | 206 +++++++++++++++++++ 3 files changed, 486 insertions(+) create mode 100644 ash/touch/touch_transformer_controller.cc create mode 100644 ash/touch/touch_transformer_controller.h create mode 100644 ash/touch/touch_transformer_controller_unittest.cc (limited to 'ash/touch') diff --git a/ash/touch/touch_transformer_controller.cc b/ash/touch/touch_transformer_controller.cc new file mode 100644 index 0000000..87954a3 --- /dev/null +++ b/ash/touch/touch_transformer_controller.cc @@ -0,0 +1,225 @@ +// Copyright 2014 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 "ash/touch/touch_transformer_controller.h" + +#include "ash/display/display_controller.h" +#include "ash/display/display_manager.h" +#include "ash/host/ash_window_tree_host.h" +#include "ash/root_window_controller.h" +#include "ash/shell.h" +#include "ui/aura/window_tree_host.h" +#include "ui/display/chromeos/display_configurator.h" +#include "ui/display/types/chromeos/display_snapshot.h" +#include "ui/events/x/device_data_manager.h" + +namespace ash { + +namespace { + +DisplayManager* GetDisplayManager() { + return Shell::GetInstance()->display_manager(); +} + +} // namespace + +// This function computes the extended mode TouchTransformer for +// |touch_display|. The TouchTransformer maps the touch event position +// from framebuffer size to the display size. +gfx::Transform +TouchTransformerController::GetExtendedModeTouchTransformer( + const DisplayInfo& touch_display, const gfx::Size& fb_size) const { + gfx::Transform ctm; + if (touch_display.touch_device_id() == 0 || + fb_size.width() == 0.0 || + fb_size.height() == 0.0) + return ctm; + float width = touch_display.bounds_in_native().width(); + float height = touch_display.bounds_in_native().height(); + ctm.Scale(width / fb_size.width(), height / fb_size.height()); + return ctm; +} + +bool TouchTransformerController::ShouldComputeMirrorModeTouchTransformer( + const DisplayInfo& touch_display) const { + if (force_compute_mirror_mode_touch_transformer_) + return true; + + if (touch_display.touch_device_id() == 0) + return false; + + const ui::DisplayConfigurator::DisplayState* state = NULL; + const std::vector& cached_displays = + Shell::GetInstance()->display_configurator()->cached_displays(); + for (size_t i = 0; i < cached_displays.size(); i++) { + if (cached_displays[i].touch_device_id == touch_display.touch_device_id()) { + state = &cached_displays[i]; + break; + } + } + + if (!state || state->mirror_mode == state->display->native_mode() || + !state->display->is_aspect_preserving_scaling()) { + return false; + } + return true; +} + +// This function computes the mirror mode TouchTransformer for |touch_display|. +// When internal monitor is applied a resolution that does not have +// the same aspect ratio as its native resolution, there would be +// blank regions in the letterboxing/pillarboxing mode. +// The TouchTransformer will make sure the touch events on the blank region +// have negative coordinates and touch events within the chrome region +// have the correct positive coordinates. +gfx::Transform TouchTransformerController::GetMirrorModeTouchTransformer( + const DisplayInfo& touch_display) const { + gfx::Transform ctm; + if (!ShouldComputeMirrorModeTouchTransformer(touch_display)) + return ctm; + + float mirror_width = touch_display.bounds_in_native().width(); + float mirror_height = touch_display.bounds_in_native().height(); + float native_width = 0; + float native_height = 0; + + std::vector modes = touch_display.display_modes(); + for (size_t i = 0; i < modes.size(); i++) { + if (modes[i].native) { + native_width = modes[i].size.width(); + native_height = modes[i].size.height(); + break; + } + } + + if (native_height == 0.0 || mirror_height == 0.0 || + native_width == 0.0 || mirror_width == 0.0) + return ctm; + + float native_ar = static_cast(native_width) / + static_cast(native_height); + float mirror_ar = static_cast(mirror_width) / + static_cast(mirror_height); + + if (mirror_ar > native_ar) { // Letterboxing + // Translate before scale. + ctm.Translate(0.0, (1.0 - mirror_ar / native_ar) * 0.5 * mirror_height); + ctm.Scale(1.0, mirror_ar / native_ar); + return ctm; + } + + if (native_ar > mirror_ar) { // Pillarboxing + // Translate before scale. + ctm.Translate((1.0 - native_ar / mirror_ar) * 0.5 * mirror_width, 0.0); + ctm.Scale(native_ar / mirror_ar, 1.0); + return ctm; + } + + return ctm; // Same aspect ratio - return identity +} + +TouchTransformerController::TouchTransformerController() : + force_compute_mirror_mode_touch_transformer_ (false) { + Shell::GetInstance()->display_controller()->AddObserver(this); +} + +TouchTransformerController::~TouchTransformerController() { + Shell::GetInstance()->display_controller()->RemoveObserver(this); +} + +void TouchTransformerController::UpdateTouchTransformer() const { + ui::DeviceDataManager* device_manager = ui::DeviceDataManager::GetInstance(); + device_manager->ClearTouchTransformerRecord(); + + // Display IDs and DisplayInfo for mirror or extended mode. + int64 display1_id = gfx::Display::kInvalidDisplayID; + int64 display2_id = gfx::Display::kInvalidDisplayID; + DisplayInfo display1; + DisplayInfo display2; + // Display ID and DisplayInfo for single display mode. + int64 single_display_id = gfx::Display::kInvalidDisplayID; + DisplayInfo single_display; + + DisplayController* display_controller = + Shell::GetInstance()->display_controller(); + ui::MultipleDisplayState display_state = + Shell::GetInstance()->display_configurator()->display_state(); + if (display_state == ui::MULTIPLE_DISPLAY_STATE_INVALID || + display_state == ui::MULTIPLE_DISPLAY_STATE_HEADLESS) { + return; + } else if (display_state == ui::MULTIPLE_DISPLAY_STATE_DUAL_MIRROR || + display_state == ui::MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED) { + // TODO(miletus) : Handle DUAL_EXTENDED with software mirroring. + DisplayIdPair id_pair = GetDisplayManager()->GetCurrentDisplayIdPair(); + display1_id = id_pair.first; + display2_id = id_pair.second; + DCHECK(display1_id != gfx::Display::kInvalidDisplayID && + display2_id != gfx::Display::kInvalidDisplayID); + display1 = GetDisplayManager()->GetDisplayInfo(display1_id); + display2 = GetDisplayManager()->GetDisplayInfo(display2_id); + } else { + single_display_id = GetDisplayManager()->first_display_id(); + DCHECK(single_display_id != gfx::Display::kInvalidDisplayID); + single_display = GetDisplayManager()->GetDisplayInfo(single_display_id); + } + + if (display_state == ui::MULTIPLE_DISPLAY_STATE_DUAL_MIRROR) { + // In mirror mode, both displays share the same root window so + // both display ids are associated with the root window. + aura::Window* root = display_controller->GetPrimaryRootWindow(); + RootWindowController::ForWindow(root)->ash_host()->UpdateDisplayID( + display1_id, display2_id); + device_manager->UpdateTouchInfoForDisplay( + display1_id, + display1.touch_device_id(), + GetMirrorModeTouchTransformer(display1)); + device_manager->UpdateTouchInfoForDisplay( + display2_id, + display2.touch_device_id(), + GetMirrorModeTouchTransformer(display2)); + return; + } + + if (display_state == ui::MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED) { + // In extended mode, each display is associated with one root window. + aura::Window* root1 = + display_controller->GetRootWindowForDisplayId(display1_id); + aura::Window* root2 = + display_controller->GetRootWindowForDisplayId(display2_id); + RootWindowController::ForWindow(root1)->ash_host()->UpdateDisplayID( + display1_id, gfx::Display::kInvalidDisplayID); + RootWindowController::ForWindow(root2)->ash_host()->UpdateDisplayID( + display2_id, gfx::Display::kInvalidDisplayID); + gfx::Size fb_size = + Shell::GetInstance()->display_configurator()->framebuffer_size(); + device_manager->UpdateTouchInfoForDisplay( + display1_id, + display1.touch_device_id(), + GetExtendedModeTouchTransformer(display1, fb_size)); + device_manager->UpdateTouchInfoForDisplay( + display2_id, + display2.touch_device_id(), + GetExtendedModeTouchTransformer(display2, fb_size)); + return; + } + + // Single display mode. The root window has one associated display id. + aura::Window* root = + display_controller->GetRootWindowForDisplayId(single_display.id()); + RootWindowController::ForWindow(root)->ash_host()->UpdateDisplayID( + single_display.id(), gfx::Display::kInvalidDisplayID); + device_manager->UpdateTouchInfoForDisplay(single_display_id, + single_display.touch_device_id(), + gfx::Transform()); +} + +void TouchTransformerController::OnDisplaysInitialized() { + UpdateTouchTransformer(); +} + +void TouchTransformerController::OnDisplayConfigurationChanged() { + UpdateTouchTransformer(); +} + +} // namespace ash diff --git a/ash/touch/touch_transformer_controller.h b/ash/touch/touch_transformer_controller.h new file mode 100644 index 0000000..f06be29 --- /dev/null +++ b/ash/touch/touch_transformer_controller.h @@ -0,0 +1,55 @@ +// Copyright 2014 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. + +#ifndef ASH_TOUCH_TOUCH_TRANSFORMER_CONTROLLER_H_ +#define ASH_TOUCH_TOUCH_TRANSFORMER_CONTROLLER_H_ + +#include "ash/ash_export.h" +#include "ash/display/display_controller.h" +#include "ui/gfx/transform.h" + +namespace ash { + +// TouchTransformerController listens to display configuration change +// and updates the touch transformation for touch displays. +class ASH_EXPORT TouchTransformerController + : public DisplayController::Observer { + public: + TouchTransformerController(); + virtual ~TouchTransformerController(); + + // Updates the TouchTransformer for touch device and pushes the new + // TouchTransformer into device manager. + void UpdateTouchTransformer() const; + + // DisplayController::Observer: + virtual void OnDisplaysInitialized() OVERRIDE; + virtual void OnDisplayConfigurationChanged() OVERRIDE; + + private: + FRIEND_TEST_ALL_PREFIXES(TouchTransformerControllerTest, + TouchTransformerMirrorModeLetterboxing); + FRIEND_TEST_ALL_PREFIXES(TouchTransformerControllerTest, + TouchTransformerMirrorModePillarboxing); + FRIEND_TEST_ALL_PREFIXES(TouchTransformerControllerTest, + TouchTransformerExtendedMode); + + bool ShouldComputeMirrorModeTouchTransformer( + const DisplayInfo& touch_display) const ; + + gfx::Transform GetMirrorModeTouchTransformer( + const DisplayInfo& touch_display) const; + + gfx::Transform GetExtendedModeTouchTransformer( + const DisplayInfo& touch_display, const gfx::Size& fb_size) const; + + // For unittests only. + bool force_compute_mirror_mode_touch_transformer_; + + DISALLOW_COPY_AND_ASSIGN(TouchTransformerController); +}; + +} // namespace ash + +#endif // ASH_TOUCH_TOUCH_TRANSFORMER_CONTROLLER_H_ diff --git a/ash/touch/touch_transformer_controller_unittest.cc b/ash/touch/touch_transformer_controller_unittest.cc new file mode 100644 index 0000000..1b8fa1f --- /dev/null +++ b/ash/touch/touch_transformer_controller_unittest.cc @@ -0,0 +1,206 @@ +// Copyright 2014 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 "ash/touch/touch_transformer_controller.h" + +#include "ash/shell.h" +#include "ash/test/ash_test_base.h" +#include "ui/aura/window_tree_host.h" +#include "ui/events/x/device_data_manager.h" +#include "ui/gfx/display.h" + +namespace ash { + +namespace { +DisplayInfo CreateDisplayInfo(int64 id, + int touch_device_id, + const gfx::Rect& bounds) { + DisplayInfo info(id, std::string(), false); + info.SetBounds(bounds); + info.set_touch_device_id(touch_device_id); + return info; +} +} // namespace + +typedef test::AshTestBase TouchTransformerControllerTest; + +TEST_F(TouchTransformerControllerTest, TouchTransformerMirrorModeLetterboxing) { + // The internal display has native resolution of 2560x1700, and in + // mirror mode it is configured as 1920x1200. This is in letterboxing + // mode. + DisplayInfo internal_display_info = + CreateDisplayInfo(1, 10, gfx::Rect(0, 0, 1920, 1200)); + std::vector internal_modes; + internal_modes.push_back( + DisplayMode(gfx::Size(2560, 1700), 60, false, true)); + internal_modes.push_back( + DisplayMode(gfx::Size(1920, 1200), 60, false, false)); + internal_display_info.set_display_modes(internal_modes); + + DisplayInfo external_display_info = + CreateDisplayInfo(2, 11, gfx::Rect(0, 0, 1920, 1200)); + + TouchTransformerController* tt_controller = + Shell::GetInstance()->touch_transformer_controller(); + ui::DeviceDataManager* device_manager = ui::DeviceDataManager::GetInstance(); + + tt_controller->force_compute_mirror_mode_touch_transformer_ = true; + device_manager->UpdateTouchInfoForDisplay( + internal_display_info.id(), + internal_display_info.touch_device_id(), + tt_controller->GetMirrorModeTouchTransformer(internal_display_info)); + + tt_controller->force_compute_mirror_mode_touch_transformer_ = false; + device_manager->UpdateTouchInfoForDisplay( + external_display_info.id(), + external_display_info.touch_device_id(), + tt_controller->GetMirrorModeTouchTransformer(external_display_info)); + + EXPECT_EQ(1, device_manager->GetDisplayForTouchDevice(10)); + EXPECT_EQ(2, device_manager->GetDisplayForTouchDevice(11)); + + // External touch display has the default TouchTransformer. + float x = 100.0; + float y = 100.0; + device_manager->ApplyTouchTransformer(11, &x, &y); + EXPECT_EQ(100, x); + EXPECT_EQ(100, y); + + // In letterboxing, there is (1-2560*(1200/1920)/1700)/2 = 2.95% of the + // height on both the top & bottom region of the screen is blank. + // When touch events coming at Y range [0, 1200), the mapping should be + // [0, ~35] ---> < 0 + // [~35, ~1165] ---> [0, 1200) + // [~1165, 1200] ---> >= 1200 + x = 100.0; + y = 35.0; + device_manager->ApplyTouchTransformer(10, &x, &y); + EXPECT_EQ(100, static_cast(x)); + EXPECT_EQ(0, static_cast(y)); + + x = 100.0; + y = 1165.0; + device_manager->ApplyTouchTransformer(10, &x, &y); + EXPECT_EQ(100, static_cast(x)); + EXPECT_EQ(1200, static_cast(y)); +} + +TEST_F(TouchTransformerControllerTest, TouchTransformerMirrorModePillarboxing) { + // The internal display has native resolution of 1366x768, and in + // mirror mode it is configured as 1024x768. This is in pillarboxing + // mode. + DisplayInfo internal_display_info = + CreateDisplayInfo(1, 10, gfx::Rect(0, 0, 1024, 768)); + std::vector internal_modes; + internal_modes.push_back( + DisplayMode(gfx::Size(1366, 768), 60, false, true)); + internal_modes.push_back( + DisplayMode(gfx::Size(1024, 768), 60, false, false)); + internal_display_info.set_display_modes(internal_modes); + + DisplayInfo external_display_info = + CreateDisplayInfo(2, 11, gfx::Rect(0, 0, 1024, 768)); + + TouchTransformerController* tt_controller = + Shell::GetInstance()->touch_transformer_controller(); + ui::DeviceDataManager* device_manager = ui::DeviceDataManager::GetInstance(); + + tt_controller->force_compute_mirror_mode_touch_transformer_ = true; + device_manager->UpdateTouchInfoForDisplay( + internal_display_info.id(), + internal_display_info.touch_device_id(), + tt_controller->GetMirrorModeTouchTransformer(internal_display_info)); + + tt_controller->force_compute_mirror_mode_touch_transformer_ = false; + device_manager->UpdateTouchInfoForDisplay( + external_display_info.id(), + external_display_info.touch_device_id(), + tt_controller->GetMirrorModeTouchTransformer(external_display_info)); + + EXPECT_EQ(1, device_manager->GetDisplayForTouchDevice(10)); + EXPECT_EQ(2, device_manager->GetDisplayForTouchDevice(11)); + + // External touch display has the default TouchTransformer. + float x = 100.0; + float y = 100.0; + device_manager->ApplyTouchTransformer(11, &x, &y); + EXPECT_EQ(100, x); + EXPECT_EQ(100, y); + + // In pillarboxing, there is (1-768*(1024/768)/1366)/2 = 12.5% of the + // width on both the left & rigth region of the screen is blank. + // When touch events coming at X range [0, 1024), the mapping should be + // [0, ~128] ---> < 0 + // [~128, ~896] ---> [0, 1024) + // [~896, 1024] ---> >= 1024 + x = 128.0; + y = 100.0; + device_manager->ApplyTouchTransformer(10, &x, &y); + EXPECT_EQ(0, static_cast(x)); + EXPECT_EQ(100, static_cast(y)); + + x = 896.0; + y = 100.0; + device_manager->ApplyTouchTransformer(10, &x, &y); + EXPECT_EQ(1024, static_cast(x)); + EXPECT_EQ(100, static_cast(y)); +} + +TEST_F(TouchTransformerControllerTest, TouchTransformerExtendedMode) { + // The internal display has size 1366 x 768. The external display has + // size 2560x1600. The total frame buffer is 2560x2428, + // where 2428 = 768 + 60 (hidden gap) + 1600 + // and the sceond monitor is translated to Point (0, 828) in the + // framebuffer. + DisplayInfo display1 = CreateDisplayInfo(1, 5, gfx::Rect(0, 0, 1366, 768)); + DisplayInfo display2 = CreateDisplayInfo(2, 6, gfx::Rect(0, 828, 2560, 1600)); + gfx::Size fb_size(2560, 2428); + + TouchTransformerController* tt_controller = + Shell::GetInstance()->touch_transformer_controller(); + ui::DeviceDataManager* device_manager = ui::DeviceDataManager::GetInstance(); + + device_manager->UpdateTouchInfoForDisplay( + display1.id(), + display1.touch_device_id(), + tt_controller->GetExtendedModeTouchTransformer(display1, fb_size)); + + device_manager->UpdateTouchInfoForDisplay( + display2.id(), + display2.touch_device_id(), + tt_controller->GetExtendedModeTouchTransformer(display2, fb_size)); + + EXPECT_EQ(1, device_manager->GetDisplayForTouchDevice(5)); + EXPECT_EQ(2, device_manager->GetDisplayForTouchDevice(6)); + + // Mapping for touch events from internal touch display: + // [0, 2560) x [0, 2428) -> [0, 1366) x [0, 768) + float x = 0.0; + float y = 0.0; + device_manager->ApplyTouchTransformer(5, &x, &y); + EXPECT_EQ(0, static_cast(x)); + EXPECT_EQ(0, static_cast(y)); + + x = 2559.0; + y = 2427.0; + device_manager->ApplyTouchTransformer(5, &x, &y); + EXPECT_EQ(1365, static_cast(x)); + EXPECT_EQ(767, static_cast(y)); + + // Mapping for touch events from external touch display: + // [0, 2560) x [0, 2428) -> [0, 2560) x [0, 1600) + x = 0.0; + y = 0.0; + device_manager->ApplyTouchTransformer(6, &x, &y); + EXPECT_EQ(0, static_cast(x)); + EXPECT_EQ(0, static_cast(y)); + + x = 2559.0; + y = 2427.0; + device_manager->ApplyTouchTransformer(6, &x, &y); + EXPECT_EQ(2559, static_cast(x)); + EXPECT_EQ(1599, static_cast(y)); +} + +} // namespace ash -- cgit v1.1