| // Copyright (c) 2012 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 "remoting/host/desktop_resizer_x11.h" |
| |
| #include <string.h> |
| |
| #include "base/command_line.h" |
| #include "base/cxx17_backports.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "remoting/base/logging.h" |
| #include "remoting/host/linux/x11_util.h" |
| #include "ui/gfx/x/future.h" |
| #include "ui/gfx/x/randr.h" |
| #include "ui/gfx/x/scoped_ignore_errors.h" |
| |
| // On Linux, we use the xrandr extension to change the desktop resolution. In |
| // curtain mode, we do exact resize where supported (currently only using a |
| // patched Xvfb server). Otherwise, we try to pick the best resolution from the |
| // existing modes. |
| // |
| // Xrandr has a number of restrictions that make exact resize more complex: |
| // |
| // 1. It's not possible to change the resolution of an existing mode. Instead, |
| // the mode must be deleted and recreated. |
| // 2. It's not possible to delete a mode that's in use. |
| // 3. Errors are communicated via Xlib's spectacularly unhelpful mechanism |
| // of terminating the process unless you install an error handler. |
| // |
| // The basic approach is as follows: |
| // |
| // 1. Create a new mode with the correct resolution; |
| // 2. Switch to the new mode; |
| // 3. Delete the old mode. |
| // |
| // Since the new mode must have a different name, and we want the current mode |
| // name to be consistent, we then additionally: |
| // |
| // 4. Recreate the old mode at the new resolution; |
| // 5. Switch to the old mode; |
| // 6. Delete the temporary mode. |
| // |
| // Name consistency will allow a future CL to disable resize-to-client if the |
| // user has changed the mode to something other than "Chrome Remote Desktop |
| // client resolution". It doesn't make the code significantly more complex. |
| |
| namespace { |
| |
| constexpr auto kInvalidMode = static_cast<x11::RandR::Mode>(0); |
| |
| int PixelsToMillimeters(int pixels, int dpi) { |
| DCHECK(dpi != 0); |
| |
| const double kMillimetersPerInch = 25.4; |
| |
| // (pixels / dpi) is the length in inches. Multiplying by |
| // kMillimetersPerInch converts to mm. Multiplication is done first to |
| // avoid integer division. |
| return static_cast<int>(kMillimetersPerInch * pixels / dpi); |
| } |
| |
| // TODO(jamiewalch): Use the correct DPI for the mode: http://crbug.com/172405. |
| const int kDefaultDPI = 96; |
| |
| } // namespace |
| |
| namespace remoting { |
| |
| ScreenResources::ScreenResources() = default; |
| |
| ScreenResources::~ScreenResources() = default; |
| |
| bool ScreenResources::Refresh(x11::RandR* randr, x11::Window window) { |
| resources_ = nullptr; |
| if (auto response = randr->GetScreenResourcesCurrent({window}).Sync()) |
| resources_ = std::move(response.reply); |
| return resources_ != nullptr; |
| } |
| |
| x11::RandR::Mode ScreenResources::GetIdForMode(const std::string& name) { |
| CHECK(resources_); |
| const char* names = reinterpret_cast<const char*>(resources_->names.data()); |
| for (const auto& mode_info : resources_->modes) { |
| std::string mode_name(names, mode_info.name_len); |
| names += mode_info.name_len; |
| if (name == mode_name) |
| return static_cast<x11::RandR::Mode>(mode_info.id); |
| } |
| return kInvalidMode; |
| } |
| |
| x11::RandR::Output ScreenResources::GetOutput() { |
| CHECK(resources_); |
| return resources_->outputs[0]; |
| } |
| |
| x11::RandR::Crtc ScreenResources::GetCrtc() { |
| CHECK(resources_); |
| return resources_->crtcs[0]; |
| } |
| |
| x11::RandR::GetScreenResourcesCurrentReply* ScreenResources::get() { |
| return resources_.get(); |
| } |
| |
| DesktopResizerX11::DesktopResizerX11() |
| : connection_(x11::Connection::Get()), |
| randr_(&connection_->randr()), |
| screen_(&connection_->default_screen()), |
| root_(screen_->root), |
| exact_resize_(base::CommandLine::ForCurrentProcess()->HasSwitch( |
| "server-supports-exact-resize")) { |
| has_randr_ = randr_->present(); |
| if (!has_randr_) |
| return; |
| // Let the server know the client version so it sends us data consistent with |
| // xcbproto's definitions. We don't care about the returned server version, |
| // so no need to sync. |
| randr_->QueryVersion({x11::RandR::major_version, x11::RandR::minor_version}); |
| randr_->SelectInput({root_, x11::RandR::NotifyMask::ScreenChange}); |
| } |
| |
| DesktopResizerX11::~DesktopResizerX11() = default; |
| |
| ScreenResolution DesktopResizerX11::GetCurrentResolution() { |
| // Process pending events so that the connection setup data is updated |
| // with the correct display metrics. |
| if (has_randr_) |
| connection_->DispatchAll(); |
| |
| ScreenResolution result( |
| webrtc::DesktopSize(connection_->default_screen().width_in_pixels, |
| connection_->default_screen().height_in_pixels), |
| webrtc::DesktopVector(kDefaultDPI, kDefaultDPI)); |
| return result; |
| } |
| |
| std::list<ScreenResolution> DesktopResizerX11::GetSupportedResolutions( |
| const ScreenResolution& preferred) { |
| std::list<ScreenResolution> result; |
| if (!has_randr_) |
| return result; |
| if (exact_resize_) { |
| // Clamp the specified size to something valid for the X server. |
| if (auto response = randr_->GetScreenSizeRange({root_}).Sync()) { |
| int width = |
| base::clamp(static_cast<uint16_t>(preferred.dimensions().width()), |
| response->min_width, response->max_width); |
| int height = |
| base::clamp(static_cast<uint16_t>(preferred.dimensions().height()), |
| response->min_height, response->max_height); |
| // Additionally impose a minimum size of 640x480, since anything smaller |
| // doesn't seem very useful. |
| ScreenResolution actual( |
| webrtc::DesktopSize(std::max(640, width), std::max(480, height)), |
| webrtc::DesktopVector(kDefaultDPI, kDefaultDPI)); |
| result.push_back(actual); |
| } |
| } else { |
| // Retrieve supported resolutions with RandR |
| if (auto response = randr_->GetScreenInfo({root_}).Sync()) { |
| for (const auto& size : response->sizes) { |
| result.emplace_back(webrtc::DesktopSize(size.width, size.height), |
| webrtc::DesktopVector(kDefaultDPI, kDefaultDPI)); |
| } |
| } |
| } |
| return result; |
| } |
| |
| void DesktopResizerX11::SetResolution(const ScreenResolution& resolution) { |
| if (!has_randr_) |
| return; |
| |
| // Ignore X errors encountered while resizing the display. We might hit an |
| // error, for example if xrandr has been used to add a mode with the same |
| // name as our temporary mode, or to remove the "client resolution" mode. We |
| // don't want to terminate the process if this happens. |
| x11::ScopedIgnoreErrors ignore_errors(connection_); |
| |
| // Grab the X server while we're changing the display resolution. This ensures |
| // that the display configuration doesn't change under our feet. |
| ScopedXGrabServer grabber(connection_); |
| |
| if (exact_resize_) |
| SetResolutionNewMode(resolution); |
| else |
| SetResolutionExistingMode(resolution); |
| } |
| |
| void DesktopResizerX11::RestoreResolution(const ScreenResolution& original) { |
| SetResolution(original); |
| } |
| |
| void DesktopResizerX11::SetResolutionNewMode( |
| const ScreenResolution& resolution) { |
| // The name of the mode representing the current client view resolution and |
| // the temporary mode used for the reasons described at the top of this file. |
| // The former should be localized if it's user-visible; the latter only |
| // exists briefly and does not need to localized. |
| const char* kModeName = "Chrome Remote Desktop client resolution"; |
| const char* kTempModeName = "Chrome Remote Desktop temporary mode"; |
| |
| // Actually do the resize operation, preserving the current mode name. Note |
| // that we have to detach the output from any mode in order to resize it |
| // (strictly speaking, this is only required when reducing the size, but it |
| // seems safe to do it regardless). |
| HOST_LOG << "Changing desktop size to " << resolution.dimensions().width() |
| << "x" << resolution.dimensions().height(); |
| |
| // TODO(lambroslambrou): Use the DPI from client size information. |
| uint32_t width_mm = |
| PixelsToMillimeters(resolution.dimensions().width(), kDefaultDPI); |
| uint32_t height_mm = |
| PixelsToMillimeters(resolution.dimensions().height(), kDefaultDPI); |
| CreateMode(kTempModeName, resolution.dimensions().width(), |
| resolution.dimensions().height()); |
| SwitchToMode(nullptr); |
| randr_->SetScreenSize( |
| {root_, static_cast<uint16_t>(resolution.dimensions().width()), |
| static_cast<uint16_t>(resolution.dimensions().height()), width_mm, |
| height_mm}); |
| SwitchToMode(kTempModeName); |
| DeleteMode(kModeName); |
| CreateMode(kModeName, resolution.dimensions().width(), |
| resolution.dimensions().height()); |
| SwitchToMode(kModeName); |
| DeleteMode(kTempModeName); |
| } |
| |
| void DesktopResizerX11::SetResolutionExistingMode( |
| const ScreenResolution& resolution) { |
| if (auto config = randr_->GetScreenInfo({root_}).Sync()) { |
| x11::RandR::Rotation current_rotation = config->rotation; |
| const std::vector<x11::RandR::ScreenSize>& sizes = config->sizes; |
| for (size_t i = 0; i < sizes.size(); ++i) { |
| if (sizes[i].width == resolution.dimensions().width() && |
| sizes[i].height == resolution.dimensions().height()) { |
| randr_->SetScreenConfig({ |
| .window = root_, |
| .timestamp = x11::Time::CurrentTime, |
| .config_timestamp = config->config_timestamp, |
| .sizeID = static_cast<uint16_t>(i), |
| .rotation = current_rotation, |
| .rate = 0, |
| }); |
| break; |
| } |
| } |
| } |
| } |
| |
| void DesktopResizerX11::CreateMode(const char* name, int width, int height) { |
| x11::RandR::ModeInfo mode; |
| mode.width = width; |
| mode.height = height; |
| mode.name_len = strlen(name); |
| randr_->CreateMode({root_, mode, name}); |
| |
| if (!resources_.Refresh(randr_, root_)) |
| return; |
| x11::RandR::Mode mode_id = resources_.GetIdForMode(name); |
| if (mode_id == kInvalidMode) |
| return; |
| randr_->AddOutputMode({ |
| resources_.GetOutput(), |
| mode_id, |
| }); |
| } |
| |
| void DesktopResizerX11::DeleteMode(const char* name) { |
| x11::RandR::Mode mode_id = resources_.GetIdForMode(name); |
| if (mode_id != kInvalidMode) { |
| randr_->DeleteOutputMode({resources_.GetOutput(), mode_id}); |
| randr_->DestroyMode({mode_id}); |
| resources_.Refresh(randr_, root_); |
| } |
| } |
| |
| void DesktopResizerX11::SwitchToMode(const char* name) { |
| auto mode_id = kInvalidMode; |
| std::vector<x11::RandR::Output> outputs; |
| if (name) { |
| mode_id = resources_.GetIdForMode(name); |
| CHECK_NE(mode_id, kInvalidMode); |
| outputs = resources_.get()->outputs; |
| } |
| const auto* resources = resources_.get(); |
| randr_->SetCrtcConfig({ |
| .crtc = resources_.GetCrtc(), |
| .timestamp = x11::Time::CurrentTime, |
| .config_timestamp = resources->config_timestamp, |
| .x = 0, |
| .y = 0, |
| .mode = mode_id, |
| .rotation = x11::RandR::Rotation::Rotate_0, |
| .outputs = outputs, |
| }); |
| } |
| |
| } // namespace remoting |