mirror of
				https://bitbucket.org/chromiumembedded/cef
				synced 2025-06-05 21:39:12 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			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);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 |