You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

randr.cxx 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
  2. * Copyright 2009-2017 Pierre Ossman for Cendio AB
  3. * Copyright 2018 Peter Astrand <astrand@cendio.se> for Cendio AB
  4. * 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. #ifdef HAVE_CONFIG_H
  22. #include <config.h>
  23. #endif
  24. #include <stdlib.h>
  25. #include <unixcommon.h>
  26. #include <rfb/screenTypes.h>
  27. #include <rfb/LogWriter.h>
  28. #include <RandrGlue.h>
  29. static rfb::LogWriter vlog("RandR");
  30. static int ResizeScreen(int fb_width, int fb_height,
  31. std::set<unsigned int>* disabledOutputs)
  32. {
  33. vlog.debug("Resizing screen framebuffer to %dx%d", fb_width, fb_height);
  34. /*
  35. * Disable outputs which are larger than the target size
  36. */
  37. for (int i = 0;i < vncRandRGetOutputCount();i++) {
  38. int x, y, width, height;
  39. if (vncRandRGetOutputDimensions(i, &x, &y, &width, &height) == 0) {
  40. if (x + width > fb_width || y + height > fb_height) {
  41. /* Currently ignoring errors */
  42. /* FIXME: Save output rotation and restore when configuring output */
  43. char *name = vncRandRGetOutputName(i);
  44. vlog.debug("Temporarily disabling output '%s'", name);
  45. free(name);
  46. vncRandRDisableOutput(i);
  47. disabledOutputs->insert(vncRandRGetOutputId(i));
  48. }
  49. }
  50. }
  51. return vncRandRResizeScreen(fb_width, fb_height);
  52. }
  53. /* Return output index of preferred output, -1 on failure */
  54. int getPreferredScreenOutput(OutputIdMap *outputIdMap,
  55. const std::set<unsigned int>& disabledOutputs)
  56. {
  57. int firstDisabled = -1;
  58. int firstEnabled = -1;
  59. int firstConnected = -1;
  60. int firstUsable = -1;
  61. for (int i = 0;i < vncRandRGetOutputCount();i++) {
  62. unsigned int output = vncRandRGetOutputId(i);
  63. /* In use? */
  64. if (outputIdMap->count(output) == 1) {
  65. continue;
  66. }
  67. /* Can it be used? */
  68. if (!vncRandRIsOutputUsable(i)) {
  69. continue;
  70. }
  71. /* Temporarily disabled? */
  72. if (disabledOutputs.count(output)) {
  73. if (firstDisabled == -1) firstDisabled = i;
  74. }
  75. /* Enabled? */
  76. if (vncRandRIsOutputEnabled(i)) {
  77. if (firstEnabled == -1) firstEnabled = i;
  78. }
  79. /* Connected? */
  80. if (vncRandRIsOutputConnected(i)) {
  81. if (firstConnected == -1) firstConnected = i;
  82. }
  83. if (firstUsable == -1) firstUsable = i;
  84. }
  85. if (firstEnabled != -1) {
  86. return firstEnabled;
  87. } else if (firstDisabled != -1) {
  88. return firstDisabled;
  89. } else if (firstConnected != -1) {
  90. return firstConnected;
  91. } else {
  92. return firstUsable; /* Possibly -1 */
  93. }
  94. }
  95. rfb::ScreenSet computeScreenLayout(OutputIdMap *outputIdMap)
  96. {
  97. rfb::ScreenSet layout;
  98. OutputIdMap newIdMap;
  99. for (int i = 0;i < vncRandRGetOutputCount();i++) {
  100. unsigned int outputId;
  101. int x, y, width, height;
  102. /* Disabled? */
  103. if (!vncRandRIsOutputEnabled(i))
  104. continue;
  105. outputId = vncRandRGetOutputId(i);
  106. /* Known output? */
  107. if (outputIdMap->count(outputId) == 1)
  108. newIdMap[outputId] = (*outputIdMap)[outputId];
  109. else {
  110. rdr::U32 id;
  111. OutputIdMap::const_iterator iter;
  112. while (true) {
  113. id = rand();
  114. for (iter = outputIdMap->begin();iter != outputIdMap->end();++iter) {
  115. if (iter->second == id)
  116. break;
  117. }
  118. if (iter == outputIdMap->end())
  119. break;
  120. }
  121. newIdMap[outputId] = id;
  122. }
  123. if (vncRandRGetOutputDimensions(i, &x, &y, &width, &height) == 0) {
  124. layout.add_screen(rfb::Screen(newIdMap[outputId], x, y, width, height, 0));
  125. }
  126. }
  127. /* Only keep the entries that are currently active */
  128. *outputIdMap = newIdMap;
  129. /*
  130. * Make sure we have something to display. Hopefully it's just temporary
  131. * that we have no active outputs...
  132. */
  133. if (layout.num_screens() == 0)
  134. layout.add_screen(rfb::Screen(0, 0, 0, vncGetScreenWidth(),
  135. vncGetScreenHeight(), 0));
  136. return layout;
  137. }
  138. unsigned int setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout,
  139. OutputIdMap *outputIdMap)
  140. {
  141. int ret;
  142. int availableOutputs;
  143. std::set<unsigned int> disabledOutputs;
  144. // RandR support?
  145. if (vncRandRGetOutputCount() == 0)
  146. return rfb::resultProhibited;
  147. /*
  148. * First check that we don't have any active clone modes. That's just
  149. * too messy to deal with.
  150. */
  151. if (vncRandRHasOutputClones()) {
  152. vlog.error("Clone mode active. Refusing to touch screen layout.");
  153. return rfb::resultInvalid;
  154. }
  155. /* Next count how many useful outputs we have... */
  156. availableOutputs = vncRandRGetAvailableOutputs();
  157. /* Try to create more outputs if needed... (only works on Xvnc) */
  158. if (layout.num_screens() > availableOutputs) {
  159. vlog.debug("Insufficient screens. Need to create %d more.",
  160. layout.num_screens() - availableOutputs);
  161. ret = vncRandRCreateOutputs(layout.num_screens() - availableOutputs);
  162. if (!ret) {
  163. vlog.error("Unable to create more screens, as needed by the new client layout.");
  164. return rfb::resultInvalid;
  165. }
  166. }
  167. /* First we might need to resize the screen */
  168. if ((fb_width != vncGetScreenWidth()) ||
  169. (fb_height != vncGetScreenHeight())) {
  170. ret = ResizeScreen(fb_width, fb_height, &disabledOutputs);
  171. if (!ret) {
  172. vlog.error("Failed to resize screen to %dx%d", fb_width, fb_height);
  173. return rfb::resultInvalid;
  174. }
  175. }
  176. /* Next, reconfigure all known outputs */
  177. for (int i = 0;i < vncRandRGetOutputCount();i++) {
  178. unsigned int output;
  179. rfb::ScreenSet::const_iterator iter;
  180. output = vncRandRGetOutputId(i);
  181. /* Known? */
  182. if (outputIdMap->count(output) == 0)
  183. continue;
  184. /* Find the corresponding screen... */
  185. for (iter = layout.begin();iter != layout.end();++iter) {
  186. if (iter->id == (*outputIdMap)[output])
  187. break;
  188. }
  189. /* Missing? */
  190. if (iter == layout.end()) {
  191. outputIdMap->erase(output);
  192. continue;
  193. }
  194. /* Reconfigure new mode and position */
  195. ret = vncRandRReconfigureOutput(i,
  196. iter->dimensions.tl.x,
  197. iter->dimensions.tl.y,
  198. iter->dimensions.width(),
  199. iter->dimensions.height());
  200. char *name = vncRandRGetOutputName(i);
  201. if (ret) {
  202. vlog.debug("Reconfigured output '%s' to %dx%d+%d+%d", name,
  203. iter->dimensions.width(), iter->dimensions.height(),
  204. iter->dimensions.tl.x, iter->dimensions.tl.y);
  205. } else {
  206. vlog.error("Failed to reconfigure output '%s' to %dx%d+%d+%d", name,
  207. iter->dimensions.width(), iter->dimensions.height(),
  208. iter->dimensions.tl.x, iter->dimensions.tl.y);
  209. free(name);
  210. return rfb::resultInvalid;
  211. }
  212. free(name);
  213. }
  214. /* Allocate new outputs for new screens */
  215. rfb::ScreenSet::const_iterator iter;
  216. for (iter = layout.begin();iter != layout.end();++iter) {
  217. OutputIdMap::const_iterator oi;
  218. unsigned int output;
  219. int i;
  220. /* Does this screen have an output already? */
  221. for (oi = outputIdMap->begin();oi != outputIdMap->end();++oi) {
  222. if (oi->second == iter->id)
  223. break;
  224. }
  225. if (oi != outputIdMap->end())
  226. continue;
  227. /* Find an unused output */
  228. i = getPreferredScreenOutput(outputIdMap, disabledOutputs);
  229. /* Shouldn't happen */
  230. if (i == -1)
  231. return rfb::resultInvalid;
  232. output = vncRandRGetOutputId(i);
  233. /*
  234. * Make sure we already have an entry for this, or
  235. * computeScreenLayout() will think it is a brand new output and
  236. * assign it a random id.
  237. */
  238. (*outputIdMap)[output] = iter->id;
  239. /* Reconfigure new mode and position */
  240. ret = vncRandRReconfigureOutput(i,
  241. iter->dimensions.tl.x,
  242. iter->dimensions.tl.y,
  243. iter->dimensions.width(),
  244. iter->dimensions.height());
  245. char *name = vncRandRGetOutputName(i);
  246. if (ret) {
  247. vlog.debug("Reconfigured new output '%s' to %dx%d+%d+%d", name,
  248. iter->dimensions.width(), iter->dimensions.height(),
  249. iter->dimensions.tl.x, iter->dimensions.tl.y);
  250. } else {
  251. vlog.error("Failed to reconfigure new output '%s' to %dx%d+%d+%d", 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. free(name);
  258. }
  259. /* Turn off unused outputs */
  260. for (int i = 0;i < vncRandRGetOutputCount();i++) {
  261. unsigned int output = vncRandRGetOutputId(i);
  262. /* Known? */
  263. if (outputIdMap->count(output) == 1)
  264. continue;
  265. /* Enabled? */
  266. if (!vncRandRIsOutputEnabled(i))
  267. continue;
  268. /* Disable and move on... */
  269. ret = vncRandRDisableOutput(i);
  270. char *name = vncRandRGetOutputName(i);
  271. if (ret) {
  272. vlog.debug("Disabled unused output '%s'", name);
  273. } else {
  274. vlog.error("Failed to disable unused output '%s'", name);
  275. free(name);
  276. return rfb::resultInvalid;
  277. }
  278. free(name);
  279. }
  280. /*
  281. * Update timestamp for when screen layout was last changed.
  282. * This is normally done in the X11 request handlers, which is
  283. * why we have to deal with it manually here.
  284. */
  285. vncRandRUpdateSetTime();
  286. return rfb::resultSuccess;
  287. }