476 lines
16 KiB
C++
476 lines
16 KiB
C++
// 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/alloy/alloy_browser_host_impl.h"
|
|
#include "libcef/browser/browser_host_base.h"
|
|
#include "libcef/browser/thread_util.h"
|
|
|
|
#include "net/base/network_interfaces.h"
|
|
#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/events/x/x11_event_translation.h"
|
|
#include "ui/gfx/x/connection.h"
|
|
#include "ui/gfx/x/x11_window_event_manager.h"
|
|
#include "ui/gfx/x/xproto_util.h"
|
|
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h"
|
|
|
|
namespace {
|
|
|
|
const char kNetWMPid[] = "_NET_WM_PID";
|
|
const char kNetWMPing[] = "_NET_WM_PING";
|
|
const char kNetWMState[] = "_NET_WM_STATE";
|
|
const char kNetWMStateKeepAbove[] = "_NET_WM_STATE_KEEP_ABOVE";
|
|
const char kWMDeleteWindow[] = "WM_DELETE_WINDOW";
|
|
const char kWMProtocols[] = "WM_PROTOCOLS";
|
|
const char kXdndProxy[] = "XdndProxy";
|
|
|
|
// Return true if |window| has any property with |property_name|.
|
|
// Deleted from ui/base/x/x11_util.h in https://crrev.com/62fc260067.
|
|
bool PropertyExists(x11::Window window, x11::Atom property) {
|
|
auto response = x11::Connection::Get()
|
|
->GetProperty(x11::GetPropertyRequest{
|
|
.window = window,
|
|
.property = property,
|
|
.long_length = 1,
|
|
})
|
|
.Sync();
|
|
return response && response->format;
|
|
}
|
|
|
|
// Returns true if |window| is visible.
|
|
// Deleted from ui/base/x/x11_util.h in https://crrev.com/62fc260067.
|
|
bool IsWindowVisible(x11::Window window) {
|
|
auto response = x11::Connection::Get()->GetWindowAttributes({window}).Sync();
|
|
if (!response || response->map_state != x11::MapState::Viewable) {
|
|
return false;
|
|
}
|
|
|
|
// Minimized windows are not visible.
|
|
std::vector<x11::Atom> wm_states;
|
|
if (x11::GetArrayProperty(window, x11::GetAtom("_NET_WM_STATE"),
|
|
&wm_states)) {
|
|
x11::Atom hidden_atom = x11::GetAtom("_NET_WM_STATE_HIDDEN");
|
|
if (base::Contains(wm_states, hidden_atom)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Do not check _NET_CURRENT_DESKTOP/_NET_WM_DESKTOP since some
|
|
// window managers (eg. i3) have per-monitor workspaces where more
|
|
// than one workspace can be visible at once, but only one will be
|
|
// "active".
|
|
return true;
|
|
}
|
|
|
|
x11::Window FindChild(x11::Window window) {
|
|
auto query_tree = x11::Connection::Get()->QueryTree({window}).Sync();
|
|
if (query_tree && query_tree->children.size() == 1U) {
|
|
return query_tree->children[0];
|
|
}
|
|
|
|
return x11::Window::None;
|
|
}
|
|
|
|
x11::Window FindToplevelParent(x11::Window window) {
|
|
x11::Window top_level_window = window;
|
|
|
|
do {
|
|
auto query_tree = x11::Connection::Get()->QueryTree({window}).Sync();
|
|
if (!query_tree) {
|
|
break;
|
|
}
|
|
|
|
top_level_window = window;
|
|
if (!PropertyExists(query_tree->parent, x11::GetAtom(kNetWMPid)) ||
|
|
query_tree->parent == query_tree->root) {
|
|
break;
|
|
}
|
|
|
|
window = query_tree->parent;
|
|
} while (true);
|
|
|
|
return top_level_window;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
CEF_EXPORT XDisplay* cef_get_xdisplay() {
|
|
if (!CEF_CURRENTLY_ON(CEF_UIT)) {
|
|
return nullptr;
|
|
}
|
|
return x11::Connection::Get()->GetXlibDisplay();
|
|
}
|
|
|
|
CefWindowX11::CefWindowX11(CefRefPtr<CefBrowserHostBase> browser,
|
|
x11::Window parent_xwindow,
|
|
const gfx::Rect& bounds,
|
|
const std::string& title)
|
|
: browser_(browser),
|
|
connection_(x11::Connection::Get()),
|
|
parent_xwindow_(parent_xwindow),
|
|
bounds_(bounds),
|
|
weak_ptr_factory_(this) {
|
|
if (parent_xwindow_ == x11::Window::None) {
|
|
parent_xwindow_ = ui::GetX11RootWindow();
|
|
}
|
|
|
|
x11::VisualId visual;
|
|
uint8_t depth;
|
|
x11::ColorMap colormap;
|
|
ui::XVisualManager::GetInstance()->ChooseVisualForWindow(
|
|
/*want_argb_visual=*/false, &visual, &depth, &colormap,
|
|
/*visual_has_alpha=*/nullptr);
|
|
|
|
xwindow_ = connection_->GenerateId<x11::Window>();
|
|
connection_->CreateWindow({
|
|
.depth = depth,
|
|
.wid = xwindow_,
|
|
.parent = parent_xwindow_,
|
|
.x = static_cast<int16_t>(bounds.x()),
|
|
.y = static_cast<int16_t>(bounds.y()),
|
|
.width = static_cast<uint16_t>(bounds.width()),
|
|
.height = static_cast<uint16_t>(bounds.height()),
|
|
.c_class = x11::WindowClass::InputOutput,
|
|
.visual = visual,
|
|
.background_pixel = 0,
|
|
.border_pixel = 0,
|
|
.override_redirect = x11::Bool32(false),
|
|
.event_mask = x11::EventMask::FocusChange |
|
|
x11::EventMask::StructureNotify |
|
|
x11::EventMask::PropertyChange,
|
|
.colormap = colormap,
|
|
});
|
|
|
|
connection_->Flush();
|
|
|
|
DCHECK(ui::X11EventSource::HasInstance());
|
|
connection_->AddEventObserver(this);
|
|
ui::X11EventSource::GetInstance()->AddPlatformEventDispatcher(this);
|
|
|
|
std::vector<x11::Atom> protocols = {
|
|
x11::GetAtom(kWMDeleteWindow),
|
|
x11::GetAtom(kNetWMPing),
|
|
};
|
|
x11::SetArrayProperty(xwindow_, x11::GetAtom(kWMProtocols), x11::Atom::ATOM,
|
|
protocols);
|
|
|
|
// We need a WM_CLIENT_MACHINE value so we integrate with the desktop
|
|
// environment.
|
|
x11::SetStringProperty(xwindow_, x11::Atom::WM_CLIENT_MACHINE,
|
|
x11::Atom::STRING, net::GetHostName());
|
|
|
|
// 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(uint32_t) >= sizeof(pid_t),
|
|
"pid_t should not be larger than uint32_t");
|
|
uint32_t pid = getpid();
|
|
x11::SetProperty(xwindow_, x11::GetAtom(kNetWMPid), x11::Atom::CARDINAL, pid);
|
|
|
|
// Set the initial window name, if provided.
|
|
if (!title.empty()) {
|
|
x11::SetStringProperty(xwindow_, x11::Atom::WM_NAME, x11::Atom::STRING,
|
|
title);
|
|
x11::SetStringProperty(xwindow_, x11::Atom::WM_ICON_NAME, x11::Atom::STRING,
|
|
title);
|
|
}
|
|
}
|
|
|
|
CefWindowX11::~CefWindowX11() {
|
|
DCHECK_EQ(xwindow_, x11::Window::None);
|
|
DCHECK(ui::X11EventSource::HasInstance());
|
|
connection_->RemoveEventObserver(this);
|
|
ui::X11EventSource::GetInstance()->RemovePlatformEventDispatcher(this);
|
|
}
|
|
|
|
void CefWindowX11::Close() {
|
|
if (xwindow_ == x11::Window::None) {
|
|
return;
|
|
}
|
|
|
|
ui::SendClientMessage(
|
|
xwindow_, xwindow_, x11::GetAtom(kWMProtocols),
|
|
{static_cast<uint32_t>(x11::GetAtom(kWMDeleteWindow)),
|
|
static_cast<uint32_t>(x11::Time::CurrentTime), 0, 0, 0},
|
|
x11::EventMask::NoEvent);
|
|
|
|
auto host = GetHost();
|
|
if (host) {
|
|
host->Close();
|
|
}
|
|
}
|
|
|
|
void CefWindowX11::Show() {
|
|
if (xwindow_ == x11::Window::None) {
|
|
return;
|
|
}
|
|
|
|
if (!window_mapped_) {
|
|
// Before we map the window, set size hints. Otherwise, some window managers
|
|
// will ignore toplevel XMoveWindow commands.
|
|
ui::SizeHints size_hints;
|
|
memset(&size_hints, 0, sizeof(size_hints));
|
|
ui::GetWmNormalHints(xwindow_, &size_hints);
|
|
size_hints.flags |= ui::SIZE_HINT_P_POSITION;
|
|
size_hints.x = bounds_.x();
|
|
size_hints.y = bounds_.y();
|
|
ui::SetWmNormalHints(xwindow_, size_hints);
|
|
|
|
connection_->MapWindow({xwindow_});
|
|
|
|
// TODO(thomasanderson): Find out why this flush is necessary.
|
|
connection_->Flush();
|
|
window_mapped_ = true;
|
|
|
|
// Setup the drag and drop proxy on the top level window of the application
|
|
// to be the child of this window.
|
|
auto child = FindChild(xwindow_);
|
|
auto toplevel_window = FindToplevelParent(xwindow_);
|
|
DCHECK_NE(toplevel_window, x11::Window::None);
|
|
if (child != x11::Window::None && toplevel_window != x11::Window::None) {
|
|
// Configure the drag&drop proxy property for the top-most window so
|
|
// that all drag&drop-related messages will be sent to the child
|
|
// DesktopWindowTreeHostLinux. The proxy property is referenced by
|
|
// DesktopDragDropClientAuraX11::FindWindowFor.
|
|
x11::Window window = x11::Window::None;
|
|
auto dndproxy_atom = x11::GetAtom(kXdndProxy);
|
|
x11::GetProperty(toplevel_window, dndproxy_atom, &window);
|
|
|
|
if (window != child) {
|
|
// Set the proxy target for the top-most window.
|
|
x11::SetProperty(toplevel_window, dndproxy_atom, x11::Atom::WINDOW,
|
|
child);
|
|
// Do the same for the proxy target per the spec.
|
|
x11::SetProperty(child, dndproxy_atom, x11::Atom::WINDOW, child);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CefWindowX11::Hide() {
|
|
if (xwindow_ == x11::Window::None) {
|
|
return;
|
|
}
|
|
|
|
if (window_mapped_) {
|
|
ui::WithdrawWindow(xwindow_);
|
|
window_mapped_ = false;
|
|
}
|
|
}
|
|
|
|
void CefWindowX11::Focus() {
|
|
if (xwindow_ == x11::Window::None || !window_mapped_) {
|
|
return;
|
|
}
|
|
|
|
x11::Window focus_target = xwindow_;
|
|
|
|
if (browser_.get()) {
|
|
auto child = FindChild(xwindow_);
|
|
if (child != x11::Window::None && IsWindowVisible(child)) {
|
|
// Give focus to the child DesktopWindowTreeHostLinux.
|
|
focus_target = child;
|
|
}
|
|
}
|
|
|
|
// Directly ask the X server to give focus to the window. Note that the call
|
|
// would have raised an X error if the window is not mapped.
|
|
connection_
|
|
->SetInputFocus(
|
|
{x11::InputFocus::Parent, focus_target, x11::Time::CurrentTime})
|
|
.IgnoreError();
|
|
}
|
|
|
|
void CefWindowX11::SetBounds(const gfx::Rect& bounds) {
|
|
if (xwindow_ == x11::Window::None) {
|
|
return;
|
|
}
|
|
|
|
x11::ConfigureWindowRequest req{.window = xwindow_};
|
|
|
|
bool origin_changed = bounds_.origin() != bounds.origin();
|
|
bool size_changed = bounds_.size() != bounds.size();
|
|
|
|
if (size_changed) {
|
|
req.width = bounds.width();
|
|
req.height = bounds.height();
|
|
}
|
|
|
|
if (origin_changed) {
|
|
req.x = bounds.x();
|
|
req.y = bounds.y();
|
|
}
|
|
|
|
if (origin_changed || size_changed) {
|
|
connection_->ConfigureWindow(req);
|
|
}
|
|
}
|
|
|
|
gfx::Rect CefWindowX11::GetBoundsInScreen() {
|
|
if (auto coords =
|
|
connection_
|
|
->TranslateCoordinates({xwindow_, ui::GetX11RootWindow(), 0, 0})
|
|
.Sync()) {
|
|
return gfx::Rect(gfx::Point(coords->dst_x, coords->dst_y), bounds_.size());
|
|
}
|
|
|
|
return gfx::Rect();
|
|
}
|
|
|
|
views::DesktopWindowTreeHostLinux* CefWindowX11::GetHost() {
|
|
if (browser_.get()) {
|
|
auto child = FindChild(xwindow_);
|
|
if (child != x11::Window::None) {
|
|
return static_cast<views::DesktopWindowTreeHostLinux*>(
|
|
views::DesktopWindowTreeHostLinux::GetHostForWidget(
|
|
static_cast<gfx::AcceleratedWidget>(child)));
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool CefWindowX11::CanDispatchEvent(const ui::PlatformEvent& event) {
|
|
auto* dispatching_event = connection_->dispatching_event();
|
|
return dispatching_event && dispatching_event->window() == xwindow_;
|
|
}
|
|
|
|
uint32_t CefWindowX11::DispatchEvent(const ui::PlatformEvent& event) {
|
|
DCHECK_NE(xwindow_, x11::Window::None);
|
|
DCHECK(event);
|
|
|
|
auto* current_xevent = connection_->dispatching_event();
|
|
ProcessXEvent(*current_xevent);
|
|
return ui::POST_DISPATCH_STOP_PROPAGATION;
|
|
}
|
|
|
|
void CefWindowX11::OnEvent(const x11::Event& event) {
|
|
if (event.window() != xwindow_) {
|
|
return;
|
|
}
|
|
ProcessXEvent(event);
|
|
}
|
|
|
|
void CefWindowX11::ContinueFocus() {
|
|
if (!focus_pending_) {
|
|
return;
|
|
}
|
|
if (browser_.get()) {
|
|
browser_->SetFocus(true);
|
|
}
|
|
focus_pending_ = false;
|
|
}
|
|
|
|
bool CefWindowX11::TopLevelAlwaysOnTop() const {
|
|
auto toplevel_window = FindToplevelParent(xwindow_);
|
|
if (toplevel_window == x11::Window::None) {
|
|
return false;
|
|
}
|
|
|
|
std::vector<x11::Atom> wm_states;
|
|
if (x11::GetArrayProperty(toplevel_window, x11::GetAtom(kNetWMState),
|
|
&wm_states)) {
|
|
x11::Atom keep_above_atom = x11::GetAtom(kNetWMStateKeepAbove);
|
|
if (base::Contains(wm_states, keep_above_atom)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CefWindowX11::ProcessXEvent(const x11::Event& event) {
|
|
if (auto* configure = event.As<x11::ConfigureNotifyEvent>()) {
|
|
DCHECK_EQ(xwindow_, configure->event);
|
|
// 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.
|
|
bounds_ = gfx::Rect(configure->x, configure->y, configure->width,
|
|
configure->height);
|
|
|
|
if (browser_.get()) {
|
|
auto child = FindChild(xwindow_);
|
|
if (child != x11::Window::None) {
|
|
// Resize the child DesktopWindowTreeHostLinux to match this window.
|
|
x11::ConfigureWindowRequest req{
|
|
.window = child,
|
|
.width = bounds_.width(),
|
|
.height = bounds_.height(),
|
|
};
|
|
connection_->ConfigureWindow(req);
|
|
|
|
browser_->NotifyMoveOrResizeStarted();
|
|
}
|
|
}
|
|
} else if (auto* client = event.As<x11::ClientMessageEvent>()) {
|
|
if (client->type == x11::GetAtom(kWMProtocols)) {
|
|
x11::Atom protocol = static_cast<x11::Atom>(client->data.data32[0]);
|
|
if (protocol == x11::GetAtom(kWMDeleteWindow)) {
|
|
// We have received a close message from the window manager.
|
|
if (!browser_ || browser_->TryCloseBrowser()) {
|
|
// Allow the close.
|
|
connection_->DestroyWindow({xwindow_});
|
|
xwindow_ = x11::Window::None;
|
|
|
|
if (browser_) {
|
|
// Force the browser to be destroyed and release the reference
|
|
// added in PlatformCreateWindow().
|
|
static_cast<AlloyBrowserHostImpl*>(browser_.get())
|
|
->WindowDestroyed();
|
|
}
|
|
|
|
delete this;
|
|
}
|
|
} else if (protocol == x11::GetAtom(kNetWMPing)) {
|
|
x11::ClientMessageEvent reply_event = *client;
|
|
reply_event.window = parent_xwindow_;
|
|
x11::SendEvent(reply_event, reply_event.window,
|
|
x11::EventMask::SubstructureNotify |
|
|
x11::EventMask::SubstructureRedirect);
|
|
}
|
|
}
|
|
} else if (auto* focus = event.As<x11::FocusEvent>()) {
|
|
if (focus->opcode == x11::FocusEvent::In) {
|
|
// 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 DesktopWindowTreeHostLinux) 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::BindOnce(&CefWindowX11::ContinueFocus,
|
|
weak_ptr_factory_.GetWeakPtr()),
|
|
100);
|
|
}
|
|
} else {
|
|
// 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;
|
|
}
|
|
}
|
|
} else if (auto* property = event.As<x11::PropertyNotifyEvent>()) {
|
|
const auto& wm_state_atom = x11::GetAtom(kNetWMState);
|
|
if (property->atom == wm_state_atom) {
|
|
// State change event like minimize/maximize.
|
|
if (browser_.get()) {
|
|
auto child = FindChild(xwindow_);
|
|
if (child != x11::Window::None) {
|
|
// Forward the state change to the child DesktopWindowTreeHostLinux
|
|
// window so that resource usage will be reduced while the window is
|
|
// minimized. |atom_list| may be empty.
|
|
std::vector<x11::Atom> atom_list;
|
|
x11::GetArrayProperty(xwindow_, wm_state_atom, &atom_list);
|
|
x11::SetArrayProperty(child, wm_state_atom, x11::Atom::ATOM,
|
|
atom_list);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|