From 64af75c336424b6dfe0a114a4697fb522a6169e5 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 31 Aug 2018 10:57:39 +0200 Subject: Update --- LICENSE | 2 +- src/i18n/de.ts | 26 +++---- src/icons.qrc | 5 +- src/icons/projector_icon.svg | 114 ++++++++++++--------------- src/icons/refresh_icon.svg | 37 +++++++++ src/icons/screen_icon.svg | 39 +--------- src/main.cpp | 29 ++++--- src/widget.cpp | 182 +++++++++++++++++++++++++++++-------------- src/widget.h | 3 + src/widget.ui | 23 +++++- src/xprivate.cpp | 38 ++++----- src/xx.cpp | 77 +++++++++++++++++- src/xx.h | 16 +++- udev/99-beamergui.rules | 3 + 14 files changed, 386 insertions(+), 208 deletions(-) create mode 100644 src/icons/refresh_icon.svg create mode 100644 udev/99-beamergui.rules diff --git a/LICENSE b/LICENSE index c56fa74..bd83a08 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ GPL blabla Icons: -Projector: +Projector + Refresh: Icons made by "http://www.freepik.com" Freepik, from "https://www.flaticon.com/", "Creative Commons BY 3.0" (CC 3.0 BY) Screen: diff --git a/src/i18n/de.ts b/src/i18n/de.ts index b8537b6..5589628 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -4,7 +4,7 @@ QCoreApplication - + %1x%2 @@ -67,37 +67,37 @@ - + (off) - + Output - + Position - + Do you want to keep this resolution? - + Keep - + Confirm - + This terminates the GUI. It will not pop up again if further screens are connected. Are you sure? @@ -107,27 +107,27 @@ Are you sure? main - + Automatically configure modes and set up screens. - + Show config GUI if more than one screen is connected. - + Keep running in background and show GUI again when number of screens changes. - + Test mode, don't actually apply any changes. - + Connect to system bus to trigger wakeup of wainting beamergui. diff --git a/src/icons.qrc b/src/icons.qrc index 3a700e5..164f3b1 100644 --- a/src/icons.qrc +++ b/src/icons.qrc @@ -1,7 +1,8 @@ - + icons/projector_icon.svg icons/screen_icon.svg icons/checkmark.svg - + icons/refresh_icon.svg + diff --git a/src/icons/projector_icon.svg b/src/icons/projector_icon.svg index a22f105..d145e98 100644 --- a/src/icons/projector_icon.svg +++ b/src/icons/projector_icon.svg @@ -1,65 +1,53 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + diff --git a/src/icons/refresh_icon.svg b/src/icons/refresh_icon.svg new file mode 100644 index 0000000..1660754 --- /dev/null +++ b/src/icons/refresh_icon.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/screen_icon.svg b/src/icons/screen_icon.svg index f2ca511..d4f0ba4 100644 --- a/src/icons/screen_icon.svg +++ b/src/icons/screen_icon.svg @@ -1,44 +1,9 @@ - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main.cpp b/src/main.cpp index 2a85617..6c7f492 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,23 +20,34 @@ bool showGui() { return _showGui; } bool backgroundMode() { return _backgroundMode; } } -static void parseCommandLine(const QApplication &a); +static void parseCommandLine(const QCoreApplication &a); int main(int argc, char *argv[]) { - QApplication a(argc, argv); + QCoreApplication *a; + bool gui = true; + for (int i = 0; i < argc; ++i) { + if (strcmp(argv[i], "-w") == 0 || strcmp(argv[i], "--wakeup") == 0) { + gui = false; + } + } + if (gui) { + a = new QApplication(argc, argv); + } else { + a = new QCoreApplication(argc, argv); + } QCoreApplication::setApplicationName("BeamerGUI XP - Home Edition"); QCoreApplication::setApplicationVersion("2.0"); // System strings - QTranslator *qtTranslator = new QTranslator(&a); + QTranslator *qtTranslator = new QTranslator(a); qtTranslator->load(QLocale::system(), "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath)); - a.installTranslator(qtTranslator); + a->installTranslator(qtTranslator); // App specific - QTranslator *translator = new QTranslator(&a); + QTranslator *translator = new QTranslator(a); translator->load(QLocale::system(), ":"); - a.installTranslator(translator); + a->installTranslator(translator); - parseCommandLine(a); + parseCommandLine(*a); if (_wakeup) { return Bus::inst()->registerService() ? 0 : 1; @@ -60,10 +71,10 @@ int main(int argc, char *argv[]) } if (w == nullptr) return 0; - return a.exec(); + return a->exec(); } -static void parseCommandLine(const QApplication &a) +static void parseCommandLine(const QCoreApplication &a) { // Command line QCommandLineParser parser; diff --git a/src/widget.cpp b/src/widget.cpp index bc4869f..49e5285 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -68,6 +68,9 @@ static void addBoldListener(QComboBox *combo) }); } +static const QString resetButtonStyle("padding: 2px; margin: 0 0 1px 1px;"); +static const QString resetButtonHotStyle(resetButtonStyle + "background-color: #f99;"); + /* * Main widget */ @@ -77,64 +80,92 @@ Widget::Widget(QWidget *parent) : _ui(new Ui::Widget), _popupCount(0), _iProjector(QIcon(":projector")), - _iScreen(QIcon(":screen")) + _iScreen(QIcon(":screen")), + _lastScreenCount(0), + _isDbus(true) { _ui->setupUi(this); + // Add refresh button + _ui->btnReload->setStyleSheet(resetButtonStyle); + _ui->tabWidget->setCornerWidget(_ui->btnReload); + connect(_ui->btnReload, &QPushButton::clicked, [=](bool) { + _ui->btnReload->setStyleSheet(resetButtonStyle); + ScreenSetup::inst()->initModes(); + initControls(); + }); setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); QTimer *top = new QTimer(this); connect(top, &QTimer::timeout, [=]() { + if (this->isHidden()) + return; // Move window to current screen - QRect win = this->geometry(); - QPoint cursor = QCursor::pos(); - const QScreen *winScreen = nullptr, *mouseScreen = nullptr; - //qDebug() << "Mouse at" << cursor << " window at" << win; - for (auto screen : _qtScreens) { - QRect geo = screen->geometry(); - if (geo.contains(win)) { - winScreen = screen; - //qDebug() << "Window on screen" << geo; - } - if (geo.contains(cursor)) { - mouseScreen = screen; - //qDebug() << "Mouse on screen" << geo; - } - } - if (mouseScreen != nullptr && mouseScreen != winScreen) { - QPoint offset = mouseScreen->geometry().topLeft(); - QSize spacing = (mouseScreen->size() - this->size()) / 2; - offset.rx() += spacing.width(); - offset.ry() += spacing.height(); - this->move(offset); + if (qApp->mouseButtons() == Qt::NoButton) { + updateWindowPlacement(); } // Raise window if appropriate - if (_popupCount > 0) - return; - auto combos = this->findChildren(); - for (auto combo : combos) { - if (combo->view()->isVisible()) - return; + if (_popupCount == 0) { + bool ok = true; + auto combos = this->findChildren(); + for (auto combo : combos) { + if (combo->view()->isVisible()) { + ok = false; + break; + } + } + if (ok) { + raise(); + } } - raise(); }); top->start(1000); addBoldListener(_ui->cboCloneResolution); addBoldListener(_ui->cboDualLeft); addBoldListener(_ui->cboDualRight); connectButtons(); - auto fun = [this](const QScreen *scrn) { - qDebug() << "QT SEES SCREEN" << scrn->geometry(); - _qtScreens.append(scrn); - /* - connect(scrn, &QScreen::geometryChanged, [this](const QRect &geom) { - - }); - */ + // Handle screens on Qt level + // Timer + QTimer *t = new QTimer(this); + t->setSingleShot(true); + // Worked fine + connect(t, &QTimer::timeout, [=]() { + int currentCount = ScreenSetup::inst()->queryCurrentOutputCount(); + qDebug() << "Timeout. Dbus:" << _isDbus << "old count:" << _lastScreenCount << "new count:" << currentCount; + if (_isDbus && _lastScreenCount >= currentCount) + return; // Ignore dbus events if the screen count didn't change (or decreased) + if (this->isHidden()) { + ScreenSetup::inst()->initModes(); + _isDbus = true; + this->show(); + } else { + if (currentCount > _lastScreenCount) { + ScreenSetup::inst()->initModes(); + initControls(); + } else { + _ui->btnReload->setStyleSheet(resetButtonHotStyle); + } + } + }); + auto popupGui = [=]() { + if (this->isHidden()) { + t->start(1000); + } else { + _ui->btnReload->setStyleSheet(resetButtonHotStyle); + } }; for (auto scrn : QGuiApplication::screens()) { - fun(scrn); + _qtScreens.append(scrn); } - connect(qApp, &QGuiApplication::screenAdded, fun); - connect(qApp, &QGuiApplication::screenRemoved, [this](const QScreen *scrn) { + connect(qApp, &QGuiApplication::screenAdded, [=](const QScreen *scrn) { + qDebug() << "QT SEES SCREEN" << scrn->geometry(); + _qtScreens.append(scrn); + if (CommandLine::backgroundMode()) { + qDebug() << "Qt setting FALSE"; + _isDbus = false; + popupGui(); + } + }); + connect(qApp, &QGuiApplication::screenRemoved, [=](const QScreen *scrn) { + qDebug() << "Qt lost screen" << scrn->geometry(); _qtScreens.removeAll(scrn); }); _ui->btnExit->setVisible(CommandLine::backgroundMode()); @@ -144,28 +175,23 @@ Widget::Widget(QWidget *parent) : 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 - } + popupGui(); }); } + // Xlib + connect(ScreenSetup::inst(), &ScreenSetup::outputConfigChanged, [=](ConnectionEvent type) { + if (type == ConnectionEvent::Disconnected) { + if (!this->isHidden()) { + _ui->btnReload->setStyleSheet(resetButtonHotStyle); + } + } else { + _isDbus = false; + popupGui(); + } + }); } } @@ -178,6 +204,7 @@ void Widget::showEvent(QShowEvent *event) { QWidget::showEvent(event); initControls(true); + updateWindowPlacement(true); } static void fillCombo(QComboBox *combo, const ResolutionVector &resolutions, const QSize &preselected, const QSize &preferred = QSize()) @@ -223,6 +250,8 @@ void Widget::comboBold(int index) void Widget::initControls(bool jumpToTab) { + _lastScreenCount = ScreenSetup::inst()->getOutputCount(); + _ui->btnReload->setStyleSheet(resetButtonStyle); // ScreenMode currentOpMode = ScreenSetup::inst()->getCurrentMode(); _ui->tabWidget->setTabEnabled(1, CommandLine::testMode() || ScreenSetup::inst()->getOutputCount() == 2 || currentOpMode == ScreenMode::Dual); @@ -265,13 +294,24 @@ void Widget::initControls(bool jumpToTab) QSize preferredClone; for (auto screen : screenList) { if (!screen.preferredResolution.isEmpty()) { - preferredClone = screen.preferredResolution; + if (screen.isProjector // Projector always overrides + || preferredClone.isEmpty() // Have no preferred yet + || screen.preferredResolution.width() < preferredClone.width() // For normal screens, + || screen.preferredResolution.height() < preferredClone.height()) { // smallest wins + preferredClone = screen.preferredResolution; + } if (screen.isProjector) break; } } fillCombo(_ui->cboCloneResolution, modes, screenList[0].currentResolution, preferredClone); // Dual + _ui->dualContainer->takeAt(0); + _ui->dualContainer->takeAt(1); + _ui->dualContainer->takeAt(2); + _ui->dualContainer->addLayout(_ui->dualLeft); + _ui->dualContainer->addWidget(_ui->btnDualSwap); + _ui->dualContainer->addLayout(_ui->dualRight); if (screenList.size() >= 2) { auto lists = QList({_ui->cboDualLeft, _ui->cboDualRight}); int j = 0; @@ -575,4 +615,30 @@ void Widget::connectButtons() { } +void Widget::updateWindowPlacement(bool force) +{ + QRect win = this->geometry(); + QPoint cursor = QCursor::pos(); + const QScreen *winScreen = nullptr, *mouseScreen = nullptr; + //qDebug() << "Mouse at" << cursor << " window at" << win; + for (auto screen : _qtScreens) { + QRect geo = screen->geometry(); + if (geo.contains(win)) { + winScreen = screen; + //qDebug() << "Window on screen" << geo; + } + if (geo.contains(cursor)) { + mouseScreen = screen; + //qDebug() << "Mouse on screen" << geo; + } + } + if (mouseScreen != nullptr && (force || mouseScreen != winScreen)) { + QPoint offset = mouseScreen->geometry().topLeft(); + QSize spacing = (mouseScreen->size() - this->size()) / 2; + offset.rx() += spacing.width(); + offset.ry() += spacing.height(); + this->move(offset); + } +} + //////////////////////////////////////////////////////////////////////////////// diff --git a/src/widget.h b/src/widget.h index f784ce9..ddf14fb 100644 --- a/src/widget.h +++ b/src/widget.h @@ -34,10 +34,13 @@ private: QList _qtScreens; bool _popupCount; QIcon _iProjector, _iScreen; + int _lastScreenCount; + bool _isDbus; void initControls(bool jumpToTab = false); void connectButtons(); bool keepResolution(); + void updateWindowPlacement(bool force = false); }; #endif // WIDGET_H diff --git a/src/widget.ui b/src/widget.ui index a0be031..fe6f67b 100644 --- a/src/widget.ui +++ b/src/widget.ui @@ -21,12 +21,29 @@ :/projector:/projector + + + + + + + + :/refresh:/refresh + + + + 16 + 16 + + + + - 2 + 1 @@ -109,7 +126,7 @@ border: 2px solid black; - + @@ -152,7 +169,7 @@ border: 2px solid black; - + diff --git a/src/xprivate.cpp b/src/xprivate.cpp index 120fb66..ce7ce8c 100644 --- a/src/xprivate.cpp +++ b/src/xprivate.cpp @@ -82,9 +82,11 @@ void XPrivate::updateScreenResources() _modeMap.insert( _screenResources->modes[i].id, &_screenResources->modes[i]); + /* qDebug() << _screenResources->modes[i].id << "\t" << _screenResources->modes[i].width << "x" << _screenResources->modes[i].height; + */ } /* @@ -190,25 +192,23 @@ void XPrivate::updateScreenResources() 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); - } - } + for (OutputInfo* output : screens) { + if (output->mode == nullptr) { + qDebug() << "(Ignoring" << output->outputName << "since it's disconnected)"; + continue; + } + if (output->crtc->x >= endX) { + QSize res(0, 0); + res = QSize(int(output->mode->width), int(output->mode->height)); + _resolutions.append(res); + endX = -0xffff; // Reset + } + output->position = _resolutions.size() - 1; + qDebug() << "Screen (" << output->crtc->x << "," << output->crtc->y << ") @" << output->mode->width << "x" << output->mode->height << "as screen" << output->position; + if (output->crtc->x + int(output->crtc->width) > endX) { + endX = output->crtc->x + int(output->mode->width); + } + } qDebug() << "Loaded."; } diff --git a/src/xx.cpp b/src/xx.cpp index 2a8cdc5..d9c05d6 100644 --- a/src/xx.cpp +++ b/src/xx.cpp @@ -2,7 +2,7 @@ #include "xprivate.h" #include "cvt.h" #include - +#include /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// @@ -36,10 +36,58 @@ static ScreenInfo initScreenInfo(const OutputInfo *oi, const ModeMap &om) ScreenSetup * ScreenSetup::_instance = nullptr; +static int errorHandlerX(Display*) +{ + exit(1); +} + ScreenSetup::ScreenSetup() : a(new XPrivate()) { - /* Get informations about Xserver */ + int event_base_return, error_base_return; + if (!XRRQueryExtension(a->_display, &event_base_return, &error_base_return)) { + qDebug() << "No XRANDR extension found"; + exit(1); + } updateScreenResources(); + //XSelectInput(a->_display, DefaultRootWindow(a->_display), StructureNotifyMask); + XRRSelectInput(a->_display, DefaultRootWindow(a->_display), RROutputChangeNotifyMask); + //XSync(a->_display, False); + XSetIOErrorHandler((XIOErrorHandler) errorHandlerX); + _socketNotifier = new QSocketNotifier(qintptr(ConnectionNumber(a->_display)), QSocketNotifier::Read); + connect(_socketNotifier, &QSocketNotifier::activated, [=](int) { + XEvent ev; + qDebug() << "Socket Event"; + while (XPending(a->_display) > 0) { + XNextEvent(a->_display, &ev); + if (ev.type - event_base_return != RRNotify) { + qDebug() << "Received unknown X event"; + continue; + } + qDebug() << "Got Change Event"; + XRROutputChangeNotifyEvent *oce = reinterpret_cast(&ev); + XRRScreenResources *sr = XRRGetScreenResources(oce->display, oce->window); + if (sr == nullptr) { + emit outputConfigChanged(ConnectionEvent::Unknown); + continue; + } + XRROutputInfo *oi = XRRGetOutputInfo(a->_display, sr, oce->output); + if (oi == nullptr) { + XRRFreeScreenResources(sr); + emit outputConfigChanged(ConnectionEvent::Unknown); + continue; + } + if (oi->connection == RR_Connected) { + emit outputConfigChanged(ConnectionEvent::Connected); + } else if (oi->connection == RR_Disconnected) { + emit outputConfigChanged(ConnectionEvent::Disconnected); + } else { + emit outputConfigChanged(ConnectionEvent::Unknown); + } + XRRFreeOutputInfo(oi); + XRRFreeScreenResources(sr); + } + }); + } @@ -337,11 +385,36 @@ ResolutionVector ScreenSetup::getCommonModes() const return ret; } +/** + * Return number of connected (no active) outputs according to last query + */ int ScreenSetup::getOutputCount() const { return a->_outputMap.size(); } +/** + * Query currently connected number of outputs + */ +int ScreenSetup::queryCurrentOutputCount() const +{ + auto sr = XRRGetScreenResourcesCurrent(a->_display, DefaultRootWindow(a->_display)); + if (sr == nullptr) + return 0; + int count = 0; + for (int i = 0; i < sr->noutput; ++i) { + XRROutputInfo* info = XRRGetOutputInfo(a->_display, sr, sr->outputs[i]); + if (info == nullptr) + continue; + if (info->connection != RR_Disconnected) { + count++; + } + XRRFreeOutputInfo(info); + } + XRRFreeScreenResources(sr); + return count; +} + const ResolutionVector &ScreenSetup::getVirtualResolutions() const { return a->_resolutions; diff --git a/src/xx.h b/src/xx.h index a17d4b7..ff0ea11 100644 --- a/src/xx.h +++ b/src/xx.h @@ -7,6 +7,7 @@ struct ScreenInfo; class XPrivate; +class QSocketNotifier; typedef QVector ResolutionVector; @@ -31,8 +32,16 @@ struct ScreenInfo ResolutionVector modes; }; -class ScreenSetup +enum class ConnectionEvent { + Connected, + Disconnected, + Unknown, +}; + +class ScreenSetup : public QObject +{ + Q_OBJECT public: void updateScreenResources(); void initModes(); @@ -44,6 +53,7 @@ public: bool setCustom(const QList>> &list); ResolutionVector getCommonModes() const; int getOutputCount() const; + int queryCurrentOutputCount() const; QMap getScreenPositions() const; const ResolutionVector &getVirtualResolutions() const; @@ -59,6 +69,10 @@ private: static ScreenSetup * _instance; XPrivate *a; + QSocketNotifier *_socketNotifier; +signals: + void outputConfigChanged(ConnectionEvent event); + }; /////////////////////////////////////////////////////////////////////////// diff --git a/udev/99-beamergui.rules b/udev/99-beamergui.rules new file mode 100644 index 0000000..06364e2 --- /dev/null +++ b/udev/99-beamergui.rules @@ -0,0 +1,3 @@ +# Tell beamergui to show up +# +SUBSYSTEM=="drm", ACTION=="change", RUN+="/opt/openslx/bin/beamergui -w" -- cgit v1.2.3-55-g7522