8f6edea521
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.
339 lines
14 KiB
C++
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;
|
|
}
|
|
}
|
|
|
|
}
|