File indexing completed on 2024-04-28 16:52:24

0001 /*
0002  *  SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
0003  *  SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "windowutil.h"
0009 
0010 #include <QGuiApplication>
0011 
0012 constexpr int ACTIVE_WINDOW_UPDATE_INVERVAL = 0;
0013 
0014 WindowUtil::WindowUtil(QObject *parent)
0015     : QObject{parent}
0016     , m_activeWindowTimer{new QTimer{this}}
0017 {
0018     // use 0 tick timer to update active window to ensure window state has finished changing
0019     m_activeWindowTimer->setSingleShot(true);
0020     m_activeWindowTimer->setInterval(ACTIVE_WINDOW_UPDATE_INVERVAL);
0021     connect(m_activeWindowTimer, &QTimer::timeout, this, &WindowUtil::updateActiveWindow);
0022 
0023     connect(this, &WindowUtil::activeWindowChanged, this, &WindowUtil::updateActiveWindowIsShell);
0024 
0025     initWayland();
0026 }
0027 
0028 WindowUtil *WindowUtil::instance()
0029 {
0030     static WindowUtil *inst = new WindowUtil();
0031     return inst;
0032 }
0033 
0034 bool WindowUtil::isShowingDesktop() const
0035 {
0036     return m_showingDesktop;
0037 }
0038 
0039 bool WindowUtil::allWindowsMinimized() const
0040 {
0041     return m_allWindowsMinimized;
0042 }
0043 
0044 bool WindowUtil::allWindowsMinimizedExcludingShell() const
0045 {
0046     return m_allWindowsMinimizedExcludingShell;
0047 }
0048 
0049 bool WindowUtil::activeWindowIsShell() const
0050 {
0051     return m_activeWindowIsShell;
0052 }
0053 
0054 void WindowUtil::initWayland()
0055 {
0056     if (!QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) {
0057         qWarning() << "Plasma Mobile must use wayland! The current platform detected is:" << QGuiApplication::platformName();
0058         return;
0059     }
0060 
0061     using namespace KWayland::Client;
0062     ConnectionThread *connection = ConnectionThread::fromApplication(this);
0063 
0064     if (!connection) {
0065         return;
0066     }
0067 
0068     auto *registry = new Registry(this);
0069     registry->create(connection);
0070 
0071     connect(registry, &Registry::plasmaWindowManagementAnnounced, this, [this, registry](quint32 name, quint32 version) {
0072         m_windowManagement = registry->createPlasmaWindowManagement(name, version, this);
0073         qRegisterMetaType<QVector<int>>("QVector<int>");
0074 
0075         connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, this, [this](KWayland::Client::PlasmaWindow *window) {
0076             Q_EMIT windowCreated(window);
0077         });
0078         connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, this, &WindowUtil::windowCreatedSlot);
0079 
0080         connect(m_windowManagement, &PlasmaWindowManagement::showingDesktopChanged, this, &WindowUtil::updateShowingDesktop);
0081         connect(m_windowManagement, &PlasmaWindowManagement::activeWindowChanged, m_activeWindowTimer, qOverload<>(&QTimer::start));
0082 
0083         m_activeWindowTimer->start();
0084     });
0085 
0086     registry->setup();
0087     connection->roundtrip();
0088 }
0089 
0090 void WindowUtil::updateActiveWindow()
0091 {
0092     if (!m_windowManagement || m_activeWindow == m_windowManagement->activeWindow()) {
0093         return;
0094     }
0095 
0096     using namespace KWayland::Client;
0097     if (m_activeWindow) {
0098         disconnect(m_activeWindow.data(), &PlasmaWindow::closeableChanged, this, &WindowUtil::hasCloseableActiveWindowChanged);
0099         disconnect(m_activeWindow.data(), &PlasmaWindow::unmapped, this, &WindowUtil::forgetActiveWindow);
0100     }
0101     m_activeWindow = m_windowManagement->activeWindow();
0102     Q_EMIT activeWindowChanged();
0103 
0104     if (m_activeWindow) {
0105         connect(m_activeWindow.data(), &PlasmaWindow::closeableChanged, this, &WindowUtil::hasCloseableActiveWindowChanged);
0106         connect(m_activeWindow.data(), &PlasmaWindow::unmapped, this, &WindowUtil::forgetActiveWindow);
0107     }
0108 
0109     // loop through windows
0110     bool newAllMinimized = true;
0111     bool newAllMinimizedExcludingShell = true;
0112     for (auto *w : m_windowManagement->windows()) {
0113         if (!w->isMinimized() && !w->skipTaskbar() && !w->isFullscreen()) {
0114             newAllMinimized = false;
0115 
0116             if (w->appId() != QStringLiteral("org.kde.plasmashell")) {
0117                 newAllMinimizedExcludingShell = false;
0118             }
0119         }
0120     }
0121 
0122     if (newAllMinimized != m_allWindowsMinimized) {
0123         m_allWindowsMinimized = newAllMinimized;
0124         Q_EMIT allWindowsMinimizedChanged();
0125     }
0126     if (newAllMinimizedExcludingShell != m_allWindowsMinimizedExcludingShell) {
0127         m_allWindowsMinimizedExcludingShell = newAllMinimizedExcludingShell;
0128         Q_EMIT allWindowsMinimizedExcludingShellChanged();
0129     }
0130 
0131     // TODO: connect to closeableChanged, not needed right now as KWin doesn't provide this changeable
0132     Q_EMIT hasCloseableActiveWindowChanged();
0133 }
0134 
0135 bool WindowUtil::hasCloseableActiveWindow() const
0136 {
0137     return m_activeWindow && m_activeWindow->isCloseable() /*&& !m_activeWindow->isMinimized()*/;
0138 }
0139 
0140 void WindowUtil::closeActiveWindow()
0141 {
0142     if (m_activeWindow) {
0143         m_activeWindow->requestClose();
0144     }
0145 }
0146 
0147 void WindowUtil::requestShowingDesktop(bool showingDesktop)
0148 {
0149     if (!m_windowManagement) {
0150         return;
0151     }
0152     m_windowManagement->setShowingDesktop(showingDesktop);
0153 }
0154 
0155 void WindowUtil::minimizeAll()
0156 {
0157     if (!m_windowManagement) {
0158         qWarning() << "Ignoring request for minimizing all windows since window management hasn't been announced yet!";
0159         return;
0160     }
0161 
0162     for (auto *w : m_windowManagement->windows()) {
0163         if (!w->isMinimized()) {
0164             w->requestToggleMinimized();
0165         }
0166     }
0167 }
0168 
0169 void WindowUtil::unsetAllMinimizedGeometries(QQuickItem *parent)
0170 {
0171     if (!m_windowManagement) {
0172         qWarning() << "Ignoring request for minimizing all windows since window management hasn't been announced yet!";
0173         return;
0174     }
0175 
0176     if (!parent) {
0177         return;
0178     }
0179 
0180     QWindow *window = parent->window();
0181     if (!window) {
0182         return;
0183     }
0184 
0185     KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window);
0186     if (!surface) {
0187         return;
0188     }
0189 
0190     for (auto *w : m_windowManagement->windows()) {
0191         w->unsetMinimizedGeometry(surface);
0192     }
0193 }
0194 
0195 void WindowUtil::updateShowingDesktop(bool showing)
0196 {
0197     if (showing != m_showingDesktop) {
0198         m_showingDesktop = showing;
0199         Q_EMIT showingDesktopChanged(m_showingDesktop);
0200     }
0201 }
0202 
0203 void WindowUtil::updateActiveWindowIsShell()
0204 {
0205     if (m_activeWindow) {
0206         if (m_activeWindow->appId() == QStringLiteral("org.kde.plasmashell") && !m_activeWindowIsShell) {
0207             m_activeWindowIsShell = true;
0208             Q_EMIT activeWindowIsShellChanged();
0209         } else if (m_activeWindow->appId() != QStringLiteral("org.kde.plasmashell") && m_activeWindowIsShell) {
0210             m_activeWindowIsShell = false;
0211             Q_EMIT activeWindowIsShellChanged();
0212         }
0213     }
0214 }
0215 
0216 void WindowUtil::forgetActiveWindow()
0217 {
0218     using namespace KWayland::Client;
0219     if (m_activeWindow) {
0220         disconnect(m_activeWindow.data(), &PlasmaWindow::closeableChanged, this, &WindowUtil::hasCloseableActiveWindowChanged);
0221         disconnect(m_activeWindow.data(), &PlasmaWindow::unmapped, this, &WindowUtil::forgetActiveWindow);
0222     }
0223     m_activeWindow.clear();
0224     Q_EMIT hasCloseableActiveWindowChanged();
0225 }
0226 
0227 QList<KWayland::Client::PlasmaWindow *> WindowUtil::windowsFromStorageId(const QString &storageId) const
0228 {
0229     if (!m_windows.contains(storageId)) {
0230         return {};
0231     }
0232     return m_windows[storageId];
0233 }
0234 
0235 void WindowUtil::windowCreatedSlot(KWayland::Client::PlasmaWindow *window)
0236 {
0237     QString storageId = window->appId() + QStringLiteral(".desktop");
0238 
0239     // ignore empty windows
0240     if (storageId == ".desktop" || storageId == "org.kde.plasmashell.desktop") {
0241         return;
0242     }
0243 
0244     if (!m_windows.contains(storageId)) {
0245         m_windows[storageId] = {};
0246     }
0247     m_windows[storageId].push_back(window);
0248 
0249     // listen for window close
0250     connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [this, storageId]() {
0251         m_windows.remove(storageId);
0252         Q_EMIT windowChanged(storageId);
0253     });
0254 
0255     Q_EMIT windowChanged(storageId);
0256 }