File indexing completed on 2024-04-28 16:54:27

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 <kdeclarative/qmlobject.h>
0029 
0030 #include <KWayland/Client/connection_thread.h>
0031 #include <KWayland/Client/registry.h>
0032 #include <KWayland/Client/surface.h>
0033 
0034 #include "appadaptor.h"
0035 
0036 View::View(QWindow *)
0037     : PlasmaQuick::Dialog()
0038     , m_offset(.5)
0039     , m_floating(false)
0040 {
0041     setColor(QColor(Qt::transparent));
0042     setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
0043 
0044     KCrash::initialize();
0045 
0046     // used only by screen readers
0047     setTitle(i18n("KRunner"));
0048 
0049     m_config = KConfigGroup(KSharedConfig::openConfig(), "General");
0050     m_stateData = KSharedConfig::openConfig(QStringLiteral("krunnerstaterc"), //
0051                                             KConfig::NoGlobals,
0052                                             QStandardPaths::GenericDataLocation)
0053                       ->group("General");
0054     m_configWatcher = KConfigWatcher::create(KSharedConfig::openConfig());
0055     connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) {
0056         Q_UNUSED(names);
0057         if (group.name() == QLatin1String("General")) {
0058             loadConfig();
0059         } else if (group.name() == QLatin1String("Plugins")) {
0060             Q_EMIT helpEnabledChanged();
0061         }
0062     });
0063 
0064     loadConfig();
0065 
0066     new AppAdaptor(this);
0067     QDBusConnection::sessionBus().registerObject(QStringLiteral("/App"), this);
0068 
0069     m_qmlObj = new KDeclarative::QmlObject(this);
0070     m_qmlObj->setInitializationDelayed(true);
0071     connect(m_qmlObj, &KDeclarative::QmlObject::finished, this, &View::objectIncubated);
0072 
0073     m_qmlObj->engine()->rootContext()->setContextProperty(QStringLiteral("runnerWindow"), this);
0074     m_qmlObj->setSource(QUrl(QStringLiteral("qrc:/krunner/RunCommand.qml")));
0075     m_qmlObj->completeInitialization();
0076 
0077     auto screenRemoved = [this](QScreen *screen) {
0078         if (screen == this->screen()) {
0079             setScreen(qGuiApp->primaryScreen());
0080             hide();
0081         }
0082     };
0083 
0084     auto screenAdded = [this](const QScreen *screen) {
0085         connect(screen, &QScreen::geometryChanged, this, &View::screenGeometryChanged);
0086         screenGeometryChanged();
0087     };
0088 
0089     const auto screens = QGuiApplication::screens();
0090     for (QScreen *s : screens) {
0091         screenAdded(s);
0092     }
0093     connect(qGuiApp, &QGuiApplication::screenAdded, this, screenAdded);
0094     connect(qGuiApp, &QGuiApplication::screenRemoved, this, screenRemoved);
0095 
0096     connect(KX11Extras::self(), &KX11Extras::workAreaChanged, this, &View::resetScreenPos);
0097 
0098     connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &View::slotFocusWindowChanged);
0099 }
0100 
0101 View::~View()
0102 {
0103 }
0104 
0105 void View::objectIncubated()
0106 {
0107     auto mainItem = qobject_cast<QQuickItem *>(m_qmlObj->rootObject());
0108     connect(mainItem, &QQuickItem::widthChanged, this, &View::resetScreenPos);
0109     setMainItem(mainItem);
0110 }
0111 
0112 void View::slotFocusWindowChanged()
0113 {
0114     if (QGuiApplication::focusWindow() && m_requestedClipboardSelection) {
0115         displayWithClipboardContents();
0116     }
0117 
0118     if (!QGuiApplication::focusWindow() && !m_pinned) {
0119         setVisible(false);
0120     }
0121 }
0122 
0123 bool View::freeFloating() const
0124 {
0125     return m_floating;
0126 }
0127 
0128 void View::setFreeFloating(bool floating)
0129 {
0130     if (m_floating == floating) {
0131         return;
0132     }
0133 
0134     m_floating = floating;
0135     if (m_floating) {
0136         setLocation(Plasma::Types::Floating);
0137     } else {
0138         setLocation(Plasma::Types::TopEdge);
0139     }
0140 
0141     positionOnScreen();
0142 }
0143 
0144 void View::loadConfig()
0145 {
0146     setFreeFloating(m_config.readEntry("FreeFloating", false));
0147     setPinned(m_stateData.readEntry("Pinned", false));
0148 }
0149 
0150 bool View::event(QEvent *event)
0151 {
0152     if (KWindowSystem::isPlatformWayland() && event->type() == QEvent::Expose && !dynamic_cast<QExposeEvent *>(event)->region().isNull()) {
0153         auto surface = KWayland::Client::Surface::fromWindow(this);
0154         auto shellSurface = KWayland::Client::PlasmaShellSurface::get(surface);
0155         if (shellSurface && isVisible()) {
0156             shellSurface->setPanelBehavior(KWayland::Client::PlasmaShellSurface::PanelBehavior::WindowsGoBelow);
0157             shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel);
0158             shellSurface->setPanelTakesFocus(true);
0159         }
0160     }
0161     const bool retval = Dialog::event(event);
0162     // QXcbWindow overwrites the state in its show event. There are plans
0163     // to fix this in 5.4, but till then we must explicitly overwrite it
0164     // each time.
0165     bool setState = event->type() == QEvent::Show;
0166     if (event->type() == QEvent::PlatformSurface) {
0167         setState = (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated);
0168     }
0169     if (setState) {
0170         KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager);
0171     }
0172 
0173     return retval;
0174 }
0175 
0176 void View::resizeEvent(QResizeEvent *event)
0177 {
0178     if (event->oldSize().width() != event->size().width()) {
0179         positionOnScreen();
0180     }
0181 }
0182 
0183 void View::showEvent(QShowEvent *event)
0184 {
0185     KX11Extras::setOnAllDesktops(winId(), true);
0186     Dialog::showEvent(event);
0187     positionOnScreen();
0188     requestActivate();
0189 }
0190 
0191 void View::screenGeometryChanged()
0192 {
0193     if (isVisible()) {
0194         positionOnScreen();
0195     }
0196 }
0197 
0198 void View::resetScreenPos()
0199 {
0200     if (isVisible() && !m_floating) {
0201         positionOnScreen();
0202     }
0203 }
0204 
0205 void View::positionOnScreen()
0206 {
0207     if (!m_requestedVisible) {
0208         return;
0209     }
0210 
0211     const auto screens = QGuiApplication::screens();
0212     auto screenIt = screens.cend();
0213     if (KWindowSystem::isPlatformWayland()) {
0214         auto message = QDBusMessage::createMethodCall("org.kde.KWin", "/KWin", "org.kde.KWin", "activeOutputName");
0215         QDBusReply<QString> reply = QDBusConnection::sessionBus().call(message);
0216         if (reply.isValid()) {
0217             const QString activeOutputName = reply.value();
0218             screenIt = std::find_if(screens.cbegin(), screens.cend(), [&activeOutputName](QScreen *screen) {
0219                 return screen->name() == activeOutputName;
0220             });
0221         }
0222     } else if (KWindowSystem::isPlatformX11()) {
0223         screenIt = std::find_if(screens.cbegin(), screens.cend(), [](QScreen *screen) {
0224             return screen->geometry().contains(QCursor::pos(screen));
0225         });
0226     }
0227 
0228     QScreen *const shownOnScreen = screenIt != screens.cend() ? *screenIt : QGuiApplication::primaryScreen();
0229 
0230     // in wayland, QScreen::availableGeometry() returns QScreen::geometry()
0231     // we could get a better value from plasmashell
0232     // BUG: 386114
0233     auto message = QDBusMessage::createMethodCall("org.kde.plasmashell", "/StrutManager", "org.kde.PlasmaShell.StrutManager", "availableScreenRect");
0234     message.setArguments({shownOnScreen->name()});
0235     QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(message);
0236     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
0237 
0238     QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, shownOnScreen]() {
0239         watcher->deleteLater();
0240         QDBusPendingReply<QRect> reply = *watcher;
0241 
0242         const QRect r = reply.isValid() ? reply.value() : shownOnScreen->availableGeometry();
0243 
0244         if (m_floating && !m_customPos.isNull()) {
0245             int x = qBound(r.left(), m_customPos.x(), r.right() - width());
0246             int y = qBound(r.top(), m_customPos.y(), r.bottom() - height());
0247             setPosition(x, y);
0248             PlasmaQuick::Dialog::setVisible(true);
0249             return;
0250         }
0251 
0252         const int w = width();
0253         int x = r.left() + (r.width() * m_offset) - (w / 2);
0254 
0255         int y = r.top();
0256         if (m_floating) {
0257             y += r.height() / 3;
0258         }
0259 
0260         x = qBound(r.left(), x, r.right() - width());
0261         y = qBound(r.top(), y, r.bottom() - height());
0262 
0263         setPosition(x, y);
0264         setLocation(m_floating ? Plasma::Types::Floating : Plasma::Types::TopEdge);
0265         PlasmaQuick::Dialog::setVisible(true);
0266 
0267         if (m_floating) {
0268             KX11Extras::setOnDesktop(winId(), KX11Extras::currentDesktop());
0269             KWindowSystem::setType(winId(), NET::Normal);
0270         } else {
0271             KX11Extras::setOnAllDesktops(winId(), true);
0272         }
0273 
0274         KX11Extras::forceActiveWindow(winId());
0275     });
0276 }
0277 
0278 void View::toggleDisplay()
0279 {
0280     if (isVisible() && !QGuiApplication::focusWindow()) {
0281         KX11Extras::forceActiveWindow(winId());
0282         return;
0283     }
0284     setVisible(!isVisible());
0285 }
0286 
0287 void View::display()
0288 {
0289     setVisible(true);
0290 }
0291 
0292 void View::displaySingleRunner(const QString &runnerName)
0293 {
0294     setVisible(true);
0295 
0296     m_qmlObj->rootObject()->setProperty("runner", runnerName);
0297     m_qmlObj->rootObject()->setProperty("query", QString());
0298 }
0299 
0300 void View::displayWithClipboardContents()
0301 {
0302     setVisible(true);
0303 
0304     // On Wayland we cannot retrieve the clipboard selection until we get the focus
0305     if (QGuiApplication::focusWindow()) {
0306         m_requestedClipboardSelection = false;
0307         m_qmlObj->rootObject()->setProperty("runner", QString());
0308         m_qmlObj->rootObject()->setProperty("query", QGuiApplication::clipboard()->text(QClipboard::Selection));
0309     } else {
0310         m_requestedClipboardSelection = true;
0311     }
0312 }
0313 
0314 void View::query(const QString &term)
0315 {
0316     setVisible(true);
0317 
0318     m_qmlObj->rootObject()->setProperty("runner", QString());
0319     m_qmlObj->rootObject()->setProperty("query", term);
0320 }
0321 
0322 void View::querySingleRunner(const QString &runnerName, const QString &term)
0323 {
0324     setVisible(true);
0325 
0326     m_qmlObj->rootObject()->setProperty("runner", runnerName);
0327     m_qmlObj->rootObject()->setProperty("query", term);
0328 }
0329 
0330 void View::switchUser()
0331 {
0332     QDBusConnection::sessionBus().asyncCall(QDBusMessage::createMethodCall(QStringLiteral("org.kde.ksmserver"),
0333                                                                            QStringLiteral("/KSMServer"),
0334                                                                            QStringLiteral("org.kde.KSMServerInterface"),
0335                                                                            QStringLiteral("openSwitchUserDialog")));
0336 }
0337 
0338 void View::displayConfiguration()
0339 {
0340     auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), {QStringLiteral("plasma/kcms/desktop/kcm_krunnersettings")});
0341     job->start();
0342 }
0343 
0344 void View::setVisible(bool visible)
0345 {
0346     m_requestedVisible = visible;
0347 
0348     if (visible && !m_floating) {
0349         positionOnScreen();
0350     } else {
0351         PlasmaQuick::Dialog::setVisible(visible);
0352     }
0353 }
0354 
0355 bool View::pinned() const
0356 {
0357     return m_pinned;
0358 }
0359 
0360 void View::setPinned(bool pinned)
0361 {
0362     if (m_pinned != pinned) {
0363         m_pinned = pinned;
0364         m_stateData.writeEntry("Pinned", pinned);
0365         Q_EMIT pinnedChanged();
0366     }
0367 }