blob: 95c1f332305c962bc7dc4139a0d7cb855c663508 [file] [log] [blame]
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +01001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 * Copyright 2009-2017 Pierre Ossman for Cendio AB
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +01003 * Copyright 2018 Peter Astrand <astrand@cendio.se> for Cendio AB
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +01004 * Copyright 2014 Brian P. Hinz
5 *
6 * This is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This software is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this software; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
19 * USA.
20 */
21
22#ifdef HAVE_CONFIG_H
23#include <config.h>
24#endif
25
26#include <stdlib.h>
27
28#include <unixcommon.h>
29#include <rfb/screenTypes.h>
30#include <rfb/LogWriter.h>
31#include <RandrGlue.h>
32static rfb::LogWriter vlog("RandR");
33
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +020034static int ResizeScreen(bool dryrun, int fb_width, int fb_height,
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +010035 std::set<unsigned int>* disabledOutputs)
Peter Åstrand (astrand)396f8c92018-03-20 08:10:12 +010036{
Peter Åstrand (astrand)651faf82018-03-19 11:07:32 +010037 vlog.debug("Resizing screen framebuffer to %dx%d", fb_width, fb_height);
38
Peter Åstrand (astrand)396f8c92018-03-20 08:10:12 +010039 /*
40 * Disable outputs which are larger than the target size
41 */
42 for (int i = 0;i < vncRandRGetOutputCount();i++) {
43 int x, y, width, height;
44 if (vncRandRGetOutputDimensions(i, &x, &y, &width, &height) == 0) {
45 if (x + width > fb_width || y + height > fb_height) {
Peter Åstrand (astrand)651faf82018-03-19 11:07:32 +010046 char *name = vncRandRGetOutputName(i);
47 vlog.debug("Temporarily disabling output '%s'", name);
48 free(name);
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +020049 if (!dryrun) {
50 /* Currently ignoring errors */
51 /* FIXME: Save output rotation and restore when configuring output */
52 vncRandRDisableOutput(i);
53 disabledOutputs->insert(vncRandRGetOutputId(i));
54 }
Peter Åstrand (astrand)396f8c92018-03-20 08:10:12 +010055 }
56 }
57 }
58
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +020059 if (!vncRandRIsValidScreenSize(fb_width, fb_height))
60 return 0;
61
62 if (dryrun)
63 return 1;
64
Peter Åstrand (astrand)396f8c92018-03-20 08:10:12 +010065 return vncRandRResizeScreen(fb_width, fb_height);
66}
67
68
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +010069/* Return output index of preferred output, -1 on failure */
70int getPreferredScreenOutput(OutputIdMap *outputIdMap,
71 const std::set<unsigned int>& disabledOutputs)
72{
73 int firstDisabled = -1;
74 int firstEnabled = -1;
75 int firstConnected = -1;
76 int firstUsable = -1;
77
78 for (int i = 0;i < vncRandRGetOutputCount();i++) {
79 unsigned int output = vncRandRGetOutputId(i);
80
81 /* In use? */
82 if (outputIdMap->count(output) == 1) {
83 continue;
84 }
85
86 /* Can it be used? */
87 if (!vncRandRIsOutputUsable(i)) {
88 continue;
89 }
90
91 /* Temporarily disabled? */
92 if (disabledOutputs.count(output)) {
93 if (firstDisabled == -1) firstDisabled = i;
94 }
95
96 /* Enabled? */
97 if (vncRandRIsOutputEnabled(i)) {
98 if (firstEnabled == -1) firstEnabled = i;
99 }
100
101 /* Connected? */
102 if (vncRandRIsOutputConnected(i)) {
103 if (firstConnected == -1) firstConnected = i;
104 }
105
106 if (firstUsable == -1) firstUsable = i;
107 }
108
109 if (firstEnabled != -1) {
110 return firstEnabled;
111 } else if (firstDisabled != -1) {
112 return firstDisabled;
113 } else if (firstConnected != -1) {
114 return firstConnected;
115 } else {
116 return firstUsable; /* Possibly -1 */
117 }
118}
119
120
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200121rfb::ScreenSet computeScreenLayout(OutputIdMap *outputIdMap)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100122{
123 rfb::ScreenSet layout;
124 OutputIdMap newIdMap;
125
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200126 for (int i = 0;i < vncRandRGetOutputCount();i++) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100127 unsigned int outputId;
128 int x, y, width, height;
129
130 /* Disabled? */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200131 if (!vncRandRIsOutputEnabled(i))
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100132 continue;
133
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200134 outputId = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100135
136 /* Known output? */
137 if (outputIdMap->count(outputId) == 1)
138 newIdMap[outputId] = (*outputIdMap)[outputId];
139 else {
140 rdr::U32 id;
141 OutputIdMap::const_iterator iter;
142
143 while (true) {
144 id = rand();
145 for (iter = outputIdMap->begin();iter != outputIdMap->end();++iter) {
146 if (iter->second == id)
147 break;
148 }
149 if (iter == outputIdMap->end())
150 break;
151 }
152
153 newIdMap[outputId] = id;
154 }
155
Peter Åstrand (astrand)d57acc32018-03-19 10:47:40 +0100156 if (vncRandRGetOutputDimensions(i, &x, &y, &width, &height) == 0) {
157 layout.add_screen(rfb::Screen(newIdMap[outputId], x, y, width, height, 0));
158 }
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100159 }
160
161 /* Only keep the entries that are currently active */
162 *outputIdMap = newIdMap;
163
164 /*
165 * Make sure we have something to display. Hopefully it's just temporary
166 * that we have no active outputs...
167 */
168 if (layout.num_screens() == 0)
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200169 layout.add_screen(rfb::Screen(0, 0, 0, vncGetScreenWidth(),
170 vncGetScreenHeight(), 0));
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100171
172 return layout;
173}
174
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200175static unsigned int _setScreenLayout(bool dryrun,
176 int fb_width, int fb_height, const rfb::ScreenSet& layout,
177 OutputIdMap *outputIdMap)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100178{
179 int ret;
180 int availableOutputs;
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100181 std::set<unsigned int> disabledOutputs;
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200182 /* Printing errors in the dryrun pass might be confusing */
183 const bool logErrors = !dryrun || vlog.getLevel() >= vlog.LEVEL_DEBUG;
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100184
185 // RandR support?
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200186 if (vncRandRGetOutputCount() == 0)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100187 return rfb::resultProhibited;
188
189 /*
190 * First check that we don't have any active clone modes. That's just
191 * too messy to deal with.
192 */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200193 if (vncRandRHasOutputClones()) {
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200194 if (logErrors) {
195 vlog.error("Clone mode active. Refusing to touch screen layout.");
196 }
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100197 return rfb::resultInvalid;
198 }
199
200 /* Next count how many useful outputs we have... */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200201 availableOutputs = vncRandRGetAvailableOutputs();
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100202
203 /* Try to create more outputs if needed... (only works on Xvnc) */
204 if (layout.num_screens() > availableOutputs) {
205 vlog.debug("Insufficient screens. Need to create %d more.",
206 layout.num_screens() - availableOutputs);
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200207
208 if (!vncRandRCanCreateOutputs(layout.num_screens() - availableOutputs)) {
209 if (logErrors)
210 vlog.error("Unable to create more screens, as needed by the new client layout.");
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100211 return rfb::resultInvalid;
212 }
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200213
214 if (!dryrun) {
215 ret = vncRandRCreateOutputs(layout.num_screens() - availableOutputs);
216 if (!ret) {
217 if (logErrors)
218 vlog.error("Unable to create more screens, as needed by the new client layout.");
219 return rfb::resultInvalid;
220 }
221 }
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100222 }
223
224 /* First we might need to resize the screen */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200225 if ((fb_width != vncGetScreenWidth()) ||
226 (fb_height != vncGetScreenHeight())) {
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200227 ret = ResizeScreen(dryrun, fb_width, fb_height, &disabledOutputs);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100228 if (!ret) {
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200229 if (logErrors) {
230 vlog.error("Failed to resize screen to %dx%d", fb_width, fb_height);
231 }
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100232 return rfb::resultInvalid;
233 }
234 }
235
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100236 /* Next, reconfigure all known outputs */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200237 for (int i = 0;i < vncRandRGetOutputCount();i++) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100238 unsigned int output;
239
240 rfb::ScreenSet::const_iterator iter;
241
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200242 output = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100243
244 /* Known? */
245 if (outputIdMap->count(output) == 0)
246 continue;
247
248 /* Find the corresponding screen... */
249 for (iter = layout.begin();iter != layout.end();++iter) {
250 if (iter->id == (*outputIdMap)[output])
251 break;
252 }
253
254 /* Missing? */
255 if (iter == layout.end()) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100256 outputIdMap->erase(output);
257 continue;
258 }
259
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200260 /* Probably not needed, but let's be safe */
261 if (!vncRandRIsOutputUsable(i)) {
262 if (logErrors) {
263 char *name = vncRandRGetOutputName(i);
264 vlog.error("Required output '%s' cannot be used", name);
265 free(name);
266 }
267 return rfb::resultInvalid;
268 }
269
270 /* Possible mode? */
271 if (!vncRandRCheckOutputMode(i, iter->dimensions.width(),
272 iter->dimensions.height())) {
273 if (logErrors) {
274 char *name = vncRandRGetOutputName(i);
275 vlog.error("Output '%s' does not support required mode %dx%d", name,
276 iter->dimensions.width(), iter->dimensions.height());
277 free(name);
278 }
279 return rfb::resultInvalid;
280 }
281
282 char *name = vncRandRGetOutputName(i);
283 vlog.debug("Reconfiguring output '%s' to %dx%d+%d+%d", name,
284 iter->dimensions.width(), iter->dimensions.height(),
285 iter->dimensions.tl.x, iter->dimensions.tl.y);
286 free(name);
287
288 if (dryrun)
289 continue;
290
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100291 /* Reconfigure new mode and position */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200292 ret = vncRandRReconfigureOutput(i,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100293 iter->dimensions.tl.x,
294 iter->dimensions.tl.y,
295 iter->dimensions.width(),
296 iter->dimensions.height());
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200297 if (!ret) {
298 if (logErrors) {
299 char *name = vncRandRGetOutputName(i);
300 vlog.error("Failed to reconfigure output '%s' to %dx%d+%d+%d", name,
301 iter->dimensions.width(), iter->dimensions.height(),
302 iter->dimensions.tl.x, iter->dimensions.tl.y);
303 free(name);
304 }
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100305 return rfb::resultInvalid;
306 }
307 }
308
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100309 /* Allocate new outputs for new screens */
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100310 rfb::ScreenSet::const_iterator iter;
311 for (iter = layout.begin();iter != layout.end();++iter) {
312 OutputIdMap::const_iterator oi;
313 unsigned int output;
314 int i;
315
316 /* Does this screen have an output already? */
317 for (oi = outputIdMap->begin();oi != outputIdMap->end();++oi) {
318 if (oi->second == iter->id)
319 break;
320 }
321
322 if (oi != outputIdMap->end())
323 continue;
324
325 /* Find an unused output */
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100326 i = getPreferredScreenOutput(outputIdMap, disabledOutputs);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100327
328 /* Shouldn't happen */
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100329 if (i == -1)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100330 return rfb::resultInvalid;
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100331 output = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100332
333 /*
334 * Make sure we already have an entry for this, or
335 * computeScreenLayout() will think it is a brand new output and
336 * assign it a random id.
337 */
338 (*outputIdMap)[output] = iter->id;
339
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200340 /* Probably not needed, but let's be safe */
341 if (!vncRandRIsOutputUsable(i)) {
342 if (logErrors) {
343 char *name = vncRandRGetOutputName(i);
344 vlog.error("Required new output '%s' cannot be used", name);
345 free(name);
346 }
347 return rfb::resultInvalid;
348 }
349
350 /* Possible mode? */
351 if (!vncRandRCheckOutputMode(i, iter->dimensions.width(),
352 iter->dimensions.height())) {
353 if (logErrors) {
354 char *name = vncRandRGetOutputName(i);
355 vlog.error("New output '%s' does not support required mode %dx%d", name,
356 iter->dimensions.width(), iter->dimensions.height());
357 free(name);
358 }
359 return rfb::resultInvalid;
360 }
361
362 char *name = vncRandRGetOutputName(i);
363 vlog.debug("Reconfiguring new output '%s' to %dx%d+%d+%d", name,
364 iter->dimensions.width(), iter->dimensions.height(),
365 iter->dimensions.tl.x, iter->dimensions.tl.y);
366 free(name);
367
368 if (dryrun)
369 continue;
370
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100371 /* Reconfigure new mode and position */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200372 ret = vncRandRReconfigureOutput(i,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100373 iter->dimensions.tl.x,
374 iter->dimensions.tl.y,
375 iter->dimensions.width(),
376 iter->dimensions.height());
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200377 if (!ret) {
378 if (logErrors) {
379 char *name = vncRandRGetOutputName(i);
380 vlog.error("Failed to reconfigure new output '%s' to %dx%d+%d+%d", name,
381 iter->dimensions.width(), iter->dimensions.height(),
382 iter->dimensions.tl.x, iter->dimensions.tl.y);
383 free(name);
384 }
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100385 return rfb::resultInvalid;
386 }
387 }
388
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100389 /* Turn off unused outputs */
390 for (int i = 0;i < vncRandRGetOutputCount();i++) {
391 unsigned int output = vncRandRGetOutputId(i);
392
393 /* Known? */
394 if (outputIdMap->count(output) == 1)
395 continue;
396
Peter Åstrand (astrand)8fcf6cc2018-03-20 10:26:40 +0100397 /* Enabled? */
398 if (!vncRandRIsOutputEnabled(i))
399 continue;
400
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100401 /* Disable and move on... */
402 ret = vncRandRDisableOutput(i);
Peter Åstrand (astrand)651faf82018-03-19 11:07:32 +0100403 char *name = vncRandRGetOutputName(i);
404 if (ret) {
405 vlog.debug("Disabled unused output '%s'", name);
406 } else {
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200407 if (logErrors) {
408 vlog.error("Failed to disable unused output '%s'", name);
409 }
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100410 free(name);
411 return rfb::resultInvalid;
412 }
Peter Åstrand (astrand)651faf82018-03-19 11:07:32 +0100413 free(name);
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100414 }
415
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100416 /*
417 * Update timestamp for when screen layout was last changed.
418 * This is normally done in the X11 request handlers, which is
419 * why we have to deal with it manually here.
420 */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200421 vncRandRUpdateSetTime();
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100422
423 return rfb::resultSuccess;
424}
Peter Åstrand (astrand)54f49fd2018-03-27 09:14:38 +0200425
426
427unsigned int setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout,
428 OutputIdMap *outputIdMap)
429{
430 return _setScreenLayout(false, fb_width, fb_height, layout, outputIdMap);
431}
432
433
434unsigned int tryScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout,
435 OutputIdMap *outputIdMap)
436{
437 OutputIdMap dryrunIdMap = *outputIdMap;
438 return _setScreenLayout(true, fb_width, fb_height, layout, &dryrunIdMap);
439}