1 // Copyright 2014 The Chromium Embedded Framework Authors.
2 // Portions copyright 2014 The Chromium Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5
6 #include "libcef/browser/native/window_x11.h"
7
8 #include "libcef/browser/alloy/alloy_browser_host_impl.h"
9 #include "libcef/browser/browser_host_base.h"
10 #include "libcef/browser/thread_util.h"
11
12 #include "net/base/network_interfaces.h"
13 #include "ui/base/x/x11_util.h"
14 #include "ui/events/platform/platform_event_source.h"
15 #include "ui/events/platform/x11/x11_event_source.h"
16 #include "ui/events/x/x11_event_translation.h"
17 #include "ui/gfx/x/connection.h"
18 #include "ui/gfx/x/x11_window_event_manager.h"
19 #include "ui/gfx/x/xproto_util.h"
20 #include "ui/platform_window/x11/x11_topmost_window_finder.h"
21 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h"
22
23 namespace {
24
25 const char kNetWMPid[] = "_NET_WM_PID";
26 const char kNetWMPing[] = "_NET_WM_PING";
27 const char kNetWMState[] = "_NET_WM_STATE";
28 const char kNetWMStateKeepAbove[] = "_NET_WM_STATE_KEEP_ABOVE";
29 const char kWMDeleteWindow[] = "WM_DELETE_WINDOW";
30 const char kWMProtocols[] = "WM_PROTOCOLS";
31 const char kXdndProxy[] = "XdndProxy";
32
FindChild(x11::Window window)33 x11::Window FindChild(x11::Window window) {
34 auto query_tree = x11::Connection::Get()->QueryTree({window}).Sync();
35 if (query_tree && query_tree->children.size() == 1U) {
36 return query_tree->children[0];
37 }
38
39 return x11::Window::None;
40 }
41
FindToplevelParent(x11::Window window)42 x11::Window FindToplevelParent(x11::Window window) {
43 x11::Window top_level_window = window;
44
45 do {
46 auto query_tree = x11::Connection::Get()->QueryTree({window}).Sync();
47 if (!query_tree)
48 break;
49
50 top_level_window = window;
51 if (!ui::PropertyExists(query_tree->parent, x11::GetAtom(kNetWMPid)) ||
52 query_tree->parent == query_tree->root) {
53 break;
54 }
55
56 window = query_tree->parent;
57 } while (true);
58
59 return top_level_window;
60 }
61
62 } // namespace
63
cef_get_xdisplay()64 CEF_EXPORT XDisplay* cef_get_xdisplay() {
65 if (!CEF_CURRENTLY_ON(CEF_UIT))
66 return nullptr;
67 return x11::Connection::Get()->GetXlibDisplay();
68 }
69
CefWindowX11(CefRefPtr<CefBrowserHostBase> browser,x11::Window parent_xwindow,const gfx::Rect & bounds,const std::string & title)70 CefWindowX11::CefWindowX11(CefRefPtr<CefBrowserHostBase> browser,
71 x11::Window parent_xwindow,
72 const gfx::Rect& bounds,
73 const std::string& title)
74 : browser_(browser),
75 connection_(x11::Connection::Get()),
76 parent_xwindow_(parent_xwindow),
77 bounds_(bounds),
78 weak_ptr_factory_(this) {
79 if (parent_xwindow_ == x11::Window::None)
80 parent_xwindow_ = ui::GetX11RootWindow();
81
82 x11::VisualId visual;
83 uint8_t depth;
84 x11::ColorMap colormap;
85 ui::XVisualManager::GetInstance()->ChooseVisualForWindow(
86 /*want_argb_visual=*/false, &visual, &depth, &colormap,
87 /*visual_has_alpha=*/nullptr);
88
89 xwindow_ = connection_->GenerateId<x11::Window>();
90 connection_->CreateWindow({
91 .depth = depth,
92 .wid = xwindow_,
93 .parent = parent_xwindow_,
94 .x = static_cast<int16_t>(bounds.x()),
95 .y = static_cast<int16_t>(bounds.y()),
96 .width = static_cast<uint16_t>(bounds.width()),
97 .height = static_cast<uint16_t>(bounds.height()),
98 .c_class = x11::WindowClass::InputOutput,
99 .visual = visual,
100 .background_pixel = 0,
101 .border_pixel = 0,
102 .override_redirect = x11::Bool32(false),
103 .event_mask = x11::EventMask::FocusChange |
104 x11::EventMask::StructureNotify |
105 x11::EventMask::PropertyChange,
106 .colormap = colormap,
107 });
108
109 connection_->Flush();
110
111 DCHECK(ui::X11EventSource::HasInstance());
112 connection_->AddEventObserver(this);
113 ui::X11EventSource::GetInstance()->AddPlatformEventDispatcher(this);
114
115 std::vector<x11::Atom> protocols = {
116 x11::GetAtom(kWMDeleteWindow),
117 x11::GetAtom(kNetWMPing),
118 };
119 x11::SetArrayProperty(xwindow_, x11::GetAtom(kWMProtocols), x11::Atom::ATOM,
120 protocols);
121
122 // We need a WM_CLIENT_MACHINE value so we integrate with the desktop
123 // environment.
124 x11::SetStringProperty(xwindow_, x11::Atom::WM_CLIENT_MACHINE,
125 x11::Atom::STRING, net::GetHostName());
126
127 // Likewise, the X server needs to know this window's pid so it knows which
128 // program to kill if the window hangs.
129 // XChangeProperty() expects "pid" to be long.
130 static_assert(sizeof(uint32_t) >= sizeof(pid_t),
131 "pid_t should not be larger than uint32_t");
132 uint32_t pid = getpid();
133 x11::SetProperty(xwindow_, x11::GetAtom(kNetWMPid), x11::Atom::CARDINAL, pid);
134
135 // Set the initial window name, if provided.
136 if (!title.empty()) {
137 x11::SetStringProperty(xwindow_, x11::Atom::WM_NAME, x11::Atom::STRING,
138 title);
139 x11::SetStringProperty(xwindow_, x11::Atom::WM_ICON_NAME, x11::Atom::STRING,
140 title);
141 }
142 }
143
~CefWindowX11()144 CefWindowX11::~CefWindowX11() {
145 DCHECK_EQ(xwindow_, x11::Window::None);
146 DCHECK(ui::X11EventSource::HasInstance());
147 connection_->RemoveEventObserver(this);
148 ui::X11EventSource::GetInstance()->RemovePlatformEventDispatcher(this);
149 }
150
Close()151 void CefWindowX11::Close() {
152 if (xwindow_ == x11::Window::None)
153 return;
154
155 ui::SendClientMessage(
156 xwindow_, xwindow_, x11::GetAtom(kWMProtocols),
157 {static_cast<uint32_t>(x11::GetAtom(kWMDeleteWindow)),
158 static_cast<uint32_t>(x11::Time::CurrentTime), 0, 0, 0},
159 x11::EventMask::NoEvent);
160
161 auto host = GetHost();
162 if (host)
163 host->Close();
164 }
165
Show()166 void CefWindowX11::Show() {
167 if (xwindow_ == x11::Window::None)
168 return;
169
170 if (!window_mapped_) {
171 // Before we map the window, set size hints. Otherwise, some window managers
172 // will ignore toplevel XMoveWindow commands.
173 ui::SizeHints size_hints;
174 memset(&size_hints, 0, sizeof(size_hints));
175 ui::GetWmNormalHints(xwindow_, &size_hints);
176 size_hints.flags |= ui::SIZE_HINT_P_POSITION;
177 size_hints.x = bounds_.x();
178 size_hints.y = bounds_.y();
179 ui::SetWmNormalHints(xwindow_, size_hints);
180
181 connection_->MapWindow({xwindow_});
182
183 // TODO(thomasanderson): Find out why this flush is necessary.
184 connection_->Flush();
185 window_mapped_ = true;
186
187 // Setup the drag and drop proxy on the top level window of the application
188 // to be the child of this window.
189 auto child = FindChild(xwindow_);
190 auto toplevel_window = FindToplevelParent(xwindow_);
191 DCHECK_NE(toplevel_window, x11::Window::None);
192 if (child != x11::Window::None && toplevel_window != x11::Window::None) {
193 // Configure the drag&drop proxy property for the top-most window so
194 // that all drag&drop-related messages will be sent to the child
195 // DesktopWindowTreeHostLinux. The proxy property is referenced by
196 // DesktopDragDropClientAuraX11::FindWindowFor.
197 x11::Window window = x11::Window::None;
198 auto dndproxy_atom = x11::GetAtom(kXdndProxy);
199 x11::GetProperty(toplevel_window, dndproxy_atom, &window);
200
201 if (window != child) {
202 // Set the proxy target for the top-most window.
203 x11::SetProperty(toplevel_window, dndproxy_atom, x11::Atom::WINDOW,
204 child);
205 // Do the same for the proxy target per the spec.
206 x11::SetProperty(child, dndproxy_atom, x11::Atom::WINDOW, child);
207 }
208 }
209 }
210 }
211
Hide()212 void CefWindowX11::Hide() {
213 if (xwindow_ == x11::Window::None)
214 return;
215
216 if (window_mapped_) {
217 ui::WithdrawWindow(xwindow_);
218 window_mapped_ = false;
219 }
220 }
221
Focus()222 void CefWindowX11::Focus() {
223 if (xwindow_ == x11::Window::None || !window_mapped_)
224 return;
225
226 x11::Window focus_target = xwindow_;
227
228 if (browser_.get()) {
229 auto child = FindChild(xwindow_);
230 if (child != x11::Window::None && ui::IsWindowVisible(child)) {
231 // Give focus to the child DesktopWindowTreeHostLinux.
232 focus_target = child;
233 }
234 }
235
236 // Directly ask the X server to give focus to the window. Note that the call
237 // would have raised an X error if the window is not mapped.
238 connection_
239 ->SetInputFocus(
240 {x11::InputFocus::Parent, focus_target, x11::Time::CurrentTime})
241 .IgnoreError();
242 }
243
SetBounds(const gfx::Rect & bounds)244 void CefWindowX11::SetBounds(const gfx::Rect& bounds) {
245 if (xwindow_ == x11::Window::None)
246 return;
247
248 x11::ConfigureWindowRequest req{.window = xwindow_};
249
250 bool origin_changed = bounds_.origin() != bounds.origin();
251 bool size_changed = bounds_.size() != bounds.size();
252
253 if (size_changed) {
254 req.width = bounds.width();
255 req.height = bounds.height();
256 }
257
258 if (origin_changed) {
259 req.x = bounds.x();
260 req.y = bounds.y();
261 }
262
263 if (origin_changed || size_changed) {
264 connection_->ConfigureWindow(req);
265 }
266 }
267
GetBoundsInScreen()268 gfx::Rect CefWindowX11::GetBoundsInScreen() {
269 if (auto coords =
270 connection_
271 ->TranslateCoordinates({xwindow_, ui::GetX11RootWindow(), 0, 0})
272 .Sync()) {
273 return gfx::Rect(gfx::Point(coords->dst_x, coords->dst_y), bounds_.size());
274 }
275
276 return gfx::Rect();
277 }
278
GetHost()279 views::DesktopWindowTreeHostLinux* CefWindowX11::GetHost() {
280 if (browser_.get()) {
281 auto child = FindChild(xwindow_);
282 if (child != x11::Window::None) {
283 return static_cast<views::DesktopWindowTreeHostLinux*>(
284 views::DesktopWindowTreeHostLinux::GetHostForWidget(
285 static_cast<gfx::AcceleratedWidget>(child)));
286 }
287 }
288 return nullptr;
289 }
290
CanDispatchEvent(const ui::PlatformEvent & event)291 bool CefWindowX11::CanDispatchEvent(const ui::PlatformEvent& event) {
292 auto* dispatching_event = connection_->dispatching_event();
293 return dispatching_event && dispatching_event->window() == xwindow_;
294 }
295
DispatchEvent(const ui::PlatformEvent & event)296 uint32_t CefWindowX11::DispatchEvent(const ui::PlatformEvent& event) {
297 DCHECK_NE(xwindow_, x11::Window::None);
298 DCHECK(event);
299
300 auto* current_xevent = connection_->dispatching_event();
301 ProcessXEvent(*current_xevent);
302 return ui::POST_DISPATCH_STOP_PROPAGATION;
303 }
304
OnEvent(const x11::Event & event)305 void CefWindowX11::OnEvent(const x11::Event& event) {
306 if (event.window() != xwindow_)
307 return;
308 ProcessXEvent(event);
309 }
310
ContinueFocus()311 void CefWindowX11::ContinueFocus() {
312 if (!focus_pending_)
313 return;
314 if (browser_.get())
315 browser_->SetFocus(true);
316 focus_pending_ = false;
317 }
318
TopLevelAlwaysOnTop() const319 bool CefWindowX11::TopLevelAlwaysOnTop() const {
320 auto toplevel_window = FindToplevelParent(xwindow_);
321 if (toplevel_window == x11::Window::None)
322 return false;
323
324 std::vector<x11::Atom> wm_states;
325 if (x11::GetArrayProperty(toplevel_window, x11::GetAtom(kNetWMState),
326 &wm_states)) {
327 x11::Atom keep_above_atom = x11::GetAtom(kNetWMStateKeepAbove);
328 if (base::Contains(wm_states, keep_above_atom))
329 return true;
330 }
331 return false;
332 }
333
ProcessXEvent(const x11::Event & event)334 void CefWindowX11::ProcessXEvent(const x11::Event& event) {
335 if (auto* configure = event.As<x11::ConfigureNotifyEvent>()) {
336 DCHECK_EQ(xwindow_, configure->event);
337 DCHECK_EQ(xwindow_, configure->window);
338 // It's possible that the X window may be resized by some other means
339 // than from within Aura (e.g. the X window manager can change the
340 // size). Make sure the root window size is maintained properly.
341 bounds_ = gfx::Rect(configure->x, configure->y, configure->width,
342 configure->height);
343
344 if (browser_.get()) {
345 auto child = FindChild(xwindow_);
346 if (child != x11::Window::None) {
347 // Resize the child DesktopWindowTreeHostLinux to match this window.
348 x11::ConfigureWindowRequest req{
349 .window = child,
350 .width = bounds_.width(),
351 .height = bounds_.height(),
352 };
353 connection_->ConfigureWindow(req);
354
355 browser_->NotifyMoveOrResizeStarted();
356 }
357 }
358 } else if (auto* client = event.As<x11::ClientMessageEvent>()) {
359 if (client->type == x11::GetAtom(kWMProtocols)) {
360 x11::Atom protocol = static_cast<x11::Atom>(client->data.data32[0]);
361 if (protocol == x11::GetAtom(kWMDeleteWindow)) {
362 // We have received a close message from the window manager.
363 if (!browser_ || browser_->TryCloseBrowser()) {
364 // Allow the close.
365 connection_->DestroyWindow({xwindow_});
366 xwindow_ = x11::Window::None;
367
368 if (browser_) {
369 // Force the browser to be destroyed and release the reference
370 // added in PlatformCreateWindow().
371 static_cast<AlloyBrowserHostImpl*>(browser_.get())
372 ->WindowDestroyed();
373 }
374
375 delete this;
376 }
377 } else if (protocol == x11::GetAtom(kNetWMPing)) {
378 x11::ClientMessageEvent reply_event = *client;
379 reply_event.window = parent_xwindow_;
380 x11::SendEvent(reply_event, reply_event.window,
381 x11::EventMask::SubstructureNotify |
382 x11::EventMask::SubstructureRedirect);
383 }
384 }
385 } else if (auto* focus = event.As<x11::FocusEvent>()) {
386 if (focus->opcode == x11::FocusEvent::In) {
387 // This message is received first followed by a "_NET_ACTIVE_WINDOW"
388 // message sent to the root window. When X11DesktopHandler handles the
389 // "_NET_ACTIVE_WINDOW" message it will erroneously mark the WebView
390 // (hosted in a DesktopWindowTreeHostLinux) as unfocused. Use a delayed
391 // task here to restore the WebView's focus state.
392 if (!focus_pending_) {
393 focus_pending_ = true;
394 CEF_POST_DELAYED_TASK(CEF_UIT,
395 base::BindOnce(&CefWindowX11::ContinueFocus,
396 weak_ptr_factory_.GetWeakPtr()),
397 100);
398 }
399 } else {
400 // Cancel the pending focus change if some other window has gained focus
401 // while waiting for the async task to run. Otherwise we can get stuck in
402 // a focus change loop.
403 if (focus_pending_)
404 focus_pending_ = false;
405 }
406 } else if (auto* property = event.As<x11::PropertyNotifyEvent>()) {
407 const auto& wm_state_atom = x11::GetAtom(kNetWMState);
408 if (property->atom == wm_state_atom) {
409 // State change event like minimize/maximize.
410 if (browser_.get()) {
411 auto child = FindChild(xwindow_);
412 if (child != x11::Window::None) {
413 // Forward the state change to the child DesktopWindowTreeHostLinux
414 // window so that resource usage will be reduced while the window is
415 // minimized. |atom_list| may be empty.
416 std::vector<x11::Atom> atom_list;
417 x11::GetArrayProperty(xwindow_, wm_state_atom, &atom_list);
418 x11::SetArrayProperty(child, wm_state_atom, x11::Atom::ATOM,
419 atom_list);
420 }
421 }
422 }
423 }
424 }
425