/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright 2009-2017 Pierre Ossman for Cendio AB * Copyright 2018 Peter Astrand for Cendio AB * Copyright 2014 Brian P. Hinz * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include static rfb::LogWriter vlog("RandR"); static int ResizeScreen(bool dryrun, int fb_width, int fb_height, std::set* disabledOutputs) { vlog.debug("Resizing screen framebuffer to %dx%d", fb_width, fb_height); /* * Disable outputs which are larger than the target size */ for (int i = 0;i < vncRandRGetOutputCount();i++) { int x, y, width, height; if (vncRandRGetOutputDimensions(i, &x, &y, &width, &height) == 0) { if (x + width > fb_width || y + height > fb_height) { char *name = vncRandRGetOutputName(i); vlog.debug("Temporarily disabling output '%s'", name); free(name); if (!dryrun) { /* Currently ignoring errors */ /* FIXME: Save output rotation and restore when configuring output */ vncRandRDisableOutput(i); disabledOutputs->insert(vncRandRGetOutputId(i)); } } } } if (!vncRandRIsValidScreenSize(fb_width, fb_height)) return 0; if (dryrun) return 1; return vncRandRResizeScreen(fb_width, fb_height); } /* Return output index of preferred output, -1 on failure */ int getPreferredScreenOutput(OutputIdMap *outputIdMap, const std::set& disabledOutputs) { int firstDisabled = -1; int firstEnabled = -1; int firstConnected = -1; int firstUsable = -1; for (int i = 0;i < vncRandRGetOutputCount();i++) { unsigned int output = vncRandRGetOutputId(i); /* In use? */ if (outputIdMap->count(output) == 1) { continue; } /* Can it be used? */ if (!vncRandRIsOutputUsable(i)) { continue; } /* Temporarily disabled? */ if (disabledOutputs.count(output)) { if (firstDisabled == -1) firstDisabled = i; } /* Enabled? */ if (vncRandRIsOutputEnabled(i)) { if (firstEnabled == -1) firstEnabled = i; } /* Connected? */ if (vncRandRIsOutputConnected(i)) { if (firstConnected == -1) firstConnected = i; } if (firstUsable == -1) firstUsable = i; } if (firstEnabled != -1) { return firstEnabled; } else if (firstDisabled != -1) { return firstDisabled; } else if (firstConnected != -1) { return firstConnected; } else { return firstUsable; /* Possibly -1 */ } } rfb::ScreenSet computeScreenLayout(OutputIdMap *outputIdMap) { rfb::ScreenSet layout; OutputIdMap newIdMap; for (int i = 0;i < vncRandRGetOutputCount();i++) { unsigned int outputId; int x, y, width, height; /* Disabled? */ if (!vncRandRIsOutputEnabled(i)) continue; outputId = vncRandRGetOutputId(i); /* Known output? */ if (outputIdMap->count(outputId) == 1) newIdMap[outputId] = (*outputIdMap)[outputId]; else { rdr::U32 id; OutputIdMap::const_iterator iter; while (true) { id = rand(); for (iter = outputIdMap->begin();iter != outputIdMap->end();++iter) { if (iter->second == id) break; } if (iter == outputIdMap->end()) break; } newIdMap[outputId] = id; } if (vncRandRGetOutputDimensions(i, &x, &y, &width, &height) == 0) { layout.add_screen(rfb::Screen(newIdMap[outputId], x, y, width, height, 0)); } } /* Only keep the entries that are currently active */ *outputIdMap = newIdMap; /* * Make sure we have something to display. Hopefully it's just temporary * that we have no active outputs... */ if (layout.num_screens() == 0) layout.add_screen(rfb::Screen(0, 0, 0, vncGetScreenWidth(), vncGetScreenHeight(), 0)); return layout; } static unsigned int _setScreenLayout(bool dryrun, int fb_width, int fb_height, const rfb::ScreenSet& layout, OutputIdMap *outputIdMap) { int ret; int availableOutputs; std::set disabledOutputs; /* Printing errors in the dryrun pass might be confusing */ const bool logErrors = !dryrun || vlog.getLevel() >= vlog.LEVEL_DEBUG; // RandR support? if (vncRandRGetOutputCount() == 0) return rfb::resultProhibited; /* * First check that we don't have any active clone modes. That's just * too messy to deal with. */ if (vncRandRHasOutputClones()) { if (logErrors) { vlog.error("Clone mode active. Refusing to touch screen layout."); } return rfb::resultInvalid; } /* Next count how many useful outputs we have... */ availableOutputs = vncRandRGetAvailableOutputs(); /* Try to create more outputs if needed... (only works on Xvnc) */ if (layout.num_screens() > availableOutputs) { vlog.debug("Insufficient screens. Need to create %d more.", layout.num_screens() - availableOutputs); if (!vncRandRCanCreateOutputs(layout.num_screens() - availableOutputs)) { if (logErrors) vlog.error("Unable to create more screens, as needed by the new client layout."); return rfb::resultInvalid; } if (!dryrun) { ret = vncRandRCreateOutputs(layout.num_screens() - availableOutputs); if (!ret) { if (logErrors) vlog.error("Unable to create more screens, as needed by the new client layout."); return rfb::resultInvalid; } } } /* First we might need to resize the screen */ if ((fb_width != vncGetScreenWidth()) || (fb_height != vncGetScreenHeight())) { ret = ResizeScreen(dryrun, fb_width, fb_height, &disabledOutputs); if (!ret) { if (logErrors) { vlog.error("Failed to resize screen to %dx%d", fb_width, fb_height); } return rfb::resultInvalid; } } /* Next, reconfigure all known outputs */ for (int i = 0;i < vncRandRGetOutputCount();i++) { unsigned int output; rfb::ScreenSet::const_iterator iter; output = vncRandRGetOutputId(i); /* Known? */ if (outputIdMap->count(output) == 0) continue; /* Find the corresponding screen... */ for (iter = layout.begin();iter != layout.end();++iter) { if (iter->id == (*outputIdMap)[output]) break; } /* Missing? */ if (iter == layout.end()) { ret = vncRandRDisableOutput(i); if (!ret) { char *name = vncRandRGetOutputName(i); vlog.error("Failed to disable unused output '%s'", name); free(name); return rfb::resultInvalid; } outputIdMap->erase(output); continue; } /* Probably not needed, but let's be safe */ if (!vncRandRIsOutputUsable(i)) { if (logErrors) { char *name = vncRandRGetOutputName(i); vlog.error("Required output '%s' cannot be used", name); free(name); } return rfb::resultInvalid; } /* Possible mode? */ if (!vncRandRCheckOutputMode(i, iter->dimensions.width(), iter->dimensions.height())) { if (logErrors) { char *name = vncRandRGetOutputName(i); vlog.error("Output '%s' does not support required mode %dx%d", name, iter->dimensions.width(), iter->dimensions.height()); free(name); } return rfb::resultInvalid; } char *name = vncRandRGetOutputName(i); vlog.debug("Reconfiguring output '%s' to %dx%d+%d+%d", name, iter->dimensions.width(), iter->dimensions.height(), iter->dimensions.tl.x, iter->dimensions.tl.y); free(name); if (dryrun) continue; /* Reconfigure new mode and position */ ret = vncRandRReconfigureOutput(i, iter->dimensions.tl.x, iter->dimensions.tl.y, iter->dimensions.width(), iter->dimensions.height()); if (!ret) { if (logErrors) { char *name = vncRandRGetOutputName(i); vlog.error("Failed to reconfigure output '%s' to %dx%d+%d+%d", name, iter->dimensions.width(), iter->dimensions.height(), iter->dimensions.tl.x, iter->dimensions.tl.y); free(name); } return rfb::resultInvalid; } } /* Allocate new outputs for new screens */ rfb::ScreenSet::const_iterator iter; for (iter = layout.begin();iter != layout.end();++iter) { OutputIdMap::const_iterator oi; unsigned int output; int i; /* Does this screen have an output already? */ for (oi = outputIdMap->begin();oi != outputIdMap->end();++oi) { if (oi->second == iter->id) break; } if (oi != outputIdMap->end()) continue; /* Find an unused output */ i = getPreferredScreenOutput(outputIdMap, disabledOutputs); /* Shouldn't happen */ if (i == -1) return rfb::resultInvalid; output = vncRandRGetOutputId(i); /* * Make sure we already have an entry for this, or * computeScreenLayout() will think it is a brand new output and * assign it a random id. */ (*outputIdMap)[output] = iter->id; /* Probably not needed, but let's be safe */ if (!vncRandRIsOutputUsable(i)) { if (logErrors) { char *name = vncRandRGetOutputName(i); vlog.error("Required new output '%s' cannot be used", name); free(name); } return rfb::resultInvalid; } /* Possible mode? */ if (!vncRandRCheckOutputMode(i, iter->dimensions.width(), iter->dimensions.height())) { if (logErrors) { char *name = vncRandRGetOutputName(i); vlog.error("New output '%s' does not support required mode %dx%d", name, iter->dimensions.width(), iter->dimensions.height()); free(name); } return rfb::resultInvalid; } char *name = vncRandRGetOutputName(i); vlog.debug("Reconfiguring new output '%s' to %dx%d+%d+%d", name, iter->dimensions.width(), iter->dimensions.height(), iter->dimensions.tl.x, iter->dimensions.tl.y); free(name); if (dryrun) continue; /* Reconfigure new mode and position */ ret = vncRandRReconfigureOutput(i, iter->dimensions.tl.x, iter->dimensions.tl.y, iter->dimensions.width(), iter->dimensions.height()); if (!ret) { if (logErrors) { char *name = vncRandRGetOutputName(i); vlog.error("Failed to reconfigure new output '%s' to %dx%d+%d+%d", name, iter->dimensions.width(), iter->dimensions.height(), iter->dimensions.tl.x, iter->dimensions.tl.y); free(name); } return rfb::resultInvalid; } } /* Turn off unused outputs */ for (int i = 0;i < vncRandRGetOutputCount();i++) { unsigned int output = vncRandRGetOutputId(i); /* Known? */ if (outputIdMap->count(output) == 1) continue; /* Enabled? */ if (!vncRandRIsOutputEnabled(i)) continue; /* Disable and move on... */ ret = vncRandRDisableOutput(i); char *name = vncRandRGetOutputName(i); if (ret) { vlog.debug("Disabled unused output '%s'", name); } else { if (logErrors) { vlog.error("Failed to disable unused output '%s'", name); } free(name); return rfb::resultInvalid; } free(name); } /* * Update timestamp for when screen layout was last changed. * This is normally done in the X11 request handlers, which is * why we have to deal with it manually here. */ vncRandRUpdateSetTime(); return rfb::resultSuccess; } unsigned int setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout, OutputIdMap *outputIdMap) { return _setScreenLayout(false, fb_width, fb_height, layout, outputIdMap); } unsigned int tryScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout, OutputIdMap *outputIdMap) { OutputIdMap dryrunIdMap = *outputIdMap; return _setScreenLayout(true, fb_width, fb_height, layout, &dryrunIdMap); }