summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2018-08-28 15:27:40 +0200
committerSimon Rettberg2018-08-28 15:27:40 +0200
commitef0a61233a08363691e523f009e8132319e3582b (patch)
tree5e60a42a3b81893cb76567c49249dcd6de3f5f0f
parentUI improvements (diff)
downloadbeamergui-ef0a61233a08363691e523f009e8132319e3582b.tar.gz
beamergui-ef0a61233a08363691e523f009e8132319e3582b.tar.xz
beamergui-ef0a61233a08363691e523f009e8132319e3582b.zip
Make wakeup work
-rw-r--r--CMakeLists.txt3
-rw-r--r--dbus/de.bwlehrpool.beamergui.conf14
-rw-r--r--src/bus.cpp40
-rw-r--r--src/bus.h26
-rw-r--r--src/i18n/de.ts47
-rw-r--r--src/main.cpp14
-rw-r--r--src/widget.cpp62
-rw-r--r--src/widget.ui37
-rw-r--r--src/x.cpp796
-rw-r--r--src/x.h97
-rw-r--r--src/xprivate.cpp452
-rw-r--r--src/xprivate.h70
-rw-r--r--src/xx.cpp348
-rw-r--r--src/xx.h66
14 files changed, 1160 insertions, 912 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 448676c..78e9c7c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,7 +16,7 @@ SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11" )
#
# Qt5
#
-FIND_PACKAGE(Qt5 COMPONENTS Widgets LinguistTools REQUIRED)
+FIND_PACKAGE(Qt5 COMPONENTS Widgets LinguistTools DBus REQUIRED)
FIND_PACKAGE(X11 REQUIRED)
if(NOT X11_Xrandr_FOUND)
@@ -77,6 +77,7 @@ add_executable(beamergui
target_link_libraries(beamergui
Qt5::Widgets
+ Qt5::DBus
${X11_LIBRARIES}
${X11_Xrandr_LIB}
)
diff --git a/dbus/de.bwlehrpool.beamergui.conf b/dbus/de.bwlehrpool.beamergui.conf
new file mode 100644
index 0000000..d3adf9b
--- /dev/null
+++ b/dbus/de.bwlehrpool.beamergui.conf
@@ -0,0 +1,14 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <policy user="root">
+ <allow own="de.bwlehrpool.beamergui"/>
+ <allow send_destination="de.bwlehrpool.beamergui"/>
+ </policy>
+ <policy context="default">
+ <deny own="de.bwlehrpool.beamergui"/>
+ <deny send_destination="de.bwlehrpool.beamergui"/>
+ </policy>
+</busconfig>
+
diff --git a/src/bus.cpp b/src/bus.cpp
new file mode 100644
index 0000000..1dadedb
--- /dev/null
+++ b/src/bus.cpp
@@ -0,0 +1,40 @@
+#include "bus.h"
+
+#include <QtDBus/QtDBus>
+
+static const QString SERVICE_NAME("de.bwlehrpool.beamergui");
+
+Bus* Bus::_instance = nullptr;
+
+Bus::Bus(QObject *parent) : QObject(parent), _hasListener(false)
+{
+
+}
+
+bool Bus::registerListener()
+{
+ if (!QDBusConnection::systemBus().isConnected()) {
+ qDebug() << "Cannot connect to system bus";
+ return false;
+ }
+ QDBusServiceWatcher *w = new QDBusServiceWatcher("de.bwlehrpool.beamergui", QDBusConnection::systemBus());
+ w->setParent(this);
+ connect(w, &QDBusServiceWatcher::serviceRegistered, [=](const QString &service) {
+ qDebug() << "Registered Service" << service;
+ emit serviceConnected();
+ });
+ return true;
+}
+
+bool Bus::registerService()
+{
+ if (!QDBusConnection::systemBus().isConnected()) {
+ qDebug() << "Cannot connect to system bus";
+ return false;
+ }
+ if (!QDBusConnection::systemBus().registerService(SERVICE_NAME)) {
+ qDebug() << QDBusConnection::systemBus().lastError().message();
+ return false;
+ }
+ return true;
+}
diff --git a/src/bus.h b/src/bus.h
new file mode 100644
index 0000000..f98ee65
--- /dev/null
+++ b/src/bus.h
@@ -0,0 +1,26 @@
+#ifndef BUS_H
+#define BUS_H
+
+#include <QObject>
+
+class Bus : public QObject
+{
+ Q_OBJECT
+public:
+ bool registerListener();
+ bool registerService();
+ inline static Bus* inst() {
+ if (_instance == nullptr) _instance = new Bus();
+ return _instance;
+ }
+
+signals:
+ void serviceConnected();
+
+private:
+ explicit Bus(QObject *parent = nullptr);
+ bool _hasListener;
+ static Bus *_instance;
+};
+
+#endif // BUS_H
diff --git a/src/i18n/de.ts b/src/i18n/de.ts
index a9ade8d..b8537b6 100644
--- a/src/i18n/de.ts
+++ b/src/i18n/de.ts
@@ -4,7 +4,7 @@
<context>
<name>QCoreApplication</name>
<message>
- <location filename="../widget.cpp" line="152"/>
+ <location filename="../widget.cpp" line="191"/>
<source>%1x%2</source>
<translation type="unfinished"></translation>
</message>
@@ -57,52 +57,79 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../widget.cpp" line="288"/>
+ <location filename="../widget.ui" line="265"/>
+ <source>Exit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../widget.ui" line="285"/>
+ <source>Close</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../widget.cpp" line="327"/>
<source>(off)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../widget.cpp" line="320"/>
+ <location filename="../widget.cpp" line="359"/>
<source>Output</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../widget.cpp" line="321"/>
+ <location filename="../widget.cpp" line="360"/>
<source>Position</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../widget.cpp" line="432"/>
+ <location filename="../widget.cpp" line="471"/>
<source>Do you want to keep this resolution?</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../widget.cpp" line="433"/>
+ <location filename="../widget.cpp" line="472"/>
<source>Keep</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <location filename="../widget.cpp" line="568"/>
+ <source>Confirm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../widget.cpp" line="569"/>
+ <source>This terminates the GUI.
+It will not pop up again if further screens are connected.
+Are you sure?</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>main</name>
<message>
- <location filename="../main.cpp" line="70"/>
+ <location filename="../main.cpp" line="75"/>
<source>Automatically configure modes and set up screens.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../main.cpp" line="74"/>
+ <location filename="../main.cpp" line="79"/>
<source>Show config GUI if more than one screen is connected.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../main.cpp" line="78"/>
+ <location filename="../main.cpp" line="83"/>
<source>Keep running in background and show GUI again when number of screens changes.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../main.cpp" line="82"/>
+ <location filename="../main.cpp" line="87"/>
<source>Test mode, don&apos;t actually apply any changes.</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <location filename="../main.cpp" line="91"/>
+ <source>Connect to system bus to trigger wakeup of wainting beamergui.</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
</TS>
diff --git a/src/main.cpp b/src/main.cpp
index 1493bcd..2a85617 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,6 +1,7 @@
#include "widget.h"
#include "main.h"
-#include "x.h"
+#include "xx.h"
+#include "bus.h"
#include <QApplication>
#include <QLibraryInfo>
@@ -8,7 +9,7 @@
#include <QCommandLineParser>
namespace {
-bool _testMode, _autoSetup, _showGui, _backgroundMode;
+bool _testMode, _autoSetup, _showGui, _backgroundMode, _wakeup;
}
namespace CommandLine
@@ -37,6 +38,10 @@ int main(int argc, char *argv[])
parseCommandLine(a);
+ if (_wakeup) {
+ return Bus::inst()->registerService() ? 0 : 1;
+ }
+
ScreenMode currentMode;
if (CommandLine::autoSetup()) {
currentMode = ScreenSetup::inst()->setDefaultMode(CommandLine::testMode());
@@ -81,10 +86,15 @@ static void parseCommandLine(const QApplication &a)
QCommandLineOption oTest(QStringList() << "t" << "test",
QCoreApplication::translate("main", "Test mode, don't actually apply any changes."));
parser.addOption(oTest);
+ // Wakeup beamergui daemon via DBus connect
+ QCommandLineOption oWakeup(QStringList() << "w" << "wakeup",
+ QCoreApplication::translate("main", "Connect to system bus to trigger wakeup of wainting beamergui."));
+ parser.addOption(oWakeup);
// PARSE
parser.process(a);
_testMode = parser.isSet(oTest);
_autoSetup = parser.isSet(oAutoSetup);
_showGui = parser.isSet(oShowGui);
_backgroundMode = parser.isSet(oBackground);
+ _wakeup = parser.isSet(oWakeup);
}
diff --git a/src/widget.cpp b/src/widget.cpp
index d0f0312..bc4869f 100644
--- a/src/widget.cpp
+++ b/src/widget.cpp
@@ -1,14 +1,20 @@
#include "widget.h"
+#include "main.h"
+#include "xx.h"
+#include "bus.h"
#include "ui_widget.h"
#include "timeoutdialog.h"
-#include "x.h"
-#include "main.h"
#include <QDebug>
#include <QtWidgets/QAction>
#include <QAbstractItemView>
#include <QScreen>
#include <QThread>
+#include <QMessageBox>
+
+/*
+ * Helper and static stuff
+ */
class ScreenWidget : public QWidget
{
@@ -62,7 +68,10 @@ static void addBoldListener(QComboBox *combo)
});
}
-//______________________________________________________________________________
+/*
+ * Main widget
+ */
+
Widget::Widget(QWidget *parent) :
QWidget(parent),
_ui(new Ui::Widget),
@@ -128,6 +137,36 @@ Widget::Widget(QWidget *parent) :
connect(qApp, &QGuiApplication::screenRemoved, [this](const QScreen *scrn) {
_qtScreens.removeAll(scrn);
});
+ _ui->btnExit->setVisible(CommandLine::backgroundMode());
+ if (CommandLine::backgroundMode()) {
+ // Listener
+ if (!Bus::inst()->registerListener()) {
+ qDebug() << "WARNING: CANNOT CONNECT TO DBUS FOR LISTENING";
+ // TODO: GUI feedback
+ } else {
+ // Worked fine
+ // Timer
+ QTimer *t = new QTimer(this);
+ t->setSingleShot(true);
+ connect(t, &QTimer::timeout, [=]() {
+ if (this->isHidden()) {
+ ScreenSetup::inst()->initModes();
+ this->show();
+ } else {
+ // TODO: Flash button
+ }
+ });
+ // GUI popup logic
+ connect(Bus::inst(), &Bus::serviceConnected, [=]() {
+ qDebug() << "\\o/ Received DBus connect notification \\o/";
+ if (this->isHidden()) {
+ t->start(1500);
+ } else {
+ // TODO: Flash button
+ }
+ });
+ }
+ }
}
//______________________________________________________________________________
@@ -516,6 +555,23 @@ void Widget::connectButtons() {
ScreenSetup::inst()->updateScreenResources();
initControls();
});
+ // Close
+ connect(_ui->btnClose, &QPushButton::clicked, [=](bool) {
+ if (CommandLine::backgroundMode()) {
+ this->hide();
+ } else {
+ qApp->exit(0);
+ }
+ });
+ // Exit
+ connect(_ui->btnExit, &QPushButton::clicked, [=](bool) {
+ if (QMessageBox::question(this, tr("Confirm"),
+ tr("This terminates the GUI.\n"
+ "It will not pop up again if further screens are connected.\n"
+ "Are you sure?")) == QMessageBox::Yes) {
+ qApp->exit(0);
+ }
+ });
}
diff --git a/src/widget.ui b/src/widget.ui
index 7a09c4c..a0be031 100644
--- a/src/widget.ui
+++ b/src/widget.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>796</width>
- <height>309</height>
+ <width>687</width>
+ <height>340</height>
</rect>
</property>
<property name="sizePolicy">
@@ -22,7 +22,7 @@
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
@@ -257,6 +257,37 @@ border: 2px solid black;
</widget>
</widget>
</item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="btnExit">
+ <property name="text">
+ <string>Exit</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnClose">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</item>
</layout>
diff --git a/src/x.cpp b/src/x.cpp
deleted file mode 100644
index ef43f08..0000000
--- a/src/x.cpp
+++ /dev/null
@@ -1,796 +0,0 @@
-#include "x.h"
-#include "cvt.h"
-#include <QDebug>
-
-
-///////////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////
-
-enum class Projector {
- No,
- Yes,
- Maybe
-};
-
-struct OutputInfo {
- OutputInfo(RROutput id, XRROutputInfo *output, XRRCrtcInfo *crtc, XRRModeInfo *mode)
- : output(output), crtc(crtc), mode(mode), id(id), position(-1), outputType(Projector::No) {}
- ~OutputInfo() {
- XRRFreeOutputInfo(output);
- }
- XRROutputInfo* output;
- XRRCrtcInfo* crtc;
- XRRModeInfo* mode;
- RROutput id;
- QString modelName;
- QString outputName;
- int position;
- Projector outputType;
-};
-
-ScreenInfo::ScreenInfo(const OutputInfo *oi, const ModeMap &om)
- : position(oi->position), name(oi->modelName), output(oi->outputName),
- isProjector(oi->outputType == Projector::Yes)
-{
- if (oi->mode != nullptr) {
- currentResolution = QSize(QSize(int(oi->mode->width), int(oi->mode->height)));
- }
- for (int i = 0; i < oi->output->nmode; ++i) {
- if (om.contains(oi->output->modes[i])) {
- auto m = om.value(oi->output->modes[i]);
- const QSize size(int(m->width), int(m->height));
- if (!modes.contains(size)) {
- modes.append(size);
- }
- if (i == 0 && oi->output->npreferred > 0) {
- preferredResolution = size;
- }
- }
- }
-}
-
-ScreenSetup * ScreenSetup::_instance = nullptr;
-
-ScreenSetup::ScreenSetup() : _screenResources(nullptr)
-{
- // Get initial data (to be freed)
- _display = XOpenDisplay(nullptr);
- if (_display == nullptr) {
- qFatal("Cannot open display");
- ::exit(1);
- }
- _EDID_ATOM = XInternAtom(_display, RR_PROPERTY_RANDR_EDID, False);
- /* Get informations about Xserver */
- updateScreenResources();
-
-}
-
-void ScreenSetup::freeResources()
-{
- freeCrtcBackup();
- // Clear the modemap (nothing to be freed, stored in screenResources)
- _modeMap.clear();
- _resolutions.clear();
-
- // Clear output info
- for (OutputInfo *output : _outputMap.values()) {
- delete output;
- }
- _outputMap.clear();
-
- // Clear crtc info
- for (XRRCrtcInfo *info : _crtcMap.values()) {
- XRRFreeCrtcInfo(info);
- }
- _crtcMap.clear();
-
- // Release resources
- if (_screenResources != nullptr) {
- XRRFreeScreenResources(_screenResources);
- _screenResources = nullptr;
- }
- XRRFreeScreenResources(_screenResources);
-}
-
-static bool xRectLessThan(const OutputInfo* const &a, const OutputInfo* const &b)
-{
- return a->crtc->x < b->crtc->x;
-}
-
-static double toVertRefresh(const XRRModeInfo *mode)
-{
- if (mode->hTotal > 0 && mode->vTotal > 0)
- return (double(mode->dotClock) / (double(mode->hTotal) * double(mode->vTotal)));
- return 0;
-}
-
-/**
- * Check list of known model names that falsely report a screen size or similar
- */
-static QStringList initProjectorList()
-{
- QStringList list;
- list << "AT-HDVS-RX"; // Switchbox
- // TODO: Load from file
- return list;
-}
-
-static bool isProjectorName(const QString &name)
-{
- static QStringList projectors = initProjectorList();
- return projectors.contains(name);
-}
-
-//___________________________________________________________________________
-void ScreenSetup::updateScreenResources()
-{
- freeResources();
- _screenResources = XRRGetScreenResourcesCurrent(_display, DefaultRootWindow(_display));
- // Create the modemap
- qDebug() << "Modes";
- for (int i = 0; i < _screenResources->nmode; ++i) {
- _modeMap.insert(
- _screenResources->modes[i].id,
- &_screenResources->modes[i]);
- qDebug() << _screenResources->modes[i].id << "\t"
- << _screenResources->modes[i].width << "x"
- << _screenResources->modes[i].height;
- }
-
- /*
- int num = 0;
- XRRMonitorInfo *mi = XRRGetMonitors(_display, DefaultRootWindow(_display), True, &num);
- for (int i = 0; i < num; ++i) {
- char *name = XGetAtomName(_display, mi [i].name);
- if (name == nullptr)
- continue;
- qDebug() << "some monitor with name" << name << mi[i].primary;
- XFree(name);
- }
- */
-
- // Create crtcMap
- qDebug() << "CRTCs";
- for (int i = 0; i < _screenResources->ncrtc; ++i) {
- XRRCrtcInfo * info = XRRGetCrtcInfo(
- _display,
- _screenResources,
- _screenResources->crtcs[i]);
- if (info == nullptr) {
- qDebug() << "Error getting CRTC info for crtc" << i;
- continue;
- }
- qDebug() << _screenResources->crtcs[i] << "-- Outputs:" << info->noutput << "Possible:" << info->npossible;
- _crtcMap.insert(_screenResources->crtcs[i], info);
- }
-
- // Create outputmap and connectedOutputMap
- qDebug() << "Outputs";
- QHash<RROutput, QString> tempMap;
- QMap<Projector, int> typeCount;
- for (int i = 0; i < _screenResources->noutput; ++i) {
- XRROutputInfo* info = XRRGetOutputInfo(
- _display,
- _screenResources,
- _screenResources->outputs[i]);
- if (info == nullptr) {
- qDebug() << "Error getting info for output" << i;
- continue;
- }
- const QString outputName = QString::fromLocal8Bit(info->name, info->nameLen);
- tempMap.insert(_screenResources->outputs[i], outputName);
- if (info->connection == RR_Disconnected) {
- qDebug() << "Ignoring disconnected output" << outputName;
- XRRFreeOutputInfo(info);
- continue;
- }
- bool disconnected = false;
- if (info->crtc == None) {
- disconnected = true;
- qDebug() << "Connected output" << outputName << "has no CRTC -- trying to find free one";
- info->crtc = getFreeCrtc(info);
- }
- if (!_crtcMap.contains(info->crtc)) {
- qDebug() << "Have output" << outputName << "with no known crtc";
- XRRFreeOutputInfo(info);
- continue;
- }
- XRRCrtcInfo *crtc = _crtcMap.value(info->crtc);
- XRRModeInfo *mode = nullptr;
- if (!disconnected) {
- if (!_modeMap.contains(crtc->mode)) {
- qDebug() << "Have output" << outputName << " with crtc with no known mode -- offline?";
- } else {
- mode = _modeMap.value(crtc->mode);
- }
- }
- OutputInfo *oi = new OutputInfo(_screenResources->outputs[i], info, crtc, mode);
- oi->outputName = outputName;
- if (!this->readEdid(oi)) { // We have no EDID - take it as an indicator that there's a dumb output split/switch box
- oi->outputType = Projector::Maybe;
- } else if ((info->mm_height == 0 && info->mm_width == 0) || isProjectorName(oi->modelName)) {
- oi->outputType = Projector::Yes; // Screens with size 0x0 are projectors by convention
- } else if (info->mm_width > 500 && info->mm_height > 500) { // Big screen - probably should be handled like a projector
- oi->outputType = Projector::Yes;
- } else if (info->mm_width > 350 && info->mm_height > 350) { // Somewhere in-between
- oi->outputType = Projector::Maybe;
- }
- typeCount[oi->outputType]++;
- _outputMap.insert(_screenResources->outputs[i], oi);
- qDebug() << "Connected" << outputName << "-- Clones:" << info->nclone << "Modes:" << info->nmode << "Crtcs:" << info->ncrtc << "Preferred:" << info->npreferred;
- }
- // Final checks for projector
- if (typeCount[Projector::Yes] == 0) {
- // No definitive projector
- if (typeCount[Projector::Maybe] > 0 && typeCount[Projector::No] > 0) {
- // Potential projector(s) and normal screen, promote
- for (OutputInfo *info : _outputMap) {
- if (info->outputType == Projector::Maybe) {
- info->outputType = Projector::Yes;
- break;
- }
- }
- }
- }
- // Print mappings
- for (XRRCrtcInfo *info : _crtcMap) {
- qDebug() << info << ":";
- for (int i = 0; i < info->npossible; ++i) {
- qDebug() << "Possible:" << tempMap[info->possible[i]];
- }
- }
- // Determine each screen's position
- QList<OutputInfo*> screens = _outputMap.values();
- // Nothing connected?
- if (screens.isEmpty())
- return;
- qSort(screens.begin(), screens.end(), xRectLessThan);
- int endX = -0xffff;
- qDebug() << "From left to right";
- for (OutputInfo* output : screens) {
- if (output->mode == nullptr) {
- qDebug() << "(Ignoring" << output->outputName << "since it's disconnected)";
- }
- if (output->crtc->x >= endX) {
- QSize res(0, 0);
- if (_modeMap.contains(output->crtc->mode)) {
- auto mode = _modeMap.value(output->crtc->mode);
- res = QSize(int(mode->width), int(mode->height));
- }
- _resolutions.append(res);
- endX = -0xffff; // Reset
- }
- output->position = _resolutions.size() - 1;
- qDebug() << "Screen (" << output->crtc->x << "," << output->crtc->y << ") @" << output->crtc->width << "x" << output->crtc->height << "as screen" << output->position;
- if (output->crtc->x + int(output->crtc->width) > endX) {
- endX = output->crtc->x + int(output->crtc->width);
- }
- }
- qDebug() << "Loaded.";
-}
-
-QMap<QString, ScreenInfo> ScreenSetup::getScreenPositions() const
-{
- QMap<QString, ScreenInfo> ret;
- for (auto oi : _outputMap) {
- ret.insert(oi->outputName, ScreenInfo(oi, _modeMap));
- }
- return ret;
-}
-
-
-
-bool ScreenSetup::readEdid(OutputInfo* output)
-{
- int numProps = 0;
- bool found = false;
- Atom* properties = XRRListOutputProperties(_display, output->id, &numProps);
- for (int i = 0; i < numProps; ++i) {
- if (properties[i] == _EDID_ATOM) {
- found = true;
- break;
- }
- }
- XFree(properties);
- if (!found)
- return false;
-
- unsigned long nitems, bytes_after;
- unsigned char *prop;
- int actual_format;
- Atom actual_type;
- bool valid;
-
- XRRGetOutputProperty(_display, output->id, _EDID_ATOM,
- 0, 128, False, False,
- AnyPropertyType,
- &actual_type, &actual_format,
- &nitems, &bytes_after, &prop);
- valid = (actual_format == 8 && nitems >= 128);
-
- if (valid) {
- int idx;
- for (unsigned char *byte = prop + 54; byte < prop + 126; byte += 18) {
- if (byte[0] != 0 || byte[1] != 0 || byte[2] != 0)
- continue; // Not a text block
- if (byte[3] == 0xfc) { // Serial number
- output->modelName = QString::fromLatin1(reinterpret_cast<const char*>(byte) + 5, 13); // It's actually CP-437 but meh
- if ((idx = output->modelName.indexOf('\r')) != -1) {
- output->modelName.truncate(idx);
- }
- output->modelName = output->modelName.trimmed();
- qDebug() << "Display name:" << output->modelName;
- }
- }
- }
- XFree(prop);
- return valid;
-}
-
-/**
- * Create common modes and add them to all outputs.
- * Make sure every output has some variant of the most commonly
- * used modes.
- */
-void ScreenSetup::initModes()
-{
- // First copy typical resolutions to all outputs
-#define RES(x,y) (((y) << 16) | (x))
- QSet<quint32> wanted;
- wanted << RES(1280, 720) << RES(1280, 800) << RES(1920, 1080);
- for (XRRModeInfo *mode : _modeMap) {
- if (toVertRefresh(mode) < 58 || toVertRefresh(mode) > 61)
- continue; // Play it safe and consider only those for copying that are 60Hz
- wanted.remove(RES(mode->width, mode->height));
- // Make sure all outputs got it
- for (OutputInfo *info : _outputMap) {
- if (getOutputModeForResolution(info->output, mode->width, mode->height))
- continue;
- XRRAddOutputMode(_display, info->id, mode->id);
- }
- }
-#undef RES
- // Create those that no output supported
- for (auto res : wanted) {
- unsigned int x = res & 0xffff;
- unsigned int y = res >> 16;
- createMode(x, y, 60, QString::asprintf("%ux%u", x, y));
- }
- if (!wanted.isEmpty()) {
- updateScreenResources();
- }
- // Finally copy all those the projector supports to other outputs
- for (auto key : _outputMap.keys()) {
- OutputInfo *oi = _outputMap[key];
- if (oi->outputType == Projector::Yes) {
- copyModesToAll(key, oi->output->nmode);
- }
- }
- updateScreenResources();
-}
-
-XRRModeInfo* ScreenSetup::getPreferredMode(OutputInfo *oi) const
-{
- if (oi->output->nmode == 0) {
- qDebug() << "getPreferredMode: Output" << oi->outputName << "has no modes!?";
- return nullptr; // WTF!?
- }
- RRMode mode;
- if (oi->output->npreferred > 0) {
- mode = oi->output->modes[0];
- } else {
- mode = getOutputModeForResolution(oi->output, 1920, 1080);
- if (mode == None) {
- mode = getOutputModeForResolution(oi->output, 1280, 720);
- }
- if (mode == None) {
- mode = getOutputModeForResolution(oi->output, 1280, 800);
- }
- if (mode == None) {
- mode = oi->output->modes[0];
- }
- }
- if (!_modeMap.contains(mode))
- return nullptr;
- return _modeMap[mode];
-}
-
-QList<QSize> ScreenSetup::getTotalSize(const QList<OutputInfo*> &projectors, const QList<OutputInfo*> &screens) const
-{
- const int max = qMax(screens.size(), projectors.size());
- QList<QSize> modes;
- for (int i = 0; i < max; ++i) {
- XRRModeInfo *mode = nullptr;
- if (i < projectors.size()) {
- mode = getPreferredMode(projectors.at(i));
- }
- if (mode == nullptr && i < screens.size()) {
- mode = getPreferredMode(screens.at(i));
- }
- if (mode != nullptr) {
- modes.append(QSize(int(mode->width), int(mode->height)));
- }
- }
- return modes;
-}
-
-static QSize getTotalSizeHorz(const QList<QSize> &list)
-{
- QSize ret(0, 0);
- for (auto e : list) {
- ret.rwidth() += e.width();
- ret.rheight() = qMax(ret.height(), e.height());
- }
- return ret;
-}
-
-bool ScreenSetup::setOutputResolution(OutputInfo *oi, int x, int y, const QSize &size, bool dryRun)
-{
- RRMode mode = getOutputModeForResolution(oi->output, size);
- if (mode == None) {
- qDebug() << "Cannot set" << oi->outputName << "to" << size << " since it's not supported";
- if (oi->output->nmode == 0)
- return false;
- qDebug() << "falling back to its default";
- mode = oi->output->modes[0];
- }
- if (!dryRun) {
- XRRSetCrtcConfig(_display,
- _screenResources,
- oi->output->crtc,
- CurrentTime,
- x, y, mode,
- RR_Rotate_0,
- &oi->id, 1);
- }
- qDebug() << "Set" << oi->outputName << "to" << _modeMap[mode]->width << "x" << _modeMap[mode]->height << "-- offset" << x << "/" << y;
- return true;
-}
-
-ScreenMode ScreenSetup::getCurrentMode()
-{
- bool notAtOrigin = false;
- for (auto oi : _outputMap) {
- if (oi->mode != nullptr) {
- if (oi->crtc->x != 0 || oi->crtc->y != 0) {
- notAtOrigin = true;
- }
- }
- }
- if (_outputMap.size() == 1)
- return ScreenMode::Single;
- if (_outputMap.size() > 2)
- return ScreenMode::Advanced;
- if (notAtOrigin)
- return ScreenMode::Dual;
- return ScreenMode::Clone;
-}
-
-ScreenMode ScreenSetup::setDefaultMode(bool dryRun)
-{
- if (_outputMap.size() == 1) // Only one output exists, do nothing
- return ScreenMode::Single;
- QMap<QString, OutputInfo*> screenMap;
- QMap<QString, OutputInfo*> projectorMap;
- for (auto o : _outputMap) {
- qDebug() << o->outputName << quint32(o->outputType);
- if (o->outputType == Projector::Yes) {
- projectorMap.insert(o->outputName, o);
- } else {
- screenMap.insert(o->outputName, o);
- }
- }
- auto projectors = projectorMap.values();
- auto screens = screenMap.values();
- qDebug() << projectors.size() << "projectors," << screens.size() << "screens.";
- QList<QSize> outputSizes = getTotalSize(projectors, screens);
- if (outputSizes.isEmpty())
- return ScreenMode::Advanced; // Dunno lol
- QSize screenSize = getTotalSizeHorz(outputSizes);
- if (!dryRun) {
- XGrabServer(_display);
- disconnectAllCrtcs();
- // Set new screen size
- setScreenSize(screenSize);
- }
-
- qDebug() << "Virtual size:" << screenSize << "with" << outputSizes.size() << "different screens.";
-
- int offset = 0;
- for (int i = 0; i < outputSizes.size(); ++i) {
- const QSize &size = outputSizes.at(i);
- if (i < projectors.size()) {
- setOutputResolution(projectors.at(i), offset, 0, size, dryRun);
- }
- if (i < screens.size()) {
- setOutputResolution(screens.at(i), offset, 0, size, dryRun);
- }
- offset += size.width();
- }
- if (!dryRun) {
- XUngrabServer(_display);
- XSync(_display, False);
- }
- updateScreenResources(); // Re-Read
- if (outputSizes.size() == 1) // One output size, at least 2 outputs in total -- clone mode
- return ScreenMode::Clone;
- if (outputSizes.size() == 2 && _outputMap.size() == 2) // Two outputs, two sizes -- extended
- return ScreenMode::Dual;
- return ScreenMode::Advanced; // Must be more than 2 outputs -> something more involved
-}
-
-/**
- * Copy first "num" modes from output to all other outputs if they
- * dont have a suitable mode yet.
- */
-void ScreenSetup::copyModesToAll(RROutput sourceId, int num)
-{
- const XRROutputInfo *outputInfo = _outputMap[sourceId]->output;
- for (int i = 0; i < num; ++i) {
- if (!_modeMap.contains(outputInfo->modes[i])) {
- qDebug() << "BUG: Mode" << outputInfo->modes[i] << "not found in copyModesToAll";
- continue;
- }
- const XRRModeInfo *mode = _modeMap[outputInfo->modes[i]];
- for (auto other : _outputMap.keys()) {
- if (other == sourceId)
- continue;
- const XRROutputInfo *otherInfo = _outputMap[other]->output;
- if (getOutputModeForResolution(otherInfo, mode->width, mode->height) != None)
- continue;
- XRRAddOutputMode(_display, other, outputInfo->modes[i]);
- }
- }
-}
-
-/**
- * Get the RRMode for the specified output matching the given resolution.
- * If multiple modes match the given resolution, the function will
- * return the first matching preferred mode. If no preferred
- * mode matches, the non-preferred mode with the highest refresh rate
- * will be returned.
- * Otherwise, None will be returned.
- */
-RRMode ScreenSetup::getOutputModeForResolution(const XRROutputInfo *output, unsigned int width, unsigned int height) const
-{
- XRRModeInfo *retval = nullptr;
- for (int i = 0; i < output->nmode; ++i) {
- if (!_modeMap.contains(output->modes[i]))
- continue;
- XRRModeInfo *info = _modeMap[output->modes[i]];
- if (info->width == width && info->height == height) {
- if (i < output->npreferred)
- return output->modes[i];
- if (retval == nullptr || retval->dotClock < info->dotClock) {
- retval = info;
- }
- }
- }
- return retval == nullptr ? None : retval->id;
-}
-
-RRMode ScreenSetup::getOutputModeForResolution(const XRROutputInfo *output, const QSize &resolution) const
-{
- return getOutputModeForResolution(output, static_cast<unsigned int>(resolution.width()), static_cast<unsigned int>(resolution.height()));
-}
-
-RRCrtc ScreenSetup::getFreeCrtc(const XRROutputInfo* output) const
-{
- for (int i = 0; i < output->ncrtc; ++i) {
- RRCrtc c = output->crtcs[i];
- if (!_crtcMap.contains(c)) {
- // Unknown CRTC!? Scan already known outputs
- qDebug() << "BUG: Output has unknown possible CRTC";
- for (auto oi : _outputMap) {
- if (oi->output->crtc == c)
- goto next;
- }
- } else {
- // Known CRTC -- see if free
- if (_crtcMap[c]->noutput > 0)
- goto next;
- }
- return c;
-next:;
- }
- return None;
-}
-
-//___________________________________________________________________________
-bool ScreenSetup::createMode(unsigned int resX, unsigned int resY, float refresh, QString name)
-{
- QByteArray ba = name.toLocal8Bit();
- mode *mode = vert_refresh(int(resX), int(resY), refresh, 0, 0, 0);
- if (mode == nullptr)
- return false;
- XRRModeInfo m;
- m.width = static_cast<unsigned int>(mode->hr);
- m.height = static_cast<unsigned int>(mode->vr);
- m.dotClock = static_cast<unsigned long>(mode->pclk) * 1000ul * 1000ul;
- m.hSyncStart= static_cast<unsigned int>(mode->hss);
- m.hSyncEnd = static_cast<unsigned int>(mode->hse);
- m.hTotal = static_cast<unsigned int>(mode->hfl);
- m.hSkew = 0;
- m.vSyncStart= static_cast<unsigned int>(mode->vss);
- m.vSyncEnd = static_cast<unsigned int>(mode->vse);
- m.vTotal = static_cast<unsigned int>(mode->vfl);
- m.id = 0;
- m.name = ba.data();
- m.nameLength = static_cast<unsigned int>(ba.length());
- free(mode);
-
- for (XRRModeInfo *mode : _modeMap) {
- if (mode->width == m.width && mode->height == m.height && mode->dotClock == m.dotClock)
- return true; // Already exists, return true?
- }
-
- RRMode xid = XRRCreateMode(_display, DefaultRootWindow(_display), &m);
- qDebug() << "Return value of create was" << xid;
- // Immediately add to all screens
- for (OutputInfo *info : _outputMap) {
- XRRAddOutputMode(_display, info->id, xid);
- }
- return true;
-}
-
-//___________________________________________________________________________
-ScreenSetup::~ScreenSetup()
-{
- freeResources();
- XCloseDisplay(_display);
-}
-
-void ScreenSetup::setScreenSize(const QSize &size)
-{
- XRRSetScreenSize(_display, DefaultRootWindow(_display),
- size.width(), size.height(),
- int(25.4 * size.width() / 96.0), // standard dpi that X uses
- int(25.4 * size.height() / 96.0)); // standard dpi that X uses
-}
-
-void ScreenSetup::disconnectAllCrtcs()
-{
- // Disconnect everything
- for (auto crtc : _crtcMap.keys()) {
- XRRSetCrtcConfig(_display, _screenResources, crtc, CurrentTime,
- 0, 0, None, RR_Rotate_0, nullptr, 0);
- }
-}
-
-void ScreenSetup::freeCrtcBackup()
-{
- for (auto entry : _crtcBackup) {
- free(entry->outputs);
- free(entry);
- }
- _crtcBackup.clear();
- qDebug() << "CRTC freed";
-}
-
-void ScreenSetup::createCrtcBackup()
-{
- freeCrtcBackup();
- for (CrtcMap::iterator it = _crtcMap.begin(); it != _crtcMap.end(); ++it) {
- const auto src = it.value();
- XRRCrtcInfo *copy = static_cast<XRRCrtcInfo*>(calloc(1, sizeof(XRRCrtcInfo)));
- copy->outputs = static_cast<RROutput*>(calloc(size_t(src->noutput), sizeof(RROutput)));
- copy->x = src->x;
- copy->y = src->y;
- copy->mode = src->mode;
- copy->rotation = src->rotation;
- copy->noutput = src->noutput;
- for (int i = 0; i < src->noutput; ++i) {
- copy->outputs[i] = src->outputs[i];
- }
- _crtcBackup[it.key()] = copy;
- }
- qDebug() << "Created CRTC backup with entries:" << _crtcBackup.size();
-}
-
-bool ScreenSetup::setClone(const QSize &resolution)
-{
- createCrtcBackup();
- XGrabServer(_display);
- disconnectAllCrtcs();
- setScreenSize(resolution);
- bool ok = false;
- for (auto oi : _outputMap) {
- ok = setOutputResolution(oi, 0, 0, resolution) || ok;
- }
- XUngrabServer(_display);
- XSync(_display, False);
- return ok;
-}
-
-bool ScreenSetup::setCustom(const QList<QPair<QSize, QList<QString>>> &list)
-{
- QList<QSize> sizes;
- for (auto e : list) {
- if (e.second.isEmpty())
- continue;
- sizes.append(e.first);
- }
- if (sizes.isEmpty())
- return false;
- createCrtcBackup();
- auto screenSize = getTotalSizeHorz(sizes);
- if (screenSize.isEmpty())
- return false;
- XGrabServer(_display);
- disconnectAllCrtcs();
- setScreenSize(screenSize);
- int x = 0;
- bool ok = false;
- for (auto e : list) {
- if (e.second.isEmpty())
- continue;
- const QSize &res = e.first;
- for (auto outputName : e.second) {
- for (auto oi : _outputMap) {
- if (oi->outputName != outputName)
- continue;
- ok = setOutputResolution(oi, x, 0, res) || ok;
- }
- }
- x += res.width();
- }
- XUngrabServer(_display);
- XSync(_display, False);
- return ok;
-}
-
-void ScreenSetup::revertChanges()
-{
- if (_crtcBackup.isEmpty())
- return;
- qDebug() << "Starting revert";
- XGrabServer(_display);
- disconnectAllCrtcs();
- QSize screenSize;
- for (auto e : _crtcBackup) {
- if (e->mode == None || !_modeMap.contains(e->mode))
- continue;
- screenSize = screenSize.expandedTo(QSize(e->x + int(_modeMap[e->mode]->width), e->y + int(_modeMap[e->mode]->height)));
- }
- for (CrtcMap::iterator it = _crtcBackup.begin(); it != _crtcBackup.end(); ++it) {
- auto e = it.value();
- if (e->mode == None)
- continue;
- XRRSetCrtcConfig(_display, _screenResources, it.key(), CurrentTime,
- e->x, e->y, e->mode, e->rotation, e->outputs, e->noutput);
- }
- XUngrabServer(_display);
- XSync(_display, False);
-}
-
-static bool modeBiggerThan(const QSize &a, const QSize &b)
-{
- if (a.width() > b.width())
- return true;
- return a.width() == b.width() && a.height() > b.height();
-}
-
-ResolutionVector ScreenSetup::getCommonModes() const
-{
- QHash<QPair<quint32, quint32>, QSet<RROutput>> matches;
- for (auto oi : _outputMap) {
- for (int i = 0; i < oi->output->nmode; ++i) {
- if (!_modeMap.contains(oi->output->modes[i]))
- continue;
- const auto mode = _modeMap[oi->output->modes[i]];
- const QPair<quint32, quint32> pair = qMakePair(mode->width, mode->height);
- matches[pair].insert(oi->id);
- }
- }
- ResolutionVector ret;
- for (auto it = matches.begin(); it != matches.end(); ++it) {
- if (it.value().size() == _outputMap.size()) {
- ret.append(QSize(int(it.key().first), int(it.key().second)));
- }
- }
- qSort(ret.begin(), ret.end(), modeBiggerThan);
- return ret;
-}
-
-
diff --git a/src/x.h b/src/x.h
deleted file mode 100644
index b5df6e0..0000000
--- a/src/x.h
+++ /dev/null
@@ -1,97 +0,0 @@
-#ifndef XRANDR_H
-#define XRANDR_H
-
-#include <QDebug>
-#include <QList>
-#include <QHash>
-#include <QString>
-#include <QRect>
-#include <QSet>
-#include <QSize>
-#include <X11/Xlib.h>
-#include <X11/extensions/Xrandr.h>
-
-struct OutputInfo;
-
-typedef QHash<RRMode, XRRModeInfo*> ModeMap;
-typedef QHash<RRCrtc, XRRCrtcInfo*> CrtcMap;
-typedef QHash<RROutput, OutputInfo*> OutputMap;
-typedef QVector<QSize> ResolutionVector;
-
-///////////////////////////////////////////////////////////////////////////
-
-enum class ScreenMode
-{
- Single,
- Clone,
- Dual,
- Advanced,
-};
-
-class ScreenInfo
-{
-public:
- ScreenInfo(const OutputInfo *oi, const ModeMap &om);
- int position;
- QString name;
- QString output;
- QSize currentResolution;
- QSize preferredResolution;
- bool isProjector;
- ResolutionVector modes;
-};
-
-class ScreenSetup
-{
-public:
- void updateScreenResources();
- void initModes();
- XRRModeInfo* getPreferredMode(OutputInfo *oi) const;
- QList<QSize> getTotalSize(const QList<OutputInfo*> &projectors, const QList<OutputInfo*> &screens) const;
- ScreenMode getCurrentMode();
- ScreenMode setDefaultMode(bool dryRun = false);
- void copyModesToAll(RROutput id, int num);
- bool createMode(unsigned int resX, unsigned int resY, float refresh, QString name);
- void revertChanges();
- bool setClone(const QSize &resolution);
- bool setCustom(const QList<QPair<QSize, QList<QString>>> &list);
- ResolutionVector getCommonModes() const;
- int getOutputCount() const { return _outputMap.size(); }
- QMap<QString, ScreenInfo> getScreenPositions() const;
- const ResolutionVector &getVirtualResolutions() const { return _resolutions; }
-
- // Singleton
- inline static ScreenSetup* inst() {
- if (_instance == nullptr) _instance = new ScreenSetup();
- return _instance;
- }
-
-private:
- ScreenSetup();
- ~ScreenSetup();
-
- void freeResources();
- bool readEdid(OutputInfo* output);
- void createCrtcBackup();
- void freeCrtcBackup();
- void disconnectAllCrtcs();
- void setScreenSize(const QSize &size);
- RRMode getOutputModeForResolution(const XRROutputInfo *output, unsigned int width, unsigned int height) const;
- RRMode getOutputModeForResolution(const XRROutputInfo *output, const QSize &resolution) const;
- RRCrtc getFreeCrtc(const XRROutputInfo* output) const;
- bool setOutputResolution(OutputInfo *oi, int x, int y, const QSize &size, bool dryRun = false);
-
- static ScreenSetup * _instance;
- Display* _display;
- Atom _EDID_ATOM;
- XRRScreenResources* _screenResources;
- ModeMap _modeMap;
- CrtcMap _crtcMap;
- CrtcMap _crtcBackup;
- OutputMap _outputMap;
- ResolutionVector _resolutions;
-};
-
-///////////////////////////////////////////////////////////////////////////
-
-#endif // XRANDR_H
diff --git a/src/xprivate.cpp b/src/xprivate.cpp
new file mode 100644
index 0000000..120fb66
--- /dev/null
+++ b/src/xprivate.cpp
@@ -0,0 +1,452 @@
+#include "xprivate.h"
+
+#include <QDebug>
+
+/**
+ * Check list of known model names that falsely report a screen size or similar
+ */
+static QStringList initProjectorList()
+{
+ QStringList list;
+ list << "AT-HDVS-RX"; // Switchbox
+ // TODO: Load from file
+ return list;
+}
+
+static bool isProjectorName(const QString &name)
+{
+ static QStringList projectors = initProjectorList();
+ return projectors.contains(name);
+}
+
+static bool xRectLessThan(const OutputInfo* const &a, const OutputInfo* const &b)
+{
+ return a->crtc->x < b->crtc->x;
+}
+
+/*
+ * Class members
+ */
+
+XPrivate::XPrivate()
+ : _screenResources(nullptr)
+{
+ // Get initial data (to be freed)
+ _display = XOpenDisplay(nullptr);
+ if (_display == nullptr) {
+ qFatal("Cannot open display");
+ ::exit(1);
+ }
+ _EDID_ATOM = XInternAtom(_display, RR_PROPERTY_RANDR_EDID, False);
+}
+
+XPrivate::~XPrivate()
+{
+ XCloseDisplay(_display);
+}
+
+void XPrivate::freeResources()
+{
+ freeCrtcBackup();
+ // Clear the modemap (nothing to be freed, stored in screenResources)
+ _modeMap.clear();
+ _resolutions.clear();
+
+ // Clear output info
+ for (OutputInfo *output : _outputMap.values()) {
+ delete output;
+ }
+ _outputMap.clear();
+
+ // Clear crtc info
+ for (XRRCrtcInfo *info : _crtcMap.values()) {
+ XRRFreeCrtcInfo(info);
+ }
+ _crtcMap.clear();
+
+ // Release resources
+ if (_screenResources != nullptr) {
+ XRRFreeScreenResources(_screenResources);
+ _screenResources = nullptr;
+ }
+ XRRFreeScreenResources(_screenResources);
+}
+
+void XPrivate::updateScreenResources()
+{
+ freeResources();
+ _screenResources = XRRGetScreenResourcesCurrent(_display, DefaultRootWindow(_display));
+ // Create the modemap
+ qDebug() << "Modes";
+ for (int i = 0; i < _screenResources->nmode; ++i) {
+ _modeMap.insert(
+ _screenResources->modes[i].id,
+ &_screenResources->modes[i]);
+ qDebug() << _screenResources->modes[i].id << "\t"
+ << _screenResources->modes[i].width << "x"
+ << _screenResources->modes[i].height;
+ }
+
+ /*
+ int num = 0;
+ XRRMonitorInfo *mi = XRRGetMonitors(_display, DefaultRootWindow(_display), True, &num);
+ for (int i = 0; i < num; ++i) {
+ char *name = XGetAtomName(_display, mi [i].name);
+ if (name == nullptr)
+ continue;
+ qDebug() << "some monitor with name" << name << mi[i].primary;
+ XFree(name);
+ }
+ */
+
+ // Create crtcMap
+ qDebug() << "CRTCs";
+ for (int i = 0; i < _screenResources->ncrtc; ++i) {
+ XRRCrtcInfo * info = XRRGetCrtcInfo(
+ _display,
+ _screenResources,
+ _screenResources->crtcs[i]);
+ if (info == nullptr) {
+ qDebug() << "Error getting CRTC info for crtc" << i;
+ continue;
+ }
+ qDebug() << _screenResources->crtcs[i] << "-- Outputs:" << info->noutput << "Possible:" << info->npossible;
+ _crtcMap.insert(_screenResources->crtcs[i], info);
+ }
+
+ // Create outputmap and connectedOutputMap
+ qDebug() << "Outputs";
+ QHash<RROutput, QString> tempMap;
+ QMap<Projector, int> typeCount;
+ for (int i = 0; i < _screenResources->noutput; ++i) {
+ XRROutputInfo* info = XRRGetOutputInfo(
+ _display,
+ _screenResources,
+ _screenResources->outputs[i]);
+ if (info == nullptr) {
+ qDebug() << "Error getting info for output" << i;
+ continue;
+ }
+ const QString outputName = QString::fromLocal8Bit(info->name, info->nameLen);
+ tempMap.insert(_screenResources->outputs[i], outputName);
+ if (info->connection == RR_Disconnected) {
+ qDebug() << "Ignoring disconnected output" << outputName;
+ XRRFreeOutputInfo(info);
+ continue;
+ }
+ bool disconnected = false;
+ if (info->crtc == None) {
+ disconnected = true;
+ qDebug() << "Connected output" << outputName << "has no CRTC -- trying to find free one";
+ info->crtc = getFreeCrtc(info);
+ }
+ if (!_crtcMap.contains(info->crtc)) {
+ qDebug() << "Have output" << outputName << "with no known crtc";
+ XRRFreeOutputInfo(info);
+ continue;
+ }
+ XRRCrtcInfo *crtc = _crtcMap.value(info->crtc);
+ XRRModeInfo *mode = nullptr;
+ if (!disconnected) {
+ if (!_modeMap.contains(crtc->mode)) {
+ qDebug() << "Have output" << outputName << " with crtc with no known mode -- offline?";
+ } else {
+ mode = _modeMap.value(crtc->mode);
+ }
+ }
+ OutputInfo *oi = new OutputInfo(_screenResources->outputs[i], info, crtc, mode);
+ oi->outputName = outputName;
+ if (!this->readEdid(oi)) { // We have no EDID - take it as an indicator that there's a dumb output split/switch box
+ oi->outputType = Projector::Maybe;
+ } else if ((info->mm_height == 0 && info->mm_width == 0) || isProjectorName(oi->modelName)) {
+ oi->outputType = Projector::Yes; // Screens with size 0x0 are projectors by convention
+ } else if (info->mm_width > 500 && info->mm_height > 500) { // Big screen - probably should be handled like a projector
+ oi->outputType = Projector::Yes;
+ } else if (info->mm_width > 350 && info->mm_height > 350) { // Somewhere in-between
+ oi->outputType = Projector::Maybe;
+ }
+ typeCount[oi->outputType]++;
+ _outputMap.insert(_screenResources->outputs[i], oi);
+ qDebug() << "Connected" << outputName << "-- Clones:" << info->nclone << "Modes:" << info->nmode << "Crtcs:" << info->ncrtc << "Preferred:" << info->npreferred;
+ }
+ // Final checks for projector
+ if (typeCount[Projector::Yes] == 0) {
+ // No definitive projector
+ if (typeCount[Projector::Maybe] > 0 && typeCount[Projector::No] > 0) {
+ // Potential projector(s) and normal screen, promote
+ for (OutputInfo *info : _outputMap) {
+ if (info->outputType == Projector::Maybe) {
+ info->outputType = Projector::Yes;
+ break;
+ }
+ }
+ }
+ }
+ // Determine each screen's position
+ QList<OutputInfo*> screens = _outputMap.values();
+ // Nothing connected?
+ if (screens.isEmpty())
+ return;
+ qSort(screens.begin(), screens.end(), xRectLessThan);
+ int endX = -0xffff;
+ qDebug() << "From left to right";
+ for (OutputInfo* output : screens) {
+ if (output->mode == nullptr) {
+ qDebug() << "(Ignoring" << output->outputName << "since it's disconnected)";
+ }
+ if (output->crtc->x >= endX) {
+ QSize res(0, 0);
+ if (_modeMap.contains(output->crtc->mode)) {
+ auto mode = _modeMap.value(output->crtc->mode);
+ res = QSize(int(mode->width), int(mode->height));
+ }
+ _resolutions.append(res);
+ endX = -0xffff; // Reset
+ }
+ output->position = _resolutions.size() - 1;
+ qDebug() << "Screen (" << output->crtc->x << "," << output->crtc->y << ") @" << output->crtc->width << "x" << output->crtc->height << "as screen" << output->position;
+ if (output->crtc->x + int(output->crtc->width) > endX) {
+ endX = output->crtc->x + int(output->crtc->width);
+ }
+ }
+ qDebug() << "Loaded.";
+}
+
+bool XPrivate::readEdid(OutputInfo* output)
+{
+ int numProps = 0;
+ bool found = false;
+ Atom* properties = XRRListOutputProperties(_display, output->id, &numProps);
+ for (int i = 0; i < numProps; ++i) {
+ if (properties[i] == _EDID_ATOM) {
+ found = true;
+ break;
+ }
+ }
+ XFree(properties);
+ if (!found)
+ return false;
+
+ unsigned long nitems, bytes_after;
+ unsigned char *prop;
+ int actual_format;
+ Atom actual_type;
+ bool valid;
+
+ XRRGetOutputProperty(_display, output->id, _EDID_ATOM,
+ 0, 128, False, False,
+ AnyPropertyType,
+ &actual_type, &actual_format,
+ &nitems, &bytes_after, &prop);
+ valid = (actual_format == 8 && nitems >= 128);
+
+ if (valid) {
+ int idx;
+ for (unsigned char *byte = prop + 54; byte < prop + 126; byte += 18) {
+ if (byte[0] != 0 || byte[1] != 0 || byte[2] != 0)
+ continue; // Not a text block
+ if (byte[3] == 0xfc) { // Serial number
+ output->modelName = QString::fromLatin1(reinterpret_cast<const char*>(byte) + 5, 13); // It's actually CP-437 but meh
+ if ((idx = output->modelName.indexOf('\r')) != -1) {
+ output->modelName.truncate(idx);
+ }
+ output->modelName = output->modelName.trimmed();
+ qDebug() << "Display name:" << output->modelName;
+ }
+ }
+ }
+ XFree(prop);
+ return valid;
+}
+
+void XPrivate::setScreenSize(const QSize &size)
+{
+ XRRSetScreenSize(_display, DefaultRootWindow(_display),
+ size.width(), size.height(),
+ int(25.4 * size.width() / 96.0), // standard dpi that X uses
+ int(25.4 * size.height() / 96.0)); // standard dpi that X uses
+}
+
+void XPrivate::disconnectAllCrtcs()
+{
+ // Disconnect everything
+ for (auto crtc : _crtcMap.keys()) {
+ XRRSetCrtcConfig(_display, _screenResources, crtc, CurrentTime,
+ 0, 0, None, RR_Rotate_0, nullptr, 0);
+ }
+}
+
+void XPrivate::freeCrtcBackup()
+{
+ for (auto entry : _crtcBackup) {
+ free(entry->outputs);
+ free(entry);
+ }
+ _crtcBackup.clear();
+ qDebug() << "CRTC freed";
+}
+
+void XPrivate::createCrtcBackup()
+{
+ freeCrtcBackup();
+ for (CrtcMap::iterator it = _crtcMap.begin(); it != _crtcMap.end(); ++it) {
+ const auto src = it.value();
+ XRRCrtcInfo *copy = static_cast<XRRCrtcInfo*>(calloc(1, sizeof(XRRCrtcInfo)));
+ copy->outputs = static_cast<RROutput*>(calloc(size_t(src->noutput), sizeof(RROutput)));
+ copy->x = src->x;
+ copy->y = src->y;
+ copy->mode = src->mode;
+ copy->rotation = src->rotation;
+ copy->noutput = src->noutput;
+ for (int i = 0; i < src->noutput; ++i) {
+ copy->outputs[i] = src->outputs[i];
+ }
+ _crtcBackup[it.key()] = copy;
+ }
+ qDebug() << "Created CRTC backup with entries:" << _crtcBackup.size();
+}
+
+/**
+ * Copy first "num" modes from output to all other outputs if they
+ * dont have a suitable mode yet.
+ */
+void XPrivate::copyModesToAll(RROutput sourceId, int num)
+{
+ const XRROutputInfo *outputInfo = _outputMap[sourceId]->output;
+ for (int i = 0; i < num; ++i) {
+ if (!_modeMap.contains(outputInfo->modes[i])) {
+ qDebug() << "BUG: Mode" << outputInfo->modes[i] << "not found in copyModesToAll";
+ continue;
+ }
+ const XRRModeInfo *mode = _modeMap[outputInfo->modes[i]];
+ for (auto other : _outputMap.keys()) {
+ if (other == sourceId)
+ continue;
+ const XRROutputInfo *otherInfo = _outputMap[other]->output;
+ if (getOutputModeForResolution(otherInfo, mode->width, mode->height) != None)
+ continue;
+ XRRAddOutputMode(_display, other, outputInfo->modes[i]);
+ }
+ }
+}
+
+/**
+ * Get the RRMode for the specified output matching the given resolution.
+ * If multiple modes match the given resolution, the function will
+ * return the first matching preferred mode. If no preferred
+ * mode matches, the non-preferred mode with the highest refresh rate
+ * will be returned.
+ * Otherwise, None will be returned.
+ */
+RRMode XPrivate::getOutputModeForResolution(const XRROutputInfo *output, unsigned int width, unsigned int height) const
+{
+ XRRModeInfo *retval = nullptr;
+ for (int i = 0; i < output->nmode; ++i) {
+ if (!_modeMap.contains(output->modes[i]))
+ continue;
+ XRRModeInfo *info = _modeMap[output->modes[i]];
+ if (info->width == width && info->height == height) {
+ if (i < output->npreferred)
+ return output->modes[i];
+ if (retval == nullptr || retval->dotClock < info->dotClock) {
+ retval = info;
+ }
+ }
+ }
+ return retval == nullptr ? None : retval->id;
+}
+
+RRMode XPrivate::getOutputModeForResolution(const XRROutputInfo *output, const QSize &resolution) const
+{
+ return getOutputModeForResolution(output, static_cast<unsigned int>(resolution.width()), static_cast<unsigned int>(resolution.height()));
+}
+
+RRCrtc XPrivate::getFreeCrtc(const XRROutputInfo* output) const
+{
+ for (int i = 0; i < output->ncrtc; ++i) {
+ RRCrtc c = output->crtcs[i];
+ if (!_crtcMap.contains(c)) {
+ // Unknown CRTC!? Scan already known outputs
+ qDebug() << "BUG: Output has unknown possible CRTC";
+ for (auto oi : _outputMap) {
+ if (oi->output->crtc == c)
+ goto next;
+ }
+ } else {
+ // Known CRTC -- see if free
+ if (_crtcMap[c]->noutput > 0)
+ goto next;
+ }
+ return c;
+next:;
+ }
+ return None;
+}
+
+XRRModeInfo* XPrivate::getPreferredMode(OutputInfo *oi) const
+{
+ if (oi->output->nmode == 0) {
+ qDebug() << "getPreferredMode: Output" << oi->outputName << "has no modes!?";
+ return nullptr; // WTF!?
+ }
+ RRMode mode;
+ if (oi->output->npreferred > 0) {
+ mode = oi->output->modes[0];
+ } else {
+ mode = getOutputModeForResolution(oi->output, 1920, 1080);
+ if (mode == None) {
+ mode = getOutputModeForResolution(oi->output, 1280, 720);
+ }
+ if (mode == None) {
+ mode = getOutputModeForResolution(oi->output, 1280, 800);
+ }
+ if (mode == None) {
+ mode = oi->output->modes[0];
+ }
+ }
+ if (!_modeMap.contains(mode))
+ return nullptr;
+ return _modeMap[mode];
+}
+
+QList<QSize> XPrivate::getTotalSize(const QList<OutputInfo*> &projectors, const QList<OutputInfo*> &screens) const
+{
+ const int max = qMax(screens.size(), projectors.size());
+ QList<QSize> modes;
+ for (int i = 0; i < max; ++i) {
+ XRRModeInfo *mode = nullptr;
+ if (i < projectors.size()) {
+ mode = getPreferredMode(projectors.at(i));
+ }
+ if (mode == nullptr && i < screens.size()) {
+ mode = getPreferredMode(screens.at(i));
+ }
+ if (mode != nullptr) {
+ modes.append(QSize(int(mode->width), int(mode->height)));
+ }
+ }
+ return modes;
+}
+
+bool XPrivate::setOutputResolution(OutputInfo *oi, int x, int y, const QSize &size, bool dryRun)
+{
+ RRMode mode = getOutputModeForResolution(oi->output, size);
+ if (mode == None) {
+ qDebug() << "Cannot set" << oi->outputName << "to" << size << " since it's not supported";
+ if (oi->output->nmode == 0)
+ return false;
+ qDebug() << "falling back to its default";
+ mode = oi->output->modes[0];
+ }
+ if (!dryRun) {
+ XRRSetCrtcConfig(_display,
+ _screenResources,
+ oi->output->crtc,
+ CurrentTime,
+ x, y, mode,
+ RR_Rotate_0,
+ &oi->id, 1);
+ }
+ qDebug() << "Set" << oi->outputName << "to" << _modeMap[mode]->width << "x" << _modeMap[mode]->height << "-- offset" << x << "/" << y;
+ return true;
+}
diff --git a/src/xprivate.h b/src/xprivate.h
new file mode 100644
index 0000000..6725273
--- /dev/null
+++ b/src/xprivate.h
@@ -0,0 +1,70 @@
+#ifndef XPRIVATE_H
+#define XPRIVATE_H
+
+#include "xx.h"
+
+#include <QHash>
+#include <QSize>
+#include <QTextStream>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+
+enum class Projector {
+ No,
+ Yes,
+ Maybe
+};
+
+struct OutputInfo {
+ OutputInfo(RROutput id, XRROutputInfo *output, XRRCrtcInfo *crtc, XRRModeInfo *mode)
+ : output(output), crtc(crtc), mode(mode), id(id), position(-1), outputType(Projector::No) {}
+ ~OutputInfo() {
+ XRRFreeOutputInfo(output);
+ }
+ XRROutputInfo* output;
+ XRRCrtcInfo* crtc;
+ XRRModeInfo* mode;
+ RROutput id;
+ QString modelName;
+ QString outputName;
+ int position;
+ Projector outputType;
+};
+
+typedef QHash<RRMode, XRRModeInfo*> ModeMap;
+typedef QHash<RRCrtc, XRRCrtcInfo*> CrtcMap;
+typedef QHash<RROutput, OutputInfo*> OutputMap;
+
+class XPrivate
+{
+public:
+ explicit XPrivate();
+ virtual ~XPrivate();
+
+ void freeResources();
+ void updateScreenResources();
+ bool readEdid(OutputInfo* output);
+ void createCrtcBackup();
+ void freeCrtcBackup();
+ void disconnectAllCrtcs();
+ XRRModeInfo* getPreferredMode(OutputInfo *oi) const;
+ void setScreenSize(const QSize &size);
+ RRMode getOutputModeForResolution(const XRROutputInfo *output, unsigned int width, unsigned int height) const;
+ RRMode getOutputModeForResolution(const XRROutputInfo *output, const QSize &resolution) const;
+ RRCrtc getFreeCrtc(const XRROutputInfo* output) const;
+ bool setOutputResolution(OutputInfo *oi, int x, int y, const QSize &size, bool dryRun = false);
+ QList<QSize> getTotalSize(const QList<OutputInfo*> &projectors, const QList<OutputInfo*> &screens) const;
+ void copyModesToAll(RROutput id, int num);
+
+ Display* _display;
+ Atom _EDID_ATOM;
+ XRRScreenResources* _screenResources;
+ ModeMap _modeMap;
+ CrtcMap _crtcMap;
+ CrtcMap _crtcBackup;
+ OutputMap _outputMap;
+ ResolutionVector _resolutions;
+};
+
+#endif // XPRIVATE_H
diff --git a/src/xx.cpp b/src/xx.cpp
new file mode 100644
index 0000000..2a8cdc5
--- /dev/null
+++ b/src/xx.cpp
@@ -0,0 +1,348 @@
+#include "xx.h"
+#include "xprivate.h"
+#include "cvt.h"
+#include <QDebug>
+
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+
+static ScreenInfo initScreenInfo(const OutputInfo *oi, const ModeMap &om)
+{
+ ScreenInfo si;
+ si.position = oi->position;
+ si.name = oi->modelName;
+ si.output = oi->outputName;
+ si.isProjector = (oi->outputType == Projector::Yes);
+ if (oi->mode != nullptr) {
+ si.currentResolution = QSize(QSize(int(oi->mode->width), int(oi->mode->height)));
+ }
+ for (int i = 0; i < oi->output->nmode; ++i) {
+ if (om.contains(oi->output->modes[i])) {
+ auto m = om.value(oi->output->modes[i]);
+ const QSize size(int(m->width), int(m->height));
+ if (!si.modes.contains(size)) {
+ si.modes.append(size);
+ }
+ if (i == 0 && oi->output->npreferred > 0) {
+ si.preferredResolution = size;
+ }
+ }
+ }
+ return si;
+}
+
+ScreenSetup * ScreenSetup::_instance = nullptr;
+
+ScreenSetup::ScreenSetup() : a(new XPrivate())
+{
+ /* Get informations about Xserver */
+ updateScreenResources();
+
+}
+
+static double toVertRefresh(const XRRModeInfo *mode)
+{
+ if (mode->hTotal > 0 && mode->vTotal > 0)
+ return (double(mode->dotClock) / (double(mode->hTotal) * double(mode->vTotal)));
+ return 0;
+}
+
+//___________________________________________________________________________
+void ScreenSetup::updateScreenResources()
+{
+ a->updateScreenResources();
+}
+
+QMap<QString, ScreenInfo> ScreenSetup::getScreenPositions() const
+{
+ QMap<QString, ScreenInfo> ret;
+ for (auto oi : a->_outputMap) {
+ ret.insert(oi->outputName, initScreenInfo(oi, a->_modeMap));
+ }
+ return ret;
+}
+
+/**
+ * Create common modes and add them to all outputs.
+ * Make sure every output has some variant of the most commonly
+ * used modes.
+ */
+void ScreenSetup::initModes()
+{
+ // First copy typical resolutions to all outputs
+#define RES(x,y) (((y) << 16) | (x))
+ QSet<quint32> wanted;
+ wanted << RES(1280, 720) << RES(1280, 800) << RES(1920, 1080);
+ for (XRRModeInfo *mode : a->_modeMap) {
+ if (toVertRefresh(mode) < 58 || toVertRefresh(mode) > 61)
+ continue; // Play it safe and consider only those for copying that are 60Hz
+ wanted.remove(RES(mode->width, mode->height));
+ // Make sure all outputs got it
+ for (OutputInfo *info : a->_outputMap) {
+ if (a->getOutputModeForResolution(info->output, mode->width, mode->height))
+ continue;
+ XRRAddOutputMode(a->_display, info->id, mode->id);
+ }
+ }
+#undef RES
+ // Create those that no output supported
+ for (auto res : wanted) {
+ unsigned int x = res & 0xffff;
+ unsigned int y = res >> 16;
+ createMode(x, y, 60, QString::asprintf("%ux%u", x, y));
+ }
+ if (!wanted.isEmpty()) {
+ updateScreenResources();
+ }
+ // Finally copy all those the projector supports to other outputs
+ for (auto key : a->_outputMap.keys()) {
+ OutputInfo *oi = a->_outputMap[key];
+ if (oi->outputType == Projector::Yes) {
+ a->copyModesToAll(key, oi->output->nmode);
+ }
+ }
+ updateScreenResources();
+}
+
+static QSize getTotalSizeHorz(const QList<QSize> &list)
+{
+ QSize ret(0, 0);
+ for (auto e : list) {
+ ret.rwidth() += e.width();
+ ret.rheight() = qMax(ret.height(), e.height());
+ }
+ return ret;
+}
+
+ScreenMode ScreenSetup::getCurrentMode()
+{
+ bool notAtOrigin = false;
+ for (auto oi : a->_outputMap) {
+ if (oi->mode != nullptr) {
+ if (oi->crtc->x != 0 || oi->crtc->y != 0) {
+ notAtOrigin = true;
+ }
+ }
+ }
+ if (a->_outputMap.size() == 1)
+ return ScreenMode::Single;
+ if (a->_outputMap.size() > 2)
+ return ScreenMode::Advanced;
+ if (notAtOrigin)
+ return ScreenMode::Dual;
+ return ScreenMode::Clone;
+}
+
+ScreenMode ScreenSetup::setDefaultMode(bool dryRun)
+{
+ if (a->_outputMap.size() == 1) // Only one output exists, do nothing
+ return ScreenMode::Single;
+ QMap<QString, OutputInfo*> screenMap;
+ QMap<QString, OutputInfo*> projectorMap;
+ for (auto o : a->_outputMap) {
+ qDebug() << o->outputName << quint32(o->outputType);
+ if (o->outputType == Projector::Yes) {
+ projectorMap.insert(o->outputName, o);
+ } else {
+ screenMap.insert(o->outputName, o);
+ }
+ }
+ auto projectors = projectorMap.values();
+ auto screens = screenMap.values();
+ qDebug() << projectors.size() << "projectors," << screens.size() << "screens.";
+ QList<QSize> outputSizes = a->getTotalSize(projectors, screens);
+ if (outputSizes.isEmpty())
+ return ScreenMode::Advanced; // Dunno lol
+ QSize screenSize = getTotalSizeHorz(outputSizes);
+ if (!dryRun) {
+ XGrabServer(a->_display);
+ a->disconnectAllCrtcs();
+ // Set new screen size
+ a->setScreenSize(screenSize);
+ }
+
+ qDebug() << "Virtual screen size:" << screenSize << "with" << outputSizes.size() << "different screens.";
+
+ int offset = 0;
+ for (int i = 0; i < outputSizes.size(); ++i) {
+ const QSize &size = outputSizes.at(i);
+ if (i < projectors.size()) {
+ a->setOutputResolution(projectors.at(i), offset, 0, size, dryRun);
+ }
+ if (i < screens.size()) {
+ a->setOutputResolution(screens.at(i), offset, 0, size, dryRun);
+ }
+ offset += size.width();
+ }
+ if (!dryRun) {
+ XUngrabServer(a->_display);
+ XSync(a->_display, False);
+ }
+ updateScreenResources(); // Re-Read
+ if (outputSizes.size() == 1) // One output size, at least 2 outputs in total -- clone mode
+ return ScreenMode::Clone;
+ if (outputSizes.size() == 2 && a->_outputMap.size() == 2) // Two outputs, two sizes -- extended
+ return ScreenMode::Dual;
+ return ScreenMode::Advanced; // Must be more than 2 outputs -> something more involved
+}
+
+//___________________________________________________________________________
+bool ScreenSetup::createMode(unsigned int resX, unsigned int resY, float refresh, QString name)
+{
+ QByteArray ba = name.toLocal8Bit();
+ mode *mode = vert_refresh(int(resX), int(resY), refresh, 0, 0, 0);
+ if (mode == nullptr)
+ return false;
+ XRRModeInfo m;
+ m.width = static_cast<unsigned int>(mode->hr);
+ m.height = static_cast<unsigned int>(mode->vr);
+ m.dotClock = static_cast<unsigned long>(mode->pclk) * 1000ul * 1000ul;
+ m.hSyncStart= static_cast<unsigned int>(mode->hss);
+ m.hSyncEnd = static_cast<unsigned int>(mode->hse);
+ m.hTotal = static_cast<unsigned int>(mode->hfl);
+ m.hSkew = 0;
+ m.vSyncStart= static_cast<unsigned int>(mode->vss);
+ m.vSyncEnd = static_cast<unsigned int>(mode->vse);
+ m.vTotal = static_cast<unsigned int>(mode->vfl);
+ m.id = 0;
+ m.name = ba.data();
+ m.nameLength = static_cast<unsigned int>(ba.length());
+ free(mode);
+
+ for (XRRModeInfo *mode : a->_modeMap) {
+ if (mode->width == m.width && mode->height == m.height && mode->dotClock == m.dotClock)
+ return true; // Already exists, return true?
+ }
+
+ RRMode xid = XRRCreateMode(a->_display, DefaultRootWindow(a->_display), &m);
+ qDebug() << "Return value of create was" << xid;
+ // Immediately add to all screens
+ for (OutputInfo *info : a->_outputMap) {
+ XRRAddOutputMode(a->_display, info->id, xid);
+ }
+ return true;
+}
+
+//___________________________________________________________________________
+ScreenSetup::~ScreenSetup()
+{
+ delete a;
+}
+
+bool ScreenSetup::setClone(const QSize &resolution)
+{
+ a->createCrtcBackup();
+ XGrabServer(a->_display);
+ a->disconnectAllCrtcs();
+ a->setScreenSize(resolution);
+ bool ok = false;
+ for (auto oi : a->_outputMap) {
+ ok = a->setOutputResolution(oi, 0, 0, resolution) || ok;
+ }
+ XUngrabServer(a->_display);
+ XSync(a->_display, False);
+ return ok;
+}
+
+bool ScreenSetup::setCustom(const QList<QPair<QSize, QList<QString>>> &list)
+{
+ QList<QSize> sizes;
+ for (auto e : list) {
+ if (e.second.isEmpty())
+ continue;
+ sizes.append(e.first);
+ }
+ if (sizes.isEmpty())
+ return false;
+ a->createCrtcBackup();
+ auto screenSize = getTotalSizeHorz(sizes);
+ if (screenSize.isEmpty())
+ return false;
+ XGrabServer(a->_display);
+ a->disconnectAllCrtcs();
+ a->setScreenSize(screenSize);
+ int x = 0;
+ bool ok = false;
+ for (auto e : list) {
+ if (e.second.isEmpty())
+ continue;
+ const QSize &res = e.first;
+ for (auto outputName : e.second) {
+ for (auto oi : a->_outputMap) {
+ if (oi->outputName != outputName)
+ continue;
+ ok = a->setOutputResolution(oi, x, 0, res) || ok;
+ }
+ }
+ x += res.width();
+ }
+ XUngrabServer(a->_display);
+ XSync(a->_display, False);
+ return ok;
+}
+
+void ScreenSetup::revertChanges()
+{
+ if (a->_crtcBackup.isEmpty())
+ return;
+ qDebug() << "Starting revert";
+ XGrabServer(a->_display);
+ a->disconnectAllCrtcs();
+ QSize screenSize;
+ for (auto e : a->_crtcBackup) {
+ if (e->mode == None || !a->_modeMap.contains(e->mode))
+ continue;
+ screenSize = screenSize.expandedTo(QSize(e->x + int(a->_modeMap[e->mode]->width), e->y + int(a->_modeMap[e->mode]->height)));
+ }
+ for (CrtcMap::iterator it = a->_crtcBackup.begin(); it != a->_crtcBackup.end(); ++it) {
+ auto e = it.value();
+ if (e->mode == None)
+ continue;
+ XRRSetCrtcConfig(a->_display, a->_screenResources, it.key(), CurrentTime,
+ e->x, e->y, e->mode, e->rotation, e->outputs, e->noutput);
+ }
+ XUngrabServer(a->_display);
+ XSync(a->_display, False);
+}
+
+static bool modeBiggerThan(const QSize &a, const QSize &b)
+{
+ if (a.width() > b.width())
+ return true;
+ return a.width() == b.width() && a.height() > b.height();
+}
+
+ResolutionVector ScreenSetup::getCommonModes() const
+{
+ QHash<QPair<quint32, quint32>, QSet<RROutput>> matches;
+ for (auto oi : a->_outputMap) {
+ for (int i = 0; i < oi->output->nmode; ++i) {
+ if (!a->_modeMap.contains(oi->output->modes[i]))
+ continue;
+ const auto mode = a->_modeMap[oi->output->modes[i]];
+ const QPair<quint32, quint32> pair = qMakePair(mode->width, mode->height);
+ matches[pair].insert(oi->id);
+ }
+ }
+ ResolutionVector ret;
+ for (auto it = matches.begin(); it != matches.end(); ++it) {
+ if (it.value().size() == a->_outputMap.size()) {
+ ret.append(QSize(int(it.key().first), int(it.key().second)));
+ }
+ }
+ qSort(ret.begin(), ret.end(), modeBiggerThan);
+ return ret;
+}
+
+int ScreenSetup::getOutputCount() const
+{
+ return a->_outputMap.size();
+}
+
+const ResolutionVector &ScreenSetup::getVirtualResolutions() const
+{
+ return a->_resolutions;
+}
diff --git a/src/xx.h b/src/xx.h
new file mode 100644
index 0000000..a17d4b7
--- /dev/null
+++ b/src/xx.h
@@ -0,0 +1,66 @@
+#ifndef XRANDR_H
+#define XRANDR_H
+
+#include <QList>
+#include <QString>
+#include <QSize>
+
+struct ScreenInfo;
+class XPrivate;
+
+typedef QVector<QSize> ResolutionVector;
+
+///////////////////////////////////////////////////////////////////////////
+
+enum class ScreenMode
+{
+ Single,
+ Clone,
+ Dual,
+ Advanced,
+};
+
+struct ScreenInfo
+{
+ int position;
+ QString name;
+ QString output;
+ QSize currentResolution;
+ QSize preferredResolution;
+ bool isProjector;
+ ResolutionVector modes;
+};
+
+class ScreenSetup
+{
+public:
+ void updateScreenResources();
+ void initModes();
+ ScreenMode getCurrentMode();
+ ScreenMode setDefaultMode(bool dryRun = false);
+ bool createMode(unsigned int resX, unsigned int resY, float refresh, QString name);
+ void revertChanges();
+ bool setClone(const QSize &resolution);
+ bool setCustom(const QList<QPair<QSize, QList<QString>>> &list);
+ ResolutionVector getCommonModes() const;
+ int getOutputCount() const;
+ QMap<QString, ScreenInfo> getScreenPositions() const;
+ const ResolutionVector &getVirtualResolutions() const;
+
+ // Singleton
+ inline static ScreenSetup* inst() {
+ if (_instance == nullptr) _instance = new ScreenSetup();
+ return _instance;
+ }
+
+private:
+ ScreenSetup();
+ ~ScreenSetup();
+
+ static ScreenSetup * _instance;
+ XPrivate *a;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+#endif // XRANDR_H