File indexing completed on 2024-11-10 04:58:06
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "xdgactivationv1.h" 0011 #include "effect/effecthandler.h" 0012 #include "utils/common.h" 0013 #include "wayland/clientconnection.h" 0014 #include "wayland/display.h" 0015 #include "wayland/plasmawindowmanagement.h" 0016 #include "wayland/surface.h" 0017 #include "wayland/xdgactivation_v1.h" 0018 #include "wayland_server.h" 0019 #include "window.h" 0020 #include "workspace.h" 0021 #include <KApplicationTrader> 0022 #include <KDesktopFile> 0023 0024 namespace KWin 0025 { 0026 0027 static bool isPrivilegedInWindowManagement(const ClientConnection *client) 0028 { 0029 Q_ASSERT(client); 0030 auto requestedInterfaces = client->property("requestedInterfaces").toStringList(); 0031 return requestedInterfaces.contains(QLatin1String("org_kde_plasma_window_management")) || requestedInterfaces.contains(QLatin1String("kde_lockscreen_overlay_v1")); 0032 } 0033 0034 static const QString windowDesktopFileName(Window *window) 0035 { 0036 QString ret = window->desktopFileName(); 0037 if (!ret.isEmpty()) { 0038 return ret; 0039 } 0040 0041 // Fallback to StartupWMClass for legacy apps 0042 const auto resourceName = window->resourceName(); 0043 const auto service = KApplicationTrader::query([&resourceName](const KService::Ptr &service) { 0044 return service->property<QString>("StartupWMClass").compare(resourceName, Qt::CaseInsensitive) == 0; 0045 }); 0046 0047 if (!service.isEmpty()) { 0048 ret = service.constFirst()->desktopEntryName(); 0049 } 0050 return ret; 0051 } 0052 0053 XdgActivationV1Integration::XdgActivationV1Integration(XdgActivationV1Interface *activation, QObject *parent) 0054 : QObject(parent) 0055 { 0056 Workspace *ws = Workspace::self(); 0057 connect(ws, &Workspace::windowActivated, this, [this](Window *window) { 0058 if (!m_currentActivationToken || !window || window->property("token").toString() == m_currentActivationToken->token) { 0059 return; 0060 } 0061 0062 // We check that it's not the app that we are trying to activate 0063 if (windowDesktopFileName(window) != m_currentActivationToken->applicationId) { 0064 // But also that the new one has been requested after the token was requested 0065 if (window->lastUsageSerial() < m_currentActivationToken->serial) { 0066 return; 0067 } 0068 } 0069 0070 clear(); 0071 }); 0072 activation->setActivationTokenCreator([this](ClientConnection *client, SurfaceInterface *surface, uint serial, SeatInterface *seat, const QString &appId) -> QString { 0073 Workspace *ws = Workspace::self(); 0074 Q_ASSERT(client); // Should always be available as it's coming straight from the wayland implementation 0075 const bool isPrivileged = isPrivilegedInWindowManagement(client); 0076 if (!isPrivileged && ws->activeWindow() && ws->activeWindow()->surface() != surface) { 0077 qCWarning(KWIN_CORE) << "Cannot grant a token to" << client; 0078 return QStringLiteral("not-granted-666"); 0079 } 0080 0081 return requestToken(isPrivileged, surface, serial, seat, appId); 0082 }); 0083 0084 connect(activation, &XdgActivationV1Interface::activateRequested, this, &XdgActivationV1Integration::activateSurface); 0085 } 0086 0087 QString XdgActivationV1Integration::requestToken(bool isPrivileged, SurfaceInterface *surface, uint serial, SeatInterface *seat, const QString &appId) 0088 { 0089 static int i = 0; 0090 const auto newToken = QStringLiteral("kwin-%1").arg(++i); 0091 0092 if (m_currentActivationToken) { 0093 clear(); 0094 } 0095 bool showNotify = false; 0096 QIcon icon = QIcon::fromTheme(QStringLiteral("system-run")); 0097 if (const QString desktopFilePath = Window::findDesktopFile(appId); !desktopFilePath.isEmpty()) { 0098 KDesktopFile df(desktopFilePath); 0099 Window *window = Workspace::self()->activeWindow(); 0100 if (!window || appId != window->desktopFileName()) { 0101 const auto desktop = df.desktopGroup(); 0102 showNotify = desktop.readEntry("X-KDE-StartupNotify", desktop.readEntry("StartupNotify", true)); 0103 } 0104 icon = QIcon::fromTheme(df.readIcon(), icon); 0105 } 0106 std::unique_ptr<PlasmaWindowActivationInterface> activation; 0107 if (showNotify) { 0108 activation = waylandServer()->plasmaActivationFeedback()->createActivation(appId); 0109 } 0110 m_currentActivationToken = std::make_unique<ActivationToken>(ActivationToken{newToken, isPrivileged, surface, serial, seat, appId, showNotify, std::move(activation)}); 0111 if (showNotify) { 0112 Q_EMIT effects->startupAdded(m_currentActivationToken->token, icon); 0113 } 0114 return newToken; 0115 } 0116 0117 void XdgActivationV1Integration::activateSurface(SurfaceInterface *surface, const QString &token) 0118 { 0119 Workspace *ws = Workspace::self(); 0120 auto window = waylandServer()->findWindow(surface); 0121 if (!window) { 0122 qCWarning(KWIN_CORE) << "could not find the toplevel to activate" << surface; 0123 return; 0124 } 0125 0126 if (!m_currentActivationToken || m_currentActivationToken->token != token) { 0127 qCDebug(KWIN_CORE) << "Refusing to activate " << window << " (provided token: " << token << ", current token:" << (m_currentActivationToken ? m_currentActivationToken->token : QStringLiteral("null")) << ")"; 0128 window->demandAttention(); 0129 return; 0130 } 0131 0132 auto ownerWindow = waylandServer()->findWindow(m_currentActivationToken->surface); 0133 qCDebug(KWIN_CORE) << "activating" << window << surface << "on behalf of" << m_currentActivationToken->surface << "into" << ownerWindow; 0134 if (!ws->activeWindow() || ws->activeWindow() == ownerWindow || ws->activeWindow()->lastUsageSerial() < m_currentActivationToken->serial || m_currentActivationToken->isPrivileged) { 0135 ws->activateWindow(window); 0136 } else { 0137 qCWarning(KWIN_CORE) << "Activation requested while owner isn't active" << (ownerWindow ? ownerWindow->desktopFileName() : "null") 0138 << m_currentActivationToken->applicationId; 0139 window->demandAttention(); 0140 clear(); 0141 } 0142 } 0143 0144 void XdgActivationV1Integration::clear() 0145 { 0146 Q_ASSERT(m_currentActivationToken); 0147 if (m_currentActivationToken->showNotify) { 0148 Q_EMIT effects->startupRemoved(m_currentActivationToken->token); 0149 } 0150 m_currentActivationToken.reset(); 0151 } 0152 0153 } 0154 0155 #include "moc_xdgactivationv1.cpp"