#include "remoting/host/linux/desktop_resizer_x11.h"
#include <gio/gio.h>
#include <algorithm>
#include <cstdlib>
#include <iterator>
#include <memory>
#include <ranges>
#include <string>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/system/sys_info.h"
#include "base/types/cxx23_to_underlying.h"
#include "remoting/base/logging.h"
#include "remoting/host/desktop_geometry.h"
#include "remoting/host/linux/gvariant_ref.h"
#include "remoting/host/linux/x11_display_util.h"
#include "remoting/host/linux/x11_util.h"
#include "ui/base/glib/gsettings.h"
#include "ui/base/glib/scoped_gobject.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/x/future.h"
#include "ui/gfx/x/randr.h"
#include "ui/gfx/x/x11_crtc_resizer.h"
namespace remoting {
namespace {
const int kDefaultDPI = 96;
constexpr base::TimeDelta kGnomeWaitTime = base::Seconds(1);
uint32_t GetDotClockForModeInfo() {
static int proc_num = base::SysInfo::NumberOfProcessors();
if (proc_num > 16) {
return 120 * 1e6;
}
return 60 * 1e6;
}
}
DesktopResizerX11::DesktopResizerX11()
: connection_(x11::Connection::Get()),
randr_output_manager_("CRD_", GetDotClockForModeInfo()),
is_virtual_session_(IsVirtualSession(connection_)) {
has_randr_ = RandR()->present();
if (!has_randr_) {
return;
}
RandR()->SelectInput({RootWindow(), x11::RandR::NotifyMask::ScreenChange});
gnome_display_config_.Init();
registry_ = ui::GSettingsNew("org.gnome.desktop.interface");
}
DesktopResizerX11::~DesktopResizerX11() = default;
ScreenResolution DesktopResizerX11::GetCurrentResolution(
webrtc::ScreenId screen_id) {
if (has_randr_) {
connection_->DispatchAll();
}
auto reply = RandR()->GetMonitors({RootWindow()}).Sync();
if (reply) {
for (const auto& monitor : reply->monitors) {
if (static_cast<x11::RandRMonitorConfig::ScreenId>(monitor.name) !=
static_cast<x11::RandRMonitorConfig::ScreenId>(screen_id)) {
continue;
}
gfx::Vector2d dpi = GetMonitorDpi(monitor);
return ScreenResolution(
webrtc::DesktopSize(monitor.width, monitor.height),
webrtc::DesktopVector(dpi.x(), dpi.y()));
}
}
LOG(ERROR) << "Cannot find current resolution for screen ID " << screen_id
<< ". Resolution of the default screen will be returned.";
return ScreenResolution(
webrtc::DesktopSize(connection_->default_screen().width_in_pixels,
connection_->default_screen().height_in_pixels),
webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
}
std::list<ScreenResolution> DesktopResizerX11::GetSupportedResolutions(
const ScreenResolution& preferred,
webrtc::ScreenId screen_id) {
std::list<ScreenResolution> result;
if (!has_randr_ || !is_virtual_session_) {
return result;
}
if (auto response = RandR()->GetScreenSizeRange({RootWindow()}).Sync()) {
int width =
std::clamp(static_cast<uint16_t>(preferred.dimensions().width()),
response->min_width, response->max_width);
int height =
std::clamp(static_cast<uint16_t>(preferred.dimensions().height()),
response->min_height, response->max_height);
result.emplace_back(
webrtc::DesktopSize(std::max(640, width), std::max(480, height)),
preferred.dpi());
}
return result;
}
void DesktopResizerX11::SetResolution(const ScreenResolution& resolution,
webrtc::ScreenId screen_id) {
if (!has_randr_ || !is_virtual_session_) {
return;
}
x11::ScopedXGrabServer grabber(connection_);
std::vector<x11::RandR::MonitorInfo> monitors;
if (!randr_output_manager_.TryGetCurrentMonitors(monitors)) {
return;
}
for (const auto& monitor : monitors) {
if (static_cast<x11::RandRMonitorConfig::ScreenId>(monitor.name) !=
static_cast<x11::RandRMonitorConfig::ScreenId>(screen_id)) {
continue;
}
if (monitor.outputs.size() != 1) {
LOG(ERROR) << "Monitor " << screen_id
<< " has unexpected #outputs: " << monitor.outputs.size();
return;
}
if (!monitor.automatic) {
LOG(ERROR) << "Not resizing Monitor " << screen_id
<< " that was created manually.";
return;
}
SetResolutionForOutput(monitor.outputs[0], resolution);
return;
}
LOG(ERROR) << "Monitor " << screen_id << " not found.";
}
void DesktopResizerX11::RestoreResolution(const ScreenResolution& original,
webrtc::ScreenId screen_id) {
SetResolution(original, screen_id);
}
void DesktopResizerX11::SetVideoLayout(const protocol::VideoLayout& layout) {
if (!has_randr_ || !is_virtual_session_) {
return;
}
x11::RandRMonitorLayout desktop_layouts;
if (layout.has_primary_screen_id()) {
desktop_layouts.primary_screen_id = layout.primary_screen_id();
}
for (const auto& track : layout.video_track()) {
desktop_layouts.configs.emplace_back(
track.has_screen_id() ? std::make_optional(track.screen_id())
: std::nullopt,
gfx::Rect(track.position_x(), track.position_y(), track.width(),
track.height()),
gfx::Vector2d(track.x_dpi(), track.y_dpi()));
}
randr_output_manager_.SetLayout(desktop_layouts);
}
void DesktopResizerX11::SetResolutionForOutput(
x11::RandR::Output output,
const ScreenResolution& resolution) {
HOST_LOG << "Resizing RANDR Output " << base::to_underlying(output) << " to "
<< resolution.dimensions().width() << "x"
<< resolution.dimensions().height();
randr_output_manager_.SetResolutionForOutput(
output,
gfx::Size(resolution.dimensions().width(),
resolution.dimensions().height()),
gfx::Vector2d(resolution.dpi().x(), resolution.dpi().y()));
if (registry_ &&
g_settings_get_uint(registry_.get(), "scaling-factor") == 0U) {
requested_dpi_ = resolution.dpi().x();
gnome_delay_timer_.Start(FROM_HERE, kGnomeWaitTime, this,
&DesktopResizerX11::RequestGnomeDisplayConfig);
}
}
void DesktopResizerX11::RequestGnomeDisplayConfig() {
gnome_display_config_.GetMonitorsConfig(
base::BindOnce(&DesktopResizerX11::OnGnomeDisplayConfigReceived,
base::Unretained(this)));
}
void DesktopResizerX11::OnGnomeDisplayConfigReceived(
GnomeDisplayConfig config) {
auto monitor_iter =
std::ranges::find_if(config.monitors, [](const auto& entry) {
return entry.second.GetCurrentMode() != nullptr;
});
if (monitor_iter == std::ranges::end(config.monitors)) {
LOG(ERROR) << "No enabled monitor found in GNOME config.";
return;
}
const auto& monitor = monitor_iter->second;
if (monitor.scale == 0) {
return;
}
double text_scaling_factor =
static_cast<double>(requested_dpi_) / kDefaultDPI / monitor.scale;
HOST_LOG << "Target DPI = " << requested_dpi_
<< ", GNOME scale = " << monitor.scale
<< ", calculated text-scaling = " << text_scaling_factor;
if (!registry_ ||
!g_settings_set_double(registry_.get(), "text-scaling-factor",
text_scaling_factor)) {
LOG(WARNING) << "Failed to set text-scaling-factor.";
}
}
bool DesktopResizerX11::supportsHighDpiResize() {
ScopedGObject<GDBusConnection> connection =
TakeGObject(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr));
if (!connection) {
return false;
}
ScopedGObject<GDBusProxy> dbus = TakeGObject(g_dbus_proxy_new_sync(
connection, G_DBUS_PROXY_FLAGS_NONE, nullptr, "org.freedesktop.DBus",
"/org/freedesktop/DBus", "org.freedesktop.DBus", nullptr, nullptr));
if (!dbus) {
return false;
}
auto has_owner = GVariantRef<>::Take(g_dbus_proxy_call_sync(
dbus, "NameHasOwner", g_variant_new("(s)", "org.gnome.Shell"),
G_DBUS_CALL_FLAGS_NONE, -1, nullptr, nullptr));
auto has_owner_bool = GVariantRef<"(b)">::TryFrom(has_owner);
if (!has_owner_bool.has_value()) {
return false;
}
return has_owner_bool->get<0>().Into<bool>();
}
}