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 }