普通文本  |  303行  |  11.69 KB

// 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/display_snapshot.h"
#include "ui/events/device_data_manager.h"
#include "ui/events/x/device_data_manager_x11.h"

namespace ash {

namespace {

DisplayManager* GetDisplayManager() {
  return Shell::GetInstance()->display_manager();
}

}  // namespace

// This is to compute the scale ratio for the TouchEvent's radius. The
// configured resolution of the display is not always the same as the touch
// screen's reporting resolution, e.g. the display could be set as
// 1920x1080 while the touchscreen is reporting touch position range at
// 32767x32767. Touch radius is reported in the units the same as touch position
// so we need to scale the touch radius to be compatible with the display's
// resolution. We compute the scale as
// sqrt of (display_area / touchscreen_area)
double TouchTransformerController::GetTouchResolutionScale(
    const DisplayInfo& touch_display) const {
  if (touch_display.touch_device_id() == 0)
    return 1.0;

  double min_x, max_x;
  double min_y, max_y;
  if (!ui::DeviceDataManagerX11::GetInstance()->GetDataRange(
          touch_display.touch_device_id(),
          ui::DeviceDataManagerX11::DT_TOUCH_POSITION_X,
          &min_x, &max_x) ||
      !ui::DeviceDataManagerX11::GetInstance()->GetDataRange(
          touch_display.touch_device_id(),
          ui::DeviceDataManagerX11::DT_TOUCH_POSITION_Y,
          &min_y, &max_y)) {
    return 1.0;
  }

  double width = touch_display.bounds_in_native().width();
  double height = touch_display.bounds_in_native().height();

  if (max_x == 0.0 || max_y == 0.0 || width == 0.0 || height == 0.0)
    return 1.0;

  // [0, max_x] -> touchscreen width = max_x + 1
  // [0, max_y] -> touchscreen height = max_y + 1
  max_x += 1.0;
  max_y += 1.0;

  double ratio = std::sqrt((width * height) / (max_x * max_y));

  VLOG(2) << "Screen width/height: " << width << "/" << height
          << ", Touchscreen width/height: " << max_x << "/" << max_y
          << ", Touch radius scale ratio: " << ratio;
  return ratio;
}

// 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;

  DisplayManager* display_manager = Shell::GetInstance()->display_manager();
  const std::vector<gfx::Display>& displays = display_manager->displays();
  const DisplayInfo* info = NULL;
  for (size_t i = 0; i < displays.size(); i++) {
    const DisplayInfo& current_info =
        display_manager->GetDisplayInfo(displays[i].id());
    if (current_info.touch_device_id() == touch_display.touch_device_id()) {
      info = &current_info;
      break;
    }
  }

  if (!info || info->size_in_pixel() == info->GetNativeModeSize() ||
      !info->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<DisplayMode> 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<float>(native_width) /
      static_cast<float>(native_height);
  float mirror_ar = static_cast<float>(mirror_width) /
      static_cast<float>(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) {
    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);
    device_manager->UpdateTouchRadiusScale(display1.touch_device_id(),
                                           GetTouchResolutionScale(display1));
    device_manager->UpdateTouchRadiusScale(display2.touch_device_id(),
                                           GetTouchResolutionScale(display2));
  } else {
    single_display_id = GetDisplayManager()->first_display_id();
    DCHECK(single_display_id != gfx::Display::kInvalidDisplayID);
    single_display = GetDisplayManager()->GetDisplayInfo(single_display_id);
    device_manager->UpdateTouchRadiusScale(
        single_display.touch_device_id(),
        GetTouchResolutionScale(single_display));
  }

  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) {
    gfx::Size fb_size =
        Shell::GetInstance()->display_configurator()->framebuffer_size();
    // In extended but software mirroring mode, ther is only one X root window
    // that associates with both displays.
    if (GetDisplayManager()->software_mirroring_enabled())  {
      aura::Window* root = display_controller->GetPrimaryRootWindow();
      RootWindowController::ForWindow(root)->ash_host()->UpdateDisplayID(
          display1_id, display2_id);
      DisplayInfo source_display =
          gfx::Display::InternalDisplayId() == display1_id ?
          display1 : display2;
      // Mapping from framebuffer size to the source display's native
      // resolution.
      device_manager->UpdateTouchInfoForDisplay(
          display1_id,
          display1.touch_device_id(),
          GetExtendedModeTouchTransformer(source_display, fb_size));
      device_manager->UpdateTouchInfoForDisplay(
          display2_id,
          display2.touch_device_id(),
          GetExtendedModeTouchTransformer(source_display, fb_size));
    } else {
      // In actual 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);
      // Mapping from framebuffer size to each display's native resolution.
      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