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