desktop_resizer_x11.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "remoting/host/desktop_resizer_x11.h"
  5. #include <memory>
  6. #include <string>
  7. #include "base/command_line.h"
  8. #include "base/cxx17_backports.h"
  9. #include "base/memory/ptr_util.h"
  10. #include "base/stl_util.h"
  11. #include "base/strings/string_number_conversions.h"
  12. #include "remoting/base/logging.h"
  13. #include "remoting/host/linux/x11_util.h"
  14. #include "remoting/host/x11_crtc_resizer.h"
  15. #include "ui/gfx/x/future.h"
  16. #include "ui/gfx/x/randr.h"
  17. #include "ui/gfx/x/scoped_ignore_errors.h"
  18. // On Linux, we use the xrandr extension to change the desktop resolution. In
  19. // curtain mode, we do exact resize where supported. Otherwise, we try to pick
  20. // the best resolution from the existing modes.
  21. //
  22. // Xrandr has a number of restrictions that make exact resize more complex:
  23. //
  24. // 1. It's not possible to change the resolution of an existing mode. Instead,
  25. // the mode must be deleted and recreated.
  26. // 2. It's not possible to delete a mode that's in use.
  27. // 3. Errors are communicated via Xlib's spectacularly unhelpful mechanism
  28. // of terminating the process unless you install an error handler.
  29. // 4. The root window size must always enclose any enabled Outputs (that is,
  30. // any output which is attached to a CRTC).
  31. // 5. An Output cannot be given properties (xy-offsets, mode) which would
  32. // extend its rectangle beyond the root window size.
  33. //
  34. // Since we want the current mode name to be consistent (for each Output), the
  35. // approach is as follows:
  36. //
  37. // 1. Fetch information about all the active (enabled) CRTCs.
  38. // 2. Disable the RANDR Output being resized.
  39. // 3. Delete the CRD mode, if it exists.
  40. // 4. Create the CRD mode at the new resolution, and add it to the Output's
  41. // list of modes.
  42. // 5. Adjust the properties (in memory) of any CRTCs to be modified:
  43. // * Width/height (mode) of the CRTC being resized.
  44. // * xy-offsets to avoid overlapping CRTCs.
  45. // 6. Disable any CRTCs that might prevent changing the root window size.
  46. // 7. Compute the bounding rectangle of all CRTCs (after adjustment), and set
  47. // it as the new root window size.
  48. // 8. Apply all adjusted CRTC properties to the CRTCs. This will set the
  49. // Output being resized to the new CRD mode (which re-enables it), and it
  50. // will re-enable any other CRTCs that were disabled.
  51. namespace {
  52. constexpr auto kInvalidMode = static_cast<x11::RandR::Mode>(0);
  53. constexpr auto kDisabledCrtc = static_cast<x11::RandR::Crtc>(0);
  54. int PixelsToMillimeters(int pixels, int dpi) {
  55. DCHECK(dpi != 0);
  56. const double kMillimetersPerInch = 25.4;
  57. // (pixels / dpi) is the length in inches. Multiplying by
  58. // kMillimetersPerInch converts to mm. Multiplication is done first to
  59. // avoid integer division.
  60. return static_cast<int>(kMillimetersPerInch * pixels / dpi);
  61. }
  62. // TODO(jamiewalch): Use the correct DPI for the mode: http://crbug.com/172405.
  63. const int kDefaultDPI = 96;
  64. } // namespace
  65. namespace remoting {
  66. ScreenResources::ScreenResources() = default;
  67. ScreenResources::~ScreenResources() = default;
  68. bool ScreenResources::Refresh(x11::RandR* randr, x11::Window window) {
  69. resources_ = nullptr;
  70. if (auto response = randr->GetScreenResourcesCurrent({window}).Sync())
  71. resources_ = std::move(response.reply);
  72. return resources_ != nullptr;
  73. }
  74. x11::RandR::Mode ScreenResources::GetIdForMode(const std::string& name) {
  75. CHECK(resources_);
  76. const char* names = reinterpret_cast<const char*>(resources_->names.data());
  77. for (const auto& mode_info : resources_->modes) {
  78. std::string mode_name(names, mode_info.name_len);
  79. names += mode_info.name_len;
  80. if (name == mode_name)
  81. return static_cast<x11::RandR::Mode>(mode_info.id);
  82. }
  83. return kInvalidMode;
  84. }
  85. x11::RandR::GetScreenResourcesCurrentReply* ScreenResources::get() {
  86. return resources_.get();
  87. }
  88. DesktopResizerX11::DesktopResizerX11()
  89. : connection_(x11::Connection::Get()),
  90. randr_(&connection_->randr()),
  91. screen_(&connection_->default_screen()),
  92. root_(screen_->root),
  93. exact_resize_(base::CommandLine::ForCurrentProcess()->HasSwitch(
  94. "server-supports-exact-resize")) {
  95. has_randr_ = randr_->present();
  96. if (!has_randr_)
  97. return;
  98. // Let the server know the client version so it sends us data consistent with
  99. // xcbproto's definitions. We don't care about the returned server version,
  100. // so no need to sync.
  101. randr_->QueryVersion({x11::RandR::major_version, x11::RandR::minor_version});
  102. randr_->SelectInput({root_, x11::RandR::NotifyMask::ScreenChange});
  103. }
  104. DesktopResizerX11::~DesktopResizerX11() = default;
  105. ScreenResolution DesktopResizerX11::GetCurrentResolution(
  106. webrtc::ScreenId screen_id) {
  107. // Process pending events so that the connection setup data is updated
  108. // with the correct display metrics.
  109. if (has_randr_)
  110. connection_->DispatchAll();
  111. ScreenResolution result(
  112. webrtc::DesktopSize(connection_->default_screen().width_in_pixels,
  113. connection_->default_screen().height_in_pixels),
  114. webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
  115. return result;
  116. }
  117. std::list<ScreenResolution> DesktopResizerX11::GetSupportedResolutions(
  118. const ScreenResolution& preferred,
  119. webrtc::ScreenId screen_id) {
  120. std::list<ScreenResolution> result;
  121. if (!has_randr_)
  122. return result;
  123. if (exact_resize_) {
  124. // Clamp the specified size to something valid for the X server.
  125. if (auto response = randr_->GetScreenSizeRange({root_}).Sync()) {
  126. int width =
  127. base::clamp(static_cast<uint16_t>(preferred.dimensions().width()),
  128. response->min_width, response->max_width);
  129. int height =
  130. base::clamp(static_cast<uint16_t>(preferred.dimensions().height()),
  131. response->min_height, response->max_height);
  132. // Additionally impose a minimum size of 640x480, since anything smaller
  133. // doesn't seem very useful.
  134. ScreenResolution actual(
  135. webrtc::DesktopSize(std::max(640, width), std::max(480, height)),
  136. webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
  137. result.push_back(actual);
  138. }
  139. } else {
  140. // Retrieve supported resolutions with RandR
  141. if (auto response = randr_->GetScreenInfo({root_}).Sync()) {
  142. for (const auto& size : response->sizes) {
  143. result.emplace_back(webrtc::DesktopSize(size.width, size.height),
  144. webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
  145. }
  146. }
  147. }
  148. return result;
  149. }
  150. void DesktopResizerX11::SetResolution(const ScreenResolution& resolution,
  151. webrtc::ScreenId screen_id) {
  152. if (!has_randr_)
  153. return;
  154. // Ignore X errors encountered while resizing the display. We might hit an
  155. // error, for example if xrandr has been used to add a mode with the same
  156. // name as our mode, or to remove it. We don't want to terminate the process
  157. // if this happens.
  158. x11::ScopedIgnoreErrors ignore_errors(connection_);
  159. // Grab the X server while we're changing the display resolution. This ensures
  160. // that the display configuration doesn't change under our feet.
  161. ScopedXGrabServer grabber(connection_);
  162. if (!resources_.Refresh(randr_, root_))
  163. return;
  164. // RANDR does not allow fetching information on a particular monitor. So
  165. // fetch all of them and try to find the requested monitor.
  166. auto reply = randr_->GetMonitors({root_}).Sync();
  167. if (!reply) {
  168. return;
  169. }
  170. for (const auto& monitor : reply->monitors) {
  171. if (static_cast<webrtc::ScreenId>(monitor.name) != screen_id) {
  172. continue;
  173. }
  174. if (monitor.outputs.size() != 1) {
  175. // This implementation only supports resizing a Monitor attached to a
  176. // single output. The case where size() > 1 should never occur with
  177. // Xorg+video-dummy.
  178. // TODO(crbug.com/1326339): Maybe support resizing a Monitor not
  179. // attached to any Output?
  180. LOG(ERROR) << "Monitor " << screen_id
  181. << " has unexpected #outputs: " << monitor.outputs.size();
  182. return;
  183. }
  184. if (!monitor.automatic) {
  185. // This implementation only supports resizing synthesized Monitors which
  186. // automatically track their Outputs.
  187. // TODO(crbug.com/1326339): Maybe support resizing manually-created
  188. // Monitors?
  189. LOG(ERROR) << "Not resizing Monitor " << screen_id
  190. << " that was created manually.";
  191. return;
  192. }
  193. auto output = monitor.outputs[0];
  194. if (exact_resize_)
  195. SetResolutionNewMode(output, resolution);
  196. else
  197. SetResolutionExistingMode(resolution);
  198. return;
  199. }
  200. LOG(ERROR) << "Monitor " << screen_id << " not found.";
  201. }
  202. void DesktopResizerX11::RestoreResolution(const ScreenResolution& original,
  203. webrtc::ScreenId screen_id) {
  204. SetResolution(original, screen_id);
  205. }
  206. void DesktopResizerX11::SetResolutionNewMode(
  207. x11::RandR::Output output,
  208. const ScreenResolution& resolution) {
  209. // The name of the mode representing the current client view resolution. This
  210. // must be unique per Output, so that Outputs can be resized independently.
  211. std::string mode_name =
  212. "CRD_" + base::NumberToString(base::to_underlying(output));
  213. // Actually do the resize operation, preserving the current mode name. Note
  214. // that we have to detach the output from the mode in order to delete the
  215. // mode and re-create it with the new resolution. The output may also need to
  216. // be detached from all modes in order to reduce the root window size.
  217. HOST_LOG << "Changing desktop size to " << resolution.dimensions().width()
  218. << "x" << resolution.dimensions().height();
  219. X11CrtcResizer resizer(resources_.get(), randr_);
  220. resizer.FetchActiveCrtcs();
  221. auto crtc = resizer.GetCrtcForOutput(output);
  222. if (crtc == kDisabledCrtc) {
  223. // This is not expected to happen. Disabled Outputs are not expected to
  224. // have any Monitor, but |output| was found in the RRGetMonitors response,
  225. // so it should have a CRTC attached.
  226. LOG(ERROR) << "No CRTC found for output: " << base::to_underlying(output);
  227. return;
  228. }
  229. // Disable the output now, so that the old mode can be deleted and the new
  230. // mode created and added to the output's available modes. The previous size
  231. // and offsets will be stored in |resizer|.
  232. resizer.DisableCrtc(crtc);
  233. DeleteMode(output, mode_name);
  234. auto mode = CreateMode(output, mode_name, resolution.dimensions().width(),
  235. resolution.dimensions().height());
  236. if (mode == kInvalidMode) {
  237. // The CRTC is disabled, but there's no easy way to recover it here
  238. // (the mode it was attached to has gone).
  239. LOG(ERROR) << "Failed to create new mode.";
  240. return;
  241. }
  242. // Update |active_crtcs_| with new sizes and offsets.
  243. resizer.UpdateActiveCrtcs(crtc, mode, resolution.dimensions());
  244. // Disable any CRTCs that have been changed, so that the root window can be
  245. // safely resized to the bounding-box of the new CRTCs.
  246. // This is non-optimal: the only CRTCs that need disabling are those whose
  247. // original rectangles don't fit into the new root window - they are the ones
  248. // that would prevent resizing the root window. But figuring these out would
  249. // involve keeping track of all the original rectangles as well as the new
  250. // ones. So, to keep the implementation simple (and working for any arbitrary
  251. // layout algorithm), all changed CRTCs are disabled here.
  252. resizer.DisableChangedCrtcs();
  253. // Get the dimensions to resize the root window to.
  254. auto dimensions = resizer.GetBoundingBox();
  255. // TODO(lambroslambrou): Use the DPI from client size information.
  256. uint32_t width_mm = PixelsToMillimeters(dimensions.width(), kDefaultDPI);
  257. uint32_t height_mm = PixelsToMillimeters(dimensions.height(), kDefaultDPI);
  258. randr_->SetScreenSize({root_, static_cast<uint16_t>(dimensions.width()),
  259. static_cast<uint16_t>(dimensions.height()), width_mm,
  260. height_mm});
  261. // Apply the new CRTCs, which will re-enable any that were disabled.
  262. resizer.ApplyActiveCrtcs();
  263. }
  264. void DesktopResizerX11::SetResolutionExistingMode(
  265. const ScreenResolution& resolution) {
  266. if (auto config = randr_->GetScreenInfo({root_}).Sync()) {
  267. x11::RandR::Rotation current_rotation = config->rotation;
  268. const std::vector<x11::RandR::ScreenSize>& sizes = config->sizes;
  269. for (size_t i = 0; i < sizes.size(); ++i) {
  270. if (sizes[i].width == resolution.dimensions().width() &&
  271. sizes[i].height == resolution.dimensions().height()) {
  272. randr_->SetScreenConfig({
  273. .window = root_,
  274. .timestamp = x11::Time::CurrentTime,
  275. .config_timestamp = config->config_timestamp,
  276. .sizeID = static_cast<uint16_t>(i),
  277. .rotation = current_rotation,
  278. .rate = 0,
  279. });
  280. break;
  281. }
  282. }
  283. }
  284. }
  285. x11::RandR::Mode DesktopResizerX11::CreateMode(x11::RandR::Output output,
  286. const std::string& name,
  287. int width,
  288. int height) {
  289. x11::RandR::ModeInfo mode;
  290. mode.width = width;
  291. mode.height = height;
  292. mode.name_len = name.size();
  293. if (auto reply = randr_->CreateMode({root_, mode, name.c_str()}).Sync()) {
  294. randr_->AddOutputMode({
  295. output,
  296. reply->mode,
  297. });
  298. return reply->mode;
  299. }
  300. return kInvalidMode;
  301. }
  302. void DesktopResizerX11::DeleteMode(x11::RandR::Output output,
  303. const std::string& name) {
  304. x11::RandR::Mode mode_id = resources_.GetIdForMode(name);
  305. if (mode_id != kInvalidMode) {
  306. randr_->DeleteOutputMode({output, mode_id});
  307. randr_->DestroyMode({mode_id});
  308. resources_.Refresh(randr_, root_);
  309. }
  310. }
  311. // static
  312. std::unique_ptr<DesktopResizer> DesktopResizer::Create() {
  313. return std::make_unique<DesktopResizerX11>();
  314. }
  315. } // namespace remoting