blob: 08bd45e6e71eb1992ed8d934a372253fa68cf9df [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
3 * Copyright 2014 Brian P. Hinz
4 *
5 * This is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This software is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this software; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18 * USA.
19 */
20
21#ifdef HAVE_CONFIG_H
22#include <config.h>
23#endif
24
25#include <stdlib.h>
26
27#include <unixcommon.h>
28#include <rfb/screenTypes.h>
29#include <rfb/LogWriter.h>
30#include <RandrGlue.h>
31static rfb::LogWriter vlog("RandR");
32
33rfb::ScreenSet computeScreenLayout(int screenIndex, OutputIdMap *outputIdMap)
34{
35 rfb::ScreenSet layout;
36 OutputIdMap newIdMap;
37
38 for (int i = 0;i < vncRandRGetOutputCount(screenIndex);i++) {
39 unsigned int outputId;
40 int x, y, width, height;
41
42 /* Disabled? */
43 if (!vncRandRIsOutputEnabled(screenIndex, i))
44 continue;
45
46 outputId = vncRandRGetOutputId(screenIndex, i);
47
48 /* Known output? */
49 if (outputIdMap->count(outputId) == 1)
50 newIdMap[outputId] = (*outputIdMap)[outputId];
51 else {
52 rdr::U32 id;
53 OutputIdMap::const_iterator iter;
54
55 while (true) {
56 id = rand();
57 for (iter = outputIdMap->begin();iter != outputIdMap->end();++iter) {
58 if (iter->second == id)
59 break;
60 }
61 if (iter == outputIdMap->end())
62 break;
63 }
64
65 newIdMap[outputId] = id;
66 }
67
68 vncRandRGetOutputDimensions(screenIndex, i, &x, &y, &width, &height);
69
70 layout.add_screen(rfb::Screen(newIdMap[outputId], x, y, width, height, 0));
71 }
72
73 /* Only keep the entries that are currently active */
74 *outputIdMap = newIdMap;
75
76 /*
77 * Make sure we have something to display. Hopefully it's just temporary
78 * that we have no active outputs...
79 */
80 if (layout.num_screens() == 0)
81 layout.add_screen(rfb::Screen(0, 0, 0, vncGetScreenWidth(screenIndex),
82 vncGetScreenHeight(screenIndex), 0));
83
84 return layout;
85}
86
87unsigned int setScreenLayout(int screenIndex,
88 int fb_width, int fb_height, const rfb::ScreenSet& layout,
89 OutputIdMap *outputIdMap)
90{
91 int ret;
92 int availableOutputs;
93
94 // RandR support?
95 if (vncRandRGetOutputCount(screenIndex) == 0)
96 return rfb::resultProhibited;
97
98 /*
99 * First check that we don't have any active clone modes. That's just
100 * too messy to deal with.
101 */
102 if (vncRandRHasOutputClones(screenIndex)) {
103 vlog.error("Clone mode active. Refusing to touch screen layout.");
104 return rfb::resultInvalid;
105 }
106
107 /* Next count how many useful outputs we have... */
108 availableOutputs = vncRandRGetAvailableOutputs(screenIndex);
109
110 /* Try to create more outputs if needed... (only works on Xvnc) */
111 if (layout.num_screens() > availableOutputs) {
112 vlog.debug("Insufficient screens. Need to create %d more.",
113 layout.num_screens() - availableOutputs);
114 ret = vncRandRCreateOutputs(screenIndex,
115 layout.num_screens() - availableOutputs);
Peter Åstrand (astrand)94ab2db2018-03-07 12:32:30 +0100116 if (!ret) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100117 vlog.error("Unable to create more screens, as needed by the new client layout.");
118 return rfb::resultInvalid;
119 }
120 }
121
122 /* First we might need to resize the screen */
123 if ((fb_width != vncGetScreenWidth(screenIndex)) ||
124 (fb_height != vncGetScreenHeight(screenIndex))) {
125 ret = vncRandRResizeScreen(screenIndex, fb_width, fb_height);
126 if (!ret) {
127 vlog.error("Failed to resize screen to %dx%d", fb_width, fb_height);
128 return rfb::resultInvalid;
129 }
130 }
131
132 /* Next, reconfigure all known outputs, and turn off the other ones */
133 for (int i = 0;i < vncRandRGetOutputCount(screenIndex);i++) {
134 unsigned int output;
135
136 rfb::ScreenSet::const_iterator iter;
137
138 output = vncRandRGetOutputId(screenIndex, i);
139
140 /* Known? */
141 if (outputIdMap->count(output) == 0)
142 continue;
143
144 /* Find the corresponding screen... */
145 for (iter = layout.begin();iter != layout.end();++iter) {
146 if (iter->id == (*outputIdMap)[output])
147 break;
148 }
149
150 /* Missing? */
151 if (iter == layout.end()) {
152 /* Disable and move on... */
153 ret = vncRandRDisableOutput(screenIndex, i);
154 if (!ret) {
155 char *name = vncRandRGetOutputName(screenIndex, i);
156 vlog.error("Failed to disable unused output '%s'",
157 name);
158 free(name);
159 return rfb::resultInvalid;
160 }
161 outputIdMap->erase(output);
162 continue;
163 }
164
165 /* Reconfigure new mode and position */
166 ret = vncRandRReconfigureOutput(screenIndex, i,
167 iter->dimensions.tl.x,
168 iter->dimensions.tl.y,
169 iter->dimensions.width(),
170 iter->dimensions.height());
171 if (!ret) {
172 char *name = vncRandRGetOutputName(screenIndex, i);
173 vlog.error("Failed to reconfigure output '%s' to %dx%d+%d+%d",
174 name,
175 iter->dimensions.width(), iter->dimensions.height(),
176 iter->dimensions.tl.x, iter->dimensions.tl.y);
177 free(name);
178 return rfb::resultInvalid;
179 }
180 }
181
182 /* Finally, allocate new outputs for new screens */
183 rfb::ScreenSet::const_iterator iter;
184 for (iter = layout.begin();iter != layout.end();++iter) {
185 OutputIdMap::const_iterator oi;
186 unsigned int output;
187 int i;
188
189 /* Does this screen have an output already? */
190 for (oi = outputIdMap->begin();oi != outputIdMap->end();++oi) {
191 if (oi->second == iter->id)
192 break;
193 }
194
195 if (oi != outputIdMap->end())
196 continue;
197
198 /* Find an unused output */
199 for (i = 0;i < vncRandRGetOutputCount(screenIndex);i++) {
200 output = vncRandRGetOutputId(screenIndex, i);
201
202 /* In use? */
203 if (outputIdMap->count(output) == 1)
204 continue;
205
206 /* Can it be used? */
207 if (!vncRandRIsOutputUsable(screenIndex, i))
208 continue;
209
210 break;
211 }
212
213 /* Shouldn't happen */
214 if (i == vncRandRGetOutputCount(screenIndex))
215 return rfb::resultInvalid;
216
217 /*
218 * Make sure we already have an entry for this, or
219 * computeScreenLayout() will think it is a brand new output and
220 * assign it a random id.
221 */
222 (*outputIdMap)[output] = iter->id;
223
224 /* Reconfigure new mode and position */
225 ret = vncRandRReconfigureOutput(screenIndex, i,
226 iter->dimensions.tl.x,
227 iter->dimensions.tl.y,
228 iter->dimensions.width(),
229 iter->dimensions.height());
230 if (!ret) {
231 char *name = vncRandRGetOutputName(screenIndex, i);
232 vlog.error("Failed to reconfigure output '%s' to %dx%d+%d+%d",
233 name,
234 iter->dimensions.width(), iter->dimensions.height(),
235 iter->dimensions.tl.x, iter->dimensions.tl.y);
236 free(name);
237 return rfb::resultInvalid;
238 }
239 }
240
241 /*
242 * Update timestamp for when screen layout was last changed.
243 * This is normally done in the X11 request handlers, which is
244 * why we have to deal with it manually here.
245 */
246 vncRandRUpdateSetTime(screenIndex);
247
248 return rfb::resultSuccess;
249}