Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / remoting / host / desktop_resizer_x11.cc
blob1df858ca02180527ab690603bd3aeca49aae1893
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.
5 #include "remoting/host/desktop_resizer.h"
7 #include <string.h>
8 #include <X11/extensions/Xrandr.h>
9 #include <X11/Xlib.h>
11 #include "base/command_line.h"
12 #include "remoting/base/logging.h"
13 #include "remoting/host/linux/x11_util.h"
15 // On Linux, we use the xrandr extension to change the desktop resolution. For
16 // now, we only support resize-to-client for Xvfb-based servers that can match
17 // the client resolution exactly. To support best-resolution matching, it would
18 // be necessary to implement |GetSupportedResolutions|, but it's not considered
19 // a priority now.
21 // Xrandr has a number of restrictions that make this code more complex:
23 // 1. It's not possible to change the resolution of an existing mode. Instead,
24 // the mode must be deleted and recreated.
25 // 2. It's not possible to delete a mode that's in use.
26 // 3. Errors are communicated via Xlib's spectacularly unhelpful mechanism
27 // of terminating the process unless you install an error handler.
29 // The basic approach is as follows:
31 // 1. Create a new mode with the correct resolution;
32 // 2. Switch to the new mode;
33 // 3. Delete the old mode.
35 // Since the new mode must have a different name, and we want the current mode
36 // name to be consistent, we then additionally:
38 // 4. Recreate the old mode at the new resolution;
39 // 5. Switch to the old mode;
40 // 6. Delete the temporary mode.
42 // Name consistency will allow a future CL to disable resize-to-client if the
43 // user has changed the mode to something other than "Chrome Remote Desktop
44 // client resolution". It doesn't make the code significantly more complex.
46 namespace {
48 int PixelsToMillimeters(int pixels, int dpi) {
49 DCHECK(dpi != 0);
51 const double kMillimetersPerInch = 25.4;
53 // (pixels / dpi) is the length in inches. Multiplying by
54 // kMillimetersPerInch converts to mm. Multiplication is done first to
55 // avoid integer division.
56 return static_cast<int>(kMillimetersPerInch * pixels / dpi);
59 // TODO(jamiewalch): Use the correct DPI for the mode: http://crbug.com/172405.
60 const int kDefaultDPI = 96;
62 } // namespace
64 namespace remoting {
66 // Wrapper class for the XRRScreenResources struct.
67 class ScreenResources {
68 public:
69 ScreenResources() : resources_(nullptr) {
72 ~ScreenResources() {
73 Release();
76 bool Refresh(Display* display, Window window) {
77 Release();
78 resources_ = XRRGetScreenResources(display, window);
79 return resources_ != nullptr;
82 void Release() {
83 if (resources_) {
84 XRRFreeScreenResources(resources_);
85 resources_ = nullptr;
89 RRMode GetIdForMode(const char* name) {
90 CHECK(resources_);
91 for (int i = 0; i < resources_->nmode; ++i) {
92 const XRRModeInfo& mode = resources_->modes[i];
93 if (strcmp(mode.name, name) == 0) {
94 return mode.id;
97 return 0;
100 // For now, assume we're only ever interested in the first output.
101 RROutput GetOutput() {
102 CHECK(resources_);
103 return resources_->outputs[0];
106 // For now, assume we're only ever interested in the first crtc.
107 RRCrtc GetCrtc() {
108 CHECK(resources_);
109 return resources_->crtcs[0];
112 XRROutputInfo* GetOutputInfo(Display* display, RROutput output_id) {
113 CHECK(resources_);
114 return XRRGetOutputInfo(display, resources_, output_id);
117 XRRScreenResources* get() { return resources_; }
119 private:
120 XRRScreenResources* resources_;
124 class DesktopResizerX11 : public DesktopResizer {
125 public:
126 DesktopResizerX11();
127 ~DesktopResizerX11() override;
129 // DesktopResizer interface
130 ScreenResolution GetCurrentResolution() override;
131 std::list<ScreenResolution> GetSupportedResolutions(
132 const ScreenResolution& preferred) override;
133 void SetResolution(const ScreenResolution& resolution) override;
134 void RestoreResolution(const ScreenResolution& original) override;
136 private:
137 // Create a mode, and attach it to the primary output. If the mode already
138 // exists, it is left unchanged.
139 void CreateMode(const char* name, int width, int height);
141 // Remove the specified mode from the primary output, and delete it. If the
142 // mode is in use, it is not deleted.
143 void DeleteMode(const char* name);
145 // Switch the primary output to the specified mode. If name is nullptr, the
146 // primary output is disabled instead, which is required before changing
147 // its resolution.
148 void SwitchToMode(const char* name);
150 Display* display_;
151 int screen_;
152 Window root_;
153 ScreenResources resources_;
154 bool exact_resize_;
156 DISALLOW_COPY_AND_ASSIGN(DesktopResizerX11);
159 DesktopResizerX11::DesktopResizerX11()
160 : display_(XOpenDisplay(nullptr)),
161 screen_(DefaultScreen(display_)),
162 root_(RootWindow(display_, screen_)),
163 exact_resize_(base::CommandLine::ForCurrentProcess()->
164 HasSwitch("server-supports-exact-resize")) {
165 XRRSelectInput(display_, root_, RRScreenChangeNotifyMask);
168 DesktopResizerX11::~DesktopResizerX11() {
169 XCloseDisplay(display_);
172 ScreenResolution DesktopResizerX11::GetCurrentResolution() {
173 if (!exact_resize_) {
174 // TODO(jamiewalch): Remove this early return if we decide to support
175 // non-Xvfb servers.
176 return ScreenResolution();
179 // TODO(lambroslambrou): Xrandr requires that we process RRScreenChangeNotify
180 // events, otherwise DisplayWidth and DisplayHeight do not return the current
181 // values. Normally, this would be done via a central X event loop, but we
182 // don't have one, hence this horrible hack.
184 // Note that the WatchFileDescriptor approach taken in XServerClipboard
185 // doesn't work here because resize events have already been read from the
186 // X server socket by the time the resize function returns, hence the
187 // file descriptor is never seen as readable.
188 while (XEventsQueued(display_, QueuedAlready)) {
189 XEvent event;
190 XNextEvent(display_, &event);
191 XRRUpdateConfiguration(&event);
194 ScreenResolution result(
195 webrtc::DesktopSize(
196 DisplayWidth(display_, DefaultScreen(display_)),
197 DisplayHeight(display_, DefaultScreen(display_))),
198 webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
199 return result;
202 std::list<ScreenResolution> DesktopResizerX11::GetSupportedResolutions(
203 const ScreenResolution& preferred) {
204 std::list<ScreenResolution> result;
205 if (exact_resize_) {
206 // Clamp the specified size to something valid for the X server.
207 int min_width = 0, min_height = 0, max_width = 0, max_height = 0;
208 XRRGetScreenSizeRange(display_, root_,
209 &min_width, &min_height,
210 &max_width, &max_height);
211 int width = std::min(std::max(preferred.dimensions().width(), min_width),
212 max_width);
213 int height = std::min(std::max(preferred.dimensions().height(), min_height),
214 max_height);
215 // Additionally impose a minimum size of 640x480, since anything smaller
216 // doesn't seem very useful.
217 ScreenResolution actual(
218 webrtc::DesktopSize(std::max(640, width), std::max(480, height)),
219 webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
220 result.push_back(actual);
221 } else {
222 // TODO(jamiewalch): Return the list of supported resolutions if we can't
223 // support exact-size matching.
225 return result;
228 void DesktopResizerX11::SetResolution(const ScreenResolution& resolution) {
229 if (!exact_resize_) {
230 // TODO(jamiewalch): Remove this early return if we decide to support
231 // non-Xvfb servers.
232 return;
235 // Ignore X errors encountered while resizing the display. We might hit an
236 // error, for example if xrandr has been used to add a mode with the same
237 // name as our temporary mode, or to remove the "client resolution" mode. We
238 // don't want to terminate the process if this happens.
239 ScopedXErrorHandler handler(ScopedXErrorHandler::Ignore());
241 // Grab the X server while we're changing the display resolution. This ensures
242 // that the display configuration doesn't change under our feet.
243 ScopedXGrabServer grabber(display_);
245 // The name of the mode representing the current client view resolution and
246 // the temporary mode used for the reasons described at the top of this file.
247 // The former should be localized if it's user-visible; the latter only
248 // exists briefly and does not need to localized.
249 const char* kModeName = "Chrome Remote Desktop client resolution";
250 const char* kTempModeName = "Chrome Remote Desktop temporary mode";
252 // Actually do the resize operation, preserving the current mode name. Note
253 // that we have to detach the output from any mode in order to resize it
254 // (strictly speaking, this is only required when reducing the size, but it
255 // seems safe to do it regardless).
256 HOST_LOG << "Changing desktop size to " << resolution.dimensions().width()
257 << "x" << resolution.dimensions().height();
259 // TODO(lambroslambrou): Use the DPI from client size information.
260 int width_mm = PixelsToMillimeters(resolution.dimensions().width(),
261 kDefaultDPI);
262 int height_mm = PixelsToMillimeters(resolution.dimensions().height(),
263 kDefaultDPI);
264 CreateMode(kTempModeName, resolution.dimensions().width(),
265 resolution.dimensions().height());
266 SwitchToMode(nullptr);
267 XRRSetScreenSize(display_, root_, resolution.dimensions().width(),
268 resolution.dimensions().height(), width_mm, height_mm);
269 SwitchToMode(kTempModeName);
270 DeleteMode(kModeName);
271 CreateMode(kModeName, resolution.dimensions().width(),
272 resolution.dimensions().height());
273 SwitchToMode(kModeName);
274 DeleteMode(kTempModeName);
277 void DesktopResizerX11::RestoreResolution(const ScreenResolution& original) {
278 // Since the desktop is only visible via a remote connection, the original
279 // resolution of the desktop will never been seen and there's no point
280 // restoring it; if we did, we'd just risk messing up the user's window
281 // layout.
284 void DesktopResizerX11::CreateMode(const char* name, int width, int height) {
285 XRRModeInfo mode;
286 memset(&mode, 0, sizeof(mode));
287 mode.width = width;
288 mode.height = height;
289 mode.name = const_cast<char*>(name);
290 mode.nameLength = strlen(name);
291 XRRCreateMode(display_, root_, &mode);
293 if (!resources_.Refresh(display_, root_)) {
294 return;
296 RRMode mode_id = resources_.GetIdForMode(name);
297 if (!mode_id) {
298 return;
300 XRRAddOutputMode(display_, resources_.GetOutput(), mode_id);
303 void DesktopResizerX11::DeleteMode(const char* name) {
304 RRMode mode_id = resources_.GetIdForMode(name);
305 if (mode_id) {
306 XRRDeleteOutputMode(display_, resources_.GetOutput(), mode_id);
307 XRRDestroyMode(display_, mode_id);
308 resources_.Refresh(display_, root_);
312 void DesktopResizerX11::SwitchToMode(const char* name) {
313 RRMode mode_id = None;
314 RROutput* outputs = nullptr;
315 int number_of_outputs = 0;
316 if (name) {
317 mode_id = resources_.GetIdForMode(name);
318 CHECK(mode_id);
319 outputs = resources_.get()->outputs;
320 number_of_outputs = resources_.get()->noutput;
322 XRRSetCrtcConfig(display_, resources_.get(), resources_.GetCrtc(),
323 CurrentTime, 0, 0, mode_id, 1, outputs, number_of_outputs);
326 scoped_ptr<DesktopResizer> DesktopResizer::Create() {
327 return make_scoped_ptr(new DesktopResizerX11);
330 } // namespace remoting