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