blob: 8514817c91ea6a69b8336644a5351d7ae4067e9f [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
Peter Åstrand (astrand)396f8c92018-03-20 08:10:12 +010033static int ResizeScreen(int fb_width, int fb_height)
34{
35 /*
36 * Disable outputs which are larger than the target size
37 */
38 for (int i = 0;i < vncRandRGetOutputCount();i++) {
39 int x, y, width, height;
40 if (vncRandRGetOutputDimensions(i, &x, &y, &width, &height) == 0) {
41 if (x + width > fb_width || y + height > fb_height) {
42 /* Currently ignoring errors */
43 /* FIXME: Save output rotation and restore when configuring output */
44 vncRandRDisableOutput(i);
45 }
46 }
47 }
48
49 return vncRandRResizeScreen(fb_width, fb_height);
50}
51
52
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +020053rfb::ScreenSet computeScreenLayout(OutputIdMap *outputIdMap)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010054{
55 rfb::ScreenSet layout;
56 OutputIdMap newIdMap;
57
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +020058 for (int i = 0;i < vncRandRGetOutputCount();i++) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010059 unsigned int outputId;
60 int x, y, width, height;
61
62 /* Disabled? */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +020063 if (!vncRandRIsOutputEnabled(i))
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010064 continue;
65
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +020066 outputId = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010067
68 /* Known output? */
69 if (outputIdMap->count(outputId) == 1)
70 newIdMap[outputId] = (*outputIdMap)[outputId];
71 else {
72 rdr::U32 id;
73 OutputIdMap::const_iterator iter;
74
75 while (true) {
76 id = rand();
77 for (iter = outputIdMap->begin();iter != outputIdMap->end();++iter) {
78 if (iter->second == id)
79 break;
80 }
81 if (iter == outputIdMap->end())
82 break;
83 }
84
85 newIdMap[outputId] = id;
86 }
87
Peter Åstrand (astrand)d57acc32018-03-19 10:47:40 +010088 if (vncRandRGetOutputDimensions(i, &x, &y, &width, &height) == 0) {
89 layout.add_screen(rfb::Screen(newIdMap[outputId], x, y, width, height, 0));
90 }
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010091 }
92
93 /* Only keep the entries that are currently active */
94 *outputIdMap = newIdMap;
95
96 /*
97 * Make sure we have something to display. Hopefully it's just temporary
98 * that we have no active outputs...
99 */
100 if (layout.num_screens() == 0)
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200101 layout.add_screen(rfb::Screen(0, 0, 0, vncGetScreenWidth(),
102 vncGetScreenHeight(), 0));
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100103
104 return layout;
105}
106
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200107unsigned int setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100108 OutputIdMap *outputIdMap)
109{
110 int ret;
111 int availableOutputs;
112
113 // RandR support?
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200114 if (vncRandRGetOutputCount() == 0)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100115 return rfb::resultProhibited;
116
117 /*
118 * First check that we don't have any active clone modes. That's just
119 * too messy to deal with.
120 */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200121 if (vncRandRHasOutputClones()) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100122 vlog.error("Clone mode active. Refusing to touch screen layout.");
123 return rfb::resultInvalid;
124 }
125
126 /* Next count how many useful outputs we have... */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200127 availableOutputs = vncRandRGetAvailableOutputs();
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100128
129 /* Try to create more outputs if needed... (only works on Xvnc) */
130 if (layout.num_screens() > availableOutputs) {
131 vlog.debug("Insufficient screens. Need to create %d more.",
132 layout.num_screens() - availableOutputs);
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200133 ret = vncRandRCreateOutputs(layout.num_screens() - availableOutputs);
Peter Åstrand (astrand)94ab2db2018-03-07 12:32:30 +0100134 if (!ret) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100135 vlog.error("Unable to create more screens, as needed by the new client layout.");
136 return rfb::resultInvalid;
137 }
138 }
139
140 /* First we might need to resize the screen */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200141 if ((fb_width != vncGetScreenWidth()) ||
142 (fb_height != vncGetScreenHeight())) {
Peter Åstrand (astrand)396f8c92018-03-20 08:10:12 +0100143 ret = ResizeScreen(fb_width, fb_height);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100144 if (!ret) {
145 vlog.error("Failed to resize screen to %dx%d", fb_width, fb_height);
146 return rfb::resultInvalid;
147 }
148 }
149
150 /* Next, reconfigure all known outputs, and turn off the other ones */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200151 for (int i = 0;i < vncRandRGetOutputCount();i++) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100152 unsigned int output;
153
154 rfb::ScreenSet::const_iterator iter;
155
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200156 output = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100157
158 /* Known? */
159 if (outputIdMap->count(output) == 0)
160 continue;
161
162 /* Find the corresponding screen... */
163 for (iter = layout.begin();iter != layout.end();++iter) {
164 if (iter->id == (*outputIdMap)[output])
165 break;
166 }
167
168 /* Missing? */
169 if (iter == layout.end()) {
170 /* Disable and move on... */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200171 ret = vncRandRDisableOutput(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100172 if (!ret) {
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200173 char *name = vncRandRGetOutputName(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100174 vlog.error("Failed to disable unused output '%s'",
175 name);
176 free(name);
177 return rfb::resultInvalid;
178 }
179 outputIdMap->erase(output);
180 continue;
181 }
182
183 /* Reconfigure new mode and position */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200184 ret = vncRandRReconfigureOutput(i,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100185 iter->dimensions.tl.x,
186 iter->dimensions.tl.y,
187 iter->dimensions.width(),
188 iter->dimensions.height());
189 if (!ret) {
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200190 char *name = vncRandRGetOutputName(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100191 vlog.error("Failed to reconfigure output '%s' to %dx%d+%d+%d",
192 name,
193 iter->dimensions.width(), iter->dimensions.height(),
194 iter->dimensions.tl.x, iter->dimensions.tl.y);
195 free(name);
196 return rfb::resultInvalid;
197 }
198 }
199
200 /* Finally, allocate new outputs for new screens */
201 rfb::ScreenSet::const_iterator iter;
202 for (iter = layout.begin();iter != layout.end();++iter) {
203 OutputIdMap::const_iterator oi;
204 unsigned int output;
205 int i;
206
207 /* Does this screen have an output already? */
208 for (oi = outputIdMap->begin();oi != outputIdMap->end();++oi) {
209 if (oi->second == iter->id)
210 break;
211 }
212
213 if (oi != outputIdMap->end())
214 continue;
215
216 /* Find an unused output */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200217 for (i = 0;i < vncRandRGetOutputCount();i++) {
218 output = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100219
220 /* In use? */
221 if (outputIdMap->count(output) == 1)
222 continue;
223
224 /* Can it be used? */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200225 if (!vncRandRIsOutputUsable(i))
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100226 continue;
227
228 break;
229 }
230
231 /* Shouldn't happen */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200232 if (i == vncRandRGetOutputCount())
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100233 return rfb::resultInvalid;
234
235 /*
236 * Make sure we already have an entry for this, or
237 * computeScreenLayout() will think it is a brand new output and
238 * assign it a random id.
239 */
240 (*outputIdMap)[output] = iter->id;
241
242 /* Reconfigure new mode and position */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200243 ret = vncRandRReconfigureOutput(i,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100244 iter->dimensions.tl.x,
245 iter->dimensions.tl.y,
246 iter->dimensions.width(),
247 iter->dimensions.height());
248 if (!ret) {
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200249 char *name = vncRandRGetOutputName(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100250 vlog.error("Failed to reconfigure output '%s' to %dx%d+%d+%d",
251 name,
252 iter->dimensions.width(), iter->dimensions.height(),
253 iter->dimensions.tl.x, iter->dimensions.tl.y);
254 free(name);
255 return rfb::resultInvalid;
256 }
257 }
258
259 /*
260 * Update timestamp for when screen layout was last changed.
261 * This is normally done in the X11 request handlers, which is
262 * why we have to deal with it manually here.
263 */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200264 vncRandRUpdateSetTime();
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100265
266 return rfb::resultSuccess;
267}