• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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