text: // 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/host/ash_window_tree_host_x11.h"
  
  #include <X11/extensions/Xfixes.h>
  #include <X11/extensions/XInput2.h>
  #include <X11/Xatom.h>
  #include <X11/Xlib.h>
  #include <string>
  #include <utility>
  #include <vector>
  
  #include "ash/host/ash_window_tree_host_init_params.h"
  #include "ash/host/ash_window_tree_host_unified.h"
  #include "ash/host/root_window_transformer.h"
  #include "ash/ime/input_method_event_handler.h"
  #include "base/sys_info.h"
  #include "ui/aura/env.h"
  #include "ui/aura/window.h"
  #include "ui/aura/window_event_dispatcher.h"
  #include "ui/base/x/x11_util.h"
  #include "ui/display/screen.h"
  #include "ui/events/devices/device_data_manager.h"
  #include "ui/events/devices/x11/device_list_cache_x11.h"
  #include "ui/events/devices/x11/touch_factory_x11.h"
  #include "ui/events/event.h"
  #include "ui/events/event_utils.h"
  #include "ui/events/null_event_targeter.h"
  #include "ui/events/platform/platform_event_source.h"
  #include "ui/gfx/geometry/rect.h"
  
  namespace ash {
  
  AshWindowTreeHostX11::AshWindowTreeHostX11(const gfx::Rect& initial_bounds)
      : WindowTreeHostX11(initial_bounds), transformer_helper_(this) {
    transformer_helper_.Init();
    aura::Env::GetInstance()->AddObserver(this);
  }
  
  AshWindowTreeHostX11::~AshWindowTreeHostX11() {
    aura::Env::GetInstance()->RemoveObserver(this);
    UnConfineCursor();
  }
  
  void AshWindowTreeHostX11::ToggleFullScreen() {
    NOTIMPLEMENTED();
  }
  
  bool AshWindowTreeHostX11::ConfineCursorToRootWindow() {
  #if XFIXES_MAJOR >= 5
    DCHECK(!pointer_barriers_.get());
    if (pointer_barriers_)
      return false;
    pointer_barriers_.reset(new XID[4]);
    gfx::Rect barrier(bounds());
    barrier.Inset(transformer_helper_.GetHostInsets());
    // Horizontal, top barriers.
    pointer_barriers_[0] = XFixesCreatePointerBarrier(
        xdisplay(), x_root_window(), barrier.x(), barrier.y(), barrier.right(),
        barrier.y(), BarrierPositiveY, 0, XIAllDevices);
    // Horizontal, bottom barriers.
    pointer_barriers_[1] = XFixesCreatePointerBarrier(
        xdisplay(), x_root_window(), barrier.x(), barrier.bottom(),
        barrier.right(), barrier.bottom(), BarrierNegativeY, 0, XIAllDevices);
    // Vertical, left  barriers.
    pointer_barriers_[2] = XFixesCreatePointerBarrier(
        xdisplay(), x_root_window(), barrier.x(), barrier.y(), barrier.x(),
        barrier.bottom(), BarrierPositiveX, 0, XIAllDevices);
    // Vertical, right barriers.
    pointer_barriers_[3] = XFixesCreatePointerBarrier(
        xdisplay(), x_root_window(), barrier.right(), barrier.y(),
        barrier.right(), barrier.bottom(), BarrierNegativeX, 0, XIAllDevices);
  #endif
    return true;
  }
  
  void AshWindowTreeHostX11::UnConfineCursor() {
  #if XFIXES_MAJOR >= 5
    if (pointer_barriers_) {
      XFixesDestroyPointerBarrier(xdisplay(), pointer_barriers_[0]);
      XFixesDestroyPointerBarrier(xdisplay(), pointer_barriers_[1]);
      XFixesDestroyPointerBarrier(xdisplay(), pointer_barriers_[2]);
      XFixesDestroyPointerBarrier(xdisplay(), pointer_barriers_[3]);
      pointer_barriers_.reset();
    }
  #endif
  }
  
  void AshWindowTreeHostX11::SetRootWindowTransformer(
      std::unique_ptr<RootWindowTransformer> transformer) {
    transformer_helper_.SetRootWindowTransformer(std::move(transformer));
    if (pointer_barriers_) {
      UnConfineCursor();
      ConfineCursorToRootWindow();
    }
  }
  
  gfx::Insets AshWindowTreeHostX11::GetHostInsets() const {
    return transformer_helper_.GetHostInsets();
  }
  
  aura::WindowTreeHost* AshWindowTreeHostX11::AsWindowTreeHost() {
    return this;
  }
  
  void AshWindowTreeHostX11::PrepareForShutdown() {
    // Block the root window from dispatching events because it is weird for a
    // ScreenPositionClient not to be attached to the root window and for
    // ui::EventHandlers to be unable to convert the event's location to screen
    // coordinates.
    window()->SetEventTargeter(
        std::unique_ptr<ui::EventTargeter>(new ui::NullEventTargeter));
  
    if (ui::PlatformEventSource::GetInstance()) {
      // Block X events which are not turned into ui::Events from getting
      // processed. (e.g. ConfigureNotify)
      ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
    }
  }
  
  void AshWindowTreeHostX11::SetBounds(const gfx::Rect& bounds) {
    WindowTreeHostX11::SetBounds(bounds);
    if (pointer_barriers_) {
      UnConfineCursor();
      ConfineCursorToRootWindow();
    }
  }
  
  gfx::Transform AshWindowTreeHostX11::GetRootTransform() const {
    return transformer_helper_.GetTransform();
  }
  
  void AshWindowTreeHostX11::SetRootTransform(const gfx::Transform& transform) {
    transformer_helper_.SetTransform(transform);
  }
  
  gfx::Transform AshWindowTreeHostX11::GetInverseRootTransform() const {
    return transformer_helper_.GetInverseTransform();
  }
  
  void AshWindowTreeHostX11::UpdateRootWindowSize(const gfx::Size& host_size) {
    transformer_helper_.UpdateWindowSize(host_size);
  }
  
  void AshWindowTreeHostX11::OnCursorVisibilityChangedNative(bool show) {
  #if defined(OS_CHROMEOS)
    SetCrOSTapPaused(!show);
  #endif
  }
  
  void AshWindowTreeHostX11::OnWindowInitialized(aura::Window* window) {}
  
  void AshWindowTreeHostX11::OnHostInitialized(aura::WindowTreeHost* host) {
    if (host != AsWindowTreeHost())
      return;
  
  #if defined(OS_CHROMEOS)
    // We have to enable Tap-to-click by default because the cursor is set to
    // visible in Shell::InitRootWindowController.
    SetCrOSTapPaused(false);
  #endif
  }
  
  void AshWindowTreeHostX11::OnConfigureNotify() {
    // Always update barrier and mouse location because |bounds_| might
    // have already been updated in |SetBounds|.
    if (pointer_barriers_) {
      UnConfineCursor();
      ConfineCursorToRootWindow();
    }
  }
  
  bool AshWindowTreeHostX11::CanDispatchEvent(const ui::PlatformEvent& event) {
    if (!WindowTreeHostX11::CanDispatchEvent(event))
      return false;
    XEvent* xev = event;
    ui::EventType type = ui::EventTypeFromNative(xev);
    // For touch event, check if the root window is residing on the according
    // touch display.
    switch (type) {
      case ui::ET_TOUCH_MOVED:
      case ui::ET_TOUCH_PRESSED:
      case ui::ET_TOUCH_CANCELLED:
      case ui::ET_TOUCH_RELEASED: {
  #if defined(OS_CHROMEOS)
        XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev->xcookie.data);
        int64_t touch_display_id =
            ui::DeviceDataManager::GetInstance()->GetTargetDisplayForTouchDevice(
                xiev->deviceid);
        // If we don't have record of display id for this touch device, check
        // that if the event is within the bound of the root window. Note
        // that in multi-monitor case, the event position is in framebuffer
        // space so the bounds check will not work so well.
        if (touch_display_id == display::Display::kInvalidDisplayID) {
          if (base::SysInfo::IsRunningOnChromeOS() &&
              !bounds().Contains(ui::EventLocationFromNative(xev)))
            return false;
        } else {
          display::Screen* screen = display::Screen::GetScreen();
          display::Display display = screen->GetDisplayNearestWindow(window());
          return touch_display_id == display.id();
        }
  #endif  // defined(OS_CHROMEOS)
        return true;
      }
      default:
        return true;
    }
  }
  void AshWindowTreeHostX11::TranslateAndDispatchLocatedEvent(
      ui::LocatedEvent* event) {
    TranslateLocatedEvent(event);
    SendEventToProcessor(event);
  }
  
  ui::EventDispatchDetails AshWindowTreeHostX11::DispatchKeyEventPostIME(
      ui::KeyEvent* event) {
    input_method_handler()->SetPostIME(true);
    ui::EventDispatchDetails details =
        event_processor()->OnEventFromSource(event);
    if (!details.dispatcher_destroyed)
      input_method_handler()->SetPostIME(false);
    return details;
  }
  
  #if defined(OS_CHROMEOS)
  void AshWindowTreeHostX11::SetCrOSTapPaused(bool state) {
    if (!ui::IsXInput2Available())
      return;
    // Temporarily pause tap-to-click when the cursor is hidden.
    Atom prop = atom_cache()->GetAtom("Tap Paused");
    unsigned char value = state;
    const XIDeviceList& dev_list =
        ui::DeviceListCacheX11::GetInstance()->GetXI2DeviceList(xdisplay());
  
    // Only slave pointer devices could possibly have tap-paused property.
    for (int i = 0; i < dev_list.count; i++) {
      if (dev_list[i].use == XISlavePointer) {
        Atom old_type;
        int old_format;
        unsigned long old_nvalues, bytes;
        unsigned char* data;
        int result = XIGetProperty(xdisplay(), dev_list[i].deviceid, prop, 0, 0,
                                   False, AnyPropertyType, &old_type, &old_format,
                                   &old_nvalues, &bytes, &data);
        if (result != Success)
          continue;
        XFree(data);
        XIChangeProperty(xdisplay(), dev_list[i].deviceid, prop, XA_INTEGER, 8,
                         PropModeReplace, &value, 1);
      }
    }
  }
  #endif  // defined(OS_CHROMEOS)
  
  }  // namespace ash
  