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 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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(bool dryrun, 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. char *name = vncRandRGetOutputName(i);
  42. vlog.debug("Temporarily disabling output '%s'", name);
  43. free(name);
  44. if (!dryrun) {
  45. /* Currently ignoring errors */
  46. /* FIXME: Save output rotation and restore when configuring output */
  47. vncRandRDisableOutput(i);
  48. disabledOutputs->insert(vncRandRGetOutputId(i));
  49. }
  50. }
  51. }
  52. }
  53. if (!vncRandRIsValidScreenSize(fb_width, fb_height))
  54. return 0;
  55. if (dryrun)
  56. return 1;
  57. return vncRandRResizeScreen(fb_width, fb_height);
  58. }
  59. /* Return output index of preferred output, -1 on failure */
  60. int getPreferredScreenOutput(OutputIdMap *outputIdMap,
  61. const std::set<unsigned int>& disabledOutputs)
  62. {
  63. int firstDisabled = -1;
  64. int firstEnabled = -1;
  65. int firstConnected = -1;
  66. int firstUsable = -1;
  67. for (int i = 0;i < vncRandRGetOutputCount();i++) {
  68. unsigned int output = vncRandRGetOutputId(i);
  69. /* In use? */
  70. if (outputIdMap->count(output) == 1) {
  71. continue;
  72. }
  73. /* Can it be used? */
  74. if (!vncRandRIsOutputUsable(i)) {
  75. continue;
  76. }
  77. /* Temporarily disabled? */
  78. if (disabledOutputs.count(output)) {
  79. if (firstDisabled == -1) firstDisabled = i;
  80. }
  81. /* Enabled? */
  82. if (vncRandRIsOutputEnabled(i)) {
  83. if (firstEnabled == -1) firstEnabled = i;
  84. }
  85. /* Connected? */
  86. if (vncRandRIsOutputConnected(i)) {
  87. if (firstConnected == -1) firstConnected = i;
  88. }
  89. if (firstUsable == -1) firstUsable = i;
  90. }
  91. if (firstEnabled != -1) {
  92. return firstEnabled;
  93. } else if (firstDisabled != -1) {
  94. return firstDisabled;
  95. } else if (firstConnected != -1) {
  96. return firstConnected;
  97. } else {
  98. return firstUsable; /* Possibly -1 */
  99. }
  100. }
  101. rfb::ScreenSet computeScreenLayout(OutputIdMap *outputIdMap)
  102. {
  103. rfb::ScreenSet layout;
  104. OutputIdMap newIdMap;
  105. for (int i = 0;i < vncRandRGetOutputCount();i++) {
  106. unsigned int outputId;
  107. int x, y, width, height;
  108. /* Disabled? */
  109. if (!vncRandRIsOutputEnabled(i))
  110. continue;
  111. outputId = vncRandRGetOutputId(i);
  112. /* Known output? */
  113. if (outputIdMap->count(outputId) == 1)
  114. newIdMap[outputId] = (*outputIdMap)[outputId];
  115. else {
  116. rdr::U32 id;
  117. OutputIdMap::const_iterator iter;
  118. while (true) {
  119. id = rand();
  120. for (iter = outputIdMap->begin();iter != outputIdMap->end();++iter) {
  121. if (iter->second == id)
  122. break;
  123. }
  124. if (iter == outputIdMap->end())
  125. break;
  126. }
  127. newIdMap[outputId] = id;
  128. }
  129. if (vncRandRGetOutputDimensions(i, &x, &y, &width, &height) == 0) {
  130. layout.add_screen(rfb::Screen(newIdMap[outputId], x, y, width, height, 0));
  131. }
  132. }
  133. /* Only keep the entries that are currently active */
  134. *outputIdMap = newIdMap;
  135. /*
  136. * Make sure we have something to display. Hopefully it's just temporary
  137. * that we have no active outputs...
  138. */
  139. if (layout.num_screens() == 0)
  140. layout.add_screen(rfb::Screen(0, 0, 0, vncGetScreenWidth(),
  141. vncGetScreenHeight(), 0));
  142. return layout;
  143. }
  144. static unsigned int _setScreenLayout(bool dryrun,
  145. int fb_width, int fb_height, const rfb::ScreenSet& layout,
  146. OutputIdMap *outputIdMap)
  147. {
  148. int ret;
  149. int availableOutputs;
  150. std::set<unsigned int> disabledOutputs;
  151. /* Printing errors in the dryrun pass might be confusing */
  152. const bool logErrors = !dryrun || vlog.getLevel() >= vlog.LEVEL_DEBUG;
  153. // RandR support?
  154. if (vncRandRGetOutputCount() == 0)
  155. return rfb::resultProhibited;
  156. /*
  157. * First check that we don't have any active clone modes. That's just
  158. * too messy to deal with.
  159. */
  160. if (vncRandRHasOutputClones()) {
  161. if (logErrors) {
  162. vlog.error("Clone mode active. Refusing to touch screen layout.");
  163. }
  164. return rfb::resultInvalid;
  165. }
  166. /* Next count how many useful outputs we have... */
  167. availableOutputs = vncRandRGetAvailableOutputs();
  168. /* Try to create more outputs if needed... (only works on Xvnc) */
  169. if (layout.num_screens() > availableOutputs) {
  170. vlog.debug("Insufficient screens. Need to create %d more.",
  171. layout.num_screens() - availableOutputs);
  172. if (!vncRandRCanCreateOutputs(layout.num_screens() - availableOutputs)) {
  173. if (logErrors)
  174. vlog.error("Unable to create more screens, as needed by the new client layout.");
  175. return rfb::resultInvalid;
  176. }
  177. if (!dryrun) {
  178. ret = vncRandRCreateOutputs(layout.num_screens() - availableOutputs);
  179. if (!ret) {
  180. if (logErrors)
  181. vlog.error("Unable to create more screens, as needed by the new client layout.");
  182. return rfb::resultInvalid;
  183. }
  184. }
  185. }
  186. /* First we might need to resize the screen */
  187. if ((fb_width != vncGetScreenWidth()) ||
  188. (fb_height != vncGetScreenHeight())) {
  189. ret = ResizeScreen(dryrun, fb_width, fb_height, &disabledOutputs);
  190. if (!ret) {
  191. if (logErrors) {
  192. vlog.error("Failed to resize screen to %dx%d", fb_width, fb_height);
  193. }
  194. return rfb::resultInvalid;
  195. }
  196. }
  197. /* Next, reconfigure all known outputs */
  198. for (int i = 0;i < vncRandRGetOutputCount();i++) {
  199. unsigned int output;
  200. rfb::ScreenSet::const_iterator iter;
  201. output = vncRandRGetOutputId(i);
  202. /* Known? */
  203. if (outputIdMap->count(output) == 0)
  204. continue;
  205. /* Find the corresponding screen... */
  206. for (iter = layout.begin();iter != layout.end();++iter) {
  207. if (iter->id == (*outputIdMap)[output])
  208. break;
  209. }
  210. /* Missing? */
  211. if (iter == layout.end()) {
  212. outputIdMap->erase(output);
  213. continue;
  214. }
  215. /* Probably not needed, but let's be safe */
  216. if (!vncRandRIsOutputUsable(i)) {
  217. if (logErrors) {
  218. char *name = vncRandRGetOutputName(i);
  219. vlog.error("Required output '%s' cannot be used", name);
  220. free(name);
  221. }
  222. return rfb::resultInvalid;
  223. }
  224. /* Possible mode? */
  225. if (!vncRandRCheckOutputMode(i, iter->dimensions.width(),
  226. iter->dimensions.height())) {
  227. if (logErrors) {
  228. char *name = vncRandRGetOutputName(i);
  229. vlog.error("Output '%s' does not support required mode %dx%d", name,
  230. iter->dimensions.width(), iter->dimensions.height());
  231. free(name);
  232. }
  233. return rfb::resultInvalid;
  234. }
  235. char *name = vncRandRGetOutputName(i);
  236. vlog.debug("Reconfiguring output '%s' to %dx%d+%d+%d", name,
  237. iter->dimensions.width(), iter->dimensions.height(),
  238. iter->dimensions.tl.x, iter->dimensions.tl.y);
  239. free(name);
  240. if (dryrun)
  241. continue;
  242. /* Reconfigure new mode and position */
  243. ret = vncRandRReconfigureOutput(i,
  244. iter->dimensions.tl.x,
  245. iter->dimensions.tl.y,
  246. iter->dimensions.width(),
  247. iter->dimensions.height());
  248. if (!ret) {
  249. if (logErrors) {
  250. char *name = vncRandRGetOutputName(i);
  251. vlog.error("Failed to reconfigure 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. }
  256. return rfb::resultInvalid;
  257. }
  258. }
  259. /* Allocate new outputs for new screens */
  260. rfb::ScreenSet::const_iterator iter;
  261. for (iter = layout.begin();iter != layout.end();++iter) {
  262. OutputIdMap::const_iterator oi;
  263. unsigned int output;
  264. int i;
  265. /* Does this screen have an output already? */
  266. for (oi = outputIdMap->begin();oi != outputIdMap->end();++oi) {
  267. if (oi->second == iter->id)
  268. break;
  269. }
  270. if (oi != outputIdMap->end())
  271. continue;
  272. /* Find an unused output */
  273. i = getPreferredScreenOutput(outputIdMap, disabledOutputs);
  274. /* Shouldn't happen */
  275. if (i == -1)
  276. return rfb::resultInvalid;
  277. output = vncRandRGetOutputId(i);
  278. /*
  279. * Make sure we already have an entry for this, or
  280. * computeScreenLayout() will think it is a brand new output and
  281. * assign it a random id.
  282. */
  283. (*outputIdMap)[output] = iter->id;
  284. /* Probably not needed, but let's be safe */
  285. if (!vncRandRIsOutputUsable(i)) {
  286. if (logErrors) {
  287. char *name = vncRandRGetOutputName(i);
  288. vlog.error("Required new output '%s' cannot be used", name);
  289. free(name);
  290. }
  291. return rfb::resultInvalid;
  292. }
  293. /* Possible mode? */
  294. if (!vncRandRCheckOutputMode(i, iter->dimensions.width(),
  295. iter->dimensions.height())) {
  296. if (logErrors) {
  297. char *name = vncRandRGetOutputName(i);
  298. vlog.error("New output '%s' does not support required mode %dx%d", name,
  299. iter->dimensions.width(), iter->dimensions.height());
  300. free(name);
  301. }
  302. return rfb::resultInvalid;
  303. }
  304. char *name = vncRandRGetOutputName(i);
  305. vlog.debug("Reconfiguring new output '%s' to %dx%d+%d+%d", name,
  306. iter->dimensions.width(), iter->dimensions.height(),
  307. iter->dimensions.tl.x, iter->dimensions.tl.y);
  308. free(name);
  309. if (dryrun)
  310. continue;
  311. /* Reconfigure new mode and position */
  312. ret = vncRandRReconfigureOutput(i,
  313. iter->dimensions.tl.x,
  314. iter->dimensions.tl.y,
  315. iter->dimensions.width(),
  316. iter->dimensions.height());
  317. if (!ret) {
  318. if (logErrors) {
  319. char *name = vncRandRGetOutputName(i);
  320. vlog.error("Failed to reconfigure new output '%s' to %dx%d+%d+%d", name,
  321. iter->dimensions.width(), iter->dimensions.height(),
  322. iter->dimensions.tl.x, iter->dimensions.tl.y);
  323. free(name);
  324. }
  325. return rfb::resultInvalid;
  326. }
  327. }
  328. /* Turn off unused outputs */
  329. for (int i = 0;i < vncRandRGetOutputCount();i++) {
  330. unsigned int output = vncRandRGetOutputId(i);
  331. /* Known? */
  332. if (outputIdMap->count(output) == 1)
  333. continue;
  334. /* Enabled? */
  335. if (!vncRandRIsOutputEnabled(i))
  336. continue;
  337. /* Disable and move on... */
  338. ret = vncRandRDisableOutput(i);
  339. char *name = vncRandRGetOutputName(i);
  340. if (ret) {
  341. vlog.debug("Disabled unused output '%s'", name);
  342. } else {
  343. if (logErrors) {
  344. vlog.error("Failed to disable unused output '%s'", name);
  345. }
  346. free(name);
  347. return rfb::resultInvalid;
  348. }
  349. free(name);
  350. }
  351. /*
  352. * Update timestamp for when screen layout was last changed.
  353. * This is normally done in the X11 request handlers, which is
  354. * why we have to deal with it manually here.
  355. */
  356. vncRandRUpdateSetTime();
  357. return rfb::resultSuccess;
  358. }
  359. unsigned int setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout,
  360. OutputIdMap *outputIdMap)
  361. {
  362. return _setScreenLayout(false, fb_width, fb_height, layout, outputIdMap);
  363. }
  364. unsigned int tryScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout,
  365. OutputIdMap *outputIdMap)
  366. {
  367. OutputIdMap dryrunIdMap = *outputIdMap;
  368. return _setScreenLayout(true, fb_width, fb_height, layout, &dryrunIdMap);
  369. }