| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/platform_window/x11/x11_window.h" |
| |
| #include <X11/extensions/XInput2.h> |
| #include <X11/Xatom.h> |
| #include <X11/Xlib.h> |
| #include <X11/Xutil.h> |
| |
| #include "ui/events/devices/x11/touch_factory_x11.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/events/platform/platform_event_dispatcher.h" |
| #include "ui/events/platform/platform_event_source.h" |
| #include "ui/events/platform/x11/x11_event_source.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/x/x11_atom_cache.h" |
| #include "ui/gfx/x/x11_types.h" |
| #include "ui/platform_window/platform_window_delegate.h" |
| |
| namespace ui { |
| |
| namespace { |
| |
| const char* kAtomsToCache[] = { |
| "WM_DELETE_WINDOW", |
| "_NET_WM_PING", |
| "_NET_WM_PID", |
| NULL |
| }; |
| |
| XID FindXEventTarget(XEvent* xevent) { |
| XID target = xevent->xany.window; |
| if (xevent->type == GenericEvent) |
| target = static_cast<XIDeviceEvent*>(xevent->xcookie.data)->event; |
| return target; |
| } |
| |
| } // namespace |
| |
| X11Window::X11Window(PlatformWindowDelegate* delegate) |
| : delegate_(delegate), |
| xdisplay_(gfx::GetXDisplay()), |
| xwindow_(None), |
| xroot_window_(DefaultRootWindow(xdisplay_)), |
| atom_cache_(xdisplay_, kAtomsToCache), |
| window_mapped_(false) { |
| CHECK(delegate_); |
| TouchFactory::SetTouchDeviceListFromCommandLine(); |
| } |
| |
| X11Window::~X11Window() { |
| } |
| |
| void X11Window::Destroy() { |
| if (xwindow_ == None) |
| return; |
| |
| // Stop processing events. |
| PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this); |
| XID xwindow = xwindow_; |
| XDisplay* xdisplay = xdisplay_; |
| xwindow_ = None; |
| delegate_->OnClosed(); |
| // |this| might be deleted because of the above call. |
| |
| XDestroyWindow(xdisplay, xwindow); |
| } |
| |
| void X11Window::ProcessXInput2Event(XEvent* xev) { |
| if (!TouchFactory::GetInstance()->ShouldProcessXI2Event(xev)) |
| return; |
| EventType event_type = EventTypeFromNative(xev); |
| switch (event_type) { |
| case ET_KEY_PRESSED: |
| case ET_KEY_RELEASED: { |
| KeyEvent key_event(xev); |
| delegate_->DispatchEvent(&key_event); |
| break; |
| } |
| case ET_MOUSE_PRESSED: |
| case ET_MOUSE_MOVED: |
| case ET_MOUSE_DRAGGED: |
| case ET_MOUSE_RELEASED: { |
| MouseEvent mouse_event(xev); |
| delegate_->DispatchEvent(&mouse_event); |
| break; |
| } |
| case ET_MOUSEWHEEL: { |
| MouseWheelEvent wheel_event(xev); |
| delegate_->DispatchEvent(&wheel_event); |
| break; |
| } |
| case ET_SCROLL_FLING_START: |
| case ET_SCROLL_FLING_CANCEL: |
| case ET_SCROLL: { |
| ScrollEvent scroll_event(xev); |
| delegate_->DispatchEvent(&scroll_event); |
| break; |
| } |
| case ET_TOUCH_MOVED: |
| case ET_TOUCH_PRESSED: |
| case ET_TOUCH_CANCELLED: |
| case ET_TOUCH_RELEASED: { |
| TouchEvent touch_event(xev); |
| delegate_->DispatchEvent(&touch_event); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| void X11Window::Show() { |
| if (window_mapped_) |
| return; |
| |
| CHECK(PlatformEventSource::GetInstance()); |
| PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this); |
| |
| XSetWindowAttributes swa; |
| memset(&swa, 0, sizeof(swa)); |
| swa.background_pixmap = None; |
| swa.override_redirect = False; |
| xwindow_ = XCreateWindow(xdisplay_, |
| xroot_window_, |
| requested_bounds_.x(), |
| requested_bounds_.y(), |
| requested_bounds_.width(), |
| requested_bounds_.height(), |
| 0, // border width |
| CopyFromParent, // depth |
| InputOutput, |
| CopyFromParent, // visual |
| CWBackPixmap | CWOverrideRedirect, |
| &swa); |
| |
| long event_mask = ButtonPressMask | ButtonReleaseMask | FocusChangeMask | |
| KeyPressMask | KeyReleaseMask | EnterWindowMask | |
| LeaveWindowMask | ExposureMask | VisibilityChangeMask | |
| StructureNotifyMask | PropertyChangeMask | |
| PointerMotionMask; |
| XSelectInput(xdisplay_, xwindow_, event_mask); |
| |
| unsigned char mask[XIMaskLen(XI_LASTEVENT)]; |
| memset(mask, 0, sizeof(mask)); |
| |
| XISetMask(mask, XI_TouchBegin); |
| XISetMask(mask, XI_TouchUpdate); |
| XISetMask(mask, XI_TouchEnd); |
| XISetMask(mask, XI_ButtonPress); |
| XISetMask(mask, XI_ButtonRelease); |
| XISetMask(mask, XI_Motion); |
| XISetMask(mask, XI_KeyPress); |
| XISetMask(mask, XI_KeyRelease); |
| |
| XIEventMask evmask; |
| evmask.deviceid = XIAllDevices; |
| evmask.mask_len = sizeof(mask); |
| evmask.mask = mask; |
| XISelectEvents(xdisplay_, xwindow_, &evmask, 1); |
| XFlush(xdisplay_); |
| |
| ::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. |
| static_assert(sizeof(long) >= sizeof(pid_t), |
| "pid_t should not be larger than long"); |
| long pid = getpid(); |
| XChangeProperty(xdisplay_, |
| xwindow_, |
| atom_cache_.GetAtom("_NET_WM_PID"), |
| XA_CARDINAL, |
| 32, |
| PropModeReplace, |
| reinterpret_cast<unsigned char*>(&pid), |
| 1); |
| // 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 = requested_bounds_.x(); |
| size_hints.y = requested_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); |
| |
| // TODO(sky): provide real scale factor. |
| delegate_->OnAcceleratedWidgetAvailable(xwindow_); |
| |
| 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. |
| if (X11EventSource::GetInstance()) |
| X11EventSource::GetInstance()->BlockUntilWindowMapped(xwindow_); |
| window_mapped_ = true; |
| } |
| |
| void X11Window::Hide() { |
| if (!window_mapped_) |
| return; |
| XWithdrawWindow(xdisplay_, xwindow_, 0); |
| window_mapped_ = false; |
| } |
| |
| void X11Window::Close() { |
| Destroy(); |
| } |
| |
| void X11Window::SetBounds(const gfx::Rect& bounds) { |
| requested_bounds_ = bounds; |
| if (!window_mapped_) |
| return; |
| XWindowChanges changes = {0}; |
| unsigned value_mask = CWX | CWY | CWWidth | CWHeight; |
| changes.x = bounds.x(); |
| changes.y = bounds.y(); |
| changes.width = bounds.width(); |
| changes.height = bounds.height(); |
| XConfigureWindow(xdisplay_, xwindow_, value_mask, &changes); |
| } |
| |
| gfx::Rect X11Window::GetBounds() { |
| return confirmed_bounds_; |
| } |
| |
| void X11Window::SetCapture() {} |
| |
| void X11Window::ReleaseCapture() {} |
| |
| void X11Window::ToggleFullscreen() {} |
| |
| void X11Window::Maximize() {} |
| |
| void X11Window::Minimize() {} |
| |
| void X11Window::Restore() {} |
| |
| void X11Window::SetCursor(PlatformCursor cursor) {} |
| |
| void X11Window::MoveCursorTo(const gfx::Point& location) {} |
| |
| void X11Window::ConfineCursorToBounds(const gfx::Rect& bounds) { |
| } |
| |
| bool X11Window::CanDispatchEvent(const PlatformEvent& event) { |
| return FindXEventTarget(event) == xwindow_; |
| } |
| |
| uint32_t X11Window::DispatchEvent(const PlatformEvent& event) { |
| XEvent* xev = event; |
| switch (xev->type) { |
| case EnterNotify: { |
| // EnterNotify creates ET_MOUSE_MOVED. Mark as synthesized as this is |
| // not real mouse move event. |
| MouseEvent mouse_event(xev); |
| CHECK_EQ(ET_MOUSE_MOVED, mouse_event.type()); |
| mouse_event.set_flags(mouse_event.flags() | EF_IS_SYNTHESIZED); |
| delegate_->DispatchEvent(&mouse_event); |
| break; |
| } |
| case LeaveNotify: { |
| MouseEvent mouse_event(xev); |
| delegate_->DispatchEvent(&mouse_event); |
| break; |
| } |
| |
| case Expose: { |
| gfx::Rect damage_rect(xev->xexpose.x, |
| xev->xexpose.y, |
| xev->xexpose.width, |
| xev->xexpose.height); |
| delegate_->OnDamageRect(damage_rect); |
| break; |
| } |
| |
| case KeyPress: |
| case KeyRelease: { |
| KeyEvent key_event(xev); |
| delegate_->DispatchEvent(&key_event); |
| break; |
| } |
| |
| case ButtonPress: |
| case ButtonRelease: { |
| switch (EventTypeFromNative(xev)) { |
| case ET_MOUSEWHEEL: { |
| MouseWheelEvent mouseev(xev); |
| delegate_->DispatchEvent(&mouseev); |
| break; |
| } |
| case ET_MOUSE_PRESSED: |
| case ET_MOUSE_RELEASED: { |
| MouseEvent mouseev(xev); |
| delegate_->DispatchEvent(&mouseev); |
| break; |
| } |
| case 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_->OnLostCapture(); |
| break; |
| |
| case ConfigureNotify: { |
| DCHECK_EQ(xwindow_, xev->xconfigure.event); |
| DCHECK_EQ(xwindow_, xev->xconfigure.window); |
| gfx::Rect bounds(xev->xconfigure.x, |
| xev->xconfigure.y, |
| xev->xconfigure.width, |
| xev->xconfigure.height); |
| if (confirmed_bounds_ != bounds) { |
| confirmed_bounds_ = bounds; |
| delegate_->OnBoundsChanged(confirmed_bounds_); |
| } |
| break; |
| } |
| |
| case ClientMessage: { |
| Atom message = static_cast<Atom>(xev->xclient.data.l[0]); |
| if (message == atom_cache_.GetAtom("WM_DELETE_WINDOW")) { |
| delegate_->OnCloseRequest(); |
| } else if (message == atom_cache_.GetAtom("_NET_WM_PING")) { |
| XEvent reply_event = *xev; |
| reply_event.xclient.window = xroot_window_; |
| |
| XSendEvent(xdisplay_, |
| reply_event.xclient.window, |
| False, |
| SubstructureRedirectMask | SubstructureNotifyMask, |
| &reply_event); |
| XFlush(xdisplay_); |
| } |
| break; |
| } |
| |
| case GenericEvent: { |
| ProcessXInput2Event(xev); |
| break; |
| } |
| } |
| return POST_DISPATCH_STOP_PROPAGATION; |
| } |
| |
| } // namespace ui |