diff --git a/runners/windows/windowsrunner.cpp b/runners/windows/windowsrunner.cpp index a1c033e99..a4c30b682 100644 --- a/runners/windows/windowsrunner.cpp +++ b/runners/windows/windowsrunner.cpp @@ -1,490 +1,486 @@ /*************************************************************************** * Copyright 2009 by Martin Gräßlin * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "windowsrunner.h" #include "config-windowsrunner.h" #include #include #include #include #include #include #if HAVE_X11 #include #include #endif K_EXPORT_PLASMA_RUNNER(windows, WindowsRunner) WindowsRunner::WindowsRunner(QObject* parent, const QVariantList& args) : AbstractRunner(parent, args), - m_inSession(false), - m_ready(false) + m_inSession(false) { Q_UNUSED(args) setObjectName( QLatin1String("Windows") ); addSyntax(Plasma::RunnerSyntax(QStringLiteral(":q:"), i18n("Finds windows whose name, window class or window role 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."))); addSyntax(Plasma::RunnerSyntax(QStringLiteral(":q:"), i18n("Finds windows which are on desktop named :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."))); addSyntax(Plasma::RunnerSyntax(QStringLiteral(":q:"), i18n("Switch to desktop named :q:"))); setDefaultSyntax(Plasma::RunnerSyntax(i18nc("Note this is a KRunner keyword", "window"), i18n("Lists all windows and allows to activate them. " "With name=, class=, role= and desktop= the list can be reduced to " "windows matching these restrictions. " "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."))); addSyntax(Plasma::RunnerSyntax(i18nc("Note this is a KRunner keyword", "desktop"), i18n("Lists all other desktops and allows to switch to them."))); connect(this, &Plasma::AbstractRunner::prepare, this, &WindowsRunner::prepareForMatchSession); connect(this, &Plasma::AbstractRunner::teardown, this, &WindowsRunner::matchSessionComplete); } WindowsRunner::~WindowsRunner() { } // Called in the main thread void WindowsRunner::gatherInfo() { - QMutexLocker locker(&m_mutex); if (!m_inSession) { return; } const auto windows = KWindowSystem::windows(); for (const WId &w : windows) { KWindowInfo info(w, NET::WMWindowType | NET::WMDesktop | NET::WMState | NET::XAWMState | NET::WMName, NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2AllowedActions); if (info.valid()) { // ignore NET::Tool and other special window types NET::WindowType wType = info.windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask); if (wType != NET::Normal && wType != NET::Override && wType != NET::Unknown && wType != NET::Dialog && wType != NET::Utility) { continue; } m_windows.insert(w, info); m_icons.insert(w, QIcon(KWindowSystem::icon(w))); } } for (int i=1; i<=KWindowSystem::numberOfDesktops(); i++) { m_desktopNames << KWindowSystem::desktopName(i); } - m_ready = true; + // unlock lock locked in prepareForMatchSession + m_mutex.unlock(); } // Called in the main thread void WindowsRunner::prepareForMatchSession() { - QMutexLocker locker(&m_mutex); + // gatherInfo will unlock the lock + m_mutex.lock(); m_inSession = true; - m_ready = false; QTimer::singleShot(0, this, &WindowsRunner::gatherInfo); } // Called in the main thread void WindowsRunner::matchSessionComplete() { QMutexLocker locker(&m_mutex); m_inSession = false; - m_ready = false; m_desktopNames.clear(); m_icons.clear(); m_windows.clear(); } // Called in the secondary thread void WindowsRunner::match(Plasma::RunnerContext& context) { + // will run block as long as gatherInfo has not finished QMutexLocker locker(&m_mutex); - if (!m_ready) { - return; - } QString term = context.query(); if (!context.singleRunnerQueryMode() && (term.length() < 3)) { return; } QList matches; // check if the search term ends with an action keyword WindowAction 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 windowClass; QString windowRole; int desktop = -1; 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", "class") + QStringLiteral("=") , Qt::CaseInsensitive)) { windowClass = keyword.split(QStringLiteral("="))[1]; } else if (keyword.startsWith(i18nc("Note this is a KRunner keyword", "role") + QStringLiteral("=") , Qt::CaseInsensitive)) { windowRole = keyword.split(QStringLiteral("="))[1]; } else if (keyword.startsWith(i18nc("Note this is a KRunner keyword", "desktop") + QStringLiteral("=") , Qt::CaseInsensitive)) { bool ok; desktop = keyword.split(QStringLiteral("="))[1].toInt(&ok); if (!ok || desktop > KWindowSystem::numberOfDesktops()) { desktop = -1; // sanity check } } else { // not a keyword - use as name if name is unused, but another option is set if (windowName.isEmpty() && !keyword.contains(QLatin1Char('=')) && (!windowRole.isEmpty() || !windowClass.isEmpty() || desktop != -1)) { windowName = keyword; } } } QHashIterator it(m_windows); while(it.hasNext()) { it.next(); WId w = it.key(); KWindowInfo info = it.value(); QString windowClassCompare = QString::fromUtf8(info.windowClassName()) + QLatin1Char(' ') + QString::fromUtf8(info.windowClassClass()); // exclude not matching windows if (!KWindowSystem::hasWId(w)) { continue; } if (!windowName.isEmpty() && !info.name().startsWith(windowName, Qt::CaseInsensitive)) { continue; } if (!windowClass.isEmpty() && !windowClassCompare.contains(windowClass, Qt::CaseInsensitive)) { continue; } if (!windowRole.isEmpty() && !QString::fromUtf8(info.windowRole()).contains(windowRole, Qt::CaseInsensitive)) { continue; } if (desktop != -1 && !info.isOnDesktop(desktop)) { continue; } // check for windows when no keywords were used // check the name, class and role for containing the query without the keyword if (windowName.isEmpty() && windowClass.isEmpty() && windowRole.isEmpty() && desktop == -1) { const QString& test = term.mid(keywords[0].length() + 1); if (!info.name().contains(test, Qt::CaseInsensitive) && !windowClassCompare.contains(test, Qt::CaseInsensitive) && !QString::fromUtf8(info.windowRole()).contains(test, Qt::CaseInsensitive)) { continue; } } // blacklisted everything else: we have a match if (actionSupported(info, action)){ matches << windowMatch(info, action); } } if (!matches.isEmpty()) { // the window keyword found matches - do not process other syntax possibilities context.addMatches(matches); return; } } 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 (int i=1; i<=KWindowSystem::numberOfDesktops(); i++) { if (i == KWindowSystem::currentDesktop()) { continue; } matches << desktopMatch(i); desktopAdded = true; } } else { // keyword + desktop - restrict matches bool isInt; int desktop = term.midRef(parts[0].length() + 1).toInt(&isInt); if (isInt && desktop != KWindowSystem::currentDesktop()) { matches << desktopMatch(desktop); desktopAdded = true; } } } // check for matches without keywords QHashIterator it(m_windows); while (it.hasNext()) { it.next(); WId w = it.key(); if (!KWindowSystem::hasWId(w)) { continue; } // check if window name, class or role contains the query KWindowInfo info = it.value(); QString className = QString::fromUtf8(info.windowClassName()); if (info.name().startsWith(term, Qt::CaseInsensitive) || className.startsWith(term, Qt::CaseInsensitive)) { matches << windowMatch(info, action, 0.8, Plasma::QueryMatch::ExactMatch); } else if ((info.name().contains(term, Qt::CaseInsensitive) || className.contains(term, Qt::CaseInsensitive)) && actionSupported(info, action)) { matches << windowMatch(info, action, 0.7, Plasma::QueryMatch::PossibleMatch); } } // check for matching desktops by name for (const QString& desktopName : qAsConst(m_desktopNames)) { int desktop = m_desktopNames.indexOf(desktopName) +1; if (desktopName.contains(term, Qt::CaseInsensitive)) { // desktop name matches - offer switch to // only add desktops if it hasn't been added by the keyword which is quite likely if (!desktopAdded && desktop != KWindowSystem::currentDesktop()) { matches << desktopMatch(desktop, 0.8); } // search for windows on desktop and list them with less relevance QHashIterator it(m_windows); while (it.hasNext()) { it.next(); KWindowInfo info = it.value(); if (info.isOnDesktop(desktop) && actionSupported(info, action)) { matches << windowMatch(info, action, 0.5, Plasma::QueryMatch::PossibleMatch); } } } } if (!matches.isEmpty()) { context.addMatches(matches); } } // Called in the main thread void WindowsRunner::run(const Plasma::RunnerContext& context, const Plasma::QueryMatch& match) { QMutexLocker locker(&m_mutex); Q_UNUSED(context) // check if it's a desktop if (match.id().startsWith(QLatin1String("windows_desktop"))) { KWindowSystem::setCurrentDesktop(match.data().toInt()); return; } const QStringList parts = match.data().toString().split(QLatin1Char('_')); WindowAction action = WindowAction(parts[0].toInt()); WId w(parts[1].toULong()); KWindowInfo info(w, NET::WMWindowType | NET::WMDesktop | NET::WMState | NET::XAWMState | NET::WMName, NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2AllowedActions); if (!info.valid()) { return; } switch (action) { case ActivateAction: KWindowSystem::forceActiveWindow(w); break; case CloseAction: { NETRootInfo ri(QX11Info::connection(), NET::CloseWindow); ri.closeWindowRequest(w); break; } case MinimizeAction: if (info.isMinimized()) { KWindowSystem::unminimizeWindow(w); } else { KWindowSystem::minimizeWindow(w); } break; case MaximizeAction: if (info.hasState(NET::Max)) { KWindowSystem::clearState(w, NET::Max); } else { KWindowSystem::setState(w, NET::Max); } break; case FullscreenAction: if (info.hasState(NET::FullScreen)) { KWindowSystem::clearState(w, NET::FullScreen); } else { KWindowSystem::setState(w, NET::FullScreen); } break; case ShadeAction: if (info.hasState(NET::Shaded)) { KWindowSystem::clearState(w, NET::Shaded); } else { KWindowSystem::setState(w, NET::Shaded); } break; case KeepAboveAction: if (info.hasState(NET::KeepAbove)) { KWindowSystem::clearState(w, NET::KeepAbove); } else { KWindowSystem::setState(w, NET::KeepAbove); } break; case KeepBelowAction: if (info.hasState(NET::KeepBelow)) { KWindowSystem::clearState(w, NET::KeepBelow); } else { KWindowSystem::setState(w, NET::KeepBelow); } break; } } Plasma::QueryMatch WindowsRunner::desktopMatch(int desktop, qreal relevance) { Plasma::QueryMatch match(this); match.setType(Plasma::QueryMatch::ExactMatch); match.setData(desktop); match.setId(QStringLiteral("desktop-") + QString::number(desktop)); match.setIconName(QStringLiteral("user-desktop")); QString desktopName; if (desktop <= m_desktopNames.size()) { desktopName = m_desktopNames[desktop - 1]; } else { desktopName = KWindowSystem::desktopName(desktop); } match.setText(desktopName); match.setSubtext(i18n("Switch to desktop %1", desktop)); match.setRelevance(relevance); return match; } Plasma::QueryMatch WindowsRunner::windowMatch(const KWindowInfo& info, WindowAction action, qreal relevance, Plasma::QueryMatch::Type type) { Plasma::QueryMatch match(this); match.setType(type); match.setData(QString(QString::number((int)action) + QLatin1Char('_') + QString::number(info.win()))); match.setIcon(m_icons[info.win()]); match.setText(info.name()); QString desktopName; int desktop = info.desktop(); if (desktop == NET::OnAllDesktops) { desktop = KWindowSystem::currentDesktop(); } if (desktop <= m_desktopNames.size()) { desktopName = m_desktopNames[desktop - 1]; } else { desktopName = KWindowSystem::desktopName(desktop); } switch (action) { case CloseAction: match.setSubtext(i18n("Close running window on %1", desktopName)); break; case MinimizeAction: match.setSubtext(i18n("(Un)minimize running window on %1", desktopName)); break; case MaximizeAction: match.setSubtext(i18n("Maximize/restore running window on %1", desktopName)); break; case FullscreenAction: match.setSubtext(i18n("Toggle fullscreen for running window on %1", desktopName)); break; case ShadeAction: match.setSubtext(i18n("(Un)shade running window on %1", desktopName)); break; case KeepAboveAction: match.setSubtext(i18n("Toggle keep above for running window on %1", desktopName)); break; case KeepBelowAction: match.setSubtext(i18n("Toggle keep below running window on %1", desktopName)); break; case ActivateAction: default: match.setSubtext(i18n("Activate running window on %1", desktopName)); break; } match.setRelevance(relevance); return match; } bool WindowsRunner::actionSupported(const KWindowInfo& info, WindowAction action) { switch (action) { case CloseAction: return info.actionSupported(NET::ActionClose); case MinimizeAction: return info.actionSupported(NET::ActionMinimize); case MaximizeAction: return info.actionSupported(NET::ActionMax); case ShadeAction: return info.actionSupported(NET::ActionShade); case FullscreenAction: return info.actionSupported(NET::ActionFullScreen); case KeepAboveAction: case KeepBelowAction: case ActivateAction: default: return true; } } #include "windowsrunner.moc" diff --git a/runners/windows/windowsrunner.h b/runners/windows/windowsrunner.h index a106af8b0..f4a303c4b 100644 --- a/runners/windows/windowsrunner.h +++ b/runners/windows/windowsrunner.h @@ -1,69 +1,68 @@ /*************************************************************************** * Copyright 2009 by Martin Gräßlin * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #ifndef WINDOWSRUNNER_H #define WINDOWSRUNNER_H #include #include class KWindowInfo; class WindowsRunner : public Plasma::AbstractRunner { Q_OBJECT public: WindowsRunner(QObject* parent, const QVariantList &args); ~WindowsRunner() override; void match(Plasma::RunnerContext& context) override; void run(const Plasma::RunnerContext& context, const Plasma::QueryMatch& match) override; private Q_SLOTS: void prepareForMatchSession(); void matchSessionComplete(); void gatherInfo(); private: enum WindowAction { ActivateAction, CloseAction, MinimizeAction, MaximizeAction, FullscreenAction, ShadeAction, KeepAboveAction, KeepBelowAction }; Plasma::QueryMatch desktopMatch(int desktop, qreal relevance = 1.0); Plasma::QueryMatch windowMatch(const KWindowInfo& info, WindowAction action, qreal relevance = 1.0, Plasma::QueryMatch::Type type = Plasma::QueryMatch::ExactMatch); bool actionSupported(const KWindowInfo& info, WindowAction action); QHash m_windows; // protected by m_mutex QHash m_icons; // protected by m_mutex QStringList m_desktopNames; // protected by m_mutex QMutex m_mutex; bool m_inSession : 1; // only used in the main thread - bool m_ready : 1; // protected by m_mutex }; #endif // WINDOWSRUNNER_H