blob: 858199165b3e6a9815eefac9c785b71ce9ecd82e [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)ffeda072018-04-02 22:15:47 +020033rfb::ScreenSet computeScreenLayout(OutputIdMap *outputIdMap)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010034{
35 rfb::ScreenSet layout;
36 OutputIdMap newIdMap;
37
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +020038 for (int i = 0;i < vncRandRGetOutputCount();i++) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010039 unsigned int outputId;
40 int x, y, width, height;
41
42 /* Disabled? */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +020043 if (!vncRandRIsOutputEnabled(i))
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010044 continue;
45
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +020046 outputId = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010047
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
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +020068 vncRandRGetOutputDimensions(i, &x, &y, &width, &height);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010069
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)
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +020081 layout.add_screen(rfb::Screen(0, 0, 0, vncGetScreenWidth(),
82 vncGetScreenHeight(), 0));
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010083
84 return layout;
85}
86
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +020087unsigned int setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010088 OutputIdMap *outputIdMap)
89{
90 int ret;
91 int availableOutputs;
92
93 // RandR support?
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +020094 if (vncRandRGetOutputCount() == 0)
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +010095 return rfb::resultProhibited;
96
97 /*
98 * First check that we don't have any active clone modes. That's just
99 * too messy to deal with.
100 */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200101 if (vncRandRHasOutputClones()) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100102 vlog.error("Clone mode active. Refusing to touch screen layout.");
103 return rfb::resultInvalid;
104 }
105
106 /* Next count how many useful outputs we have... */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200107 availableOutputs = vncRandRGetAvailableOutputs();
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100108
109 /* Try to create more outputs if needed... (only works on Xvnc) */
110 if (layout.num_screens() > availableOutputs) {
111 vlog.debug("Insufficient screens. Need to create %d more.",
112 layout.num_screens() - availableOutputs);
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200113 ret = vncRandRCreateOutputs(layout.num_screens() - availableOutputs);
Peter Åstrand (astrand)94ab2db2018-03-07 12:32:30 +0100114 if (!ret) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100115 vlog.error("Unable to create more screens, as needed by the new client layout.");
116 return rfb::resultInvalid;
117 }
118 }
119
120 /* First we might need to resize the screen */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200121 if ((fb_width != vncGetScreenWidth()) ||
122 (fb_height != vncGetScreenHeight())) {
123 ret = vncRandRResizeScreen(fb_width, fb_height);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100124 if (!ret) {
125 vlog.error("Failed to resize screen to %dx%d", fb_width, fb_height);
126 return rfb::resultInvalid;
127 }
128 }
129
130 /* Next, reconfigure all known outputs, and turn off the other ones */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200131 for (int i = 0;i < vncRandRGetOutputCount();i++) {
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100132 unsigned int output;
133
134 rfb::ScreenSet::const_iterator iter;
135
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200136 output = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100137
138 /* Known? */
139 if (outputIdMap->count(output) == 0)
140 continue;
141
142 /* Find the corresponding screen... */
143 for (iter = layout.begin();iter != layout.end();++iter) {
144 if (iter->id == (*outputIdMap)[output])
145 break;
146 }
147
148 /* Missing? */
149 if (iter == layout.end()) {
150 /* Disable and move on... */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200151 ret = vncRandRDisableOutput(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100152 if (!ret) {
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200153 char *name = vncRandRGetOutputName(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100154 vlog.error("Failed to disable unused output '%s'",
155 name);
156 free(name);
157 return rfb::resultInvalid;
158 }
159 outputIdMap->erase(output);
160 continue;
161 }
162
163 /* Reconfigure new mode and position */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200164 ret = vncRandRReconfigureOutput(i,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100165 iter->dimensions.tl.x,
166 iter->dimensions.tl.y,
167 iter->dimensions.width(),
168 iter->dimensions.height());
169 if (!ret) {
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200170 char *name = vncRandRGetOutputName(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100171 vlog.error("Failed to reconfigure output '%s' to %dx%d+%d+%d",
172 name,
173 iter->dimensions.width(), iter->dimensions.height(),
174 iter->dimensions.tl.x, iter->dimensions.tl.y);
175 free(name);
176 return rfb::resultInvalid;
177 }
178 }
179
180 /* Finally, allocate new outputs for new screens */
181 rfb::ScreenSet::const_iterator iter;
182 for (iter = layout.begin();iter != layout.end();++iter) {
183 OutputIdMap::const_iterator oi;
184 unsigned int output;
185 int i;
186
187 /* Does this screen have an output already? */
188 for (oi = outputIdMap->begin();oi != outputIdMap->end();++oi) {
189 if (oi->second == iter->id)
190 break;
191 }
192
193 if (oi != outputIdMap->end())
194 continue;
195
196 /* Find an unused output */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200197 for (i = 0;i < vncRandRGetOutputCount();i++) {
198 output = vncRandRGetOutputId(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100199
200 /* In use? */
201 if (outputIdMap->count(output) == 1)
202 continue;
203
204 /* Can it be used? */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200205 if (!vncRandRIsOutputUsable(i))
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100206 continue;
207
208 break;
209 }
210
211 /* Shouldn't happen */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200212 if (i == vncRandRGetOutputCount())
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100213 return rfb::resultInvalid;
214
215 /*
216 * Make sure we already have an entry for this, or
217 * computeScreenLayout() will think it is a brand new output and
218 * assign it a random id.
219 */
220 (*outputIdMap)[output] = iter->id;
221
222 /* Reconfigure new mode and position */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200223 ret = vncRandRReconfigureOutput(i,
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100224 iter->dimensions.tl.x,
225 iter->dimensions.tl.y,
226 iter->dimensions.width(),
227 iter->dimensions.height());
228 if (!ret) {
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200229 char *name = vncRandRGetOutputName(i);
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100230 vlog.error("Failed to reconfigure output '%s' to %dx%d+%d+%d",
231 name,
232 iter->dimensions.width(), iter->dimensions.height(),
233 iter->dimensions.tl.x, iter->dimensions.tl.y);
234 free(name);
235 return rfb::resultInvalid;
236 }
237 }
238
239 /*
240 * Update timestamp for when screen layout was last changed.
241 * This is normally done in the X11 request handlers, which is
242 * why we have to deal with it manually here.
243 */
Peter Åstrand (astrand)ffeda072018-04-02 22:15:47 +0200244 vncRandRUpdateSetTime();
Peter Åstrand (astrand)11736372018-03-07 09:29:00 +0100245
246 return rfb::resultSuccess;
247}