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 }