• 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 "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