Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / remoting / host / desktop_resizer_linux.cc
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
5 #include "remoting/host/desktop_resizer.h"
6 #include "remoting/host/linux/x11_util.h"
7
8 #include <string.h>
9 #include <X11/extensions/Xrandr.h>
10 #include <X11/Xlib.h>
11
12 #include "base/command_line.h"
13 #include "remoting/base/logging.h"
14
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.
20 //
21 // Xrandr has a number of restrictions that make this code more complex:
22 //
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.
28 //
29 // The basic approach is as follows:
30 //
31 //   1. Create a new mode with the correct resolution;
32 //   2. Switch to the new mode;
33 //   3. Delete the old mode.
34 //
35 // Since the new mode must have a different name, and we want the current mode
36 // name to be consistent, we then additionally:
37 //
38 //   4. Recreate the old mode at the new resolution;
39 //   5. Switch to the old mode;
40 //   6. Delete the temporary mode.
41 //
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.
45
46 namespace {
47
48 int PixelsToMillimeters(int pixels, int dpi) {
49   DCHECK(dpi != 0);
50
51   const double kMillimetersPerInch = 25.4;
52
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);
57 }
58
59 // TODO(jamiewalch): Use the correct DPI for the mode: http://crbug.com/172405.
60 const int kDefaultDPI = 96;
61
62 }  // namespace
63
64 namespace remoting {
65
66 // Wrapper class for the XRRScreenResources struct.
67 class ScreenResources {
68  public:
69   ScreenResources() : resources_(NULL) {
70   }
71
72   ~ScreenResources() {
73     Release();
74   }
75
76   bool Refresh(Display* display, Window window) {
77     Release();
78     resources_ = XRRGetScreenResources(display, window);
79     return resources_ != NULL;
80   }
81
82   void Release() {
83     if (resources_) {
84       XRRFreeScreenResources(resources_);
85       resources_ = NULL;
86     }
87   }
88
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;
95       }
96     }
97     return 0;
98   }
99
100   // For now, assume we're only ever interested in the first output.
101   RROutput GetOutput() {
102     CHECK(resources_);
103     return resources_->outputs[0];
104   }
105
106   // For now, assume we're only ever interested in the first crtc.
107   RRCrtc GetCrtc() {
108     CHECK(resources_);
109     return resources_->crtcs[0];
110   }
111
112   XRROutputInfo* GetOutputInfo(Display* display, RROutput output_id) {
113     CHECK(resources_);
114     return XRRGetOutputInfo(display, resources_, output_id);
115   }
116
117   XRRScreenResources* get() { return resources_; }
118
119  private:
120   XRRScreenResources* resources_;
121 };
122
123
124 class DesktopResizerLinux : public DesktopResizer {
125  public:
126   DesktopResizerLinux();
127   ~DesktopResizerLinux() override;
128
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;
135
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);
140
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);
144
145   // Switch the primary output to the specified mode. If name is NULL, the
146   // primary output is disabled instead, which is required before changing
147   // its resolution.
148   void SwitchToMode(const char* name);
149
150   Display* display_;
151   int screen_;
152   Window root_;
153   ScreenResources resources_;
154   bool exact_resize_;
155
156   DISALLOW_COPY_AND_ASSIGN(DesktopResizerLinux);
157 };
158
159 DesktopResizerLinux::DesktopResizerLinux()
160     : display_(XOpenDisplay(NULL)),
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);
166 }
167
168 DesktopResizerLinux::~DesktopResizerLinux() {
169   XCloseDisplay(display_);
170 }
171
172 ScreenResolution DesktopResizerLinux::GetCurrentResolution() {
173   if (!exact_resize_) {
174     // TODO(jamiewalch): Remove this early return if we decide to support
175     // non-Xvfb servers.
176     return ScreenResolution();
177   }
178
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.
183   //
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);
192   }
193
194   ScreenResolution result(
195       webrtc::DesktopSize(
196           DisplayWidth(display_, DefaultScreen(display_)),
197           DisplayHeight(display_, DefaultScreen(display_))),
198       webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
199   return result;
200 }
201
202 std::list<ScreenResolution> DesktopResizerLinux::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.
224   }
225   return result;
226 }
227
228 void DesktopResizerLinux::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;
233   }
234
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());
240
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_);
244
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";
251
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();
258
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(NULL);
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);
275 }
276
277 void DesktopResizerLinux::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.
282 }
283
284 void DesktopResizerLinux::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);
292
293   if (!resources_.Refresh(display_, root_)) {
294     return;
295   }
296   RRMode mode_id = resources_.GetIdForMode(name);
297   if (!mode_id) {
298     return;
299   }
300   XRRAddOutputMode(display_, resources_.GetOutput(), mode_id);
301 }
302
303 void DesktopResizerLinux::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_);
309   }
310 }
311
312 void DesktopResizerLinux::SwitchToMode(const char* name) {
313   RRMode mode_id = None;
314   RROutput* outputs = NULL;
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;
321   }
322   XRRSetCrtcConfig(display_, resources_.get(), resources_.GetCrtc(),
323                    CurrentTime, 0, 0, mode_id, 1, outputs, number_of_outputs);
324 }
325
326 scoped_ptr<DesktopResizer> DesktopResizer::Create() {
327   return make_scoped_ptr(new DesktopResizerLinux);
328 }
329
330 }  // namespace remoting