blob: 003952a4baa1ba571a5459ee9e1cc68777519e99 [file]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/linux/active_display_monitor_x11.h"
#include <memory>
#include <utility>
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "remoting/base/logging.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
#include "ui/base/x/x11_display_util.h"
#include "ui/base/x/x11_util.h"
#include "ui/gfx/x/atom_cache.h"
#include "ui/gfx/x/connection.h"
#include "ui/gfx/x/event.h"
#include "ui/gfx/x/future.h"
#include "ui/gfx/x/randr.h"
#include "ui/gfx/x/window_event_manager.h"
namespace remoting {
namespace {
// For X11, webrtc::ScreenId is implemented as a RANDR Monitor ID, which
// requires XRANDR 1.5.
constexpr std::pair<uint32_t, uint32_t> kMinRandrVersion{1, 5};
} // namespace
// This class uses Chromium X11 utilities which create global singletons, so
// this code needs to run on the UI thread.
class ActiveDisplayMonitorX11::Core : public x11::EventObserver {
public:
// `callback` will be run on the UI thread whenever the active display is
// changed. The callback is responsible for posting a task back to the
// caller's thread.
explicit Core(ActiveDisplayMonitor::Callback callback);
Core(const Core&) = delete;
Core& operator=(const Core&) = delete;
~Core() override;
void Init();
// x11::EventObserver implementation.
void OnEvent(const x11::Event& xevent) override;
private:
// Called when the active display might have changed. This gets the
// currently-focused window and determines which display it is on. If the
// display is changed, it is sent to `callback_`.
void GetAndSendActiveDisplay();
// Returns the window with input-focus, or x11::Window::None. The returned
// window may be a descendent of a top-level window.
x11::Window GetFocusedWindow() const;
// Returns the ancestor of 'window' which is a child of the root window. The
// returned window may be a window-manager frame. On error, this returns
// x11::Window::None.
x11::Window GetTopLevelWindow(x11::Window window) const;
bool IsWindowVisible(x11::Window window) const;
// Returns the display that the window is positioned on. On error, this
// returns webrtc::kInvalidScreenId.
webrtc::ScreenId GetDisplayForWindow(x11::Window window) const;
ActiveDisplayMonitor::Callback callback_;
raw_ptr<x11::Connection> connection_ = nullptr;
x11::ScopedEventSelector root_window_event_selector_;
x11::Atom net_active_window_atom_{};
webrtc::ScreenId current_active_display_{webrtc::kInvalidScreenId};
};
////////////////////////////////////////////////////////////////////////////////
// ActiveDisplayMonitorX11::Core implementation.
ActiveDisplayMonitorX11::Core::Core(ActiveDisplayMonitor::Callback callback)
: callback_(callback) {}
ActiveDisplayMonitorX11::Core::~Core() {
// Does nothing if AddEventObserver() was not called.
connection_->RemoveEventObserver(this);
}
void ActiveDisplayMonitorX11::Core::Init() {
connection_ = x11::Connection::Get();
auto xrandr_version = connection_->randr_version();
if (xrandr_version < kMinRandrVersion) {
LOG(ERROR) << "XRANDR version (" << xrandr_version.first << ", "
<< xrandr_version.second << ") is unsupported.";
return;
}
root_window_event_selector_ = connection_->ScopedSelectEvent(
ui::GetX11RootWindow(), x11::EventMask::PropertyChange);
net_active_window_atom_ = x11::GetAtom("_NET_ACTIVE_WINDOW");
connection_->AddEventObserver(this);
}
void ActiveDisplayMonitorX11::Core::OnEvent(const x11::Event& xevent) {
const auto* property_notify = xevent.As<x11::PropertyNotifyEvent>();
if (!property_notify) {
return;
}
if (property_notify->window != ui::GetX11RootWindow()) {
return;
}
if (property_notify->atom != net_active_window_atom_) {
return;
}
// Check the property was not deleted.
if (property_notify->state != x11::Property::NewValue) {
return;
}
GetAndSendActiveDisplay();
}
void ActiveDisplayMonitorX11::Core::GetAndSendActiveDisplay() {
x11::Window focused_window = GetFocusedWindow();
if (focused_window == x11::Window::None) {
HOST_LOG << "No window is focused.";
return;
}
focused_window = GetTopLevelWindow(focused_window);
if (focused_window == x11::Window::None) {
return;
}
if (!IsWindowVisible(focused_window)) {
// It's not clear if a window-manager would ever move input-focus to a
// hidden window? It seems safer to not activate a different display if the
// user can't see the window on it.
HOST_LOG << "Focused window " << std::to_underlying(focused_window)
<< " is hidden, ignoring.";
return;
}
webrtc::ScreenId active_display = GetDisplayForWindow(focused_window);
if (active_display == webrtc::kInvalidScreenId) {
LOG(ERROR) << "Failed to determine display for window "
<< std::to_underlying(focused_window);
return;
}
if (active_display == current_active_display_) {
return;
}
current_active_display_ = active_display;
HOST_LOG << "Active display changed to " << active_display
<< " due to window " << std::to_underlying(focused_window);
callback_.Run(active_display);
}
x11::Window ActiveDisplayMonitorX11::Core::GetFocusedWindow() const {
x11::Window focused_window;
if (!x11::Connection::Get()->GetPropertyAs(
ui::GetX11RootWindow(), net_active_window_atom_, &focused_window)) {
LOG(ERROR) << "Failed to read _NET_ACTIVE_WINDOW on root window.";
return x11::Window::None;
}
return focused_window;
}
x11::Window ActiveDisplayMonitorX11::Core::GetTopLevelWindow(
x11::Window window) const {
// This will not loop infinitely, as long as the X11 server respects the
// protocol and provides a correct tree of windows.
while (true) {
auto query_response = connection_->QueryTree({window}).Sync();
if (!query_response) {
LOG(ERROR) << "QueryTree failed for window "
<< std::to_underlying(window);
return x11::Window::None;
}
if (query_response->parent == x11::Window::None) {
// The root window was provided, so just return it directly. This is not
// expected to happen, but any program could set arbitrary values for the
// root window's _NET_ACTIVE_WINDOW property, so this code should handle
// all possibilities.
break;
}
if (query_response->root == query_response->parent) {
// Found top-level window.
break;
}
window = query_response->parent;
}
return window;
}
bool ActiveDisplayMonitorX11::Core::IsWindowVisible(x11::Window window) const {
auto attributes = connection_->GetWindowAttributes({window}).Sync();
if (!attributes) {
LOG(ERROR) << "Failed to get attributes for window "
<< std::to_underlying(window);
return false;
}
return attributes->map_state == x11::MapState::Viewable;
}
webrtc::ScreenId ActiveDisplayMonitorX11::Core::GetDisplayForWindow(
x11::Window window) const {
webrtc::ScreenId result = webrtc::kInvalidScreenId;
auto geometry = connection_->GetGeometry(window).Sync();
if (!geometry) {
LOG(ERROR) << "GetGeometry() failed for window "
<< std::to_underlying(window);
return result;
}
auto monitors =
connection_->randr().GetMonitors({ui::GetX11RootWindow()}).Sync();
if (!monitors) {
LOG(ERROR) << "RANDR GetMonitors() failed.";
return result;
}
auto window_rect = webrtc::DesktopRect::MakeXYWH(
geometry->x, geometry->y, geometry->width, geometry->height);
int best_area = 0;
for (const x11::RandR::MonitorInfo& info : monitors->monitors) {
auto monitor_rect =
webrtc::DesktopRect::MakeXYWH(info.x, info.y, info.width, info.height);
monitor_rect.IntersectWith(window_rect);
int area = monitor_rect.width() * monitor_rect.height();
// Strict inequality ensures that kInvalidScreenId is returned in the case
// that all overlaps have zero area.
if (area > best_area) {
// The X11 desktop-capturer uses the `name` atom as the screen ID.
result = std::to_underlying(info.name);
best_area = area;
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
// ActiveDisplayMonitorX11 implementation.
ActiveDisplayMonitorX11::ActiveDisplayMonitorX11(
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
Callback active_display_callback)
: active_display_callback_(active_display_callback) {
Callback wrapped_callback =
base::BindPostTaskToCurrentDefault(active_display_callback_.callback());
core_.emplace(ui_task_runner, wrapped_callback);
core_.AsyncCall(&ActiveDisplayMonitorX11::Core::Init);
}
ActiveDisplayMonitorX11::~ActiveDisplayMonitorX11() = default;
} // namespace remoting