// 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 #include #include #include #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/events/x/touch_factory_x11.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(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() { Destroy(); } void X11Window::Destroy() { if (xwindow_ == None) return; // Stop processing events. PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this); XDestroyWindow(xdisplay_, xwindow_); xwindow_ = None; } 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. 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(&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); 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) {} 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(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