blob: 4ec56c035d0f761ffb5678a1eb15c0b5c391180d [file] [log] [blame]
// 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) {
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