├── .gitignore ├── src ├── Messages.sh ├── config-switcher.h.cmake ├── plasma-runner-switcher.desktop ├── CMakeLists.txt ├── switcher.h └── switcher.cpp ├── Messages.sh ├── install.sh ├── CMakeLists.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | cmake-build-debug/ 3 | .idea/ -------------------------------------------------------------------------------- /src/Messages.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | $XGETTEXT *.cpp -o $podir/plasma_runner_windows.pot 3 | -------------------------------------------------------------------------------- /src/config-switcher.h.cmake: -------------------------------------------------------------------------------- 1 | /* Define if you have X11 at all */ 2 | #define HAVE_X11 ${X11_FOUND} -------------------------------------------------------------------------------- /Messages.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | $XGETTEXT src/*.cpp -o $podir/plasma_runner_org.kde.switcher.pot 3 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | mkdir -p build 4 | cd build 5 | cmake -DQT_PLUGIN_INSTALL_DIR=`kf5-config --qt-plugins` -DCMAKE_BUILD_TYPE=Release .. 6 | make 7 | sudo make install 8 | kquitapp5 krunner 2> /dev/null; kstart5 --windowclass krunner krunner > /dev/null 2>&1 & 9 | -------------------------------------------------------------------------------- /src/plasma-runner-switcher.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Switcher 3 | Comment=Switcher is an app switching plugin. Type part of the application name/title preceded with a dot, e.g. ".emacs" to switch between apps. 4 | Icon=kde 5 | 6 | X-KDE-ServiceTypes=Plasma/Runner 7 | Type=Service 8 | X-KDE-Library=krunner_switcher 9 | X-KDE-PluginInfo-Author=Tatu Lahtela 10 | X-KDE-PluginInfo-Email=lahtela@iki.fi 11 | X-KDE-PluginInfo-Name=switcher 12 | X-KDE-PluginInfo-Version=0.1 13 | X-KDE-PluginInfo-License=LGPL 2.1+ 14 | X-KDE-PluginInfo-EnabledByDefault=true 15 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(switcher) 4 | 5 | set(QT_MIN_VERSION "5.9.0") 6 | set(KF5_MIN_VERSION "5.42.0") 7 | 8 | find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) 9 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) 10 | find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Widgets X11Extras) 11 | find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Runner I18n) 12 | 13 | 14 | include(KDEInstallDirs) 15 | include(KDECMakeSettings) 16 | include(KDECompilerSettings NO_POLICY_SCOPE) 17 | include(FeatureSummary) 18 | 19 | add_subdirectory(src) 20 | 21 | 22 | 23 | feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) 24 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_definitions(-DTRANSLATION_DOMAIN=\"plasma_runner_org.kde.switcher\") 2 | 3 | set(switcher_SRCS switcher.cpp) 4 | 5 | find_package(XCB MODULE REQUIRED COMPONENTS XCB RANDR) 6 | 7 | add_library(krunner_switcher MODULE ${switcher_SRCS}) 8 | target_link_libraries(krunner_switcher Qt5::Widgets KF5::WindowSystem KF5::I18n KF5::Runner) 9 | 10 | 11 | configure_file(config-switcher.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-switcher.h) 12 | 13 | target_link_libraries(krunner_switcher ${X11_LIBRARIES} XCB::XCB ) 14 | target_link_libraries(krunner_switcher Qt5::X11Extras) 15 | 16 | 17 | install(TARGETS krunner_switcher DESTINATION ${KDE_INSTALL_PLUGINDIR}) 18 | 19 | install(FILES plasma-runner-switcher.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Switcher 2 | ---------------------- 3 | 4 | Switcher is a KRunner addon, that allows fast-switching of windows by typing a dot and the application name or title, e.g. ".emacs" 5 | 6 | ### Required Dependencies 7 | 8 | Debian/Ubuntu: 9 | `sudo apt install cmake extra-cmake-modules build-essential libkf5runner-dev libkf5textwidgets-dev qtdeclarative5-dev gettext libqt5x11extras5-dev libxcb-randr0-dev` 10 | 11 | openSUSE: 12 | `sudo zypper install cmake extra-cmake-modules libQt5Widgets5 libQt5Core5 libqt5-qtlocation-devel ki18n-devel 13 | ktextwidgets-devel kservice-devel krunner-devel gettext-tools` 14 | 15 | Fedora: 16 | `sudo dnf install cmake extra-cmake-modules kf5-ki18n-devel kf5-kservice-devel kf5-krunner-devel kf5-ktextwidgets-devel gettext` 17 | 18 | ### Installing: 19 | ``` 20 | mkdir build 21 | cd build 22 | cmake -DQT_PLUGIN_INSTALL_DIR=`kf5-config --qt-plugins` -DCMAKE_BUILD_TYPE=Release .. 23 | make 24 | sudo make install 25 | kquitapp5 krunner 2> /dev/null; kstart5 --windowclass krunner krunner > /dev/null 2>&1 & 26 | ``` 27 | 28 | #### Screenshot of overview 29 | ![Screenshot of overview](https://raw.githubusercontent.com/alex1701c/Screenshots/master/krunner-switcher/overview.png) 30 | -------------------------------------------------------------------------------- /src/switcher.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 by Tatu Lahtela 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Lesser General Public 6 | License as published by the Free Software Foundation; either 7 | version 2.1 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this library. If not, see . 16 | */ 17 | 18 | #ifndef SWITCHER_H 19 | #define SWITCHER_H 20 | 21 | #include 22 | 23 | class KWindowInfo; 24 | 25 | 26 | class Switcher : public Plasma::AbstractRunner { 27 | Q_OBJECT 28 | 29 | public: 30 | Switcher(QObject *parent, const QVariantList &args); 31 | 32 | ~Switcher() override; 33 | 34 | void match(Plasma::RunnerContext &context) override; 35 | 36 | void run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) override; 37 | 38 | private Q_SLOTS: 39 | 40 | void prepareForMatchSession(); 41 | 42 | void matchSessionComplete(); 43 | 44 | void gatherInfo(); 45 | 46 | private: 47 | Plasma::QueryMatch windowMatch(const KWindowInfo &info, qreal relevance = 1.0, 48 | Plasma::QueryMatch::Type type = Plasma::QueryMatch::ExactMatch); 49 | 50 | QHash m_windows; 51 | QHash m_icons; 52 | QStringList m_desktopNames; 53 | }; 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/switcher.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright 2009 by Martin Gräßlin * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; if not, write to the * 16 | * Free Software Foundation, Inc., * 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 18 | ***************************************************************************/ 19 | #include "switcher.h" 20 | 21 | #include "config-switcher.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #ifdef HAVE_X11 30 | 31 | #include 32 | #include 33 | 34 | #endif 35 | 36 | K_EXPORT_PLASMA_RUNNER(windows, Switcher) 37 | 38 | Switcher::Switcher(QObject *parent, const QVariantList &args) 39 | : AbstractRunner(parent, args) { 40 | Q_UNUSED(args); 41 | setObjectName(QLatin1String("Switcher")); 42 | 43 | 44 | setDefaultSyntax(Plasma::RunnerSyntax(i18nc("Note this is a KRunner keyword", "."), 45 | i18n("Switch to application by typing a dot and the app name, e.g. '.emacs'"))); 46 | 47 | connect(this, &Plasma::AbstractRunner::prepare, this, &Switcher::prepareForMatchSession); 48 | connect(this, &Plasma::AbstractRunner::teardown, this, &Switcher::matchSessionComplete); 49 | } 50 | 51 | Switcher::~Switcher() = default; 52 | 53 | // Called in the main thread 54 | void Switcher::gatherInfo() { 55 | for (const WId &w: KWindowSystem::windows()) { 56 | KWindowInfo info(w, NET::WMWindowType | NET::WMDesktop | 57 | NET::WMState | NET::XAWMState | 58 | NET::WMName, 59 | NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2AllowedActions); 60 | if (info.valid() && info.name() != "KRunner — krunner") { 61 | // ignore NET::Tool and other special window types 62 | NET::WindowType wType = info.windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask | 63 | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | 64 | NET::OverrideMask | NET::TopMenuMask | 65 | NET::UtilityMask | NET::SplashMask); 66 | 67 | if (wType != NET::Normal && wType != NET::Override && wType != NET::Unknown && 68 | wType != NET::Dialog && wType != NET::Utility) { 69 | continue; 70 | } 71 | m_windows.insert(w, info); 72 | m_icons.insert(w, QIcon(KWindowSystem::icon(w))); 73 | } 74 | } 75 | 76 | const int desktopCount = KWindowSystem::numberOfDesktops(); 77 | for (int i = 1; i <= desktopCount; ++i) { 78 | m_desktopNames.append(KWindowSystem::desktopName(i)); 79 | } 80 | } 81 | 82 | // Called in the main thread 83 | void Switcher::prepareForMatchSession() { 84 | gatherInfo(); 85 | } 86 | 87 | // Called in the main thread 88 | void Switcher::matchSessionComplete() { 89 | m_desktopNames.clear(); 90 | m_icons.clear(); 91 | m_windows.clear(); 92 | } 93 | 94 | // Called in the secondary thread 95 | void Switcher::match(Plasma::RunnerContext &context) { 96 | const QString term = context.query(); 97 | if (!context.isValid() || !term.startsWith(i18nc("Note this is a KRunner keyword", "."), Qt::CaseInsensitive)) return; 98 | 99 | QList matches; 100 | 101 | // keyword match: when term starts with "window" we list all windows 102 | // the list can be restricted to windows matching a given name, class, role or desktop 103 | const QString windowName = term.mid(1, term.size()); 104 | 105 | QHashIterator it(m_windows); 106 | while (it.hasNext()) { 107 | it.next(); 108 | const WId w = it.key(); 109 | const KWindowInfo info = it.value(); 110 | const QString windowClass = QString::fromUtf8(info.windowClassName()); 111 | 112 | // exclude not matching windows 113 | if (!KWindowSystem::hasWId(w)) continue; 114 | 115 | if (!windowName.isEmpty() && !info.name().contains(windowName, Qt::CaseInsensitive) && 116 | !windowClass.contains(windowName, Qt::CaseInsensitive)) { 117 | continue; 118 | } 119 | matches.append(windowMatch(info)); 120 | } 121 | 122 | context.addMatches(matches); 123 | } 124 | 125 | // Called in the main thread 126 | void Switcher::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) { 127 | Q_UNUSED(context) 128 | // check if it's a desktop 129 | if (match.id().startsWith(QLatin1String("windows_desktop"))) { 130 | KWindowSystem::setCurrentDesktop(match.data().toInt()); 131 | return; 132 | } 133 | 134 | WId w(match.data().toString().toULong()); 135 | KWindowSystem::forceActiveWindow(w); 136 | } 137 | 138 | 139 | Plasma::QueryMatch Switcher::windowMatch(const KWindowInfo &info, qreal relevance, const Plasma::QueryMatch::Type type) { 140 | Plasma::QueryMatch match(this); 141 | match.setType(type); 142 | match.setData(QString::number(info.win())); 143 | match.setIcon(m_icons[info.win()]); 144 | match.setText(info.name()); 145 | QString desktopName; 146 | int desktop = info.desktop(); 147 | if (desktop == NET::OnAllDesktops) { 148 | desktop = KWindowSystem::currentDesktop(); 149 | } 150 | if (desktop <= m_desktopNames.size()) { 151 | desktopName = m_desktopNames[desktop - 1]; 152 | } else { 153 | desktopName = KWindowSystem::desktopName(desktop); 154 | } 155 | 156 | match.setSubtext(i18n("Activate running window on %1", desktopName)); 157 | match.setRelevance(relevance); 158 | return match; 159 | } 160 | 161 | 162 | #include "switcher.moc" 163 | --------------------------------------------------------------------------------