// Copyright 2014 The Chromium Embedded Framework Authors. // Portions 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 "libcef/browser/native/window_x11.h" #include "libcef/browser/thread_util.h" #include #include #include #include #include "ui/base/x/x11_util.h" #include "ui/events/platform/platform_event_source.h" #include "ui/events/platform/x11/x11_event_source.h" #include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h" #include "ui/views/widget/desktop_aura/x11_topmost_window_finder.h" namespace { const char kAtom[] = "ATOM"; const char kWMDeleteWindow[] = "WM_DELETE_WINDOW"; const char kWMProtocols[] = "WM_PROTOCOLS"; const char kNetWMPid[] = "_NET_WM_PID"; const char kNetWMPing[] = "_NET_WM_PING"; const char kNetWMState[] = "_NET_WM_STATE"; const char kXdndProxy[] = "XdndProxy"; ::Window FindEventTarget(const base::NativeEvent& xev) { ::Window target = xev->xany.window; if (xev->type == GenericEvent) target = static_cast(xev->xcookie.data)->event; return target; } ::Window FindChild(::Display* display, ::Window window) { ::Window root; ::Window parent; ::Window* children; ::Window child_window = None; unsigned int nchildren; if (XQueryTree(display, window, &root, &parent, &children, &nchildren)) { DCHECK_EQ(1U, nchildren); child_window = children[0]; XFree(children); } return child_window; } ::Window FindToplevelParent(::Display* display, ::Window window) { ::Window top_level_window = window; ::Window root = None; ::Window parent = None; ::Window* children = NULL; unsigned int nchildren = 0; // Enumerate all parents of "window" to find the highest level window // that either: // - has a parent that does not contain the _NET_WM_PID property // - has a parent that is the root window. while (XQueryTree(display, window, &root, &parent, &children, &nchildren)) { if (children) { XFree(children); } top_level_window = window; if (!ui::PropertyExists(parent, kNetWMPid) || parent == root) { break; } window = parent; } return top_level_window; } } // namespace CEF_EXPORT XDisplay* cef_get_xdisplay() { if (!CEF_CURRENTLY_ON(CEF_UIT)) return NULL; return gfx::GetXDisplay(); } CefWindowX11::CefWindowX11(CefRefPtr browser, ::Window parent_xwindow, const gfx::Rect& bounds) : browser_(browser), xdisplay_(gfx::GetXDisplay()), parent_xwindow_(parent_xwindow), xwindow_(0), window_mapped_(false), bounds_(bounds), focus_pending_(false), weak_ptr_factory_(this) { if (parent_xwindow_ == None) parent_xwindow_ = DefaultRootWindow(xdisplay_); XSetWindowAttributes swa; memset(&swa, 0, sizeof(swa)); swa.background_pixmap = None; swa.override_redirect = false; xwindow_ = XCreateWindow(xdisplay_, parent_xwindow_, bounds.x(), bounds.y(), bounds.width(), bounds.height(), 0, // border width CopyFromParent, // depth InputOutput, CopyFromParent, // visual CWBackPixmap | CWOverrideRedirect, &swa); if (ui::PlatformEventSource::GetInstance()) ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this); long event_mask = FocusChangeMask | StructureNotifyMask | PropertyChangeMask; XSelectInput(xdisplay_, xwindow_, event_mask); XFlush(xdisplay_); // 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] = gfx::GetAtom(kWMDeleteWindow); protocols[1] = gfx::GetAtom(kNetWMPing); 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(kNetWMPid), XA_CARDINAL, 32, PropModeReplace, reinterpret_cast(&pid), 1); } CefWindowX11::~CefWindowX11() { DCHECK(!xwindow_); if (ui::PlatformEventSource::GetInstance()) ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this); } void CefWindowX11::Close() { XEvent ev = {0}; ev.xclient.type = ClientMessage; ev.xclient.window = xwindow_; ev.xclient.message_type = gfx::GetAtom(kWMProtocols); ev.xclient.format = 32; ev.xclient.data.l[0] = gfx::GetAtom(kWMDeleteWindow); ev.xclient.data.l[1] = CurrentTime; XSendEvent(xdisplay_, xwindow_, False, NoEventMask, &ev); } void CefWindowX11::Show() { if (xwindow_ == None) return; 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. if (ui::X11EventSource::GetInstance()) ui::X11EventSource::GetInstance()->BlockUntilWindowMapped(xwindow_); window_mapped_ = true; // Setup the drag and drop proxy on the top level window of the application // to be the child of this window. ::Window child = FindChild(xdisplay_, xwindow_); ::Window toplevel_window = FindToplevelParent(xdisplay_, xwindow_); DCHECK(toplevel_window); if (child && toplevel_window) { // Configure the drag&drop proxy property for the top-most window so // that all drag&drop-related messages will be sent to the child // DesktopWindowTreeHostX11. The proxy property is referenced by // DesktopDragDropClientAuraX11::FindWindowFor. ::Window proxy_target = gfx::kNullAcceleratedWidget; ui::GetXIDProperty(toplevel_window, kXdndProxy, &proxy_target); if (proxy_target != child) { // Set the proxy target for the top-most window. XChangeProperty(xdisplay_, toplevel_window, gfx::GetAtom(kXdndProxy), XA_WINDOW, 32, PropModeReplace, reinterpret_cast(&child), 1); // Do the same for the proxy target per the spec. XChangeProperty(xdisplay_, child, gfx::GetAtom(kXdndProxy), XA_WINDOW, 32, PropModeReplace, reinterpret_cast(&child), 1); } } } } void CefWindowX11::Hide() { if (xwindow_ == None) return; if (window_mapped_) { XWithdrawWindow(xdisplay_, xwindow_, 0); window_mapped_ = false; } } void CefWindowX11::Focus() { if (xwindow_ == None || !window_mapped_) return; if (browser_.get()) { ::Window child = FindChild(xdisplay_, xwindow_); if (child && ui::IsWindowVisible(child)) { // Give focus to the child DesktopWindowTreeHostX11. XSetInputFocus(xdisplay_, child, RevertToParent, CurrentTime); } } else { XSetInputFocus(xdisplay_, xwindow_, RevertToParent, CurrentTime); } } void CefWindowX11::SetBounds(const gfx::Rect& bounds) { if (xwindow_ == None) return; bool origin_changed = bounds_.origin() != bounds.origin(); bool size_changed = bounds_.size() != bounds.size(); XWindowChanges changes = {0}; unsigned value_mask = 0; if (size_changed) { changes.width = bounds.width(); changes.height = bounds.height(); value_mask = CWHeight | CWWidth; } if (origin_changed) { changes.x = bounds.x(); changes.y = bounds.y(); value_mask |= CWX | CWY; } if (value_mask) XConfigureWindow(xdisplay_, xwindow_, value_mask, &changes); } gfx::Rect CefWindowX11::GetBoundsInScreen() { int x, y; Window child; if (XTranslateCoordinates(xdisplay_, xwindow_, DefaultRootWindow(xdisplay_), 0, 0, &x, &y, &child)) { return gfx::Rect(gfx::Point(x, y), bounds_.size()); } return gfx::Rect(); } views::DesktopWindowTreeHostX11* CefWindowX11::GetHost() { if (browser_.get()) { ::Window child = FindChild(xdisplay_, xwindow_); if (child) return views::DesktopWindowTreeHostX11::GetHostForXID(child); } return NULL; } bool CefWindowX11::CanDispatchEvent(const ui::PlatformEvent& event) { ::Window target = FindEventTarget(event); return target == xwindow_; } uint32_t CefWindowX11::DispatchEvent(const ui::PlatformEvent& event) { XEvent* xev = event; switch (xev->type) { 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); bounds_ = bounds; if (browser_.get()) { ::Window child = FindChild(xdisplay_, xwindow_); if (child) { // Resize the child DesktopWindowTreeHostX11 to match this window. XWindowChanges changes = {0}; changes.width = bounds.width(); changes.height = bounds.height(); XConfigureWindow(xdisplay_, child, CWHeight | CWWidth, &changes); browser_->NotifyMoveOrResizeStarted(); } } break; } case ClientMessage: { Atom message_type = xev->xclient.message_type; if (message_type == gfx::GetAtom(kWMProtocols)) { Atom protocol = static_cast(xev->xclient.data.l[0]); if (protocol == gfx::GetAtom(kWMDeleteWindow)) { // We have received a close message from the window manager. if (!browser_ || browser_->TryCloseBrowser()) { // Allow the close. XDestroyWindow(xdisplay_, xwindow_); xwindow_ = None; if (browser_.get()) { // Force the browser to be destroyed and release the reference // added in PlatformCreateWindow(). browser_->WindowDestroyed(); } delete this; } } else if (protocol == gfx::GetAtom(kNetWMPing)) { XEvent reply_event = *xev; reply_event.xclient.window = parent_xwindow_; XSendEvent(xdisplay_, reply_event.xclient.window, False, SubstructureRedirectMask | SubstructureNotifyMask, &reply_event); XFlush(xdisplay_); } } break; } case FocusIn: // This message is received first followed by a "_NET_ACTIVE_WINDOW" // message sent to the root window. When X11DesktopHandler handles the // "_NET_ACTIVE_WINDOW" message it will erroneously mark the WebView // (hosted in a DesktopWindowTreeHostX11) as unfocused. Use a delayed // task here to restore the WebView's focus state. if (!focus_pending_) { focus_pending_ = true; CEF_POST_DELAYED_TASK(CEF_UIT, base::Bind(&CefWindowX11::ContinueFocus, weak_ptr_factory_.GetWeakPtr()), 100); } break; case FocusOut: // Cancel the pending focus change if some other window has gained focus // while waiting for the async task to run. Otherwise we can get stuck in // a focus change loop. if (focus_pending_) focus_pending_ = false; break; case PropertyNotify: { ::Atom changed_atom = xev->xproperty.atom; if (changed_atom == gfx::GetAtom(kNetWMState)) { // State change event like minimize/maximize. if (browser_.get()) { ::Window child = FindChild(xdisplay_, xwindow_); if (child) { // Forward the state change to the child DesktopWindowTreeHostX11 // window so that resource usage will be reduced while the window is // minimized. std::vector<::Atom> atom_list; if (ui::GetAtomArrayProperty(xwindow_, kNetWMState, &atom_list) && !atom_list.empty()) { ui::SetAtomArrayProperty(child, kNetWMState, "ATOM", atom_list); } else { // Set an empty list of property values to pass the check in // DesktopWindowTreeHostX11::OnWMStateUpdated(). XChangeProperty(xdisplay_, child, gfx::GetAtom(kNetWMState), // name gfx::GetAtom(kAtom), // type 32, // size in bits of items in 'value' PropModeReplace, NULL, 0); // num items } } } } break; } } return ui::POST_DISPATCH_STOP_PROPAGATION; } void CefWindowX11::ContinueFocus() { if (!focus_pending_) return; if (browser_.get()) browser_->SetFocus(true); focus_pending_ = false; }