blob: 6eea168a47f49487b14eb26396a618e30070b466 [file] [log] [blame]
// 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_linux.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 "base/command_line.h"
#include "base/message_loop.h"
#include "base/message_pump_aurax11.h"
#include "base/stl_util.h"
#include "base/stringprintf.h"
#include "ui/aura/client/capture_client.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/events/event.h"
#include "ui/base/keycodes/keyboard_codes.h"
#include "ui/base/touch/touch_factory.h"
#include "ui/base/ui_base_switches.h"
#include "ui/base/view_prop.h"
#include "ui/base/x/valuators.h"
#include "ui/base/x/x11_util.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/screen.h"
#if defined(OS_CHROMEOS)
#include "base/chromeos/chromeos_version.h"
#endif
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;
// These are the same values that are used to calibrate touch events in
// |CalibrateTouchCoordinates| (in ui/base/x/events_x.cc).
// TODO(sad|skuhne): Remove the duplication of values (http://crbug.com/147605)
const int kXRootWindowPaddingLeft = 40;
const int kXRootWindowPaddingRight = 40;
const int kXRootWindowPaddingBottom = 30;
const int kXRootWindowPaddingTop = 0;
const char kRootWindowHostLinuxKey[] = "__AURA_ROOT_WINDOW_HOST_LINUX__";
const char* kAtomsToCache[] = {
"WM_DELETE_WINDOW",
"_NET_WM_PING",
"_NET_WM_PID",
"WM_S0",
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;
}
// The events reported for slave devices can have incorrect information for some
// fields. This utility function is used to check for such inconsistencies.
void CheckXEventForConsistency(XEvent* xevent) {
#if defined(USE_XI2_MT) && !defined(NDEBUG)
static bool expect_master_event = false;
static XIDeviceEvent slave_event;
static gfx::Point slave_location;
static int slave_button;
// Note: If an event comes from a slave pointer device, then it will be
// followed by the same event, but reported from its master pointer device.
// However, if the event comes from a floating slave device (e.g. a
// touchscreen), then it will not be followed by a duplicate event, since the
// floating slave isn't attached to a master.
bool was_expecting_master_event = expect_master_event;
expect_master_event = false;
if (!xevent || xevent->type != GenericEvent)
return;
XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xevent->xcookie.data);
if (xievent->evtype != XI_Motion &&
xievent->evtype != XI_ButtonPress &&
xievent->evtype != XI_ButtonRelease) {
return;
}
if (xievent->sourceid == xievent->deviceid) {
slave_event = *xievent;
slave_location = ui::EventLocationFromNative(xevent);
slave_button = ui::EventButtonFromNative(xevent);
expect_master_event = true;
} else if (was_expecting_master_event) {
CHECK_EQ(slave_location.x(), ui::EventLocationFromNative(xevent).x());
CHECK_EQ(slave_location.y(), ui::EventLocationFromNative(xevent).y());
CHECK_EQ(slave_event.type, xievent->type);
CHECK_EQ(slave_event.evtype, xievent->evtype);
CHECK_EQ(slave_button, ui::EventButtonFromNative(xevent));
CHECK_EQ(slave_event.flags, xievent->flags);
CHECK_EQ(slave_event.buttons.mask_len, xievent->buttons.mask_len);
CHECK_EQ(slave_event.valuators.mask_len, xievent->valuators.mask_len);
CHECK_EQ(slave_event.mods.base, xievent->mods.base);
CHECK_EQ(slave_event.mods.latched, xievent->mods.latched);
CHECK_EQ(slave_event.mods.locked, xievent->mods.locked);
CHECK_EQ(slave_event.mods.effective, xievent->mods.effective);
}
#endif // defined(USE_XI2_MT) && !defined(NDEBUG)
}
// Coalesce all pending motion events (touch or mouse) that are at the top of
// the queue, and return the number eliminated, storing the last one in
// |last_event|.
int CoalescePendingMotionEvents(const XEvent* xev, XEvent* last_event) {
XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev->xcookie.data);
int num_coalesed = 0;
Display* display = xev->xany.display;
int event_type = xev->xgeneric.evtype;
#if defined(USE_XI2_MT)
float tracking_id = -1;
if (event_type == XI_TouchUpdate) {
if (!ui::ValuatorTracker::GetInstance()->ExtractValuator(*xev,
ui::ValuatorTracker::VAL_TRACKING_ID, &tracking_id))
tracking_id = -1;
}
#endif
while (XPending(display)) {
XEvent next_event;
XPeekEvent(display, &next_event);
// If we can't get the cookie, abort the check.
if (!XGetEventData(next_event.xgeneric.display, &next_event.xcookie))
return num_coalesed;
// If this isn't from a valid device, throw the event away, as
// that's what the message pump would do. Device events come in pairs
// with one from the master and one from the slave so there will
// always be at least one pending.
if (!ui::TouchFactory::GetInstance()->ShouldProcessXI2Event(&next_event)) {
CheckXEventForConsistency(&next_event);
XFreeEventData(display, &next_event.xcookie);
XNextEvent(display, &next_event);
continue;
}
if (next_event.type == GenericEvent &&
next_event.xgeneric.evtype == event_type &&
!ui::GetScrollOffsets(&next_event, NULL, NULL)) {
XIDeviceEvent* next_xievent =
static_cast<XIDeviceEvent*>(next_event.xcookie.data);
#if defined(USE_XI2_MT)
float next_tracking_id = -1;
if (event_type == XI_TouchUpdate) {
// If this is a touch motion event (as opposed to mouse motion event),
// then make sure the events are from the same touch-point.
if (!ui::ValuatorTracker::GetInstance()->ExtractValuator(next_event,
ui::ValuatorTracker::VAL_TRACKING_ID, &next_tracking_id))
next_tracking_id = -1;
}
#endif
// Confirm that the motion event is targeted at the same window
// and that no buttons or modifiers have changed.
if (xievent->event == next_xievent->event &&
xievent->child == next_xievent->child &&
#if defined(USE_XI2_MT)
(event_type == XI_Motion || tracking_id == next_tracking_id) &&
#endif
xievent->buttons.mask_len == next_xievent->buttons.mask_len &&
(memcmp(xievent->buttons.mask,
next_xievent->buttons.mask,
xievent->buttons.mask_len) == 0) &&
xievent->mods.base == next_xievent->mods.base &&
xievent->mods.latched == next_xievent->mods.latched &&
xievent->mods.locked == next_xievent->mods.locked &&
xievent->mods.effective == next_xievent->mods.effective) {
XFreeEventData(display, &next_event.xcookie);
// Free the previous cookie.
if (num_coalesed > 0)
XFreeEventData(display, &last_event->xcookie);
// Get the event and its cookie data.
XNextEvent(display, last_event);
XGetEventData(display, &last_event->xcookie);
CheckXEventForConsistency(last_event);
++num_coalesed;
continue;
} else {
// This isn't an event we want so free its cookie data.
XFreeEventData(display, &next_event.xcookie);
}
}
break;
}
return num_coalesed;
}
void SelectEventsForRootWindow() {
Display* display = ui::GetXDisplay();
::Window root_window = ui::GetX11RootWindow();
// Receive resize events for the root-window so |x_root_bounds_| can be
// updated.
XWindowAttributes attr;
XGetWindowAttributes(display, root_window, &attr);
if (!(attr.your_event_mask & StructureNotifyMask)) {
XSelectInput(display, root_window,
StructureNotifyMask | attr.your_event_mask);
}
XIEventMask evmask;
unsigned char mask[XIMaskLen(XI_LASTEVENT)] = {};
memset(mask, 0, sizeof(mask));
XISetMask(mask, XI_HierarchyChanged);
XISetMask(mask, XI_KeyPress);
XISetMask(mask, XI_KeyRelease);
#if defined(USE_XI2_MT)
XISetMask(mask, XI_TouchBegin);
XISetMask(mask, XI_TouchUpdate);
XISetMask(mask, XI_TouchEnd);
#endif
evmask.deviceid = XIAllDevices;
evmask.mask_len = sizeof(mask);
evmask.mask = mask;
XISelectEvents(display, root_window, &evmask, 1);
}
// We emulate Windows' WM_KEYDOWN and WM_CHAR messages. WM_CHAR events are only
// generated for certain keys; see
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646268.aspx. Per
// discussion on http://crbug.com/108480, char events should furthermore not be
// generated for Tab, Escape, and Backspace.
bool ShouldSendCharEventForKeyboardCode(ui::KeyboardCode keycode) {
if ((keycode >= ui::VKEY_0 && keycode <= ui::VKEY_9) ||
(keycode >= ui::VKEY_A && keycode <= ui::VKEY_Z) ||
(keycode >= ui::VKEY_NUMPAD0 && keycode <= ui::VKEY_NUMPAD9)) {
return true;
}
switch (keycode) {
case ui::VKEY_RETURN:
case ui::VKEY_SPACE:
// In addition to the keys listed at MSDN, we include other
// graphic-character and numpad keys.
case ui::VKEY_MULTIPLY:
case ui::VKEY_ADD:
case ui::VKEY_SUBTRACT:
case ui::VKEY_DECIMAL:
case ui::VKEY_DIVIDE:
case ui::VKEY_OEM_1:
case ui::VKEY_OEM_2:
case ui::VKEY_OEM_3:
case ui::VKEY_OEM_4:
case ui::VKEY_OEM_5:
case ui::VKEY_OEM_6:
case ui::VKEY_OEM_7:
case ui::VKEY_OEM_102:
case ui::VKEY_OEM_PLUS:
case ui::VKEY_OEM_COMMA:
case ui::VKEY_OEM_MINUS:
case ui::VKEY_OEM_PERIOD:
return true;
default:
return false;
}
}
} // namespace
namespace internal {
// A very lightweight message-pump observer that routes all the touch events to
// the X root window so that they can be calibrated properly.
class TouchEventCalibrate : public base::MessagePumpObserver {
public:
TouchEventCalibrate() {
MessageLoopForUI::current()->AddObserver(this);
}
virtual ~TouchEventCalibrate() {
MessageLoopForUI::current()->RemoveObserver(this);
}
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
return base::EVENT_CONTINUE;
}
virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE {
}
DISALLOW_COPY_AND_ASSIGN(TouchEventCalibrate);
};
} // namespace internal
RootWindowHostLinux::RootWindowHostLinux(RootWindowHostDelegate* delegate,
const gfx::Rect& bounds)
: delegate_(delegate),
xdisplay_(base::MessagePumpAuraX11::GetDefaultXDisplay()),
xwindow_(0),
x_root_window_(DefaultRootWindow(xdisplay_)),
current_cursor_(ui::kCursorNull),
window_mapped_(false),
cursor_shown_(true),
bounds_(bounds),
focus_when_shown_(false),
pointer_barriers_(NULL),
touch_calibrate_(new internal::TouchEventCalibrate),
atom_cache_(xdisplay_, kAtomsToCache) {
XSetWindowAttributes swa;
memset(&swa, 0, sizeof(swa));
swa.background_pixmap = None;
xwindow_ = XCreateWindow(
xdisplay_, x_root_window_,
bounds.x(), bounds.y(), bounds.width(), bounds.height(),
0, // border width
CopyFromParent, // depth
InputOutput,
CopyFromParent, // visual
CWBackPixmap,
&swa);
base::MessagePumpAuraX11::Current()->AddDispatcherForWindow(this, xwindow_);
base::MessagePumpAuraX11::Current()->AddDispatcherForRootWindow(this);
prop_.reset(new ui::ViewProp(xwindow_, kRootWindowHostLinuxKey, this));
long event_mask = ButtonPressMask | ButtonReleaseMask | FocusChangeMask |
KeyPressMask | KeyReleaseMask |
EnterWindowMask | LeaveWindowMask |
ExposureMask | VisibilityChangeMask |
StructureNotifyMask | PropertyChangeMask |
PointerMotionMask;
XSelectInput(xdisplay_, xwindow_, event_mask);
XFlush(xdisplay_);
if (base::MessagePumpForUI::HasXInput2())
ui::TouchFactory::GetInstance()->SetupXI2ForXWindow(xwindow_);
SelectEventsForRootWindow();
// Get the initial size of the X root window.
XWindowAttributes attrs;
XGetWindowAttributes(xdisplay_, x_root_window_, &attrs);
x_root_bounds_.SetRect(attrs.x, attrs.y, attrs.width, attrs.height);
invisible_cursor_ = ui::CreateInvisibleCursor();
// 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.
pid_t pid = getpid();
XChangeProperty(xdisplay_,
xwindow_,
atom_cache_.GetAtom("_NET_WM_PID"),
XA_CARDINAL,
32,
PropModeReplace,
reinterpret_cast<unsigned char*>(&pid), 1);
// crbug.com/120229 - set the window title so gtalk can find the primary root
// window to broadcast.
// TODO(jhorwich) Remove this once Chrome supports window-based broadcasting.
static int root_window_number = 0;
std::string name = StringPrintf("aura_root_%d", root_window_number++);
XStoreName(xdisplay_, xwindow_, name.c_str());
XRRSelectInput(xdisplay_, x_root_window_,
RRScreenChangeNotifyMask | RROutputChangeNotifyMask);
}
RootWindowHostLinux::~RootWindowHostLinux() {
base::MessagePumpAuraX11::Current()->RemoveDispatcherForRootWindow(this);
base::MessagePumpAuraX11::Current()->RemoveDispatcherForWindow(xwindow_);
UnConfineCursor();
XDestroyWindow(xdisplay_, xwindow_);
XFreeCursor(xdisplay_, invisible_cursor_);
}
bool RootWindowHostLinux::Dispatch(const base::NativeEvent& event) {
XEvent* xev = event;
CheckXEventForConsistency(xev);
if (FindEventTarget(event) == x_root_window_)
return DispatchEventForRootWindow(event);
switch (xev->type) {
case EnterNotify: {
ui::MouseEvent mouseenter_event(xev);
TranslateAndDispatchMouseEvent(&mouseenter_event);
break;
}
case Expose:
delegate_->AsRootWindow()->ScheduleFullDraw();
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());
if (gesture_client) {
gesture_client->OnUserAction(
static_cast<int>(xev->xbutton.button) == kBackMouseButton ?
client::UserActionClient::BACK :
client::UserActionClient::FORWARD);
}
break;
}
} // fallthrough
case ButtonRelease: {
ui::MouseEvent mouseev(xev);
TranslateAndDispatchMouseEvent(&mouseev);
break;
}
case FocusOut:
if (xev->xfocus.mode != NotifyGrab)
delegate_->OnHostLostCapture();
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;
// Always update barrier and mouse location because |bounds_| might
// have already been updated in |SetBounds|.
if (pointer_barriers_.get()) {
UnConfineCursor();
RootWindow* root = delegate_->AsRootWindow();
client::ScreenPositionClient* client =
client::GetScreenPositionClient(root);
if (client) {
gfx::Point p = gfx::Screen::GetCursorScreenPoint();
client->ConvertPointFromScreen(root, &p);
// TODO(oshima): Make sure the pointer is on one of root windows.
if (root->ContainsPoint(p))
root->ConvertPointToNativeScreen(&p);
else
p.SetPoint(0, 0);
XWarpPointer(
xdisplay_, None, x_root_window_, 0, 0, 0, 0, p.x(), p.y());
}
ConfineCursorToRootWindow();
}
if (size_changed)
delegate_->OnHostResized(bounds.size());
if (origin_changed)
delegate_->OnHostMoved(bounds_.origin());
#if defined(OS_CHROMEOS)
// TODO(oshima): Clear the root when the window is moved or
// resized while the extended desktop is disabled.
// crbug.com/152003.
if (base::chromeos::IsRunningOnChromeOS()) {
XGCValues gc_values = {0};
gc_values.foreground = BlackPixel(xdisplay_, DefaultScreen(xdisplay_));
GC gc = XCreateGC(xdisplay_, x_root_window_, GCForeground, &gc_values);
XFillRectangle(xdisplay_, x_root_window_, gc,
x_root_bounds_.x(),
x_root_bounds_.y(),
x_root_bounds_.width(),
x_root_bounds_.height());
XFreeGC(xdisplay_, gc);
}
#endif
break;
}
case GenericEvent:
DispatchXI2Event(event);
break;
case MapNotify: {
// If there's no window manager running, we need to assign the X input
// focus to our host window.
if (!IsWindowManagerPresent() && focus_when_shown_)
XSetInputFocus(xdisplay_, xwindow_, RevertToNone, CurrentTime);
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::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;
}
bool RootWindowHostLinux::DispatchEventForRootWindow(
const base::NativeEvent& event) {
switch (event->type) {
case ConfigureNotify:
DCHECK_EQ(x_root_window_, event->xconfigure.event);
x_root_bounds_.SetRect(event->xconfigure.x, event->xconfigure.y,
event->xconfigure.width, event->xconfigure.height);
break;
case GenericEvent:
DispatchXI2Event(event);
break;
}
return true;
}
void RootWindowHostLinux::DispatchXI2Event(const base::NativeEvent& event) {
ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
XEvent* xev = event;
if (!factory->ShouldProcessXI2Event(xev))
return;
ui::EventType type = ui::EventTypeFromNative(xev);
XEvent last_event;
int num_coalesced = 0;
switch (type) {
case ui::ET_TOUCH_MOVED:
num_coalesced = CoalescePendingMotionEvents(xev, &last_event);
if (num_coalesced > 0)
xev = &last_event;
// fallthrough
case ui::ET_TOUCH_PRESSED:
case ui::ET_TOUCH_RELEASED: {
ui::TouchEvent touchev(xev);
#if defined(OS_CHROMEOS)
// X maps the touch-surface to the size of the X root-window. In
// multi-monitor setup, the X root-window size is a combination of
// both the monitor sizes. So it is necessary to remap the location of
// the event from the X root-window to the X host-window for the aura
// root-window.
if (base::chromeos::IsRunningOnChromeOS()) {
touchev.CalibrateLocation(x_root_bounds_.size(), bounds_.size());
if (!bounds_.Contains(touchev.location())) {
// This might still be in the bezel region.
gfx::Rect expanded(bounds_);
expanded.Inset(-kXRootWindowPaddingLeft,
-kXRootWindowPaddingTop,
-kXRootWindowPaddingRight,
-kXRootWindowPaddingBottom);
if (!expanded.Contains(touchev.location()))
break;
}
}
#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 = CoalescePendingMotionEvents(xev, &last_event);
if (num_coalesced > 0)
xev = &last_event;
} else if (type == ui::ET_MOUSE_PRESSED) {
XIDeviceEvent* xievent =
static_cast<XIDeviceEvent*>(xev->xcookie.data);
int button = xievent->detail;
if (button == kBackMouseButton || button == kForwardMouseButton) {
client::UserActionClient* gesture_client =
client::GetUserActionClient(delegate_->AsRootWindow());
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_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);
}
RootWindow* RootWindowHostLinux::GetRootWindow() {
return delegate_->AsRootWindow();
}
gfx::AcceleratedWidget RootWindowHostLinux::GetAcceleratedWidget() {
return xwindow_;
}
void RootWindowHostLinux::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::MessagePumpAuraX11::Current()->BlockUntilWindowMapped(xwindow_);
window_mapped_ = true;
}
}
void RootWindowHostLinux::Hide() {
if (window_mapped_) {
XWithdrawWindow(xdisplay_, xwindow_, 0);
window_mapped_ = false;
}
}
void RootWindowHostLinux::ToggleFullScreen() {
NOTIMPLEMENTED();
}
gfx::Rect RootWindowHostLinux::GetBounds() const {
return bounds_;
}
void RootWindowHostLinux::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::GetDisplayNearestWindow(
delegate_->AsRootWindow()).device_scale_factor();
bool size_changed = bounds_.size() != bounds.size() ||
current_scale != new_scale;
XWindowChanges changes = {0};
unsigned value_mask = 0;
if (bounds.size() != bounds_.size()) {
changes.width = bounds.width();
changes.height = bounds.height();
value_mask = CWHeight | CWWidth;
}
if (bounds.origin() != bounds_.origin()) {
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;
if (size_changed) {
delegate_->OnHostResized(bounds.size());
} else {
delegate_->AsRootWindow()->SchedulePaintInRect(
delegate_->AsRootWindow()->bounds());
}
}
gfx::Point RootWindowHostLinux::GetLocationOnNativeScreen() const {
return bounds_.origin();
}
void RootWindowHostLinux::SetCapture() {
// TODO(oshima): Grab x input.
}
void RootWindowHostLinux::ReleaseCapture() {
// TODO(oshima): Release x input.
}
void RootWindowHostLinux::SetCursor(gfx::NativeCursor cursor) {
if (cursor == current_cursor_)
return;
current_cursor_ = cursor;
if (cursor_shown_)
SetCursorInternal(cursor);
}
void RootWindowHostLinux::ShowCursor(bool show) {
if (show == cursor_shown_)
return;
cursor_shown_ = show;
SetCursorInternal(show ? current_cursor_ : invisible_cursor_);
}
bool RootWindowHostLinux::QueryMouseLocation(gfx::Point* location_return) {
::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 RootWindowHostLinux::ConfineCursorToRootWindow() {
#if XFIXES_MAJOR >= 5
DCHECK(!pointer_barriers_.get());
if (pointer_barriers_.get())
return false;
// TODO(oshima): There is a know issue where the pointer barrier
// leaks mouse pointer under certain conditions. crbug.com/133694.
pointer_barriers_.reset(new XID[4]);
// 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 RootWindowHostLinux::UnConfineCursor() {
#if XFIXES_MAJOR >= 5
if (pointer_barriers_.get()) {
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 RootWindowHostLinux::MoveCursorTo(const gfx::Point& location) {
XWarpPointer(xdisplay_, None, x_root_window_, 0, 0, 0, 0,
bounds_.x() + location.x(),
bounds_.y() + location.y());
}
void RootWindowHostLinux::SetFocusWhenShown(bool focus_when_shown) {
static const char* k_NET_WM_USER_TIME = "_NET_WM_USER_TIME";
focus_when_shown_ = focus_when_shown;
if (IsWindowManagerPresent() && !focus_when_shown_) {
ui::SetIntProperty(xwindow_,
k_NET_WM_USER_TIME,
k_NET_WM_USER_TIME,
0);
}
}
bool RootWindowHostLinux::GrabSnapshot(
const gfx::Rect& snapshot_bounds,
std::vector<unsigned char>* png_representation) {
ui::XScopedImage image(XGetImage(
xdisplay_, xwindow_,
snapshot_bounds.x(), snapshot_bounds.y(),
snapshot_bounds.width(), snapshot_bounds.height(),
AllPlanes, ZPixmap));
if (!image.get()) {
LOG(ERROR) << "XGetImage failed";
return false;
}
gfx::PNGCodec::ColorFormat color_format;
if (image->bits_per_pixel == 32) {
color_format = (image->byte_order == LSBFirst) ?
gfx::PNGCodec::FORMAT_BGRA : gfx::PNGCodec::FORMAT_RGBA;
} else if (image->bits_per_pixel == 24) {
// PNGCodec accepts FORMAT_RGB for 3 bytes per pixel:
color_format = gfx::PNGCodec::FORMAT_RGB;
if (image->byte_order == LSBFirst) {
LOG(WARNING) << "Converting BGR->RGB will damage the performance...";
int image_size =
image->width * image->height * image->bits_per_pixel / 8;
for (int i = 0; i < image_size; i += 3) {
char tmp = image->data[i];
image->data[i] = image->data[i+2];
image->data[i+2] = tmp;
}
}
} else {
LOG(ERROR) << "bits_per_pixel is too small";
return false;
}
unsigned char* data = reinterpret_cast<unsigned char*>(image->data);
gfx::PNGCodec::Encode(data, color_format, snapshot_bounds.size(),
image->width * image->bits_per_pixel / 8,
true, std::vector<gfx::PNGCodec::Comment>(),
png_representation);
return true;
}
void RootWindowHostLinux::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()->ConvertPointToNativeScreen(&point);
xevent.xmotion.x_root = point.x();
xevent.xmotion.y_root = point.y();
}
default:
break;
}
XSendEvent(xdisplay_, xwindow_, False, 0, &xevent);
}
void RootWindowHostLinux::OnDeviceScaleFactorChanged(
float device_scale_factor) {
}
void RootWindowHostLinux::PrepareForShutdown() {
base::MessagePumpAuraX11::Current()->RemoveDispatcherForWindow(xwindow_);
}
bool RootWindowHostLinux::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 RootWindowHostLinux::SetCursorInternal(gfx::NativeCursor cursor) {
XDefineCursor(xdisplay_, xwindow_, cursor.platform());
}
void RootWindowHostLinux::TranslateAndDispatchMouseEvent(
ui::MouseEvent* event) {
RootWindow* root = GetRootWindow();
client::ScreenPositionClient* screen_position_client =
GetScreenPositionClient(root);
if (screen_position_client && !bounds_.Contains(event->location())) {
gfx::Point location(event->location());
screen_position_client->ConvertNativePointToScreen(root, &location);
screen_position_client->ConvertPointFromScreen(root, &location);
event->set_location(location);
event->set_root_location(location);
}
delegate_->OnHostMouseEvent(event);
}
// static
RootWindowHost* RootWindowHost::Create(RootWindowHostDelegate* delegate,
const gfx::Rect& bounds) {
return new RootWindowHostLinux(delegate, bounds);
}
// static
RootWindowHost* RootWindowHost::GetForAcceleratedWidget(
gfx::AcceleratedWidget accelerated_widget) {
return reinterpret_cast<RootWindowHost*>(
ui::ViewProp::GetValue(accelerated_widget, kRootWindowHostLinuxKey));
}
// static
gfx::Size RootWindowHost::GetNativeScreenSize() {
::Display* xdisplay = base::MessagePumpAuraX11::GetDefaultXDisplay();
return gfx::Size(DisplayWidth(xdisplay, 0), DisplayHeight(xdisplay, 0));
}
} // namespace aura