| // Copyright 2016 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_base.h" |
| |
| #include <string> |
| |
| #include "base/strings/utf_string_conversions.h" |
| #include "ui/base/platform_window_defaults.h" |
| #include "ui/base/x/x11_util.h" |
| #include "ui/base/x/x11_window_event_manager.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/platform_window/platform_window_delegate.h" |
| |
| namespace ui { |
| |
| namespace { |
| |
| XID FindXEventTarget(const XEvent& xev) { |
| XID target = xev.xany.window; |
| if (xev.type == GenericEvent) |
| target = static_cast<XIDeviceEvent*>(xev.xcookie.data)->event; |
| return target; |
| } |
| |
| } // namespace |
| |
| X11WindowBase::X11WindowBase(PlatformWindowDelegate* delegate, |
| const gfx::Rect& bounds) |
| : delegate_(delegate), |
| xdisplay_(gfx::GetXDisplay()), |
| xroot_window_(DefaultRootWindow(xdisplay_)), |
| bounds_(bounds), |
| state_(ui::PlatformWindowState::PLATFORM_WINDOW_STATE_UNKNOWN) { |
| DCHECK(delegate_); |
| Create(); |
| pointer_barriers_.fill(x11::None); |
| } |
| |
| X11WindowBase::~X11WindowBase() { |
| UnConfineCursor(); |
| Destroy(); |
| } |
| |
| void X11WindowBase::Destroy() { |
| if (xwindow_ == x11::None) |
| return; |
| |
| // Stop processing events. |
| XID xwindow = xwindow_; |
| XDisplay* xdisplay = xdisplay_; |
| xwindow_ = x11::None; |
| delegate_->OnClosed(); |
| // |this| might be deleted because of the above call. |
| |
| XDestroyWindow(xdisplay, xwindow); |
| } |
| |
| void X11WindowBase::Create() { |
| DCHECK(!bounds_.size().IsEmpty()); |
| |
| XSetWindowAttributes swa; |
| memset(&swa, 0, sizeof(swa)); |
| swa.background_pixmap = x11::None; |
| swa.bit_gravity = NorthWestGravity; |
| swa.override_redirect = UseTestConfigForPlatformWindows(); |
| xwindow_ = |
| XCreateWindow(xdisplay_, xroot_window_, bounds_.x(), bounds_.y(), |
| bounds_.width(), bounds_.height(), |
| 0, // border width |
| CopyFromParent, // depth |
| InputOutput, |
| CopyFromParent, // visual |
| CWBackPixmap | CWBitGravity | CWOverrideRedirect, &swa); |
| |
| // Setup XInput event mask. |
| long event_mask = ButtonPressMask | ButtonReleaseMask | FocusChangeMask | |
| KeyPressMask | KeyReleaseMask | EnterWindowMask | |
| LeaveWindowMask | ExposureMask | VisibilityChangeMask | |
| StructureNotifyMask | PropertyChangeMask | |
| PointerMotionMask; |
| xwindow_events_.reset(new ui::XScopedEventSelector(xwindow_, event_mask)); |
| |
| // Setup XInput2 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); |
| XISetMask(mask, XI_HierarchyChanged); |
| |
| 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] = gfx::GetAtom("WM_DELETE_WINDOW"); |
| protocols[1] = gfx::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_, gfx::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 = 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); |
| |
| delegate_->OnAcceleratedWidgetAvailable(xwindow_); |
| } |
| |
| void X11WindowBase::Show() { |
| if (window_mapped_) |
| return; |
| |
| XMapWindow(xdisplay_, xwindow_); |
| // TODO(thomasanderson): Find out why this flush is necessary. |
| XFlush(xdisplay_); |
| window_mapped_ = true; |
| } |
| |
| void X11WindowBase::Hide() { |
| if (!window_mapped_) |
| return; |
| |
| XWithdrawWindow(xdisplay_, xwindow_, 0); |
| window_mapped_ = false; |
| } |
| |
| void X11WindowBase::Close() { |
| Destroy(); |
| } |
| |
| void X11WindowBase::SetBounds(const gfx::Rect& bounds) { |
| DCHECK(!bounds.size().IsEmpty()); |
| |
| if (xwindow_ != x11::None) { |
| 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; |
| |
| // Even if the pixel bounds didn't change this call to the delegate should |
| // still happen. The device scale factor may have changed which effectively |
| // changes the bounds. |
| delegate_->OnBoundsChanged(bounds_); |
| } |
| |
| gfx::Rect X11WindowBase::GetBounds() { |
| return bounds_; |
| } |
| |
| void X11WindowBase::SetTitle(const base::string16& title) { |
| if (window_title_ == title) |
| return; |
| window_title_ = title; |
| std::string utf8str = base::UTF16ToUTF8(title); |
| XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_NAME"), |
| gfx::GetAtom("UTF8_STRING"), 8, PropModeReplace, |
| reinterpret_cast<const unsigned char*>(utf8str.c_str()), |
| utf8str.size()); |
| XTextProperty xtp; |
| char* c_utf8_str = const_cast<char*>(utf8str.c_str()); |
| if (Xutf8TextListToTextProperty(xdisplay_, &c_utf8_str, 1, XUTF8StringStyle, |
| &xtp) == x11::Success) { |
| XSetWMName(xdisplay_, xwindow_, &xtp); |
| XFree(xtp.value); |
| } |
| } |
| |
| void X11WindowBase::SetCapture() {} |
| |
| void X11WindowBase::ReleaseCapture() {} |
| |
| bool X11WindowBase::HasCapture() const { |
| return false; |
| } |
| |
| void X11WindowBase::ToggleFullscreen() { |
| ui::SetWMSpecState(xwindow_, !IsFullscreen(), |
| gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"), x11::None); |
| } |
| |
| void X11WindowBase::Maximize() { |
| if (IsFullscreen()) |
| ToggleFullscreen(); |
| |
| ui::SetWMSpecState(xwindow_, true, |
| gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"), |
| gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")); |
| } |
| |
| void X11WindowBase::Minimize() { |
| XIconifyWindow(xdisplay_, xwindow_, 0); |
| } |
| |
| void X11WindowBase::Restore() { |
| if (IsFullscreen()) |
| ToggleFullscreen(); |
| |
| if (IsMaximized()) { |
| ui::SetWMSpecState(xwindow_, false, |
| gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"), |
| gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")); |
| } |
| } |
| |
| PlatformWindowState X11WindowBase::GetPlatformWindowState() const { |
| return state_; |
| } |
| |
| void X11WindowBase::MoveCursorTo(const gfx::Point& location) { |
| XWarpPointer(xdisplay_, x11::None, xroot_window_, 0, 0, 0, 0, |
| bounds_.x() + location.x(), bounds_.y() + location.y()); |
| } |
| |
| void X11WindowBase::ConfineCursorToBounds(const gfx::Rect& bounds) { |
| UnConfineCursor(); |
| |
| if (bounds.IsEmpty()) |
| return; |
| |
| gfx::Rect barrier = bounds + bounds_.OffsetFromOrigin(); |
| |
| // Top horizontal barrier. |
| pointer_barriers_[0] = XFixesCreatePointerBarrier( |
| xdisplay_, xroot_window_, barrier.x(), barrier.y(), barrier.right(), |
| barrier.y(), BarrierPositiveY, 0, XIAllDevices); |
| // Bottom horizontal barrier. |
| pointer_barriers_[1] = XFixesCreatePointerBarrier( |
| xdisplay_, xroot_window_, barrier.x(), barrier.bottom(), barrier.right(), |
| barrier.bottom(), BarrierNegativeY, 0, XIAllDevices); |
| // Left vertical barrier. |
| pointer_barriers_[2] = XFixesCreatePointerBarrier( |
| xdisplay_, xroot_window_, barrier.x(), barrier.y(), barrier.x(), |
| barrier.bottom(), BarrierPositiveX, 0, XIAllDevices); |
| // Right vertical barrier. |
| pointer_barriers_[3] = XFixesCreatePointerBarrier( |
| xdisplay_, xroot_window_, barrier.right(), barrier.y(), barrier.right(), |
| barrier.bottom(), BarrierNegativeX, 0, XIAllDevices); |
| |
| has_pointer_barriers_ = true; |
| } |
| |
| PlatformImeController* X11WindowBase::GetPlatformImeController() { |
| return nullptr; |
| } |
| |
| void X11WindowBase::SetRestoredBoundsInPixels(const gfx::Rect& bounds) { |
| // TODO: https://crbug.com/848131 |
| NOTIMPLEMENTED(); |
| } |
| |
| gfx::Rect X11WindowBase::GetRestoredBoundsInPixels() const { |
| // TODO: https://crbug.com/848131 |
| NOTIMPLEMENTED(); |
| return gfx::Rect(); |
| } |
| |
| void X11WindowBase::UnConfineCursor() { |
| if (!has_pointer_barriers_) |
| return; |
| |
| for (XID pointer_barrier : pointer_barriers_) |
| XFixesDestroyPointerBarrier(xdisplay_, pointer_barrier); |
| pointer_barriers_.fill(x11::None); |
| |
| has_pointer_barriers_ = false; |
| } |
| |
| bool X11WindowBase::IsEventForXWindow(const XEvent& xev) const { |
| return xwindow_ != x11::None && FindXEventTarget(xev) == xwindow_; |
| } |
| |
| void X11WindowBase::ProcessXWindowEvent(XEvent* xev) { |
| switch (xev->type) { |
| case Expose: { |
| gfx::Rect damage_rect(xev->xexpose.x, xev->xexpose.y, xev->xexpose.width, |
| xev->xexpose.height); |
| delegate_->OnDamageRect(damage_rect); |
| break; |
| } |
| |
| case x11::FocusOut: |
| if (xev->xfocus.mode != NotifyGrab) |
| delegate_->OnLostCapture(); |
| 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. |
| int translated_x_in_pixels = xev->xconfigure.x; |
| int translated_y_in_pixels = xev->xconfigure.y; |
| if (!xev->xconfigure.send_event && !xev->xconfigure.override_redirect) { |
| Window unused; |
| XTranslateCoordinates(xdisplay_, xwindow_, xroot_window_, 0, 0, |
| &translated_x_in_pixels, &translated_y_in_pixels, |
| &unused); |
| } |
| gfx::Rect bounds(translated_x_in_pixels, translated_y_in_pixels, |
| xev->xconfigure.width, xev->xconfigure.height); |
| if (bounds_ != bounds) { |
| bounds_ = bounds; |
| delegate_->OnBoundsChanged(bounds_); |
| } |
| break; |
| } |
| |
| case ClientMessage: { |
| Atom message = static_cast<Atom>(xev->xclient.data.l[0]); |
| if (message == gfx::GetAtom("WM_DELETE_WINDOW")) { |
| delegate_->OnCloseRequest(); |
| } else if (message == gfx::GetAtom("_NET_WM_PING")) { |
| XEvent reply_event = *xev; |
| reply_event.xclient.window = xroot_window_; |
| |
| XSendEvent(xdisplay_, reply_event.xclient.window, x11::False, |
| SubstructureRedirectMask | SubstructureNotifyMask, |
| &reply_event); |
| XFlush(xdisplay_); |
| } |
| break; |
| } |
| case PropertyNotify: { |
| ::Atom changed_atom = xev->xproperty.atom; |
| if (changed_atom == gfx::GetAtom("_NET_WM_STATE")) |
| OnWMStateUpdated(); |
| break; |
| } |
| } |
| } |
| |
| void X11WindowBase::OnWMStateUpdated() { |
| std::vector<::Atom> atom_list; |
| // Ignore the return value of ui::GetAtomArrayProperty(). Fluxbox removes the |
| // _NET_WM_STATE property when no _NET_WM_STATE atoms are set. |
| ui::GetAtomArrayProperty(xwindow_, "_NET_WM_STATE", &atom_list); |
| |
| window_properties_.clear(); |
| std::copy(atom_list.begin(), atom_list.end(), |
| inserter(window_properties_, window_properties_.begin())); |
| |
| // Propagate the window state information to the client. |
| // Note that the order of checks is important here, because window can have |
| // several proprties at the same time. |
| ui::PlatformWindowState old_state = state_; |
| if (ui::HasWMSpecProperty(window_properties_, |
| gfx::GetAtom("_NET_WM_STATE_HIDDEN"))) { |
| state_ = ui::PlatformWindowState::PLATFORM_WINDOW_STATE_MINIMIZED; |
| } else if (ui::HasWMSpecProperty(window_properties_, |
| gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"))) { |
| state_ = ui::PlatformWindowState::PLATFORM_WINDOW_STATE_FULLSCREEN; |
| } else if (ui::HasWMSpecProperty( |
| window_properties_, |
| gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT")) && |
| ui::HasWMSpecProperty( |
| window_properties_, |
| gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"))) { |
| state_ = ui::PlatformWindowState::PLATFORM_WINDOW_STATE_MAXIMIZED; |
| } else { |
| state_ = ui::PlatformWindowState::PLATFORM_WINDOW_STATE_NORMAL; |
| } |
| |
| if (old_state != state_) |
| delegate_->OnWindowStateChanged(state_); |
| } |
| |
| bool X11WindowBase::IsMaximized() const { |
| return state_ == ui::PlatformWindowState::PLATFORM_WINDOW_STATE_MAXIMIZED; |
| } |
| |
| bool X11WindowBase::IsFullscreen() const { |
| return state_ == ui::PlatformWindowState::PLATFORM_WINDOW_STATE_FULLSCREEN; |
| } |
| |
| } // namespace ui |