File indexing completed on 2024-11-10 04:57:02
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2009 Martin Gräßlin <kde@martin-graesslin.com> 0006 SPDX-FileCopyrightText: 2020 Benjamin Port <benjamin.port@enioka.com> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "windowsrunnerinterface.h" 0012 0013 #include "virtualdesktops.h" 0014 #include "window.h" 0015 #include "workspace.h" 0016 0017 #include "krunner1adaptor.h" 0018 #include <KLocalizedString> 0019 0020 namespace KWin 0021 { 0022 0023 WindowsRunner::WindowsRunner() 0024 { 0025 new Krunner1Adaptor(this); 0026 qDBusRegisterMetaType<RemoteMatch>(); 0027 qDBusRegisterMetaType<RemoteMatches>(); 0028 qDBusRegisterMetaType<RemoteAction>(); 0029 qDBusRegisterMetaType<RemoteActions>(); 0030 qDBusRegisterMetaType<RemoteImage>(); 0031 QDBusConnection::sessionBus().registerObject(QStringLiteral("/WindowsRunner"), this); 0032 QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.KWin")); 0033 } 0034 0035 RemoteMatches WindowsRunner::Match(const QString &searchTerm) 0036 { 0037 RemoteMatches matches; 0038 0039 QString term = searchTerm; 0040 WindowsRunnerAction action = ActivateAction; 0041 if (QString keyword = i18nc("Note this is a KRunner keyword", "activate"); term.endsWith(keyword, Qt::CaseInsensitive)) { 0042 action = ActivateAction; 0043 term = term.left(term.lastIndexOf(keyword) - 1); 0044 } else if (QString keyword = i18nc("Note this is a KRunner keyword", "close"); term.endsWith(keyword, Qt::CaseInsensitive)) { 0045 action = CloseAction; 0046 term = term.left(term.lastIndexOf(keyword) - 1); 0047 } else if (QString keyword = i18nc("Note this is a KRunner keyword", "min"); term.endsWith(keyword, Qt::CaseInsensitive)) { 0048 action = MinimizeAction; 0049 term = term.left(term.lastIndexOf(keyword) - 1); 0050 } else if (QString keyword = i18nc("Note this is a KRunner keyword", "minimize"); term.endsWith(keyword, Qt::CaseInsensitive)) { 0051 action = MinimizeAction; 0052 term = term.left(term.lastIndexOf(keyword) - 1); 0053 } else if (QString keyword = i18nc("Note this is a KRunner keyword", "max"); term.endsWith(keyword, Qt::CaseInsensitive)) { 0054 action = MaximizeAction; 0055 term = term.left(term.lastIndexOf(keyword) - 1); 0056 } else if (term.endsWith(i18nc("Note this is a KRunner keyword", "maximize"), Qt::CaseInsensitive)) { 0057 action = MaximizeAction; 0058 term = term.left(term.lastIndexOf(i18nc("Note this is a KRunner keyword", "maximize")) - 1); 0059 } else if (QString keyword = i18nc("Note this is a KRunner keyword", "fullscreen"); term.endsWith(keyword, Qt::CaseInsensitive)) { 0060 action = FullscreenAction; 0061 term = term.left(term.lastIndexOf(keyword) - 1); 0062 } else if (QString keyword = i18nc("Note this is a KRunner keyword", "shade"); term.endsWith(keyword, Qt::CaseInsensitive)) { 0063 action = ShadeAction; 0064 term = term.left(term.lastIndexOf(keyword) - 1); 0065 } else if (QString keyword = i18nc("Note this is a KRunner keyword", "keep above"); term.endsWith(keyword, Qt::CaseInsensitive)) { 0066 action = KeepAboveAction; 0067 term = term.left(term.lastIndexOf(keyword) - 1); 0068 } else if (QString keyword = i18nc("Note this is a KRunner keyword", "keep below"); term.endsWith(keyword, Qt::CaseInsensitive)) { 0069 action = KeepBelowAction; 0070 term = term.left(term.lastIndexOf(keyword) - 1); 0071 } 0072 0073 // keyword match: when term starts with "window" we list all windows 0074 // the list can be restricted to windows matching a given name, class, role or desktop 0075 if (term.startsWith(i18nc("Note this is a KRunner keyword", "window"), Qt::CaseInsensitive)) { 0076 const QStringList keywords = term.split(QLatin1Char(' ')); 0077 QString windowName; 0078 QString windowAppName; 0079 VirtualDesktop *targetDesktop = nullptr; 0080 QVariant desktopId; 0081 for (const QString &keyword : keywords) { 0082 if (keyword.endsWith(QLatin1Char('='))) { 0083 continue; 0084 } 0085 if (keyword.startsWith(i18nc("Note this is a KRunner keyword", "name") + QLatin1Char('='), Qt::CaseInsensitive)) { 0086 windowName = keyword.split(QLatin1Char('='))[1]; 0087 } else if (keyword.startsWith(i18nc("Note this is a KRunner keyword", "appname") + QLatin1Char('='), Qt::CaseInsensitive)) { 0088 windowAppName = keyword.split(QLatin1Char('='))[1]; 0089 } else if (keyword.startsWith(i18nc("Note this is a KRunner keyword", "desktop") + QLatin1Char('='), Qt::CaseInsensitive)) { 0090 desktopId = keyword.split(QLatin1Char('='))[1]; 0091 for (const auto desktop : VirtualDesktopManager::self()->desktops()) { 0092 if (desktop->name().contains(desktopId.toString(), Qt::CaseInsensitive) || desktop->x11DesktopNumber() == desktopId.toUInt()) { 0093 targetDesktop = desktop; 0094 } 0095 } 0096 } else { 0097 // not a keyword - use as name if name is unused, but another option is set 0098 if (windowName.isEmpty() && !keyword.contains(QLatin1Char('=')) && (!windowAppName.isEmpty() || targetDesktop)) { 0099 windowName = keyword; 0100 } 0101 } 0102 } 0103 0104 for (const Window *window : Workspace::self()->windows()) { 0105 if (window->isUnmanaged()) { 0106 continue; 0107 } 0108 if (!window->isNormalWindow()) { 0109 continue; 0110 } 0111 const QString appName = window->resourceClass(); 0112 const QString name = window->caption(); 0113 if (!windowName.isEmpty() && !name.startsWith(windowName, Qt::CaseInsensitive)) { 0114 continue; 0115 } 0116 if (!windowAppName.isEmpty() && !appName.contains(windowAppName, Qt::CaseInsensitive)) { 0117 continue; 0118 } 0119 0120 if (targetDesktop && !window->desktops().contains(targetDesktop) && !window->isOnAllDesktops()) { 0121 continue; 0122 } 0123 // check for windows when no keywords were used 0124 // check the name and app name for containing the query without the keyword 0125 if (windowName.isEmpty() && windowAppName.isEmpty() && !targetDesktop) { 0126 const QString &test = term.mid(keywords[0].length() + 1); 0127 if (!name.contains(test, Qt::CaseInsensitive) && !appName.contains(test, Qt::CaseInsensitive)) { 0128 continue; 0129 } 0130 } 0131 // blacklisted everything else: we have a match 0132 if (actionSupported(window, action)) { 0133 matches << windowsMatch(window, action); 0134 } 0135 } 0136 0137 if (!matches.isEmpty()) { 0138 // the window keyword found matches - do not process other syntax possibilities 0139 return matches; 0140 } 0141 } 0142 0143 bool desktopAdded = false; 0144 // check for desktop keyword 0145 if (term.startsWith(i18nc("Note this is a KRunner keyword", "desktop"), Qt::CaseInsensitive)) { 0146 const QStringList parts = term.split(QLatin1Char(' ')); 0147 if (parts.size() == 1) { 0148 // only keyword - list all desktops 0149 for (auto desktop : VirtualDesktopManager::self()->desktops()) { 0150 matches << desktopMatch(desktop); 0151 desktopAdded = true; 0152 } 0153 } 0154 } 0155 0156 // check for matching desktops by name 0157 for (const Window *window : Workspace::self()->windows()) { 0158 if (window->isUnmanaged()) { 0159 continue; 0160 } 0161 if (!window->isNormalWindow()) { 0162 continue; 0163 } 0164 const QString appName = window->resourceClass(); 0165 const QString name = window->caption(); 0166 if (name.startsWith(term, Qt::CaseInsensitive) || appName.startsWith(term, Qt::CaseInsensitive)) { 0167 matches << windowsMatch(window, action, 0.8, HighestCategoryRelevance); 0168 } else if ((name.contains(term, Qt::CaseInsensitive) || appName.contains(term, Qt::CaseInsensitive)) && actionSupported(window, action)) { 0169 matches << windowsMatch(window, action, 0.7, LowCategoryRelevance); 0170 } 0171 } 0172 0173 for (auto *desktop : VirtualDesktopManager::self()->desktops()) { 0174 if (desktop->name().contains(term, Qt::CaseInsensitive)) { 0175 if (!desktopAdded && desktop != VirtualDesktopManager::self()->currentDesktop()) { 0176 matches << desktopMatch(desktop, ActivateDesktopAction, 0.8); 0177 } 0178 // search for windows on desktop and list them with less relevance 0179 for (const Window *window : Workspace::self()->windows()) { 0180 if (window->isUnmanaged()) { 0181 continue; 0182 } 0183 if (!window->isNormalWindow()) { 0184 continue; 0185 } 0186 if ((window->desktops().contains(desktop) || window->isOnAllDesktops()) && actionSupported(window, action)) { 0187 matches << windowsMatch(window, action, 0.5, LowCategoryRelevance); 0188 } 0189 } 0190 } 0191 } 0192 0193 return matches; 0194 } 0195 0196 void WindowsRunner::Run(const QString &id, const QString &actionId) 0197 { 0198 // Split id to get actionId and realId. We don't use actionId because our actions list is not constant 0199 const QStringList parts = id.split(QLatin1Char('_')); 0200 auto action = WindowsRunnerAction(parts[0].toInt()); 0201 auto objectId = parts[1]; 0202 0203 if (action == ActivateDesktopAction) { 0204 QByteArray desktopId = objectId.toLocal8Bit(); 0205 auto desktop = VirtualDesktopManager::self()->desktopForId(desktopId); 0206 VirtualDesktopManager::self()->setCurrent(desktop); 0207 return; 0208 } 0209 0210 const auto window = workspace()->findWindow(QUuid::fromString(objectId)); 0211 if (!window || !window->isClient()) { 0212 return; 0213 } 0214 0215 switch (action) { 0216 case ActivateAction: 0217 workspace()->activateWindow(window); 0218 break; 0219 case CloseAction: 0220 window->closeWindow(); 0221 break; 0222 case MinimizeAction: 0223 window->setMinimized(!window->isMinimized()); 0224 break; 0225 case MaximizeAction: 0226 window->setMaximize(window->maximizeMode() == MaximizeRestore, window->maximizeMode() == MaximizeRestore); 0227 break; 0228 case FullscreenAction: 0229 window->setFullScreen(!window->isFullScreen()); 0230 break; 0231 case ShadeAction: 0232 window->toggleShade(); 0233 break; 0234 case KeepAboveAction: 0235 window->setKeepAbove(!window->keepAbove()); 0236 break; 0237 case KeepBelowAction: 0238 window->setKeepBelow(!window->keepBelow()); 0239 break; 0240 case ActivateDesktopAction: 0241 Q_UNREACHABLE(); 0242 } 0243 } 0244 0245 RemoteMatch WindowsRunner::desktopMatch(const VirtualDesktop *desktop, const WindowsRunnerAction action, qreal relevance) const 0246 { 0247 RemoteMatch match; 0248 match.id = QString::number(action) + QLatin1Char('_') + desktop->id(); 0249 match.categoryRelevance = HighestCategoryRelevance; 0250 match.iconName = QStringLiteral("user-desktop"); 0251 match.text = desktop->name(); 0252 match.relevance = relevance; 0253 match.properties.insert(QStringLiteral("subtext"), i18n("Switch to desktop %1", desktop->name())); 0254 return match; 0255 } 0256 0257 RemoteMatch WindowsRunner::windowsMatch(const Window *window, const WindowsRunnerAction action, qreal relevance, qreal categoryRelevance) const 0258 { 0259 RemoteMatch match; 0260 match.id = QString::number((int)action) + QLatin1Char('_') + window->internalId().toString(); 0261 match.text = window->caption(); 0262 match.iconName = window->icon().name(); 0263 match.relevance = relevance; 0264 match.categoryRelevance = categoryRelevance; 0265 0266 const QList<VirtualDesktop *> desktops = window->desktops(); 0267 bool allDesktops = window->isOnAllDesktops(); 0268 0269 const VirtualDesktop *targetDesktop = VirtualDesktopManager::self()->currentDesktop(); 0270 // Show on current desktop unless window is only attached to other desktop, in this case show on the first attached desktop 0271 if (!allDesktops && !window->isOnCurrentDesktop() && !desktops.isEmpty()) { 0272 targetDesktop = desktops.first(); 0273 } 0274 0275 // When there is no icon name, send a pixmap along instead 0276 if (match.iconName.isEmpty()) { 0277 QImage convertedImage = window->icon().pixmap(QSize(64, 64)).toImage().convertToFormat(QImage::Format_RGBA8888); 0278 RemoteImage remoteImage{ 0279 convertedImage.width(), 0280 convertedImage.height(), 0281 static_cast<int>(convertedImage.bytesPerLine()), 0282 true, // hasAlpha 0283 8, // bitsPerSample 0284 4, // channels 0285 QByteArray(reinterpret_cast<const char *>(convertedImage.constBits()), convertedImage.sizeInBytes())}; 0286 match.properties.insert(QStringLiteral("icon-data"), QVariant::fromValue(remoteImage)); 0287 } 0288 0289 const QString desktopName = targetDesktop->name(); 0290 switch (action) { 0291 case CloseAction: 0292 match.properties[QStringLiteral("subtext")] = i18n("Close running window on %1", desktopName); 0293 break; 0294 case MinimizeAction: 0295 match.properties[QStringLiteral("subtext")] = i18n("(Un)minimize running window on %1", desktopName); 0296 break; 0297 case MaximizeAction: 0298 match.properties[QStringLiteral("subtext")] = i18n("Maximize/restore running window on %1", desktopName); 0299 break; 0300 case FullscreenAction: 0301 match.properties[QStringLiteral("subtext")] = i18n("Toggle fullscreen for running window on %1", desktopName); 0302 break; 0303 case ShadeAction: 0304 match.properties[QStringLiteral("subtext")] = i18n("(Un)shade running window on %1", desktopName); 0305 break; 0306 case KeepAboveAction: 0307 match.properties[QStringLiteral("subtext")] = i18n("Toggle keep above for running window on %1", desktopName); 0308 break; 0309 case KeepBelowAction: 0310 match.properties[QStringLiteral("subtext")] = i18n("Toggle keep below running window on %1", desktopName); 0311 break; 0312 case ActivateAction: 0313 default: 0314 match.properties[QStringLiteral("subtext")] = i18n("Activate running window on %1", desktopName); 0315 break; 0316 } 0317 return match; 0318 } 0319 0320 bool WindowsRunner::actionSupported(const Window *window, const WindowsRunnerAction action) const 0321 { 0322 switch (action) { 0323 case CloseAction: 0324 return window->isCloseable(); 0325 case MinimizeAction: 0326 return window->isMinimizable(); 0327 case MaximizeAction: 0328 return window->isMaximizable(); 0329 case ShadeAction: 0330 return window->isShadeable(); 0331 case FullscreenAction: 0332 return window->isFullScreenable(); 0333 case KeepAboveAction: 0334 case KeepBelowAction: 0335 case ActivateAction: 0336 default: 0337 return true; 0338 } 0339 } 0340 0341 } 0342 0343 #include "moc_windowsrunnerinterface.cpp"