You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
339 lines
14 KiB
339 lines
14 KiB
/* |
|
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; |
|
} |
|
} |
|
|
|
}
|
|
|