// Copyright (c) 2012 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 "ui/aura/root_window_host_x11.h" #include <strings.h> #include <X11/cursorfont.h> #include <X11/extensions/Xfixes.h> #include <X11/extensions/XInput2.h> #include <X11/extensions/Xrandr.h> #include <X11/Xatom.h> #include <X11/Xcursor/Xcursor.h> #include <X11/Xlib.h> #include <algorithm> #include <limits> #include <string> #include "base/basictypes.h" #include "base/command_line.h" #include "base/debug/trace_event.h" #include "base/message_loop/message_loop.h" #include "base/message_loop/message_pump_x11.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/sys_info.h" #include "ui/aura/client/cursor_client.h" #include "ui/aura/client/screen_position_client.h" #include "ui/aura/client/user_action_client.h" #include "ui/aura/env.h" #include "ui/aura/root_window.h" #include "ui/base/cursor/cursor.h" #include "ui/base/ui_base_switches.h" #include "ui/base/view_prop.h" #include "ui/base/x/x11_util.h" #include "ui/compositor/dip_util.h" #include "ui/compositor/layer.h" #include "ui/events/event.h" #include "ui/events/event_utils.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/events/x/device_data_manager.h" #include "ui/events/x/device_list_cache_x.h" #include "ui/events/x/touch_factory_x11.h" #include "ui/gfx/screen.h" using std::max; using std::min; namespace aura { namespace { // Standard Linux mouse buttons for going back and forward. const int kBackMouseButton = 8; const int kForwardMouseButton = 9; const char* kAtomsToCache[] = { "WM_DELETE_WINDOW", "_NET_WM_PING", "_NET_WM_PID", "WM_S0", #if defined(OS_CHROMEOS) "Tap Paused", // Defined in the gestures library. #endif NULL }; ::Window FindEventTarget(const base::NativeEvent& xev) { ::Window target = xev->xany.window; if (xev->type == GenericEvent) target = static_cast<XIDeviceEvent*>(xev->xcookie.data)->event; return target; } #if defined(USE_XI2_MT) bool IsSideBezelsEnabled() { static bool side_bezels_enabled = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( switches::kTouchSideBezels) != "0"; return side_bezels_enabled; } #endif void SelectXInput2EventsForRootWindow(XDisplay* display, ::Window root_window) { CHECK(ui::IsXInput2Available()); unsigned char mask[XIMaskLen(XI_LASTEVENT)] = {}; memset(mask, 0, sizeof(mask)); XISetMask(mask, XI_HierarchyChanged); XISetMask(mask, XI_KeyPress); XISetMask(mask, XI_KeyRelease); XIEventMask evmask; evmask.deviceid = XIAllDevices; evmask.mask_len = sizeof(mask); evmask.mask = mask; XISelectEvents(display, root_window, &evmask, 1); #if defined(OS_CHROMEOS) if (base::SysInfo::IsRunningOnChromeOS()) { // It is necessary to listen for touch events on the root window for proper // touch event calibration on Chrome OS, but this is not currently necessary // on the desktop. This seems to fail in some cases (e.g. when logging // in incognito). So select for non-touch events first, and then select for // touch-events (but keep the other events in the mask, i.e. do not memset // |mask| back to 0). // TODO(sad): Figure out why this happens. http://crbug.com/153976 XISetMask(mask, XI_TouchBegin); XISetMask(mask, XI_TouchUpdate); XISetMask(mask, XI_TouchEnd); XISelectEvents(display, root_window, &evmask, 1); } #endif } bool default_override_redirect = false; } // namespace namespace internal { // Accomplishes 2 tasks concerning touch event calibration: // 1. Being a message-pump observer, // routes all the touch events to the X root window, // where they can be calibrated later. // 2. Has the Calibrate method that does the actual bezel calibration, // when invoked from X root window's event dispatcher. class TouchEventCalibrate : public base::MessagePumpObserver { public: TouchEventCalibrate() : left_(0), right_(0), top_(0), bottom_(0) { base::MessageLoopForUI::current()->AddObserver(this); #if defined(USE_XI2_MT) std::vector<std::string> parts; if (Tokenize(CommandLine::ForCurrentProcess()->GetSwitchValueASCII( switches::kTouchCalibration), ",", &parts) >= 4) { if (!base::StringToInt(parts[0], &left_)) DLOG(ERROR) << "Incorrect left border calibration value passed."; if (!base::StringToInt(parts[1], &right_)) DLOG(ERROR) << "Incorrect right border calibration value passed."; if (!base::StringToInt(parts[2], &top_)) DLOG(ERROR) << "Incorrect top border calibration value passed."; if (!base::StringToInt(parts[3], &bottom_)) DLOG(ERROR) << "Incorrect bottom border calibration value passed."; } #endif // defined(USE_XI2_MT) } virtual ~TouchEventCalibrate() { base::MessageLoopForUI::current()->RemoveObserver(this); } #if defined(USE_XI2_MT) bool IsEventOnSideBezels( const base::NativeEvent& xev, const gfx::Rect& bounds) { if (!left_ && !right_) return false; gfx::Point location = ui::EventLocationFromNative(xev); int x = location.x(); return x < left_ || x > bounds.width() - right_; } #endif // defined(USE_XI2_MT) // Modify the location of the |event|, // expanding it from |bounds| to (|bounds| + bezels). // Required when touchscreen is bigger than screen (i.e. has bezels), // because we receive events in touchscreen coordinates, // which need to be expanded when converting to screen coordinates, // so that location on bezels will be outside of screen area. void Calibrate(ui::TouchEvent* event, const gfx::Rect& bounds) { #if defined(USE_XI2_MT) int x = event->x(); int y = event->y(); if (!left_ && !right_ && !top_ && !bottom_) return; const int resolution_x = bounds.width(); const int resolution_y = bounds.height(); // The "grace area" (10% in this case) is to make it easier for the user to // navigate to the corner. const double kGraceAreaFraction = 0.1; if (left_ || right_) { // Offset the x position to the real x -= left_; // Check if we are in the grace area of the left side. // Note: We might not want to do this when the gesture is locked? if (x < 0 && x > -left_ * kGraceAreaFraction) x = 0; // Check if we are in the grace area of the right side. // Note: We might not want to do this when the gesture is locked? if (x > resolution_x - left_ && x < resolution_x - left_ + right_ * kGraceAreaFraction) x = resolution_x - left_; // Scale the screen area back to the full resolution of the screen. x = (x * resolution_x) / (resolution_x - (right_ + left_)); } if (top_ || bottom_) { // When there is a top bezel we add our border, y -= top_; // Check if we are in the grace area of the top side. // Note: We might not want to do this when the gesture is locked? if (y < 0 && y > -top_ * kGraceAreaFraction) y = 0; // Check if we are in the grace area of the bottom side. // Note: We might not want to do this when the gesture is locked? if (y > resolution_y - top_ && y < resolution_y - top_ + bottom_ * kGraceAreaFraction) y = resolution_y - top_; // Scale the screen area back to the full resolution of the screen. y = (y * resolution_y) / (resolution_y - (bottom_ + top_)); } // Set the modified coordinate back to the event. if (event->root_location() == event->location()) { // Usually those will be equal, // if not, I am not sure what the correct value should be. event->set_root_location(gfx::Point(x, y)); } event->set_location(gfx::Point(x, y)); #endif // defined(USE_XI2_MT) } private: // Overridden from base::MessagePumpObserver: virtual base::EventStatus WillProcessEvent( const base::NativeEvent& event) OVERRIDE { #if defined(USE_XI2_MT) if (event->type == GenericEvent && (event->xgeneric.evtype == XI_TouchBegin || event->xgeneric.evtype == XI_TouchUpdate || event->xgeneric.evtype == XI_TouchEnd)) { XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(event->xcookie.data); xievent->event = xievent->root; xievent->event_x = xievent->root_x; xievent->event_y = xievent->root_y; } #endif // defined(USE_XI2_MT) return base::EVENT_CONTINUE; } virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE { } // The difference in screen's native resolution pixels between // the border of the touchscreen and the border of the screen, // aka bezel sizes. int left_; int right_; int top_; int bottom_; DISALLOW_COPY_AND_ASSIGN(TouchEventCalibrate); }; } // namespace internal //////////////////////////////////////////////////////////////////////////////// // RootWindowHostX11::MouseMoveFilter filters out the move events that // jump back and forth between two points. This happens when sub pixel mouse // move is enabled and mouse move events could be jumping between two neighbor // pixels, e.g. move(0,0), move(1,0), move(0,0), move(1,0) and on and on. // The filtering is done by keeping track of the last two event locations and // provides a Filter method to find out whether a mouse event is in a different // location and should be processed. class RootWindowHostX11::MouseMoveFilter { public: MouseMoveFilter() : insert_index_(0) { for (size_t i = 0; i < kMaxEvents; ++i) { const int int_max = std::numeric_limits<int>::max(); recent_locations_[i] = gfx::Point(int_max, int_max); } } ~MouseMoveFilter() {} // Returns true if |event| is known and should be ignored. bool Filter(const base::NativeEvent& event) { const gfx::Point& location = ui::EventLocationFromNative(event); for (size_t i = 0; i < kMaxEvents; ++i) { if (location == recent_locations_[i]) return true; } recent_locations_[insert_index_] = location; insert_index_ = (insert_index_ + 1) % kMaxEvents; return false; } private: static const size_t kMaxEvents = 2; gfx::Point recent_locations_[kMaxEvents]; size_t insert_index_; DISALLOW_COPY_AND_ASSIGN(MouseMoveFilter); }; //////////////////////////////////////////////////////////////////////////////// // RootWindowHostX11 RootWindowHostX11::RootWindowHostX11(const gfx::Rect& bounds) : xdisplay_(gfx::GetXDisplay()), xwindow_(0), x_root_window_(DefaultRootWindow(xdisplay_)), current_cursor_(ui::kCursorNull), window_mapped_(false), bounds_(bounds), is_internal_display_(false), touch_calibrate_(new internal::TouchEventCalibrate), mouse_move_filter_(new MouseMoveFilter), atom_cache_(xdisplay_, kAtomsToCache), bezel_tracking_ids_(0) { XSetWindowAttributes swa; memset(&swa, 0, sizeof(swa)); swa.background_pixmap = None; swa.override_redirect = default_override_redirect; xwindow_ = XCreateWindow( xdisplay_, x_root_window_, bounds.x(), bounds.y(), bounds.width(), bounds.height(), 0, // border width CopyFromParent, // depth InputOutput, CopyFromParent, // visual CWBackPixmap | CWOverrideRedirect, &swa); base::MessagePumpX11::Current()->AddDispatcherForWindow(this, xwindow_); base::MessagePumpX11::Current()->AddDispatcherForRootWindow(this); long event_mask = ButtonPressMask | ButtonReleaseMask | FocusChangeMask | KeyPressMask | KeyReleaseMask | EnterWindowMask | LeaveWindowMask | ExposureMask | VisibilityChangeMask | StructureNotifyMask | PropertyChangeMask | PointerMotionMask; XSelectInput(xdisplay_, xwindow_, event_mask); XFlush(xdisplay_); if (ui::IsXInput2Available()) { ui::TouchFactory::GetInstance()->SetupXI2ForXWindow(xwindow_); SelectXInput2EventsForRootWindow(xdisplay_, x_root_window_); } // TODO(erg): We currently only request window deletion events. We also // should listen for activation events and anything else that GTK+ listens // for, and do something useful. ::Atom protocols[2]; protocols[0] = atom_cache_.GetAtom("WM_DELETE_WINDOW"); protocols[1] = atom_cache_.GetAtom("_NET_WM_PING"); XSetWMProtocols(xdisplay_, xwindow_, protocols, 2); // We need a WM_CLIENT_MACHINE and WM_LOCALE_NAME value so we integrate with // the desktop environment. XSetWMProperties(xdisplay_, xwindow_, NULL, NULL, NULL, 0, NULL, NULL, NULL); // Likewise, the X server needs to know this window's pid so it knows which // program to kill if the window hangs. // XChangeProperty() expects "pid" to be long. COMPILE_ASSERT(sizeof(long) >= sizeof(pid_t), pid_t_bigger_than_long); long pid = getpid(); XChangeProperty(xdisplay_, xwindow_, atom_cache_.GetAtom("_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, reinterpret_cast<unsigned char*>(&pid), 1); XRRSelectInput(xdisplay_, x_root_window_, RRScreenChangeNotifyMask | RROutputChangeNotifyMask); Env::GetInstance()->AddObserver(this); } RootWindowHostX11::~RootWindowHostX11() { Env::GetInstance()->RemoveObserver(this); base::MessagePumpX11::Current()->RemoveDispatcherForRootWindow(this); base::MessagePumpX11::Current()->RemoveDispatcherForWindow(xwindow_); UnConfineCursor(); XDestroyWindow(xdisplay_, xwindow_); } bool RootWindowHostX11::Dispatch(const base::NativeEvent& event) { XEvent* xev = event; if (FindEventTarget(event) == x_root_window_) return DispatchEventForRootWindow(event); switch (xev->type) { case EnterNotify: { aura::Window* root_window = GetRootWindow()->window(); client::CursorClient* cursor_client = client::GetCursorClient(root_window); if (cursor_client) { const gfx::Display display = gfx::Screen::GetScreenFor(root_window)-> GetDisplayNearestWindow(root_window); cursor_client->SetDisplay(display); } ui::MouseEvent mouse_event(xev); // EnterNotify creates ET_MOUSE_MOVE. Mark as synthesized as this is not // real mouse move event. mouse_event.set_flags(mouse_event.flags() | ui::EF_IS_SYNTHESIZED); TranslateAndDispatchMouseEvent(&mouse_event); break; } case LeaveNotify: { ui::MouseEvent mouse_event(xev); TranslateAndDispatchMouseEvent(&mouse_event); break; } case Expose: { gfx::Rect damage_rect(xev->xexpose.x, xev->xexpose.y, xev->xexpose.width, xev->xexpose.height); delegate_->AsRootWindow()->ScheduleRedrawRect(damage_rect); break; } case KeyPress: { ui::KeyEvent keydown_event(xev, false); delegate_->OnHostKeyEvent(&keydown_event); break; } case KeyRelease: { ui::KeyEvent keyup_event(xev, false); delegate_->OnHostKeyEvent(&keyup_event); break; } case ButtonPress: { if (static_cast<int>(xev->xbutton.button) == kBackMouseButton || static_cast<int>(xev->xbutton.button) == kForwardMouseButton) { client::UserActionClient* gesture_client = client::GetUserActionClient(delegate_->AsRootWindow()->window()); if (gesture_client) { gesture_client->OnUserAction( static_cast<int>(xev->xbutton.button) == kBackMouseButton ? client::UserActionClient::BACK : client::UserActionClient::FORWARD); } break; } } // fallthrough case ButtonRelease: { switch (ui::EventTypeFromNative(xev)) { case ui::ET_MOUSEWHEEL: { ui::MouseWheelEvent mouseev(xev); TranslateAndDispatchMouseEvent(&mouseev); break; } case ui::ET_MOUSE_PRESSED: case ui::ET_MOUSE_RELEASED: { ui::MouseEvent mouseev(xev); TranslateAndDispatchMouseEvent(&mouseev); break; } case ui::ET_UNKNOWN: // No event is created for X11-release events for mouse-wheel buttons. break; default: NOTREACHED(); } break; } case FocusOut: if (xev->xfocus.mode != NotifyGrab) delegate_->OnHostLostWindowCapture(); break; case ConfigureNotify: { DCHECK_EQ(xwindow_, xev->xconfigure.event); DCHECK_EQ(xwindow_, xev->xconfigure.window); // It's possible that the X window may be resized by some other means // than from within aura (e.g. the X window manager can change the // size). Make sure the root window size is maintained properly. gfx::Rect bounds(xev->xconfigure.x, xev->xconfigure.y, xev->xconfigure.width, xev->xconfigure.height); bool size_changed = bounds_.size() != bounds.size(); bool origin_changed = bounds_.origin() != bounds.origin(); bounds_ = bounds; UpdateIsInternalDisplay(); // Always update barrier and mouse location because |bounds_| might // have already been updated in |SetBounds|. if (pointer_barriers_) { UnConfineCursor(); ConfineCursorToRootWindow(); } if (size_changed) delegate_->OnHostResized(bounds.size()); if (origin_changed) delegate_->OnHostMoved(bounds_.origin()); break; } case GenericEvent: DispatchXI2Event(event); break; case ClientMessage: { Atom message_type = static_cast<Atom>(xev->xclient.data.l[0]); if (message_type == atom_cache_.GetAtom("WM_DELETE_WINDOW")) { // We have received a close message from the window manager. delegate_->AsRootWindow()->OnRootWindowHostCloseRequested(); } else if (message_type == atom_cache_.GetAtom("_NET_WM_PING")) { XEvent reply_event = *xev; reply_event.xclient.window = x_root_window_; XSendEvent(xdisplay_, reply_event.xclient.window, False, SubstructureRedirectMask | SubstructureNotifyMask, &reply_event); } break; } case MappingNotify: { switch (xev->xmapping.request) { case MappingModifier: case MappingKeyboard: XRefreshKeyboardMapping(&xev->xmapping); delegate_->AsRootWindow()->OnKeyboardMappingChanged(); break; case MappingPointer: ui::DeviceDataManager::GetInstance()->UpdateButtonMap(); break; default: NOTIMPLEMENTED() << " Unknown request: " << xev->xmapping.request; break; } break; } case MotionNotify: { // Discard all but the most recent motion event that targets the same // window with unchanged state. XEvent last_event; while (XPending(xev->xany.display)) { XEvent next_event; XPeekEvent(xev->xany.display, &next_event); if (next_event.type == MotionNotify && next_event.xmotion.window == xev->xmotion.window && next_event.xmotion.subwindow == xev->xmotion.subwindow && next_event.xmotion.state == xev->xmotion.state) { XNextEvent(xev->xany.display, &last_event); xev = &last_event; } else { break; } } ui::MouseEvent mouseev(xev); TranslateAndDispatchMouseEvent(&mouseev); break; } } return true; } RootWindow* RootWindowHostX11::GetRootWindow() { return delegate_->AsRootWindow(); } gfx::AcceleratedWidget RootWindowHostX11::GetAcceleratedWidget() { return xwindow_; } void RootWindowHostX11::Show() { if (!window_mapped_) { // Before we map the window, set size hints. Otherwise, some window managers // will ignore toplevel XMoveWindow commands. XSizeHints size_hints; size_hints.flags = PPosition | PWinGravity; size_hints.x = bounds_.x(); size_hints.y = bounds_.y(); // Set StaticGravity so that the window position is not affected by the // frame width when running with window manager. size_hints.win_gravity = StaticGravity; XSetWMNormalHints(xdisplay_, xwindow_, &size_hints); XMapWindow(xdisplay_, xwindow_); // We now block until our window is mapped. Some X11 APIs will crash and // burn if passed |xwindow_| before the window is mapped, and XMapWindow is // asynchronous. base::MessagePumpX11::Current()->BlockUntilWindowMapped(xwindow_); window_mapped_ = true; } } void RootWindowHostX11::Hide() { if (window_mapped_) { XWithdrawWindow(xdisplay_, xwindow_, 0); window_mapped_ = false; } } void RootWindowHostX11::ToggleFullScreen() { NOTIMPLEMENTED(); } gfx::Rect RootWindowHostX11::GetBounds() const { return bounds_; } void RootWindowHostX11::SetBounds(const gfx::Rect& bounds) { // Even if the host window's size doesn't change, aura's root window // size, which is in DIP, changes when the scale changes. float current_scale = delegate_->GetDeviceScaleFactor(); float new_scale = gfx::Screen::GetScreenFor( delegate_->AsRootWindow()->window())->GetDisplayNearestWindow( delegate_->AsRootWindow()->window()).device_scale_factor(); bool origin_changed = bounds_.origin() != bounds.origin(); bool size_changed = bounds_.size() != bounds.size(); XWindowChanges changes = {0}; unsigned value_mask = 0; if (size_changed) { changes.width = bounds.width(); changes.height = bounds.height(); value_mask = CWHeight | CWWidth; } if (origin_changed) { changes.x = bounds.x(); changes.y = bounds.y(); value_mask |= CWX | CWY; } if (value_mask) XConfigureWindow(xdisplay_, xwindow_, value_mask, &changes); // Assume that the resize will go through as requested, which should be the // case if we're running without a window manager. If there's a window // manager, it can modify or ignore the request, but (per ICCCM) we'll get a // (possibly synthetic) ConfigureNotify about the actual size and correct // |bounds_| later. bounds_ = bounds; UpdateIsInternalDisplay(); if (origin_changed) delegate_->OnHostMoved(bounds.origin()); if (size_changed || current_scale != new_scale) { delegate_->OnHostResized(bounds.size()); } else { delegate_->AsRootWindow()->window()->SchedulePaintInRect( delegate_->AsRootWindow()->window()->bounds()); } } gfx::Insets RootWindowHostX11::GetInsets() const { return insets_; } void RootWindowHostX11::SetInsets(const gfx::Insets& insets) { insets_ = insets; if (pointer_barriers_) { UnConfineCursor(); ConfineCursorToRootWindow(); } } gfx::Point RootWindowHostX11::GetLocationOnNativeScreen() const { return bounds_.origin(); } void RootWindowHostX11::SetCapture() { // TODO(oshima): Grab x input. } void RootWindowHostX11::ReleaseCapture() { // TODO(oshima): Release x input. } void RootWindowHostX11::SetCursor(gfx::NativeCursor cursor) { if (cursor == current_cursor_) return; current_cursor_ = cursor; SetCursorInternal(cursor); } bool RootWindowHostX11::QueryMouseLocation(gfx::Point* location_return) { client::CursorClient* cursor_client = client::GetCursorClient(GetRootWindow()->window()); if (cursor_client && !cursor_client->IsMouseEventsEnabled()) { *location_return = gfx::Point(0, 0); return false; } ::Window root_return, child_return; int root_x_return, root_y_return, win_x_return, win_y_return; unsigned int mask_return; XQueryPointer(xdisplay_, xwindow_, &root_return, &child_return, &root_x_return, &root_y_return, &win_x_return, &win_y_return, &mask_return); *location_return = gfx::Point(max(0, min(bounds_.width(), win_x_return)), max(0, min(bounds_.height(), win_y_return))); return (win_x_return >= 0 && win_x_return < bounds_.width() && win_y_return >= 0 && win_y_return < bounds_.height()); } bool RootWindowHostX11::ConfineCursorToRootWindow() { #if XFIXES_MAJOR >= 5 DCHECK(!pointer_barriers_.get()); if (pointer_barriers_) return false; pointer_barriers_.reset(new XID[4]); gfx::Rect bounds(bounds_); bounds.Inset(insets_); // Horizontal, top barriers. pointer_barriers_[0] = XFixesCreatePointerBarrier( xdisplay_, x_root_window_, bounds.x(), bounds.y(), bounds.right(), bounds.y(), BarrierPositiveY, 0, XIAllDevices); // Horizontal, bottom barriers. pointer_barriers_[1] = XFixesCreatePointerBarrier( xdisplay_, x_root_window_, bounds.x(), bounds.bottom(), bounds.right(), bounds.bottom(), BarrierNegativeY, 0, XIAllDevices); // Vertical, left barriers. pointer_barriers_[2] = XFixesCreatePointerBarrier( xdisplay_, x_root_window_, bounds.x(), bounds.y(), bounds.x(), bounds.bottom(), BarrierPositiveX, 0, XIAllDevices); // Vertical, right barriers. pointer_barriers_[3] = XFixesCreatePointerBarrier( xdisplay_, x_root_window_, bounds.right(), bounds.y(), bounds.right(), bounds.bottom(), BarrierNegativeX, 0, XIAllDevices); #endif return true; } void RootWindowHostX11::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 RootWindowHostX11::OnCursorVisibilityChanged(bool show) { SetCrOSTapPaused(!show); } void RootWindowHostX11::MoveCursorTo(const gfx::Point& location) { XWarpPointer(xdisplay_, None, x_root_window_, 0, 0, 0, 0, bounds_.x() + location.x(), bounds_.y() + location.y()); } void RootWindowHostX11::PostNativeEvent( const base::NativeEvent& native_event) { DCHECK(xwindow_); DCHECK(xdisplay_); XEvent xevent = *native_event; xevent.xany.display = xdisplay_; xevent.xany.window = xwindow_; switch (xevent.type) { case EnterNotify: case LeaveNotify: case MotionNotify: case KeyPress: case KeyRelease: case ButtonPress: case ButtonRelease: { // The fields used below are in the same place for all of events // above. Using xmotion from XEvent's unions to avoid repeating // the code. xevent.xmotion.root = x_root_window_; xevent.xmotion.time = CurrentTime; gfx::Point point(xevent.xmotion.x, xevent.xmotion.y); delegate_->AsRootWindow()->host()->ConvertPointToNativeScreen(&point); xevent.xmotion.x_root = point.x(); xevent.xmotion.y_root = point.y(); } default: break; } XSendEvent(xdisplay_, xwindow_, False, 0, &xevent); } void RootWindowHostX11::OnDeviceScaleFactorChanged( float device_scale_factor) { } void RootWindowHostX11::PrepareForShutdown() { base::MessagePumpX11::Current()->RemoveDispatcherForWindow(xwindow_); } void RootWindowHostX11::OnWindowInitialized(Window* window) { } void RootWindowHostX11::OnRootWindowInitialized(RootWindow* root_window) { // UpdateIsInternalDisplay relies on: // 1. delegate_ pointing to RootWindow - available after SetDelegate. // 2. RootWindow's kDisplayIdKey property set - available by the time // RootWindow::Init is called. // (set in DisplayManager::CreateRootWindowForDisplay) // Ready when NotifyRootWindowInitialized is called from RootWindow::Init. if (!delegate_ || root_window != GetRootWindow()) return; UpdateIsInternalDisplay(); // We have to enable Tap-to-click by default because the cursor is set to // visible in Shell::InitRootWindowController. SetCrOSTapPaused(false); } ui::EventProcessor* RootWindowHostX11::GetEventProcessor() { return delegate_->GetEventProcessor(); } bool RootWindowHostX11::DispatchEventForRootWindow( const base::NativeEvent& event) { switch (event->type) { case GenericEvent: DispatchXI2Event(event); break; } return true; } void RootWindowHostX11::DispatchXI2Event(const base::NativeEvent& event) { ui::TouchFactory* factory = ui::TouchFactory::GetInstance(); XEvent* xev = event; if (!factory->ShouldProcessXI2Event(xev)) return; TRACE_EVENT1("input", "RootWindowHostX11::DispatchXI2Event", "event_latency_us", (ui::EventTimeForNow() - ui::EventTimeFromNative(event)). InMicroseconds()); ui::EventType type = ui::EventTypeFromNative(xev); XEvent last_event; int num_coalesced = 0; 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) // Bail out early before generating a ui::TouchEvent if this event // is not within the range of this RootWindow. Converting an xevent // to ui::TouchEvent might change the state of the global touch tracking // state, e.g. touch release event can remove the touch id from the // record, and doing this multiple time when there are multiple // RootWindow will cause problem. So only generate the ui::TouchEvent // when we are sure it belongs to this RootWindow. if (base::SysInfo::IsRunningOnChromeOS() && !bounds_.Contains(ui::EventLocationFromNative(xev))) break; #endif // defined(OS_CHROMEOS) ui::TouchEvent touchev(xev); #if defined(USE_XI2_MT) // Ignore touch events with touch press happening on the side bezel. if (!IsSideBezelsEnabled()) { uint32 tracking_id = (1 << touchev.touch_id()); if (type == ui::ET_TOUCH_PRESSED && touch_calibrate_->IsEventOnSideBezels(xev, bounds_)) bezel_tracking_ids_ |= tracking_id; if (bezel_tracking_ids_ & tracking_id) { if (type == ui::ET_TOUCH_CANCELLED || type == ui::ET_TOUCH_RELEASED) bezel_tracking_ids_ = (bezel_tracking_ids_ | tracking_id) ^ tracking_id; return; } } #endif // defined(USE_XI2_MT) #if defined(OS_CHROMEOS) if (base::SysInfo::IsRunningOnChromeOS()) { // X maps the touch-surface to the size of the X root-window. // In multi-monitor setup, Coordinate Transformation Matrix // repositions the touch-surface onto part of X root-window // containing aura root-window corresponding to the touchscreen. // However, if aura root-window has non-zero origin, // we need to relocate the event into aura root-window coordinates. touchev.Relocate(bounds_.origin()); #if defined(USE_XI2_MT) if (is_internal_display_) touch_calibrate_->Calibrate(&touchev, bounds_); #endif // defined(USE_XI2_MT) } #endif // defined(OS_CHROMEOS) delegate_->OnHostTouchEvent(&touchev); break; } case ui::ET_MOUSE_MOVED: case ui::ET_MOUSE_DRAGGED: case ui::ET_MOUSE_PRESSED: case ui::ET_MOUSE_RELEASED: case ui::ET_MOUSE_ENTERED: case ui::ET_MOUSE_EXITED: { if (type == ui::ET_MOUSE_MOVED || type == ui::ET_MOUSE_DRAGGED) { // If this is a motion event, we want to coalesce all pending motion // events that are at the top of the queue. num_coalesced = ui::CoalescePendingMotionEvents(xev, &last_event); if (num_coalesced > 0) xev = &last_event; if (mouse_move_filter_ && mouse_move_filter_->Filter(xev)) break; } else if (type == ui::ET_MOUSE_PRESSED || type == ui::ET_MOUSE_RELEASED) { XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev->xcookie.data); int button = xievent->detail; if (button == kBackMouseButton || button == kForwardMouseButton) { if (type == ui::ET_MOUSE_RELEASED) break; client::UserActionClient* gesture_client = client::GetUserActionClient(delegate_->AsRootWindow()->window()); if (gesture_client) { bool reverse_direction = ui::IsTouchpadEvent(xev) && ui::IsNaturalScrollEnabled(); gesture_client->OnUserAction( (button == kBackMouseButton && !reverse_direction) || (button == kForwardMouseButton && reverse_direction) ? client::UserActionClient::BACK : client::UserActionClient::FORWARD); } break; } } ui::MouseEvent mouseev(xev); TranslateAndDispatchMouseEvent(&mouseev); break; } case ui::ET_MOUSEWHEEL: { ui::MouseWheelEvent mouseev(xev); TranslateAndDispatchMouseEvent(&mouseev); break; } case ui::ET_SCROLL_FLING_START: case ui::ET_SCROLL_FLING_CANCEL: case ui::ET_SCROLL: { ui::ScrollEvent scrollev(xev); delegate_->OnHostScrollEvent(&scrollev); break; } case ui::ET_UMA_DATA: break; case ui::ET_UNKNOWN: break; default: NOTREACHED(); } // If we coalesced an event we need to free its cookie. if (num_coalesced > 0) XFreeEventData(xev->xgeneric.display, &last_event.xcookie); } bool RootWindowHostX11::IsWindowManagerPresent() { // Per ICCCM 2.8, "Manager Selections", window managers should take ownership // of WM_Sn selections (where n is a screen number). return XGetSelectionOwner( xdisplay_, atom_cache_.GetAtom("WM_S0")) != None; } void RootWindowHostX11::SetCursorInternal(gfx::NativeCursor cursor) { XDefineCursor(xdisplay_, xwindow_, cursor.platform()); } void RootWindowHostX11::TranslateAndDispatchMouseEvent( ui::MouseEvent* event) { Window* root_window = GetRootWindow()->window(); client::ScreenPositionClient* screen_position_client = client::GetScreenPositionClient(root_window); gfx::Rect local(bounds_.size()); if (screen_position_client && !local.Contains(event->location())) { gfx::Point location(event->location()); // In order to get the correct point in screen coordinates // during passive grab, we first need to find on which host window // the mouse is on, and find out the screen coordinates on that // host window, then convert it back to this host window's coordinate. screen_position_client->ConvertHostPointToScreen(root_window, &location); screen_position_client->ConvertPointFromScreen(root_window, &location); root_window->GetDispatcher()->ConvertPointToHost(&location); event->set_location(location); event->set_root_location(location); } delegate_->OnHostMouseEvent(event); } void RootWindowHostX11::UpdateIsInternalDisplay() { Window* root_window = GetRootWindow()->window(); gfx::Screen* screen = gfx::Screen::GetScreenFor(root_window); gfx::Display display = screen->GetDisplayNearestWindow(root_window); is_internal_display_ = display.IsInternal(); } void RootWindowHostX11::SetCrOSTapPaused(bool state) { #if defined(OS_CHROMEOS) 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; XIDeviceList dev_list = ui::DeviceListCacheX::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 } // static RootWindowHost* RootWindowHost::Create(const gfx::Rect& bounds) { return new RootWindowHostX11(bounds); } // static gfx::Size RootWindowHost::GetNativeScreenSize() { ::XDisplay* xdisplay = gfx::GetXDisplay(); return gfx::Size(DisplayWidth(xdisplay, 0), DisplayHeight(xdisplay, 0)); } namespace test { void SetUseOverrideRedirectWindowByDefault(bool override_redirect) { default_override_redirect = override_redirect; } } // namespace test } // namespace aura