kwin/plugins/krunner-integration/windowsrunnerinterface.cpp
Vlad Zahorodnii 8f6edea521 Turn krunner integration into a plugin
krunner stuff doesn't really belong in kwin, it has nothing to do with
compositing or any other things that are the domain of compositors.
Given that, being as a plugin suits the krunner integration stuff best.
2020-11-25 09:34:25 +00:00

339 lines
14 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2009 Martin Gräßlin <kde@martin-graesslin.com>
SPDX-FileCopyrightText: 2020 Benjamin Port <benjamin.port@enioka.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "windowsrunnerinterface.h"
#include "abstract_client.h"
#include "workspace.h"
#include "krunner1adaptor.h"
#include <KLocalizedString>
namespace KWin
{
WindowsRunner::WindowsRunner(QObject *parent)
: Plugin(parent)
{
if (workspace()) {
initialize();
} else {
connect(kwinApp(), &Application::workspaceCreated, this, &WindowsRunner::initialize);
}
}
WindowsRunner::~WindowsRunner()
{
}
void WindowsRunner::initialize()
{
new Krunner1Adaptor(this);
qDBusRegisterMetaType<RemoteMatch>();
qDBusRegisterMetaType<RemoteMatches>();
qDBusRegisterMetaType<RemoteAction>();
qDBusRegisterMetaType<RemoteActions>();
QDBusConnection::sessionBus().registerObject(QStringLiteral("/WindowsRunner"), this);
QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.KWin"));
}
RemoteActions WindowsRunner::Actions()
{
RemoteActions actions;
return actions;
}
RemoteMatches WindowsRunner::Match(const QString &searchTerm)
{
RemoteMatches matches;
auto term = searchTerm;
WindowsRunnerAction action = ActivateAction;
if (term.endsWith(i18nc("Note this is a KRunner keyword", "activate") , Qt::CaseInsensitive)) {
action = ActivateAction;
term = term.left(term.lastIndexOf(i18nc("Note this is a KRunner keyword", "activate")) - 1);
} else if (term.endsWith(i18nc("Note this is a KRunner keyword", "close") , Qt::CaseInsensitive)) {
action = CloseAction;
term = term.left(term.lastIndexOf(i18nc("Note this is a KRunner keyword", "close")) - 1);
} else if (term.endsWith(i18nc("Note this is a KRunner keyword", "min") , Qt::CaseInsensitive)) {
action = MinimizeAction;
term = term.left(term.lastIndexOf(i18nc("Note this is a KRunner keyword", "min")) - 1);
} else if (term.endsWith(i18nc("Note this is a KRunner keyword", "minimize") , Qt::CaseInsensitive)) {
action = MinimizeAction;
term = term.left(term.lastIndexOf(i18nc("Note this is a KRunner keyword", "minimize")) - 1);
} else if (term.endsWith(i18nc("Note this is a KRunner keyword", "max") , Qt::CaseInsensitive)) {
action = MaximizeAction;
term = term.left(term.lastIndexOf(i18nc("Note this is a KRunner keyword", "max")) - 1);
} else if (term.endsWith(i18nc("Note this is a KRunner keyword", "maximize") , Qt::CaseInsensitive)) {
action = MaximizeAction;
term = term.left(term.lastIndexOf(i18nc("Note this is a KRunner keyword", "maximize")) - 1);
} else if (term.endsWith(i18nc("Note this is a KRunner keyword", "fullscreen") , Qt::CaseInsensitive)) {
action = FullscreenAction;
term = term.left(term.lastIndexOf(i18nc("Note this is a KRunner keyword", "fullscreen")) - 1);
} else if (term.endsWith(i18nc("Note this is a KRunner keyword", "shade") , Qt::CaseInsensitive)) {
action = ShadeAction;
term = term.left(term.lastIndexOf(i18nc("Note this is a KRunner keyword", "shade")) - 1);
} else if (term.endsWith(i18nc("Note this is a KRunner keyword", "keep above") , Qt::CaseInsensitive)) {
action = KeepAboveAction;
term = term.left(term.lastIndexOf(i18nc("Note this is a KRunner keyword", "keep above")) - 1);
} else if (term.endsWith(i18nc("Note this is a KRunner keyword", "keep below") , Qt::CaseInsensitive)) {
action = KeepBelowAction;
term = term.left(term.lastIndexOf(i18nc("Note this is a KRunner keyword", "keep below")) - 1);
}
// keyword match: when term starts with "window" we list all windows
// the list can be restricted to windows matching a given name, class, role or desktop
if (term.startsWith(i18nc("Note this is a KRunner keyword", "window") , Qt::CaseInsensitive)) {
const QStringList keywords = term.split(QLatin1Char(' '));
QString windowName;
QString windowAppName;
VirtualDesktop *targetDesktop = nullptr;
QVariant desktopId;
for (const QString& keyword : keywords) {
if (keyword.endsWith(QLatin1Char('='))) {
continue;
}
if (keyword.startsWith(i18nc("Note this is a KRunner keyword", "name") + QStringLiteral("=") , Qt::CaseInsensitive)) {
windowName = keyword.split(QStringLiteral("="))[1];
} else if (keyword.startsWith(i18nc("Note this is a KRunner keyword", "appname") + QStringLiteral("=") , Qt::CaseInsensitive)) {
windowAppName = keyword.split(QStringLiteral("="))[1];
} else if (keyword.startsWith(i18nc("Note this is a KRunner keyword", "desktop") + QStringLiteral("=") , Qt::CaseInsensitive)) {
desktopId = keyword.split(QStringLiteral("="))[1];
for (const auto desktop : VirtualDesktopManager::self()->desktops()) {
if (desktop->name().contains(desktopId.toString(), Qt::CaseInsensitive) || desktop->x11DesktopNumber() == desktopId.toUInt()) {
targetDesktop = desktop;
}
}
} else {
// not a keyword - use as name if name is unused, but another option is set
if (windowName.isEmpty() && !keyword.contains(QLatin1Char('=')) && (!windowAppName.isEmpty() || targetDesktop)) {
windowName = keyword;
}
}
}
for (const AbstractClient *client : Workspace::self()->allClientList()) {
if (!client->isNormalWindow()) {
continue;
}
const QString appName = client->resourceClass();
const QString name = client->caption();
if (!windowName.isEmpty() && !name.startsWith(windowName, Qt::CaseInsensitive)) {
continue;
}
if (!windowAppName.isEmpty() && !appName.contains(windowAppName, Qt::CaseInsensitive)) {
continue;
}
if (targetDesktop && !client->desktops().contains(targetDesktop) && !client->isOnAllDesktops()) {
continue;
}
// check for windows when no keywords were used
// check the name and app name for containing the query without the keyword
if (windowName.isEmpty() && windowAppName.isEmpty() && !targetDesktop) {
const QString& test = term.mid(keywords[0].length() + 1);
if (!name.contains(test, Qt::CaseInsensitive) && !appName.contains(test, Qt::CaseInsensitive)) {
continue;
}
}
// blacklisted everything else: we have a match
if (actionSupported(client, action)){
matches << windowsMatch(client, action);
}
}
if (!matches.isEmpty()) {
// the window keyword found matches - do not process other syntax possibilities
return matches;
}
}
bool desktopAdded = false;
// check for desktop keyword
if (term.startsWith(i18nc("Note this is a KRunner keyword", "desktop") , Qt::CaseInsensitive)) {
const QStringList parts = term.split(QLatin1Char(' '));
if (parts.size() == 1) {
// only keyword - list all desktops
for (auto desktop : VirtualDesktopManager::self()->desktops()) {
matches << desktopMatch(desktop);
desktopAdded = true;
}
}
}
// check for matching desktops by name
for (const AbstractClient *client : Workspace::self()->allClientList()) {
if (!client->isNormalWindow()) {
continue;
}
const QString appName = client->resourceClass();
const QString name = client->caption();
if (name.startsWith(term, Qt::CaseInsensitive) || appName.startsWith(term, Qt::CaseInsensitive)) {
matches << windowsMatch(client, action, 0.8, Plasma::QueryMatch::ExactMatch);
} else if ((name.contains(term, Qt::CaseInsensitive) || appName.contains(term, Qt::CaseInsensitive)) && actionSupported(client, action)) {
matches << windowsMatch(client, action, 0.7, Plasma::QueryMatch::PossibleMatch);
}
}
for (auto *desktop : VirtualDesktopManager::self()->desktops()) {
if (desktop->name().contains(term, Qt::CaseInsensitive)) {
if (!desktopAdded && desktop != VirtualDesktopManager::self()->currentDesktop()) {
matches << desktopMatch(desktop, ActivateDesktopAction, 0.8);
}
// search for windows on desktop and list them with less relevance
for (const AbstractClient *client : Workspace::self()->allClientList()) {
if (!client->isNormalWindow()) {
continue;
}
if ((client->desktops().contains(desktop) || client->isOnAllDesktops()) && actionSupported(client, action)) {
matches << windowsMatch(client, action, 0.5, Plasma::QueryMatch::PossibleMatch);
}
}
}
}
return matches;
}
void WindowsRunner::Run(const QString &id, const QString &actionId)
{
// Split id to get actionId and realId. We don't use actionId because our actions list is not constant
const QStringList parts = id.split(QLatin1Char('_'));
auto action = WindowsRunnerAction(parts[0].toInt());
auto objectId = parts[1];
if (action == ActivateDesktopAction) {
QByteArray desktopId = objectId.toLocal8Bit();
auto desktop = VirtualDesktopManager::self()->desktopForId(desktopId);
VirtualDesktopManager::self()->setCurrent(desktop);
return;
}
const auto uuid = QUuid::fromString(objectId);
const auto client = workspace()->findAbstractClient(uuid);
switch (action) {
case ActivateAction:
workspace()->activateClient(client);
break;
case CloseAction:
client->closeWindow();
break;
case MinimizeAction:
client->setMinimized(!client->isMinimized());
break;
case MaximizeAction:
client->setMaximize(client->maximizeMode() == MaximizeRestore, client->maximizeMode() == MaximizeRestore);
break;
case FullscreenAction:
client->setFullScreen(!client->isFullScreen());
break;
case ShadeAction:
client->toggleShade();
break;
case KeepAboveAction:
client->setKeepAbove(!client->keepAbove());
break;
case KeepBelowAction:
client->setKeepBelow(!client->keepBelow());
break;
}
}
RemoteMatch WindowsRunner::desktopMatch(const VirtualDesktop *desktop, const WindowsRunnerAction action, qreal relevance) const
{
RemoteMatch match;
match.id = QString::number((int)action) + QLatin1Char('_') + desktop->id();
match.type = Plasma::QueryMatch::ExactMatch;
match.iconName = QStringLiteral("user-desktop");
match.text = desktop->name();
match.relevance = relevance;
QVariantMap properties;
properties[QStringLiteral("subtext")] = i18n("Switch to desktop %1", desktop->name());
match.properties = properties;
return match;
}
RemoteMatch WindowsRunner::windowsMatch(const AbstractClient *client, const WindowsRunnerAction action, qreal relevance, Plasma::QueryMatch::Type type) const
{
RemoteMatch match;
match.id = QString::number((int)action) + QLatin1Char('_') + client->internalId().toString();
match.text = client->caption();
match.iconName = client->icon().name();
match.relevance = relevance;
match.type = type;
QVariantMap properties;
const QVector<VirtualDesktop *> desktops = client->desktops();
bool allDesktops = client->isOnAllDesktops();
const VirtualDesktop *targetDesktop = VirtualDesktopManager::self()->currentDesktop();
// Show on current desktop unless window is only attached to other desktop, in this case show on the first attached desktop
if (!allDesktops && !client->isOnCurrentDesktop() && !desktops.isEmpty()) {
targetDesktop = desktops.first();
}
const QString desktopName = targetDesktop->name();
switch (action) {
case CloseAction:
properties[QStringLiteral("subtext")] = i18n("Close running window on %1", desktopName);
break;
case MinimizeAction:
properties[QStringLiteral("subtext")] = i18n("(Un)minimize running window on %1", desktopName);
break;
case MaximizeAction:
properties[QStringLiteral("subtext")] = i18n("Maximize/restore running window on %1", desktopName);
break;
case FullscreenAction:
properties[QStringLiteral("subtext")] = i18n("Toggle fullscreen for running window on %1", desktopName);
break;
case ShadeAction:
properties[QStringLiteral("subtext")] = i18n("(Un)shade running window on %1", desktopName);
break;
case KeepAboveAction:
properties[QStringLiteral("subtext")] = i18n("Toggle keep above for running window on %1", desktopName);
break;
case KeepBelowAction:
properties[QStringLiteral("subtext")] = i18n("Toggle keep below running window on %1", desktopName);
break;
case ActivateAction:
default:
properties[QStringLiteral("subtext")] = i18n("Activate running window on %1", desktopName);
break;
}
match.properties = properties;
return match;
}
bool WindowsRunner::actionSupported(const AbstractClient *client, const WindowsRunnerAction action) const
{
switch (action) {
case CloseAction:
return client->isCloseable();
case MinimizeAction:
return client->isMinimizable();
case MaximizeAction:
return client->isMaximizable();
case ShadeAction:
return client->isShadeable();
case FullscreenAction:
return client->isFullScreenable();
case KeepAboveAction:
case KeepBelowAction:
case ActivateAction:
default:
return true;
}
}
}