blob: 9cfc95dd9dfb713af377f8b54909aac54768e150 [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{
37 /*
38 * Disable outputs which are larger than the target size
39 */
40 for (int i = 0;i < vncRandRGetOutputCount();i++) {
41 int x, y, width, height;
42 if (vncRandRGetOutputDimensions(i, &x, &y, &width, &height) == 0) {
43 if (x + width > fb_width || y + height > fb_height) {
44 /* Currently ignoring errors */
45 /* FIXME: Save output rotation and restore when configuring output */
46 vncRandRDisableOutput(i);
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +010047 disabledOutputs->insert(vncRandRGetOutputId(i));
Peter Åstrand (astrand)396f8c92018-03-20 08:10:12 +010048 }
49 }
50 }
51
52 return vncRandRResizeScreen(fb_width, fb_height);
53}
54
55
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +010056/* Return output index of preferred output, -1 on failure */
57int getPreferredScreenOutput(OutputIdMap *outputIdMap,
58 const std::set<unsigned int>& disabledOutputs)
59{
60 int firstDisabled = -1;
61 int firstEnabled = -1;
62 int firstConnected = -1;
63 int firstUsable = -1;
64
65 for (int i = 0;i < vncRandRGetOutputCount();i++) {
66 unsigned int output = vncRandRGetOutputId(i);
67
68 /* In use? */
69 if (outputIdMap->count(output) == 1) {
70 continue;
71 }
72
73 /* Can it be used? */
74 if (!vncRandRIsOutputUsable(i)) {
75 continue;
76 }
77
78 /* Temporarily disabled? */
79 if (disabledOutputs.count(output)) {
80 if (firstDisabled == -1) firstDisabled = i;
81 }
82
83 /* Enabled? */
84 if (vncRandRIsOutputEnabled(i)) {
85 if (firstEnabled == -1) firstEnabled = i;
86 }
87
88 /* Connected? */
89 if (vncRandRIsOutputConnected(i)) {
90 if (firstConnected == -1) firstConnected = i;
91 }
92
93 if (firstUsable == -1) firstUsable = i;
94 }
95
96 if (firstEnabled != -1) {
97 return firstEnabled;
98 } else if (firstDisabled != -1) {
99 return firstDisabled;
100 } else if (firstConnected != -1) {
101 return firstConnected;
102 } else {
103 return firstUsable; /* Possibly -1 */
104 }
105}
106
107
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200108rfb::ScreenSet computeScreenLayout(OutputIdMap *outputIdMap)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100109{
110 rfb::ScreenSet layout;
111 OutputIdMap newIdMap;
112
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200113 for (int i = 0;i < vncRandRGetOutputCount();i++) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100114 unsigned int outputId;
115 int x, y, width, height;
116
117 /* Disabled? */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200118 if (!vncRandRIsOutputEnabled(i))
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100119 continue;
120
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200121 outputId = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100122
123 /* Known output? */
124 if (outputIdMap->count(outputId) == 1)
125 newIdMap[outputId] = (*outputIdMap)[outputId];
126 else {
127 rdr::U32 id;
128 OutputIdMap::const_iterator iter;
129
130 while (true) {
131 id = rand();
132 for (iter = outputIdMap->begin();iter != outputIdMap->end();++iter) {
133 if (iter->second == id)
134 break;
135 }
136 if (iter == outputIdMap->end())
137 break;
138 }
139
140 newIdMap[outputId] = id;
141 }
142
Peter Åstrand (astrand)d57acc32018-03-19 10:47:40 +0100143 if (vncRandRGetOutputDimensions(i, &x, &y, &width, &height) == 0) {
144 layout.add_screen(rfb::Screen(newIdMap[outputId], x, y, width, height, 0));
145 }
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100146 }
147
148 /* Only keep the entries that are currently active */
149 *outputIdMap = newIdMap;
150
151 /*
152 * Make sure we have something to display. Hopefully it's just temporary
153 * that we have no active outputs...
154 */
155 if (layout.num_screens() == 0)
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200156 layout.add_screen(rfb::Screen(0, 0, 0, vncGetScreenWidth(),
157 vncGetScreenHeight(), 0));
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100158
159 return layout;
160}
161
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200162unsigned int setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100163 OutputIdMap *outputIdMap)
164{
165 int ret;
166 int availableOutputs;
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100167 std::set<unsigned int> disabledOutputs;
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100168
169 // RandR support?
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200170 if (vncRandRGetOutputCount() == 0)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100171 return rfb::resultProhibited;
172
173 /*
174 * First check that we don't have any active clone modes. That's just
175 * too messy to deal with.
176 */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200177 if (vncRandRHasOutputClones()) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100178 vlog.error("Clone mode active. Refusing to touch screen layout.");
179 return rfb::resultInvalid;
180 }
181
182 /* Next count how many useful outputs we have... */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200183 availableOutputs = vncRandRGetAvailableOutputs();
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100184
185 /* Try to create more outputs if needed... (only works on Xvnc) */
186 if (layout.num_screens() > availableOutputs) {
187 vlog.debug("Insufficient screens. Need to create %d more.",
188 layout.num_screens() - availableOutputs);
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200189 ret = vncRandRCreateOutputs(layout.num_screens() - availableOutputs);
Peter Åstrand (astrand)94ab2db2018-03-07 12:32:30 +0100190 if (!ret) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100191 vlog.error("Unable to create more screens, as needed by the new client layout.");
192 return rfb::resultInvalid;
193 }
194 }
195
196 /* First we might need to resize the screen */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200197 if ((fb_width != vncGetScreenWidth()) ||
198 (fb_height != vncGetScreenHeight())) {
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100199 ret = ResizeScreen(fb_width, fb_height, &disabledOutputs);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100200 if (!ret) {
201 vlog.error("Failed to resize screen to %dx%d", fb_width, fb_height);
202 return rfb::resultInvalid;
203 }
204 }
205
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100206 /* Next, reconfigure all known outputs */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200207 for (int i = 0;i < vncRandRGetOutputCount();i++) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100208 unsigned int output;
209
210 rfb::ScreenSet::const_iterator iter;
211
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200212 output = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100213
214 /* Known? */
215 if (outputIdMap->count(output) == 0)
216 continue;
217
218 /* Find the corresponding screen... */
219 for (iter = layout.begin();iter != layout.end();++iter) {
220 if (iter->id == (*outputIdMap)[output])
221 break;
222 }
223
224 /* Missing? */
225 if (iter == layout.end()) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100226 outputIdMap->erase(output);
227 continue;
228 }
229
230 /* Reconfigure new mode and position */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200231 ret = vncRandRReconfigureOutput(i,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100232 iter->dimensions.tl.x,
233 iter->dimensions.tl.y,
234 iter->dimensions.width(),
235 iter->dimensions.height());
236 if (!ret) {
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200237 char *name = vncRandRGetOutputName(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100238 vlog.error("Failed to reconfigure output '%s' to %dx%d+%d+%d",
239 name,
240 iter->dimensions.width(), iter->dimensions.height(),
241 iter->dimensions.tl.x, iter->dimensions.tl.y);
242 free(name);
243 return rfb::resultInvalid;
244 }
245 }
246
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100247 /* Allocate new outputs for new screens */
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100248 rfb::ScreenSet::const_iterator iter;
249 for (iter = layout.begin();iter != layout.end();++iter) {
250 OutputIdMap::const_iterator oi;
251 unsigned int output;
252 int i;
253
254 /* Does this screen have an output already? */
255 for (oi = outputIdMap->begin();oi != outputIdMap->end();++oi) {
256 if (oi->second == iter->id)
257 break;
258 }
259
260 if (oi != outputIdMap->end())
261 continue;
262
263 /* Find an unused output */
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100264 i = getPreferredScreenOutput(outputIdMap, disabledOutputs);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100265
266 /* Shouldn't happen */
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100267 if (i == -1)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100268 return rfb::resultInvalid;
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100269 output = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100270
271 /*
272 * Make sure we already have an entry for this, or
273 * computeScreenLayout() will think it is a brand new output and
274 * assign it a random id.
275 */
276 (*outputIdMap)[output] = iter->id;
277
278 /* Reconfigure new mode and position */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200279 ret = vncRandRReconfigureOutput(i,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100280 iter->dimensions.tl.x,
281 iter->dimensions.tl.y,
282 iter->dimensions.width(),
283 iter->dimensions.height());
284 if (!ret) {
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200285 char *name = vncRandRGetOutputName(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100286 vlog.error("Failed to reconfigure output '%s' to %dx%d+%d+%d",
287 name,
288 iter->dimensions.width(), iter->dimensions.height(),
289 iter->dimensions.tl.x, iter->dimensions.tl.y);
290 free(name);
291 return rfb::resultInvalid;
292 }
293 }
294
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100295 /* Turn off unused outputs */
296 for (int i = 0;i < vncRandRGetOutputCount();i++) {
297 unsigned int output = vncRandRGetOutputId(i);
298
299 /* Known? */
300 if (outputIdMap->count(output) == 1)
301 continue;
302
Peter Åstrand (astrand)8fcf6cc2018-03-20 10:26:40 +0100303 /* Enabled? */
304 if (!vncRandRIsOutputEnabled(i))
305 continue;
306
Peter Åstrand (astrand)a61c6f22018-03-20 08:15:41 +0100307 /* Disable and move on... */
308 ret = vncRandRDisableOutput(i);
309 if (!ret) {
310 char *name = vncRandRGetOutputName(i);
311 vlog.error("Failed to disable unused output '%s'",
312 name);
313 free(name);
314 return rfb::resultInvalid;
315 }
316 }
317
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100318 /*
319 * Update timestamp for when screen layout was last changed.
320 * This is normally done in the X11 request handlers, which is
321 * why we have to deal with it manually here.
322 */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200323 vncRandRUpdateSetTime();
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100324
325 return rfb::resultSuccess;
326}