File indexing completed on 2024-05-05 05:38:21

0001 /*
0002     SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "view.h"
0008 
0009 #include <QAction>
0010 #include <QClipboard>
0011 #include <QDebug>
0012 #include <QGuiApplication>
0013 #include <QPlatformSurfaceEvent>
0014 #include <QQmlContext>
0015 #include <QQmlEngine>
0016 #include <QQuickItem>
0017 #include <QScreen>
0018 
0019 #include <KAuthorized>
0020 #include <KCrash>
0021 #include <KIO/CommandLauncherJob>
0022 #include <KLocalizedString>
0023 #include <KService>
0024 #include <KWindowEffects>
0025 #include <KWindowSystem>
0026 #include <KX11Extras>
0027 
0028 #include <LayerShellQt/Window>
0029 #include <qnamespace.h>
0030 
0031 #include "appadaptor.h"
0032 #include "x11windowscreenrelativepositioner.h"
0033 
0034 KCONFIGGROUP_DECLARE_ENUM_QOBJECT(View, HistoryBehavior)
0035 
0036 View::View(PlasmaQuick::SharedQmlEngine *engine, QWindow *)
0037     : PlasmaQuick::PlasmaWindow()
0038     , m_engine(engine)
0039     , m_floating(false)
0040 {
0041     KCrash::initialize();
0042     qmlRegisterUncreatableType<View>("org.kde.krunner.private.view", 1, 0, "HistoryBehavior", u"Only for enums"_s);
0043 
0044     if (KWindowSystem::isPlatformX11()) {
0045         m_x11Positioner = new X11WindowScreenRelativePositioner(this);
0046     }
0047 
0048     // used only by screen readers
0049     setTitle(i18n("KRunner"));
0050 
0051     m_config = KConfigGroup(KSharedConfig::openConfig(), u"General"_s);
0052     m_stateData = KSharedConfig::openConfig(u"krunnerstaterc"_s, //
0053                                             KConfig::NoGlobals,
0054                                             QStandardPaths::GenericDataLocation)
0055                       ->group(u"General"_s);
0056     m_configWatcher = KConfigWatcher::create(KSharedConfig::openConfig());
0057     connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group) {
0058         const QLatin1String pluginsGrp("Plugins");
0059         if (group.name() == QLatin1String("General")) {
0060             loadConfig();
0061         } else if (group.name() == pluginsGrp) {
0062             Q_EMIT helpEnabledChanged();
0063         } else if (group.name() == QLatin1String("Favorites") && group.parent().name() == pluginsGrp) {
0064             assignFavoriteIds();
0065         }
0066     });
0067     connect(&m_consumer, &KActivities::Consumer::currentActivityChanged, this, &View::activityChanged);
0068     Q_EMIT activityChanged(m_consumer.currentActivity());
0069 
0070     loadConfig();
0071 
0072     new AppAdaptor(this);
0073     QDBusConnection::sessionBus().registerObject(u"/App"_s, this);
0074 
0075     connect(m_engine, &PlasmaQuick::SharedQmlEngine::finished, this, &View::objectIncubated);
0076     m_engine->engine()->rootContext()->setContextProperty(u"runnerWindow"_s, this);
0077     m_engine->setSource(QUrl(u"qrc:/krunner/RunCommand.qml"_s));
0078     m_engine->completeInitialization();
0079 
0080     auto screenRemoved = [this](QScreen *screen) {
0081         if (screen == this->screen()) {
0082             setScreen(qGuiApp->primaryScreen());
0083             hide();
0084         }
0085     };
0086 
0087     connect(qGuiApp, &QGuiApplication::screenRemoved, this, screenRemoved);
0088     connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &View::slotFocusWindowChanged);
0089 }
0090 
0091 View::~View()
0092 {
0093 }
0094 
0095 void View::objectIncubated()
0096 {
0097     auto item = qobject_cast<QQuickItem *>(m_engine->rootObject());
0098     setMainItem(item);
0099 
0100     auto updateSize = [this]() {
0101         resize(QSize(mainItem()->implicitWidth(), mainItem()->implicitHeight()).grownBy(padding()).boundedTo(screen()->availableSize()));
0102     };
0103 
0104     connect(item, &QQuickItem::implicitHeightChanged, this, updateSize);
0105     connect(this, &View::paddingChanged, this, updateSize);
0106     updateSize();
0107 }
0108 
0109 void View::slotFocusWindowChanged()
0110 {
0111     if (QGuiApplication::focusWindow() && m_requestedClipboardSelection) {
0112         displayWithClipboardContents();
0113     }
0114 
0115     if (!QGuiApplication::focusWindow() && !m_pinned) {
0116         setVisible(false);
0117     }
0118 }
0119 
0120 bool View::freeFloating() const
0121 {
0122     return m_floating;
0123 }
0124 
0125 void View::setFreeFloating(bool floating)
0126 {
0127     m_floating = floating;
0128     if (m_floating) {
0129         KWindowEffects::slideWindow(this, KWindowEffects::NoEdge);
0130         setBorders(Qt::LeftEdge | Qt::TopEdge | Qt::RightEdge | Qt::BottomEdge);
0131     } else {
0132         KWindowEffects::slideWindow(this, KWindowEffects::TopEdge);
0133         setBorders(Qt::LeftEdge | Qt::RightEdge | Qt::BottomEdge);
0134     }
0135 
0136     positionOnScreen();
0137 }
0138 
0139 void View::loadConfig()
0140 {
0141     setFreeFloating(m_config.readEntry("FreeFloating", false));
0142     setRetainPriorSearch(m_config.readEntry("RetainPriorSearch", true));
0143     setPinned(m_stateData.readEntry("Pinned", false));
0144     setHistoryBehavior(m_config.readEntry("historyBehavior", m_historyBehavior));
0145     assignFavoriteIds();
0146 }
0147 
0148 void View::showEvent(QShowEvent *event)
0149 {
0150     if (KWindowSystem::isPlatformX11()) {
0151         KX11Extras::setOnAllDesktops(winId(), true);
0152     }
0153     QQuickWindow::showEvent(event);
0154     requestActivate();
0155     if (KWindowSystem::isPlatformX11()) {
0156         KX11Extras::forceActiveWindow(winId());
0157     }
0158 }
0159 
0160 void View::positionOnScreen()
0161 {
0162     const auto screens = QGuiApplication::screens();
0163     auto screenIt = screens.cend();
0164     if (KWindowSystem::isPlatformWayland() && m_floating) {
0165         auto message = QDBusMessage::createMethodCall("org.kde.KWin", "/KWin", "org.kde.KWin", "activeOutputName");
0166         QDBusReply<QString> reply = QDBusConnection::sessionBus().call(message);
0167         if (reply.isValid()) {
0168             const QString activeOutputName = reply.value();
0169             screenIt = std::find_if(screens.cbegin(), screens.cend(), [&activeOutputName](QScreen *screen) {
0170                 return screen->name() == activeOutputName;
0171             });
0172         }
0173     } else if (KWindowSystem::isPlatformX11()) {
0174         screenIt = std::find_if(screens.cbegin(), screens.cend(), [](QScreen *screen) {
0175             return screen->geometry().contains(QCursor::pos(screen));
0176         });
0177     }
0178 
0179     QScreen *const shownOnScreen = screenIt != screens.cend() ? *screenIt : QGuiApplication::primaryScreen();
0180     setScreen(shownOnScreen);
0181 
0182     QMargins margins;
0183     if (m_floating) {
0184         const QRect r = shownOnScreen->availableGeometry();
0185         margins = QMargins({0, r.height() / 3, 0, 0});
0186     }
0187 
0188     if (KWindowSystem::isPlatformWayland()) {
0189         auto layerWindow = LayerShellQt::Window::get(this);
0190         layerWindow->setAnchors(LayerShellQt::Window::AnchorTop);
0191         layerWindow->setLayer(LayerShellQt::Window::LayerTop);
0192         layerWindow->setScope(u"krunner"_s);
0193         layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityOnDemand);
0194         layerWindow->setMargins(margins);
0195         layerWindow->setScreenConfiguration(m_floating ? LayerShellQt::Window::ScreenFromQWindow : LayerShellQt::Window::ScreenFromCompositor);
0196     } else if (KWindowSystem::isPlatformX11()) {
0197         m_x11Positioner->setAnchors(Qt::TopEdge);
0198         m_x11Positioner->setMargins(margins);
0199         if (m_floating) {
0200             KX11Extras::setOnDesktop(winId(), KX11Extras::currentDesktop());
0201         } else {
0202             KX11Extras::setOnAllDesktops(winId(), true);
0203         }
0204         KX11Extras::setState(winId(), NET::SkipTaskbar | NET::SkipPager);
0205     }
0206 }
0207 
0208 void View::toggleDisplay()
0209 {
0210     if (isVisible() && !QGuiApplication::focusWindow() && KWindowSystem::isPlatformX11()) {
0211         KX11Extras::forceActiveWindow(winId());
0212         return;
0213     }
0214     if (isVisible()) {
0215         setVisible(false);
0216     } else {
0217         display();
0218     }
0219 }
0220 
0221 void View::display()
0222 {
0223     positionOnScreen();
0224     setVisible(true);
0225 }
0226 
0227 void View::displaySingleRunner(const QString &runnerName)
0228 {
0229     display();
0230 
0231     m_engine->rootObject()->setProperty("singleRunner", runnerName);
0232     m_engine->rootObject()->setProperty("query", QString());
0233 }
0234 
0235 void View::displayWithClipboardContents()
0236 {
0237     display();
0238 
0239     // On Wayland we cannot retrieve the clipboard selection until we get the focus
0240     if (QGuiApplication::focusWindow()) {
0241         m_requestedClipboardSelection = false;
0242         m_engine->rootObject()->setProperty("singleRunner", QString());
0243         m_engine->rootObject()->setProperty("query", QGuiApplication::clipboard()->text(QClipboard::Selection));
0244     } else {
0245         m_requestedClipboardSelection = true;
0246     }
0247 }
0248 
0249 void View::query(const QString &term)
0250 {
0251     display();
0252 
0253     m_engine->rootObject()->setProperty("singleRunner", QString());
0254     m_engine->rootObject()->setProperty("query", term);
0255 }
0256 
0257 void View::querySingleRunner(const QString &runnerName, const QString &term)
0258 {
0259     display();
0260 
0261     m_engine->rootObject()->setProperty("singleRunner", runnerName);
0262     m_engine->rootObject()->setProperty("query", term);
0263 }
0264 
0265 bool View::pinned() const
0266 {
0267     return m_pinned;
0268 }
0269 
0270 void View::setPinned(bool pinned)
0271 {
0272     if (m_pinned != pinned) {
0273         m_pinned = pinned;
0274         m_stateData.writeEntry("Pinned", pinned);
0275         Q_EMIT pinnedChanged();
0276     }
0277 }