blob: d37beb06f9bfba292c3377e7de1123e73c09974e [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)a61c6f22018-03-20 08:15:41 +010034static int ResizeScreen(int fb_width, int fb_height,
35 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) {
46 /* Currently ignoring errors */
47 /* FIXME: Save output rotation and restore when configuring output */
Peter Åstrand (astrand)651faf82018-03-19 11:07:32 +010048 char *name = vncRandRGetOutputName(i);
49 vlog.debug("Temporarily disabling output '%s'", name);
50 free(name);
Peter Åstrand (astrand)396f8c92018-03-20 08:10:12 +010051 vncRandRDisableOutput(i);
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +010052 disabledOutputs->insert(vncRandRGetOutputId(i));
Peter Åstrand (astrand)396f8c92018-03-20 08:10:12 +010053 }
54 }
55 }
56
57 return vncRandRResizeScreen(fb_width, fb_height);
58}
59
60
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +010061/* Return output index of preferred output, -1 on failure */
62int getPreferredScreenOutput(OutputIdMap *outputIdMap,
63 const std::set<unsigned int>& disabledOutputs)
64{
65 int firstDisabled = -1;
66 int firstEnabled = -1;
67 int firstConnected = -1;
68 int firstUsable = -1;
69
70 for (int i = 0;i < vncRandRGetOutputCount();i++) {
71 unsigned int output = vncRandRGetOutputId(i);
72
73 /* In use? */
74 if (outputIdMap->count(output) == 1) {
75 continue;
76 }
77
78 /* Can it be used? */
79 if (!vncRandRIsOutputUsable(i)) {
80 continue;
81 }
82
83 /* Temporarily disabled? */
84 if (disabledOutputs.count(output)) {
85 if (firstDisabled == -1) firstDisabled = i;
86 }
87
88 /* Enabled? */
89 if (vncRandRIsOutputEnabled(i)) {
90 if (firstEnabled == -1) firstEnabled = i;
91 }
92
93 /* Connected? */
94 if (vncRandRIsOutputConnected(i)) {
95 if (firstConnected == -1) firstConnected = i;
96 }
97
98 if (firstUsable == -1) firstUsable = i;
99 }
100
101 if (firstEnabled != -1) {
102 return firstEnabled;
103 } else if (firstDisabled != -1) {
104 return firstDisabled;
105 } else if (firstConnected != -1) {
106 return firstConnected;
107 } else {
108 return firstUsable; /* Possibly -1 */
109 }
110}
111
112
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200113rfb::ScreenSet computeScreenLayout(OutputIdMap *outputIdMap)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100114{
115 rfb::ScreenSet layout;
116 OutputIdMap newIdMap;
117
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200118 for (int i = 0;i < vncRandRGetOutputCount();i++) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100119 unsigned int outputId;
120 int x, y, width, height;
121
122 /* Disabled? */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200123 if (!vncRandRIsOutputEnabled(i))
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100124 continue;
125
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200126 outputId = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100127
128 /* Known output? */
129 if (outputIdMap->count(outputId) == 1)
130 newIdMap[outputId] = (*outputIdMap)[outputId];
131 else {
132 rdr::U32 id;
133 OutputIdMap::const_iterator iter;
134
135 while (true) {
136 id = rand();
137 for (iter = outputIdMap->begin();iter != outputIdMap->end();++iter) {
138 if (iter->second == id)
139 break;
140 }
141 if (iter == outputIdMap->end())
142 break;
143 }
144
145 newIdMap[outputId] = id;
146 }
147
Peter Åstrand (astrand)d57acc32018-03-19 10:47:40 +0100148 if (vncRandRGetOutputDimensions(i, &x, &y, &width, &height) == 0) {
149 layout.add_screen(rfb::Screen(newIdMap[outputId], x, y, width, height, 0));
150 }
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100151 }
152
153 /* Only keep the entries that are currently active */
154 *outputIdMap = newIdMap;
155
156 /*
157 * Make sure we have something to display. Hopefully it's just temporary
158 * that we have no active outputs...
159 */
160 if (layout.num_screens() == 0)
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200161 layout.add_screen(rfb::Screen(0, 0, 0, vncGetScreenWidth(),
162 vncGetScreenHeight(), 0));
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100163
164 return layout;
165}
166
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200167unsigned int setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100168 OutputIdMap *outputIdMap)
169{
170 int ret;
171 int availableOutputs;
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100172 std::set<unsigned int> disabledOutputs;
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100173
174 // RandR support?
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200175 if (vncRandRGetOutputCount() == 0)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100176 return rfb::resultProhibited;
177
178 /*
179 * First check that we don't have any active clone modes. That's just
180 * too messy to deal with.
181 */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200182 if (vncRandRHasOutputClones()) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100183 vlog.error("Clone mode active. Refusing to touch screen layout.");
184 return rfb::resultInvalid;
185 }
186
187 /* Next count how many useful outputs we have... */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200188 availableOutputs = vncRandRGetAvailableOutputs();
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100189
190 /* Try to create more outputs if needed... (only works on Xvnc) */
191 if (layout.num_screens() > availableOutputs) {
192 vlog.debug("Insufficient screens. Need to create %d more.",
193 layout.num_screens() - availableOutputs);
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200194 ret = vncRandRCreateOutputs(layout.num_screens() - availableOutputs);
Peter Åstrand (astrand)94ab2db2018-03-07 12:32:30 +0100195 if (!ret) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100196 vlog.error("Unable to create more screens, as needed by the new client layout.");
197 return rfb::resultInvalid;
198 }
199 }
200
201 /* First we might need to resize the screen */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200202 if ((fb_width != vncGetScreenWidth()) ||
203 (fb_height != vncGetScreenHeight())) {
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100204 ret = ResizeScreen(fb_width, fb_height, &disabledOutputs);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100205 if (!ret) {
206 vlog.error("Failed to resize screen to %dx%d", fb_width, fb_height);
207 return rfb::resultInvalid;
208 }
209 }
210
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100211 /* Next, reconfigure all known outputs */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200212 for (int i = 0;i < vncRandRGetOutputCount();i++) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100213 unsigned int output;
214
215 rfb::ScreenSet::const_iterator iter;
216
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200217 output = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100218
219 /* Known? */
220 if (outputIdMap->count(output) == 0)
221 continue;
222
223 /* Find the corresponding screen... */
224 for (iter = layout.begin();iter != layout.end();++iter) {
225 if (iter->id == (*outputIdMap)[output])
226 break;
227 }
228
229 /* Missing? */
230 if (iter == layout.end()) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100231 outputIdMap->erase(output);
232 continue;
233 }
234
235 /* Reconfigure new mode and position */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200236 ret = vncRandRReconfigureOutput(i,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100237 iter->dimensions.tl.x,
238 iter->dimensions.tl.y,
239 iter->dimensions.width(),
240 iter->dimensions.height());
Peter Åstrand (astrand)651faf82018-03-19 11:07:32 +0100241 char *name = vncRandRGetOutputName(i);
242 if (ret) {
243 vlog.debug("Reconfigured output '%s' to %dx%d+%d+%d", name,
244 iter->dimensions.width(), iter->dimensions.height(),
245 iter->dimensions.tl.x, iter->dimensions.tl.y);
246 } else {
247 vlog.error("Failed to reconfigure output '%s' to %dx%d+%d+%d", name,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100248 iter->dimensions.width(), iter->dimensions.height(),
249 iter->dimensions.tl.x, iter->dimensions.tl.y);
250 free(name);
251 return rfb::resultInvalid;
252 }
Peter Åstrand (astrand)651faf82018-03-19 11:07:32 +0100253 free(name);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100254 }
255
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100256 /* Allocate new outputs for new screens */
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100257 rfb::ScreenSet::const_iterator iter;
258 for (iter = layout.begin();iter != layout.end();++iter) {
259 OutputIdMap::const_iterator oi;
260 unsigned int output;
261 int i;
262
263 /* Does this screen have an output already? */
264 for (oi = outputIdMap->begin();oi != outputIdMap->end();++oi) {
265 if (oi->second == iter->id)
266 break;
267 }
268
269 if (oi != outputIdMap->end())
270 continue;
271
272 /* Find an unused output */
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100273 i = getPreferredScreenOutput(outputIdMap, disabledOutputs);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100274
275 /* Shouldn't happen */
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100276 if (i == -1)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100277 return rfb::resultInvalid;
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100278 output = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100279
280 /*
281 * Make sure we already have an entry for this, or
282 * computeScreenLayout() will think it is a brand new output and
283 * assign it a random id.
284 */
285 (*outputIdMap)[output] = iter->id;
286
287 /* Reconfigure new mode and position */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200288 ret = vncRandRReconfigureOutput(i,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100289 iter->dimensions.tl.x,
290 iter->dimensions.tl.y,
291 iter->dimensions.width(),
292 iter->dimensions.height());
Peter Åstrand (astrand)651faf82018-03-19 11:07:32 +0100293 char *name = vncRandRGetOutputName(i);
294 if (ret) {
295 vlog.debug("Reconfigured new output '%s' to %dx%d+%d+%d", name,
296 iter->dimensions.width(), iter->dimensions.height(),
297 iter->dimensions.tl.x, iter->dimensions.tl.y);
298 } else {
299 vlog.error("Failed to reconfigure new output '%s' to %dx%d+%d+%d", name,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100300 iter->dimensions.width(), iter->dimensions.height(),
301 iter->dimensions.tl.x, iter->dimensions.tl.y);
302 free(name);
303 return rfb::resultInvalid;
304 }
Peter Åstrand (astrand)651faf82018-03-19 11:07:32 +0100305 free(name);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100306 }
307
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100308 /* Turn off unused outputs */
309 for (int i = 0;i < vncRandRGetOutputCount();i++) {
310 unsigned int output = vncRandRGetOutputId(i);
311
312 /* Known? */
313 if (outputIdMap->count(output) == 1)
314 continue;
315
Peter Åstrand (astrand)8fcf6cc2018-03-20 10:26:40 +0100316 /* Enabled? */
317 if (!vncRandRIsOutputEnabled(i))
318 continue;
319
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100320 /* Disable and move on... */
321 ret = vncRandRDisableOutput(i);
Peter Åstrand (astrand)651faf82018-03-19 11:07:32 +0100322 char *name = vncRandRGetOutputName(i);
323 if (ret) {
324 vlog.debug("Disabled unused output '%s'", name);
325 } else {
326 vlog.error("Failed to disable unused output '%s'", name);
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100327 free(name);
328 return rfb::resultInvalid;
329 }
Peter Åstrand (astrand)651faf82018-03-19 11:07:32 +0100330 free(name);
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100331 }
332
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100333 /*
334 * Update timestamp for when screen layout was last changed.
335 * This is normally done in the X11 request handlers, which is
336 * why we have to deal with it manually here.
337 */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200338 vncRandRUpdateSetTime();
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100339
340 return rfb::resultSuccess;
341}