Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(735)

Side by Side Diff: remoting/host/desktop_resizer_linux.cc

Issue 10918224: Cross-platform plumbing for resize-to-client (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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
7 #include <X11/Xlib.h>
8 #include <X11/extensions/Xrandr.h>
9
10 #include "base/logging.h"
11
12 // On Linux, we use the xrandr extension to change the desktop size. This has
13 // a number of quirks that make the code more complex:
14 //
15 // 1. It's not possible to change the size of an existing mode. Instead, the
16 // mode must be deleted and recreated.
17 // 2. It's not possible to delete a mode that's in use.
18 // 3. Errors are communicated via Xlib's spectacularly unhelpful mechanism
19 // of terminating the process unless you install an error handler.
20 //
21 // The basic approach is to create a new mode with the correct size, switch to
22 // it, then delete the old one. Since the new mode must have a different name,
23 // and we want the current mode name to be consistent, we then recreate the old
24 // mode at the new size, switch to it and delete the temporary. The reason for
25 // name consistency is to allow a future CL to disable resize-to-client if the
26 // user has changed the mode to something other than "Chrome Remote Desktop
27 // client size"). It doesn't make the code significantly more complex.
28
29 namespace remoting {
30
31 namespace {
32 // Temporarily install an alternative handler for X errors. The default handler
33 // exits the process, which is not what we want.
34 // TODO(jamiewalch): This doesn't really belong here, and should provide some
35 // means of checking whether or not anything went wrong.
36 class ScopedXErrorHandler {
37 public:
38 typedef int (*Handler)(Display*, XErrorEvent*);
39 ScopedXErrorHandler(Handler handler) {
40 previous_handler_ = XSetErrorHandler(handler);
41 }
42 ~ScopedXErrorHandler() {
43 XSetErrorHandler(previous_handler_);
44 }
45
46 static int Ignore(Display* display, XErrorEvent* event) {
47 LOG(INFO) << "Ignoring error.";
48 return 0;
49 }
50
51 private:
52 Handler previous_handler_;
53 };
54
55 class ScopedXGrabServer {
56 public:
57 ScopedXGrabServer(Display* display)
58 : display_(display) {
59 XGrabServer(display_);
60 }
61 ~ScopedXGrabServer() {
62 XUngrabServer(display_);
63 }
64 private:
65 Display* display_;
66 };
67
68 // Wrapper class for the XRRScreenResources struct.
69 class ScreenResources {
70 public:
71 ScreenResources() : resources_(NULL) {
72 }
73
74 ~ScreenResources() {
75 Release();
76 }
77
78 bool Refresh(Display* display, Window window) {
79 Release();
80 resources_ = XRRGetScreenResources(display, window);
81 return resources_ != NULL;
82 }
83
84 void Release() {
85 if (resources_) {
86 XRRFreeScreenResources(resources_);
87 resources_ = NULL;
88 }
89 }
90
91 RRMode GetIdForMode(const char* name) {
92 CHECK(resources_);
93 for (int i = 0; i < resources_->nmode; ++i) {
94 const XRRModeInfo& mode = resources_->modes[i];
95 if (strcmp(mode.name, name) == 0) {
96 return mode.id;
97 }
98 }
99 return 0;
100 }
101
102 // For now, assume we're only ever interested in the first output.
103 RROutput GetOutput() {
104 CHECK(resources_);
105 return resources_->outputs[0];
106 }
107
108 // For now, assume we're only ever interested in the first crtc.
109 RRCrtc GetCrtc() {
110 CHECK(resources_);
111 return resources_->crtcs[0];
112 }
113
114 XRROutputInfo* GetOutputInfo(Display* display, RROutput output_id) {
115 CHECK(resources_);
116 return XRRGetOutputInfo(display, resources_, output_id);
117 }
118
119 XRRScreenResources* get() { return resources_; }
120
121 private:
122 XRRScreenResources* resources_;
123 };
124
125
126 class DesktopResizerLinux : public DesktopResizer {
127 public:
128 DesktopResizerLinux();
129 virtual ~DesktopResizerLinux();
130
131 // DesktopResizer interface
132 virtual SkISize GetSize() OVERRIDE;
133 virtual void SetSize(const SkISize& size) OVERRIDE;
134
135 private:
136 // Create a mode, and attach it to the primary output. If the mode already
137 // exists, it is left unchanged.
138 void CreateMode(const char* name, int width, int height);
139
140 // Remove the specified mode from the primary output, and delete it. If the
141 // mode is in use, it is not deleted.
142 void DeleteMode(const char* name);
143
144 // Switch the primary output to the specified mode.
145 void SwitchToMode(const char* name);
146
147 Display* display_;
148 Window root_;
149 ScreenResources resources_;
150
151 DISALLOW_COPY_AND_ASSIGN(DesktopResizerLinux);
152 };
153 } // namespace
154
155 DesktopResizerLinux::DesktopResizerLinux()
156 : display_(XOpenDisplay(NULL)),
157 root_(RootWindow(display_, DefaultScreen(display_))) {
158 }
159
160 DesktopResizerLinux::~DesktopResizerLinux() {
161 XCloseDisplay(display_);
162 }
163
164 SkISize DesktopResizerLinux::GetSize() {
165 ScopedXErrorHandler handler(ScopedXErrorHandler::Ignore);
166 SkISize result = SkISize::Make(0, 0);
167 int num_sizes;
168 XRRScreenSize *sizes = XRRSizes(display_, 0, &num_sizes);
169 // If xrandr is not supported, num_sizes is set to zero.
170 if (num_sizes) {
171 XRRScreenConfiguration *config = XRRGetScreenInfo(display_, root_);
172 Rotation rotation;
173 SizeID size_id = XRRConfigCurrentConfiguration(config, &rotation);
174 CHECK(size_id < num_sizes);
175 result = SkISize::Make(sizes[size_id].width, sizes[size_id].height);
176 }
177 return result;
178 }
179
180 void DesktopResizerLinux::SetSize(const SkISize& size) {
181 // Ignore X errors encountered while resizing the display. We might hit an
182 // error, for example if xrandr has been used to add a mode with the same
183 // name as our temporary mode, or to remove the "client size" mode. We don't
184 // want to terminate the process if this happens.
185 ScopedXErrorHandler handler(ScopedXErrorHandler::Ignore);
186
187 // Grab the X server while we're changing the display size. This ensures
188 // that the display configuration doesn't change under our feet.
189 ScopedXGrabServer grabber(display_);
190
191 // Clamp the specified size to something valid for the X server.
192 int width = size.width(), height = size.height();
193 int min_width = 0, min_height = 0, max_width = 0, max_height = 0;
194 XRRGetScreenSizeRange(display_, root_,
195 &min_width, &min_height,
196 &max_width, &max_height);
197 if (size.width() < min_width) {
198 width = min_width;
199 } else if (width > max_width) {
200 width = max_width;
201 }
202 if (height < min_height) {
203 height = min_height;
204 } else if (height > max_height) {
205 height = max_height;
206 }
207 LOG(INFO) << "Changing desktop size to " << width << "x" << height;
208
209 // The name of the mode representing the current client view size and the
210 // temporary mode used for the reasons described at the top of this file.
211 // The former should be localized if it's user-visible; the latter only
212 // exists briefly and does not need to localized.
213 const char* kModeName = "Chrome Remote Desktop client size";
214 const char* kTempModeName = "Chrome Remote Desktop temporary mode";
215
216 // Actually do the resize operation, preserving the current mode name. Note
217 // that we have to detatch the output from any mode in order to resize it
218 // (strictly speaking, this is only required when reducing the size, but it
219 // seems safe to do it regardless).
220 CreateMode(kTempModeName, width, height);
221 SwitchToMode(NULL);
222 XRRSetScreenSize(display_, root_, width, height, -1, -1);
223 SwitchToMode(kTempModeName);
224 DeleteMode(kModeName);
225 CreateMode(kModeName, width, height);
226 SwitchToMode(kModeName);
227 DeleteMode(kTempModeName);
228 }
229
230 void DesktopResizerLinux::CreateMode(const char* name, int width, int height) {
231 XRRModeInfo mode;
232 memset(&mode, 0, sizeof(mode));
233 mode.width = width;
234 mode.height = height;
235 mode.name = const_cast<char*>(name);
Jamie 2012/09/13 20:32:05 Stupid non-const APIs...
236 mode.nameLength = strlen(name);
237 XRRCreateMode(display_, root_, &mode);
238
239 if (!resources_.Refresh(display_, root_)) {
240 return;
241 }
242 RRMode mode_id = resources_.GetIdForMode(name);
243 if (!mode_id) {
244 return;
245 }
246 XRRAddOutputMode(display_, resources_.GetOutput(), mode_id);
247 }
248
249 void DesktopResizerLinux::DeleteMode(const char* name) {
250 RRMode mode_id = resources_.GetIdForMode(name);
251 if (mode_id) {
252 XRRDeleteOutputMode(display_, resources_.GetOutput(), mode_id);
253 XRRDestroyMode(display_, mode_id);
254 resources_.Refresh(display_, root_);
255 }
256 }
257
258 void DesktopResizerLinux::SwitchToMode(const char* name) {
259 RRMode mode_id = None;
260 RROutput* outputs = NULL;
261 int number_of_outputs = 0;
262 if (name) {
263 mode_id = resources_.GetIdForMode(name);
264 CHECK(mode_id);
265 outputs = resources_.get()->outputs;
266 number_of_outputs = resources_.get()->noutput;
267 }
268 XRRSetCrtcConfig(display_, resources_.get(), resources_.GetCrtc(),
269 CurrentTime, 0, 0, mode_id, 1, outputs, number_of_outputs);
270 }
271
272 scoped_ptr<DesktopResizer> DesktopResizer::Create() {
273 return scoped_ptr<DesktopResizer>(new DesktopResizerLinux);
274 }
275
276 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698