From cf4335ca625aa41a5a656e16d4b6a397541cd96f Mon Sep 17 00:00:00 2001 From: Benjamin Port Date: Tue, 21 Jul 2020 15:33:03 +0200 Subject: [PATCH] Add windows runner Move windows runner from plasma-workspace to kwin (convert it to dbus runner) This will allow to interract with windows on wayland too --- CMakeLists.txt | 26 +++ Messages.sh | 2 +- runners/dbusutils_p.h | 91 ++++++++ runners/kwin-runner-windows.desktop | 154 +++++++++++++ runners/org.kde.krunner1.xml | 55 +++++ runners/windowsrunnerinterface.cpp | 330 ++++++++++++++++++++++++++++ runners/windowsrunnerinterface.h | 63 ++++++ workspace.cpp | 8 +- workspace.h | 1 + 9 files changed, 728 insertions(+), 2 deletions(-) create mode 100644 runners/dbusutils_p.h create mode 100644 runners/kwin-runner-windows.desktop create mode 100644 runners/org.kde.krunner1.xml create mode 100644 runners/windowsrunnerinterface.cpp create mode 100644 runners/windowsrunnerinterface.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 87c7ed7f21..7f48aebabc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,6 +111,13 @@ set_package_properties(KF5DocTools PROPERTIES ) add_feature_info("KF5DocTools" KF5DocTools_FOUND "Enable building documentation") +find_package(KF5Runner ${KF5_MIN_VERSION} CONFIG) +set_package_properties(KF5Runner PROPERTIES + PURPOSE "Enable building of KWin with krunner support" + TYPE OPTIONAL + ) +add_feature_info("KF5Runner" KF5Runner_FOUND "Enable building of KWin with krunner support") + find_package(KF5Kirigami2 ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Kirigami2 PROPERTIES DESCRIPTION "A QtQuick based components set" @@ -336,6 +343,7 @@ option(KWIN_BUILD_KCMS "Enable building of KWin configuration modules." ON) option(KWIN_BUILD_TABBOX "Enable building of KWin Tabbox functionality" ON) option(KWIN_BUILD_XRENDER_COMPOSITING "Enable building of KWin with XRender Compositing support" ON) cmake_dependent_option(KWIN_BUILD_ACTIVITIES "Enable building of KWin with kactivities support" ON "KF5Activities_FOUND" OFF) +cmake_dependent_option(KWIN_BUILD_RUNNERS "Enable building of KWin with krunner support" ON "KF5Runner_FOUND" OFF) # Binary name of KWin set(KWIN_NAME "kwin") @@ -590,6 +598,12 @@ if (KWIN_BUILD_ACTIVITIES) ) endif() +if (KWIN_BUILD_RUNNERS) + set(kwin_SRCS ${kwin_SRCS} + runners/windowsrunnerinterface.cpp + ) +endif() + if (HAVE_LINUX_VT_H) set(kwin_SRCS ${kwin_SRCS} virtual_terminal.cpp @@ -619,6 +633,9 @@ qt5_add_dbus_adaptor(kwin_SRCS org.kde.kwin.ColorCorrect.xml colorcorrection/col qt5_add_dbus_adaptor(kwin_SRCS ${kwin_effects_dbus_xml} effects.h KWin::EffectsHandlerImpl) qt5_add_dbus_adaptor(kwin_SRCS org.kde.KWin.VirtualDesktopManager.xml dbusinterface.h KWin::VirtualDesktopManagerDBusInterface) qt5_add_dbus_adaptor(kwin_SRCS org.kde.KWin.Session.xml sm.h KWin::SessionManager) +if (KWIN_BUILD_RUNNERS) + qt5_add_dbus_adaptor(kwin_SRCS "runners/org.kde.krunner1.xml" runners/windowsrunnerinterface.h KWin::WindowsRunner) +endif() qt5_add_dbus_interface(kwin_SRCS ${KSCREENLOCKER_DBUS_INTERFACES_DIR}/kf5_org.freedesktop.ScreenSaver.xml screenlocker_interface) qt5_add_dbus_interface(kwin_SRCS ${KSCREENLOCKER_DBUS_INTERFACES_DIR}/org.kde.screensaver.xml kscreenlocker_interface) @@ -695,6 +712,10 @@ if (KWIN_BUILD_ACTIVITIES) set(kwin_KDE_LIBS ${kwin_KDE_LIBS} KF5::Activities) endif() +if (KWIN_BUILD_RUNNERS) + set(kwin_KDE_LIBS ${kwin_KDE_LIBS} KF5::Runner) +endif() + set(kwinLibs ${kwin_OWN_LIBS} ${kwin_QT_LIBS} @@ -826,6 +847,11 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kwin_export.h DESTINATION ${INCLUDE_IN # Install the KWin/Script service type install(FILES scripting/kwinscript.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) +# install KWin krunner runner +if (KWIN_BUILD_RUNNERS) + install(FILES runners/kwin-runner-windows.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins) +endif() + add_subdirectory(qml) if (BUILD_TESTING) diff --git a/Messages.sh b/Messages.sh index f4d4382c46..515a32b866 100644 --- a/Messages.sh +++ b/Messages.sh @@ -1,3 +1,3 @@ #! /usr/bin/env bash $EXTRACTRC *.kcfg *.ui >> rc.cpp -$XGETTEXT *.h *.cpp colorcorrection/*.cpp helpers/killer/*.cpp plugins/scenes/opengl/*.cpp tabbox/*.cpp scripting/*.cpp -o $podir/kwin.pot +$XGETTEXT *.h *.cpp colorcorrection/*.cpp helpers/killer/*.cpp plugins/scenes/opengl/*.cpp tabbox/*.cpp scripting/*.cpp runners/*.cpp -o $podir/kwin.pot diff --git a/runners/dbusutils_p.h b/runners/dbusutils_p.h new file mode 100644 index 0000000000..a24e73dc08 --- /dev/null +++ b/runners/dbusutils_p.h @@ -0,0 +1,91 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 David Edmundson + SPDX-FileCopyrightText: 2018 Laurent Montel + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include +#include +#include +#include +#include + +struct RemoteMatch +{ + //sssuda{sv} + QString id; + QString text; + QString iconName; + Plasma::QueryMatch::Type type = Plasma::QueryMatch::NoMatch; + qreal relevance = 0; + QVariantMap properties; +}; + +typedef QList RemoteMatches; + +struct RemoteAction +{ + QString id; + QString text; + QString iconName; +}; + +typedef QList RemoteActions; + +inline QDBusArgument &operator<< (QDBusArgument &argument, const RemoteMatch &match) { + argument.beginStructure(); + argument << match.id; + argument << match.text; + argument << match.iconName; + argument << match.type; + argument << match.relevance; + argument << match.properties; + argument.endStructure(); + return argument; +} + +inline const QDBusArgument &operator>>(const QDBusArgument &argument, RemoteMatch &match) { + argument.beginStructure(); + argument >> match.id; + argument >> match.text; + argument >> match.iconName; + uint type; + argument >> type; + match.type = (Plasma::QueryMatch::Type)type; + argument >> match.relevance; + argument >> match.properties; + argument.endStructure(); + + return argument; +} + +inline QDBusArgument &operator<< (QDBusArgument &argument, const RemoteAction &action) +{ + argument.beginStructure(); + argument << action.id; + argument << action.text; + argument << action.iconName; + argument.endStructure(); + return argument; +} + +inline const QDBusArgument &operator>>(const QDBusArgument &argument, RemoteAction &action) { + argument.beginStructure(); + argument >> action.id; + argument >> action.text; + argument >> action.iconName; + argument.endStructure(); + return argument; +} + +Q_DECLARE_METATYPE(RemoteMatch) +Q_DECLARE_METATYPE(RemoteMatches) +Q_DECLARE_METATYPE(RemoteAction) +Q_DECLARE_METATYPE(RemoteActions) + diff --git a/runners/kwin-runner-windows.desktop b/runners/kwin-runner-windows.desktop new file mode 100644 index 0000000000..23bdeabdbd --- /dev/null +++ b/runners/kwin-runner-windows.desktop @@ -0,0 +1,154 @@ +[Desktop Entry] +# ctxt: plasma runner +Name=Windows +Name[ar]=النوافذ +Name[ast]=Ventanes +Name[az]=Pəncərələr +Name[bg]=Прозорци +Name[bn]=উইণ্ডো +Name[bs]=Prozori +Name[ca]=Finestres +Name[ca@valencia]=Finestres +Name[cs]=Okna +Name[csb]=Òkna +Name[da]=Vinduer +Name[de]=Fenster +Name[el]=Παράθυρα +Name[en_GB]=Windows +Name[eo]=Fenestroj +Name[es]=Ventanas +Name[et]=Aknad +Name[eu]=Leihoak +Name[fa]=پنجره‌ها +Name[fi]=Ikkunat +Name[fr]=Fenêtres +Name[fy]=Finsters +Name[ga]=Fuinneoga +Name[gl]=Xanelas +Name[gu]=વિન્ડોઝ +Name[he]=חלונות +Name[hi]=विंडोज़ +Name[hr]=Prozori +Name[hu]=Ablakok +Name[ia]=Fenestras +Name[id]=Windows +Name[is]=Gluggar +Name[it]=Finestre +Name[ja]=ウィンドウ +Name[kk]=Терезелер +Name[km]=បង្អួច +Name[kn]=ಕಿಟಕಿಗಳು +Name[ko]=창 +Name[lt]=Langai +Name[lv]=Logi +Name[mk]=Прозорци +Name[ml]=ജാലകങ്ങള്‍ +Name[mr]=चौकटी +Name[nb]=Vinduer +Name[nds]=Finstern +Name[nl]=Vensters +Name[nn]=Vindauge +Name[pa]=ਵਿੰਡੋਆਂ +Name[pl]=Okna +Name[pt]=Janelas +Name[pt_BR]=Janelas +Name[ro]=Ferestre +Name[ru]=Окна +Name[si]=කවුළු +Name[sk]=Okná +Name[sl]=Okna +Name[sr]=прозори +Name[sr@ijekavian]=прозори +Name[sr@ijekavianlatin]=prozori +Name[sr@latin]=prozori +Name[sv]=Fönster +Name[th]=หน้าต่างต่าง ๆ +Name[tr]=Pencereler +Name[ug]=كۆزنەكلەر +Name[uk]=Вікна +Name[wa]=Finiesses +Name[x-test]=xxWindowsxx +Name[zh_CN]=窗口 +Name[zh_TW]=視窗 +Comment=List windows and desktops and switch them +Comment[ar]=اسرد النوافذ وأسطح المكتب وبدّل بينها +Comment[az]=Pəncərələrin, İş Masalarının siyahısı və onlar arası keçid +Comment[bg]=Показване и превключване на прозорци и работни плотове +Comment[bs]=Nabrajanje i prebacivanje između prozora i površî +Comment[ca]=Llista finestres i escriptoris, i canvia entre ells +Comment[ca@valencia]=Llista finestres i escriptoris i canvia entre ells +Comment[cs]=Seznam oken a ploch k přepínání +Comment[da]=Oplist vinduer og skriveborde og skift mellem dem +Comment[de]=Listet Fenster und Arbeitsflächen auf und wechselt zwischen ihnen. +Comment[el]=Εμφάνιση λίστας παραθύρων και επιφανειών εργασίας για εναλλαγή +Comment[en_GB]=List windows and desktops and switch them +Comment[eo]=Listi fenestrojn kaj labortablojn kaj ŝanĝi ilin +Comment[es]=Lista ventanas y escritorios y cambia entre ellos +Comment[et]=Akende ja töölauade näitamine võimalusega neile lülituda +Comment[eu]=Zerrendatu leihoak eta mahaigainak, eta batetik bestera aldatu +Comment[fi]=Luettele ikkunat ja työpöydät ja vaihda niitä +Comment[fr]=Liste les fenêtres / bureaux et passe de l'un à l'autre +Comment[fy]=Sommet finsters en buroblêden en wikselt har +Comment[ga]=Taispeáin fuinneoga agus deasca agus athraigh eatarthu +Comment[gl]=Lista as xanelas e escritorio e salta entre eles +Comment[he]=משמש להצגת חלונות ושולחנות עבודה ולהחלפה ביניהם +Comment[hr]=Popis prozora i radnih površina za laku promjenu među njima +Comment[hu]=Kilistázza az ablakokat és asztalokat, és vált rájuk +Comment[ia]=Lista fenestras e scriptorios e commuta los +Comment[id]=Daftar window dan desktop dan alihkan mereka +Comment[is]=Telur upp glugga og skjáborð og skiptir á milli þeirra +Comment[it]=Elenca e passa tra le finestre ed i desktop +Comment[ja]=ウィンドウとデスクトップの一覧表示と切り替えを行います +Comment[kk]=Бар терезе мен үстелдерді ұсынып оларға ауысу +Comment[km]=រាយ​​បង្អួច និង​ផ្ទៃតុ​ ហើយ​ប្ដូរ​ពួកវា +Comment[kn]=ವಿಂಡೊ ಹಾಗು ಗಣಕತೆರೆಗಳನ್ನು ಪಟ್ಟಿಮಾಡು ಹಾಗು ಅವುಗಳನ್ನು ಬದಲಾಯಿಸು +Comment[ko]=창 및 바탕 화면 목록을 보여 주고 전환합니다 +Comment[lt]=Išvardyti langus ir darbalaukius bei perjungti į juos +Comment[lv]=Parāda logus un darbvirsmas un pārslēdz tos +Comment[mk]=Приказ на прозорци и раб. површини и преминување меѓу нив +Comment[ml]=ജാലകങ്ങളും പണിയിടങ്ങളും പട്ടികയായി കണ്ടു് പരസ്പരം മാറുക +Comment[mr]=वेगळ्या चौकट व डेस्कटॉप वर जाण्यासाठी यादी दर्शवा व बदला +Comment[nb]=List vinduer og skrivebord og bytt mellom dem +Comment[nds]=Finstern un Schriefdischen oplisten un wesseln +Comment[nl]=Toon vensters en bureaubladen en schakel ze om +Comment[nn]=Vis vindauge og skrivebord, og byt mellom dei +Comment[pa]=ਵਿੰਡੋ ਅਤੇ ਡੈਸਕਟਾਪ ਦੀ ਲਿਸਟ ਅਤੇ ਉਹਨਾਂ ਵਿੱਚ ਬਦਲੋ +Comment[pl]=Wypisuje okna i pulpity oraz przełącza pomiędzy nimi +Comment[pt]=Listar as janelas e ecrãs e mudar entre eles +Comment[pt_BR]=Lista as janelas e áreas de trabalho e alterna entre elas +Comment[ro]=Enumeră ferestre și birouri și le comută +Comment[ru]=Список окон и рабочих столов с возможностью переключения между ними +Comment[si]=කවුළු හා වැඩතල ලැයිස්තුගත කර ඒවා මාරුකරන්න +Comment[sk]=Zoznam okien a plôch a ich prepínanie +Comment[sl]=Seznam oken in namizij ter preklop med njimi +Comment[sr]=Набрајање и пребацивање између прозора и површи̂ +Comment[sr@ijekavian]=Набрајање и пребацивање између прозора и површи̂ +Comment[sr@ijekavianlatin]=Nabrajanje i prebacivanje između prozora i površî +Comment[sr@latin]=Nabrajanje i prebacivanje između prozora i površî +Comment[sv]=Lista fönster och skrivbord, och byt mellan dem +Comment[th]=รายการหน้าต่างและพื้นที่ทำงานต่าง ๆ ที่สามารถสลับไปใช้งานได้ +Comment[tr]=Pencereleri ve masaüstlerini listele ve seç +Comment[ug]=ھەممە كۆزنەكلەر ۋە ئۈستەلئۈستى تىزىمىنى كۆرسىتىپ، ئۇلارنى ئالماشتۇرىدۇ +Comment[uk]=Показує список вікон і стільниць і перемикає їх +Comment[vi]=Liệt kê các cửa sổ, màn hình làm việc và chuyển đổi chúng +Comment[wa]=Fé l' djivêye des fniesses eyet des scribannes eyet passer d' n' onk a l' ôte +Comment[x-test]=xxList windows and desktops and switch themxx +Comment[zh_CN]=列出所有窗口和桌面,并可供切换 +Comment[zh_TW]=列出視窗與桌面並切換 +X-KDE-ServiceTypes=Plasma/Runner +Type=Service +Icon=preferences-system-windows +X-KDE-PluginInfo-Author=Martin Gräßlin +X-KDE-PluginInfo-Email=kde@martin-graesslin.com +X-KDE-PluginInfo-Name=windows +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true +X-Plasma-AdvertiseSingleRunnerQueryMode=true +X-Plasma-API=DBus +X-Plasma-DBusRunner-Service=org.kde.KWin +X-Plasma-DBusRunner-Path=/WindowsRunner +X-Plasma-Request-Actions-Once=true +X-Plasma-Runner-Min-Letter-Count=3 +X-Plasma-Runner-Syntaxes=:q:,:q:,:q:,window,desktop +X-Plasma-Runner-Syntax-Descriptions=Finds windows whose name or window application match :q:. It is possible to interact with the windows by using one of the following keywords: activate\\, close\\, min(imize)\\, max(imize)\\, fullscreen\\, shade\\, keep above and keep below.,Finds windows which are on desktop named :q:,Switch to desktop named :q:,Lists all windows and allows to activate them.,Lists all other desktops and allows to switch to them. diff --git a/runners/org.kde.krunner1.xml b/runners/org.kde.krunner1.xml new file mode 100644 index 0000000000..b9d39a1281 --- /dev/null +++ b/runners/org.kde.krunner1.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/runners/windowsrunnerinterface.cpp b/runners/windowsrunnerinterface.cpp new file mode 100644 index 0000000000..cd758d05bc --- /dev/null +++ b/runners/windowsrunnerinterface.cpp @@ -0,0 +1,330 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2009 Martin Gräßlin + SPDX-FileCopyrightText: 2020 Benjamin Port + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "windowsrunnerinterface.h" + +#include "abstract_client.h" +#include "workspace.h" + +#include "krunner1adaptor.h" +#include + + +namespace KWin +{ +WindowsRunner::WindowsRunner(QObject *parent) + : QObject(parent) +{ + new Krunner1Adaptor(this); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + QDBusConnection::sessionBus().registerObject(QStringLiteral("/WindowsRunner"), this); + QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.KWin")); +} + +WindowsRunner::~WindowsRunner() +{ +} + +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 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; + } +} + +} diff --git a/runners/windowsrunnerinterface.h b/runners/windowsrunnerinterface.h new file mode 100644 index 0000000000..3f6381e70f --- /dev/null +++ b/runners/windowsrunnerinterface.h @@ -0,0 +1,63 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2009 Martin Gräßlin + SPDX-FileCopyrightText: 2020 Benjamin Port + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef WINDOWSRUNNER_H +#define WINDOWSRUNNER_H + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include "dbusutils_p.h" + +namespace KWin +{ +class VirtualDesktop; +class AbstractClient; + +class WindowsRunner : public QObject, protected QDBusContext +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.WindowsRunner") +public: + explicit WindowsRunner(QObject *parent = nullptr); + ~WindowsRunner() override; + + RemoteActions Actions(); + RemoteMatches Match(const QString &searchTerm); + void Run(const QString &id, const QString &actionId); + +private: + enum WindowsRunnerAction { + // Windows related actions + ActivateAction, + CloseAction, + MinimizeAction, + MaximizeAction, + FullscreenAction, + ShadeAction, + KeepAboveAction, + KeepBelowAction, + // Desktop related actions + ActivateDesktopAction + }; + + RemoteMatch desktopMatch(const VirtualDesktop *desktop, const WindowsRunnerAction action = ActivateDesktopAction, qreal relevance = 1.0) const; + RemoteMatch windowsMatch(const AbstractClient *client, const WindowsRunnerAction action = ActivateAction, qreal relevance = 1.0, Plasma::QueryMatch::Type type = Plasma::QueryMatch::ExactMatch) const; + bool actionSupported(const AbstractClient *client, const WindowsRunnerAction action) const; +}; +} + +#endif // WINDOWSRUNNER_H diff --git a/workspace.cpp b/workspace.cpp index 64da466f21..4a7b8c36e1 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -59,6 +59,7 @@ #include // Qt #include +#include namespace KWin { @@ -197,7 +198,7 @@ Workspace::Workspace() }); new DBusInterface(this); - + new WindowsRunner(this); Outline::create(this); initShortcuts(); @@ -1689,6 +1690,11 @@ AbstractClient *Workspace::findAbstractClient(std::function(findToplevel(internalId)); +} + Unmanaged *Workspace::findUnmanaged(std::function func) const { return Toplevel::findInList(m_unmanaged, func); diff --git a/workspace.h b/workspace.h index 9d76ee766e..db116270c0 100644 --- a/workspace.h +++ b/workspace.h @@ -115,6 +115,7 @@ public: */ X11Client *findClient(std::function func) const; AbstractClient *findAbstractClient(std::function func) const; + AbstractClient *findAbstractClient(const QUuid &internalId) const; /** * @brief Finds the Client matching the given match @p predicate for the given window. *