File indexing completed on 2024-05-12 05:36:11

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 <KApplicationTrader>
0011 
0012 #include <QGuiApplication>
0013 
0014 constexpr int ACTIVE_WINDOW_UPDATE_INVERVAL = 0;
0015 
0016 WindowUtil::WindowUtil(QObject *parent)
0017     : QObject{parent}
0018     , m_activeWindowTimer{new QTimer{this}}
0019 {
0020     // use 0 tick timer to update active window to ensure window state has finished changing
0021     m_activeWindowTimer->setSingleShot(true);
0022     m_activeWindowTimer->setInterval(ACTIVE_WINDOW_UPDATE_INVERVAL);
0023     connect(m_activeWindowTimer, &QTimer::timeout, this, &WindowUtil::updateActiveWindow);
0024 
0025     connect(this, &WindowUtil::activeWindowChanged, this, &WindowUtil::updateActiveWindowIsShell);
0026 
0027     initWayland();
0028 }
0029 
0030 bool WindowUtil::isShowingDesktop() const
0031 {
0032     return m_showingDesktop;
0033 }
0034 
0035 bool WindowUtil::activeWindowIsShell() const
0036 {
0037     return m_activeWindowIsShell;
0038 }
0039 
0040 void WindowUtil::initWayland()
0041 {
0042     if (!QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) {
0043         qWarning() << "Plasma Mobile must use wayland! The current platform detected is:" << QGuiApplication::platformName();
0044         return;
0045     }
0046 
0047     using namespace KWayland::Client;
0048     ConnectionThread *connection = ConnectionThread::fromApplication(this);
0049 
0050     if (!connection) {
0051         return;
0052     }
0053 
0054     auto *registry = new Registry(this);
0055     registry->create(connection);
0056 
0057     connect(registry, &Registry::plasmaWindowManagementAnnounced, this, [this, registry](quint32 name, quint32 version) {
0058         m_windowManagement = registry->createPlasmaWindowManagement(name, version, this);
0059         qRegisterMetaType<QVector<int>>("QVector<int>");
0060 
0061         connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, this, [this](KWayland::Client::PlasmaWindow *window) {
0062             Q_EMIT windowCreated(window);
0063         });
0064         connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, this, &WindowUtil::windowCreatedSlot);
0065 
0066         connect(m_windowManagement, &PlasmaWindowManagement::showingDesktopChanged, this, &WindowUtil::updateShowingDesktop);
0067         connect(m_windowManagement, &PlasmaWindowManagement::activeWindowChanged, m_activeWindowTimer, qOverload<>(&QTimer::start));
0068 
0069         m_activeWindowTimer->start();
0070     });
0071 
0072     connect(registry, &Registry::plasmaActivationFeedbackAnnounced, this, [this, registry](quint32 name, quint32 version) {
0073         auto iface = registry->createPlasmaActivationFeedback(name, version, this);
0074 
0075         connect(iface, &PlasmaActivationFeedback::activation, this, [this](PlasmaActivation *activation) {
0076             connect(activation, &PlasmaActivation::applicationId, this, [this](const QString &appId) {
0077                 // do not show activation screen for the plasmashell process
0078                 if (appId == "org.kde.plasmashell") {
0079                     return;
0080                 }
0081 
0082                 const auto servicesFound = KApplicationTrader::query([&appId](const KService::Ptr &service) {
0083                     if (service->exec().isEmpty())
0084                         return false;
0085 
0086                     if (service->desktopEntryName().compare(appId, Qt::CaseInsensitive) == 0)
0087                         return true;
0088 
0089                     const auto idWithoutDesktop = QString(appId).remove(QStringLiteral(".desktop"));
0090                     if (service->desktopEntryName().compare(idWithoutDesktop, Qt::CaseInsensitive) == 0)
0091                         return true;
0092 
0093                     const auto renamedFrom = service->property<QStringList>(QStringLiteral("X-Flatpak-RenamedFrom"));
0094                     if (renamedFrom.contains(appId, Qt::CaseInsensitive) || renamedFrom.contains(idWithoutDesktop, Qt::CaseInsensitive))
0095                         return true;
0096 
0097                     return false;
0098                 });
0099 
0100                 if (!servicesFound.isEmpty()) {
0101                     Q_EMIT appActivationStarted(appId, servicesFound.constFirst()->icon());
0102                 } else {
0103                     qDebug() << "WindowUtil: Could not find service" << appId;
0104                 }
0105             });
0106 
0107             connect(activation, &PlasmaActivation::finished, this, &WindowUtil::appActivationFinished);
0108         });
0109     });
0110 
0111     registry->setup();
0112     connection->roundtrip();
0113 }
0114 
0115 void WindowUtil::updateActiveWindow()
0116 {
0117     if (!m_windowManagement || m_activeWindow == m_windowManagement->activeWindow()) {
0118         return;
0119     }
0120 
0121     using namespace KWayland::Client;
0122     if (m_activeWindow) {
0123         disconnect(m_activeWindow.data(), &PlasmaWindow::closeableChanged, this, &WindowUtil::hasCloseableActiveWindowChanged);
0124         disconnect(m_activeWindow.data(), &PlasmaWindow::unmapped, this, &WindowUtil::forgetActiveWindow);
0125     }
0126 
0127     m_activeWindow = m_windowManagement->activeWindow();
0128     Q_EMIT activeWindowChanged();
0129 
0130     if (m_activeWindow) {
0131         connect(m_activeWindow.data(), &PlasmaWindow::closeableChanged, this, &WindowUtil::hasCloseableActiveWindowChanged);
0132         connect(m_activeWindow.data(), &PlasmaWindow::unmapped, this, &WindowUtil::forgetActiveWindow);
0133     }
0134 
0135     Q_EMIT hasCloseableActiveWindowChanged();
0136 }
0137 
0138 bool WindowUtil::hasCloseableActiveWindow() const
0139 {
0140     return m_activeWindow && m_activeWindow->isCloseable() /*&& !m_activeWindow->isMinimized()*/;
0141 }
0142 
0143 bool WindowUtil::activateWindowByStorageId(const QString &storageId)
0144 {
0145     auto windows = windowsFromStorageId(storageId);
0146 
0147     if (!windows.empty()) {
0148         windows[0]->requestActivate();
0149         return true;
0150     }
0151 
0152     return false;
0153 }
0154 
0155 void WindowUtil::closeActiveWindow()
0156 {
0157     if (m_activeWindow) {
0158         m_activeWindow->requestClose();
0159     }
0160 }
0161 
0162 void WindowUtil::requestShowingDesktop(bool showingDesktop)
0163 {
0164     if (!m_windowManagement) {
0165         return;
0166     }
0167     m_windowManagement->setShowingDesktop(showingDesktop);
0168 }
0169 
0170 void WindowUtil::minimizeAll()
0171 {
0172     if (!m_windowManagement) {
0173         qWarning() << "Ignoring request for minimizing all windows since window management hasn't been announced yet!";
0174         return;
0175     }
0176 
0177     for (auto *w : m_windowManagement->windows()) {
0178         if (!w->isMinimized()) {
0179             w->requestToggleMinimized();
0180         }
0181     }
0182 }
0183 
0184 void WindowUtil::unsetAllMinimizedGeometries(QQuickItem *parent)
0185 {
0186     if (!m_windowManagement) {
0187         qWarning() << "Ignoring request for minimizing all windows since window management hasn't been announced yet!";
0188         return;
0189     }
0190 
0191     if (!parent) {
0192         return;
0193     }
0194 
0195     QWindow *window = parent->window();
0196     if (!window) {
0197         return;
0198     }
0199 
0200     KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window);
0201     if (!surface) {
0202         return;
0203     }
0204 
0205     for (auto *w : m_windowManagement->windows()) {
0206         w->unsetMinimizedGeometry(surface);
0207     }
0208 }
0209 
0210 void WindowUtil::updateShowingDesktop(bool showing)
0211 {
0212     if (showing != m_showingDesktop) {
0213         m_showingDesktop = showing;
0214         Q_EMIT showingDesktopChanged(m_showingDesktop);
0215     }
0216 }
0217 
0218 void WindowUtil::updateActiveWindowIsShell()
0219 {
0220     auto activeWindow = m_windowManagement->activeWindow();
0221     if (activeWindow) {
0222         if (activeWindow->appId() == QStringLiteral("org.kde.plasmashell") && !m_activeWindowIsShell) {
0223             m_activeWindowIsShell = true;
0224             Q_EMIT activeWindowIsShellChanged();
0225         } else if (activeWindow->appId() != QStringLiteral("org.kde.plasmashell") && m_activeWindowIsShell) {
0226             m_activeWindowIsShell = false;
0227             Q_EMIT activeWindowIsShellChanged();
0228         }
0229     }
0230 }
0231 
0232 void WindowUtil::forgetActiveWindow()
0233 {
0234     using namespace KWayland::Client;
0235     if (m_activeWindow) {
0236         disconnect(m_activeWindow.data(), &PlasmaWindow::closeableChanged, this, &WindowUtil::hasCloseableActiveWindowChanged);
0237         disconnect(m_activeWindow.data(), &PlasmaWindow::unmapped, this, &WindowUtil::forgetActiveWindow);
0238     }
0239     m_activeWindow.clear();
0240     Q_EMIT hasCloseableActiveWindowChanged();
0241 }
0242 
0243 QList<KWayland::Client::PlasmaWindow *> WindowUtil::windowsFromStorageId(const QString &storageId) const
0244 {
0245     if (!m_windows.contains(storageId)) {
0246         return {};
0247     }
0248     return m_windows[storageId];
0249 }
0250 
0251 void WindowUtil::windowCreatedSlot(KWayland::Client::PlasmaWindow *window)
0252 {
0253     QString storageId = window->appId() + QStringLiteral(".desktop");
0254 
0255     // ignore empty windows
0256     if (storageId == ".desktop" || storageId == "org.kde.plasmashell.desktop") {
0257         return;
0258     }
0259 
0260     if (!m_windows.contains(storageId)) {
0261         m_windows[storageId] = {};
0262     }
0263     m_windows[storageId].push_back(window);
0264 
0265     // listen for window close
0266     connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [this, storageId]() {
0267         m_windows.remove(storageId);
0268         Q_EMIT windowChanged(storageId);
0269     });
0270 
0271     Q_EMIT windowChanged(storageId);
0272 }