// NOTE: removing a screen after the OpApp was started results in an invisible screen // NOTE: adding and removing a screen after the OpApp was started in tablet mode will make mmm do weird things #include "lts_multi_monitor.h" #include "lts_multi_monitor_selector_normal.h" #include "lts_multi_monitor_selector_wide.h" #include "lts_screen_normal.h" #include "lts_screen_wide.h" #include "lts_dummy_widget.h" #include "lts_screen_container.h" #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif namespace LtsMultiMonitor { static LtsMultiMonitor *s_ltsMultiMonitor = nullptr; LtsMultiMonitor *LtsMultiMonitor::getInstance() { if (!s_ltsMultiMonitor) { s_ltsMultiMonitor = new LtsMultiMonitor; qAddPostRoutine(&cleanupLtsMultiMonitor); } return s_ltsMultiMonitor; } void LtsMultiMonitor::cleanupLtsMultiMonitor() { delete s_ltsMultiMonitor; s_ltsMultiMonitor = nullptr; } LtsMultiMonitor::LtsMultiMonitor() : QObject(nullptr) , m_mainScreen(nullptr) , m_active(false) , m_initialized(false) , m_skipScreenId(-1) , m_subSkipScreenId(-1) // , m_dummy(new QWidget()) , m_overlapBaseLayouts(false) , m_debugMargins(false) {} LtsMultiMonitor::~LtsMultiMonitor() { // if (m_superParent) // m_superParent->deleteLater(); for (auto i = m_screens.begin(); i != m_screens.end(); ++i) { LtsScreenBase *screen = *i; disconnect(screen, &QObject::destroyed, this, &LtsMultiMonitor::screenDied); screen->deleteLater(); } //qDeleteAll(m_screens); //delete m_dummy; //m_dummy->deleteLater(); } void LtsMultiMonitor::screenDied() { // qWarning() << "whoops! a screen died!"; Q_ASSERT(false); } void LtsMultiMonitor::init() { // TODO: reinit if device is a tablet, to refresh screen count for attaching screens live if (LtsSessionData::LtsSessionData::getInstance()->isTablet()) { m_overlapBaseLayouts = true; m_initialized = true; return; } if (m_initialized || !m_active) return; QList screenList = QGuiApplication::screens(); const QScreen &mainScreen = *QGuiApplication::primaryScreen(); // qWarning() << "skip screen" << m_skipScreenId << "part" << m_subSkipScreenId; //for (int i = 0; i < screenList.count(); i++) for (auto i = screenList.constBegin(); i != screenList.constEnd(); ++i) { const QScreen &screen = **i;//screenList.at(i); // only add screen if it was not already added, by its serial number bool found = false; for (LtsScreenBase *ltsScreen : qAsConst(m_screens)) { if (ltsScreen->serialNumber() == screen.name()) { found = true; break; } } if (found) continue; // more like "possible" screen id as there can be some continues, etc. int screenId = m_nextScreenId++; // qWarning() << "screen id" << screenId; QRect screenGeo(screen.geometry()); if (screenId == m_skipScreenId) // do not create LtsScreenBase where AVM is (or at least limit the "sub screens") { if (m_subSkipScreenId == 0) { // left left side of screen empty QRect newScreenGeo(screenGeo); newScreenGeo.moveLeft(screenGeo.x() + (screenGeo.width() / 2)/* + 1*/); newScreenGeo.setWidth(screenGeo.width() / 2); screenGeo = newScreenGeo; } else if (m_subSkipScreenId == 1) { // left right side of screen empty QRect newScreenGeo(screenGeo); newScreenGeo.setWidth(screenGeo.width() / 2); // starts to malfunction in old gnome starting from 1.78 ... screenGeo = newScreenGeo; } else // completely skip LtsScreenBase creation if no sub screen is set { // qWarning() << "skipping" << screenId; continue; } } // qWarning() << "screenGeo" << screenGeo; bool isUltraWideView = (screenGeo.width() > 3000); LtsScreenBase *newScreen; if (isUltraWideView) { newScreen = new LtsScreenWide(screenGeo, screenId, m_debugMargins, screen.name()); } else { auto *newScreenNormal = new LtsScreenNormal(std::move(screenGeo), screenId, m_debugMargins, screen.name()); newScreen = newScreenNormal; // see https://devhub.defence-elec.de/confluence/x/yLjeDg 13.1 connect(newScreenNormal, &LtsScreenNormal::signalMakeAllSwole, this, &LtsMultiMonitor::signalMakeAllSwole); connect(this, &LtsMultiMonitor::signalMakeAllSwole, newScreenNormal, &LtsScreenNormal::signalMakeSwole); } connect(newScreen, &QObject::destroyed, this, &LtsMultiMonitor::screenDied); if ((&mainScreen) == (&screen)) m_mainScreen = newScreen; m_screens.append(newScreen); } if (m_screens.isEmpty()) { QMessageBox::critical(nullptr, QString(), tr("No screens found - cannot start application.")); abort(); } if (m_mainScreen == nullptr) m_mainScreen = m_screens.first(); m_initialized = true; emit signalScreenCountChanged(screenCount()); } int LtsMultiMonitor::screenCount() const { return m_screens.count(); } bool LtsMultiMonitor::cancelAndProcessIfInactive(QWidget *widget) const { if (m_active) return false; widget->activateWindow(); // no mmm => show as discrete window widget->show(); place(widget); return true; } void LtsMultiMonitor::place(QWidget *widget) { /* tell the blw that it was now sucessfully placed and is shown / ready to use * e.g. for autostarting a replay only if it is already visible, so the user * doesn't miss out on the replay's beginning * * -- as this method returns here, we have to emit 'successfullyPlaced' here, * as the widget is now visible as its own widget */ auto *blw = qobject_cast(widget); if (blw != nullptr) emit blw->signalSuccessfullyPlaced(); } bool LtsMultiMonitor::putScreen(QWidget *widget, int screenId, int screenPart) { Q_ASSERT(widget != nullptr); if (widget == nullptr) return false; // fail-safe if (cancelAndProcessIfInactive(widget)) return true; init(); if (overlapBaseLayouts(widget)) // NOTE: call 'init' before 'overlapBaseLayouts' return true; bool isSecondary = isWidgetSecondary(widget); qWarning() << "putScreen: isSecondary" << isSecondary; for (LtsScreenBase *screen : qAsConst(m_screens)) { if (screen->screenId() != screenId) continue; screen->switchToTemp(isSecondary ? LtsScreenBase::WidgetType::SecondaryWidget : LtsScreenBase::WidgetType::MainWidget); try { screen->putWidget(widget, screenPart); } catch (...) { screen->revertSwitch(); // qWarning() << "LtsMultiMonitor:::putScreen: cannot put widget on screen "<check(); // qWarning() << "mep" << QStringLiteral("%1:%2").arg(QString::fromUtf8(__FILE__).split(QStringLiteral("/")).last()).arg(__LINE__); place(widget); screen->finalizeSwitch(); return true; } // qWarning() << "LtsMultiMonitor:::putScreen: screen"<screenId() == m_skipScreenId || !screen->isEmpty()) continue; screen->switchToTemp(LtsScreenBase::WidgetType::SecondaryWidget); try //first try on part 1 { screen->putWidget(widget, 1); } catch (...) { try //if part 1 failed, try part 2 { screen->putWidget(widget, 2); } catch (...) //if part 2 failed, try next screen { screen->revertSwitch(); continue; } } screen->check(); place(widget); screen->finalizeSwitch(); return true; } return false; } bool LtsMultiMonitor::isOverlapBaseLayouts() const { return (m_overlapBaseLayouts /*&& m_screens.count() == 1*/); // TODO: reactivate if mmm works with external monitors on a tablet } void LtsMultiMonitor::freezeMagicCorner(int screenId) { for (auto *screen : qAsConst(m_screens)) { if (screen->screenId() == screenId) { LtsScreenNormal* normalScreen = qobject_cast(screen); if(normalScreen->child()->hasMainWidget() && normalScreen->child()->hasSecondaryWidget()) { normalScreen->hideMagicCorner(true); if(normalScreen->child()->activeWidgetType() == LtsScreenBase::WidgetType::MainWidget) normalScreen->child()->toggle(); } } } } void LtsMultiMonitor::unfreezeMagicCorner(int screenId) { for (auto *screen : qAsConst(m_screens)) { if (screen->screenId() == screenId) { LtsScreenNormal* normalScreen = qobject_cast(screen); if(normalScreen->child()->hasMainWidget() && normalScreen->child()->hasSecondaryWidget()) normalScreen->hideMagicCorner(false); } } } bool LtsMultiMonitor::overlapBaseLayouts(QWidget *widget) { if (!isOverlapBaseLayouts()) return false; /* only overlap base layouts if the main screen is the only screen - if * another screen is connected to the tablet, the new screen should be * selectable, too */ Q_ASSERT(qobject_cast(widget) == nullptr); if (qobject_cast(widget) != nullptr) { // qWarning() << "tried to put a dummy widget on a tablet device... - ignore"; return false; } // FullscreenVideoDummyWidget *dummy = qobject_cast(widget); // if (dummy != nullptr) // { // widget->setParent(m_dummy); // emit dummy->signalWidgetPlaced(QRect()); // } // else // { // widget->show(); // as discrete window // place(widget); // } #ifdef Q_OS_WIN widget->winId(); // make sure windowHandle() is not a nullptr QWindowsWindowFunctions::setHasBorderInFullScreen(widget->windowHandle(), true); #endif widget->activateWindow(); widget->showFullScreen(); place(widget); return true; } bool LtsMultiMonitor::selectAndSwap(QWidget *widget) { if (!m_active) return false; // no swap if mmm not active Q_ASSERT(!isOverlapBaseLayouts()); // swap button not allowed on tablet if (isOverlapBaseLayouts()) return false; // don't allow swap if overlay mode is active (as that would make no sense) // look for the screen which contains the widget LtsScreenContainer *oldContainer = nullptr; // TODO C++17: std::optional for (LtsScreenBase *_screen : qAsConst(m_screens)) { try { oldContainer = _screen->containerOfWidget(widget); } catch (...){} } if (oldContainer == nullptr) { // qWarning() << "!!! mep" << __FILE__ << __LINE__; LtsWidgets::LtsMessageDialog::showMessage(widget, tr("Could not determine screen id for this swap!
Swapping not possible, please try again later.")); return false; } // qWarning() << "oldContainer" << (quintptr)oldContainer; return selectScreen(widget, oldContainer); // NOTE: the screen later TAKES the widget from the clicked container and puts it into the OLD container given } LtsScreenBase *LtsMultiMonitor::widgetsScreen(QWidget *widget) { for (LtsScreenBase *screen : qAsConst(m_screens)) if (screen->hasWidget(widget)) return screen; return nullptr; } bool LtsMultiMonitor::isWidgetSecondary(QWidget *widget) { #ifndef GUEZ_MODE return false; #endif auto *blw = qobject_cast(widget); auto *secondaryWidget = qobject_cast(widget); // full screen video & script editor's screen widget bool isSecondaryWidget = ((blw != nullptr && blw->isSecondaryWidget()) || secondaryWidget != nullptr); // if (isSec) return isSecondaryWidget; } class StupidDeleter { public: explicit StupidDeleter(LtsScreenBase *screen) : m_screen(screen) {} ~StupidDeleter() { //qWarning() << "####### Deleter"; if (m_screen != nullptr) { //qWarning() << "####### REVERTED"; m_screen->revertSwitch(); } //else qWarning() << "####### NOT REVERTED !!!!"; } void reset() { //qWarning() << "####### RESET"; m_screen = nullptr; } private: LtsScreenBase *m_screen; }; bool LtsMultiMonitor::selectScreen(QWidget *widget, LtsScreenContainer *swap, bool putOnMainIfFree, bool ltsScreenNormalOnly, const QVector &irreplacableWidgets) { Q_ASSERT(widget != nullptr); if (widget == nullptr) return false; // fail-safe if (cancelAndProcessIfInactive(widget)) return true; init(); if (overlapBaseLayouts(widget)) // NOTE: call 'init' before 'overlapBaseLayouts' return true; bool isSecondary = isWidgetSecondary(widget); //qWarning() << "selectScreen: isSecondary" << isSecondary; if (!isSecondary) // the allEmpty stuff is only for main widgets!! { bool allEmpty = putOnMainIfFree; // initial value for (LtsScreenBase *screen : qAsConst(m_screens)) { screen->switchToTemp(LtsScreenBase::WidgetType::MainWidget); bool isEmpty = screen->isEmpty(); screen->revertSwitch(); if (isEmpty) continue; allEmpty = false; break; } //qWarning() << "allEmpty" << allEmpty; if (allEmpty) { /* if ALL screens in the entire mmm are empty, then just put the widget on * the main screen */ m_mainScreen->switchToTemp(LtsScreenBase::WidgetType::MainWidget); try { m_mainScreen->putWidget(widget); } catch (...) { qWarning() << "failed - reverting"; m_mainScreen->revertSwitch(); Q_ASSERT_X(false, __FUNCTION__, "screen promised to be empty but 'putWidgetSomewhere' method failed regardless"); // qWarning() << "woopsie"; auto *blw = qobject_cast(widget); if (blw != nullptr) emit blw->signalClose(); else widget->deleteLater(); return false; // fail-safe } m_mainScreen->check(); place(widget); m_mainScreen->finalizeSwitch(); return true; } } auto *selectionProcessWatcher = new QObject; // finally show selectors bool minOnce = false; for (auto i = m_screens.begin(); i != m_screens.end(); ++i) { LtsScreenBase *screen = *i; screen->switchToTemp(isSecondary ? LtsScreenBase::WidgetType::SecondaryWidget : LtsScreenBase::WidgetType::MainWidget); StupidDeleter _(screen); if (screen->isIrreplaceable()) // do not create mms for a screen that is fully irreplaceable { // qWarning() << "screen marked as irreplacable, skipping"<hasWidget(widget)) { // qWarning() << "yes, skipping!"; skip = true; continue; } // qWarning() << "no, selection available"; } if (skip) continue; MultiMonitorSelectorBase *mms; if (qobject_cast(screen) != nullptr) { auto *screenNormal = static_cast(screen); mms = new MultiMonitorSelectorNormal(screenNormal, isSecondary, irreplacableWidgets); } else if (qobject_cast(screen) != nullptr && !ltsScreenNormalOnly) { auto *screenWide = static_cast(screen); mms = new MultiMonitorSelectorWide(screenWide, nullptr, irreplacableWidgets); } else continue; QPointer widgetPtr(widget); connect(widget, &QWidget::destroyed, mms, &MultiMonitorSelectorBase::close); connect(mms, &MultiMonitorSelectorBase::rejected,//signalAbort, this, [widgetPtr, swap]() { // qWarning() << "mep" << QStringLiteral("%1:%2").arg(QString::fromUtf8(__FILE__).split(QStringLiteral("/")).last()).arg(__LINE__); if (!widgetPtr || swap != nullptr) // don't delete widget if it was a swap, just let it live where it currently is living return; // qWarning() << "mep" << QStringLiteral("%1:%2").arg(QString::fromUtf8(__FILE__).split(QStringLiteral("/")).last()).arg(__LINE__); //qWarning() << "###### REJECTED!!!!"; auto *blw = qobject_cast(widgetPtr.data()); if (blw != nullptr) emit blw->signalClose(); else widgetPtr->deleteLater(); }); connect(mms, &MultiMonitorSelectorBase::signalSelected, this, [this, mms, widgetPtr]() { // qWarning() << "mep" << QStringLiteral("%1:%2").arg(QString::fromUtf8(__FILE__).split(QStringLiteral("/")).last()).arg(__LINE__); for (LtsScreenBase *screen : qAsConst(m_screens)) screen->check(); // qWarning() << "mep" << QStringLiteral("%1:%2").arg(QString::fromUtf8(__FILE__).split(QStringLiteral("/")).last()).arg(__LINE__); if (widgetPtr) place(widgetPtr.data()); // qWarning() << "mep" << QStringLiteral("%1:%2").arg(QString::fromUtf8(__FILE__).split(QStringLiteral("/")).last()).arg(__LINE__); mms->deleteLater(); emit signalSelected(); //qWarning() << "mep" << QStringLiteral("%1:%2").arg(QString::fromUtf8(__FILE__).split(QStringLiteral("/")).last()).arg(__LINE__); }); connect(mms, &MultiMonitorSelectorBase::destroyed, selectionProcessWatcher, &QObject::deleteLater); connect(selectionProcessWatcher, &QObject::destroyed, mms, &MultiMonitorSelectorBase::close); mms->setSwap(swap); mms->setWidget(widget); mms->show(); _.reset(); minOnce = true; } if (!minOnce) { selectionProcessWatcher->deleteLater(); auto *blw = qobject_cast(widget); if (blw != nullptr) emit blw->signalClose(); else widget->deleteLater(); LtsWidgets::LtsMessageDialog::showMessage(m_mainScreen->screenDialog(), tr("There are no free screens available to select from.")); return false; } return true; } } // namespace LtsMultiMonitor