File indexing completed on 2024-04-28 16:54:57
0001 /* 0002 SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "desktopview.h" 0008 #include "containmentconfigview.h" 0009 #include "krunner_interface.h" 0010 #include "shellcorona.h" 0011 0012 #include <QDBusConnection> 0013 #include <QDBusMessage> 0014 #include <QQmlContext> 0015 #include <QQmlEngine> 0016 #include <QQuickItem> 0017 #include <QScreen> 0018 #include <qopenglshaderprogram.h> 0019 0020 #include <KAuthorized> 0021 #include <KStartupInfo> 0022 #include <kactivities/controller.h> 0023 #include <klocalizedstring.h> 0024 #include <kwindowsystem.h> 0025 0026 #include <KPackage/Package> 0027 0028 #include <KWayland/Client/plasmashell.h> 0029 #include <KWayland/Client/surface.h> 0030 0031 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0032 #include <private/qtx11extras_p.h> 0033 #else 0034 #include <QX11Info> 0035 #endif 0036 0037 DesktopView::DesktopView(Plasma::Corona *corona, QScreen *targetScreen) 0038 : PlasmaQuick::ContainmentView(corona, nullptr) 0039 , m_accentColor(Qt::transparent) 0040 , m_windowType(Desktop) 0041 , m_shellSurface(nullptr) 0042 { 0043 QObject::setParent(corona); 0044 0045 // Setting clear color to black makes the panel lose alpha channel on X11. This looks like 0046 // a QtXCB bug, so set clear color only on Wayland to let the compositor optimize rendering. 0047 if (KWindowSystem::isPlatformWayland()) { 0048 setColor(Qt::black); 0049 } 0050 0051 if (targetScreen) { 0052 setScreenToFollow(targetScreen); 0053 } else { 0054 setTitle(corona->kPackage().metadata().name()); 0055 } 0056 0057 setFlags(Qt::Window | Qt::FramelessWindowHint); 0058 rootContext()->setContextProperty(QStringLiteral("desktop"), this); 0059 setSource(corona->kPackage().fileUrl("views", QStringLiteral("Desktop.qml"))); 0060 connect(this, &ContainmentView::containmentChanged, this, &DesktopView::slotContainmentChanged); 0061 0062 QObject::connect(corona, &Plasma::Corona::kPackageChanged, this, &DesktopView::coronaPackageChanged); 0063 0064 KActivities::Controller *m_activityController = new KActivities::Controller(this); 0065 0066 QObject::connect(m_activityController, &KActivities::Controller::activityAdded, this, &DesktopView::candidateContainmentsChanged); 0067 QObject::connect(m_activityController, &KActivities::Controller::activityRemoved, this, &DesktopView::candidateContainmentsChanged); 0068 0069 // KRunner settings 0070 KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("krunnerrc")); 0071 KConfigGroup configGroup(config, "General"); 0072 m_activateKRunnerWhenTypingOnDesktop = configGroup.readEntry("ActivateWhenTypingOnDesktop", true); 0073 0074 m_configWatcher = KConfigWatcher::create(config); 0075 connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) { 0076 if (names.contains(QByteArray("ActivateWhenTypingOnDesktop"))) { 0077 m_activateKRunnerWhenTypingOnDesktop = group.readEntry("ActivateWhenTypingOnDesktop", true); 0078 } 0079 }); 0080 0081 // Accent color setting 0082 connect(static_cast<ShellCorona *>(corona), &ShellCorona::accentColorFromWallpaperEnabledChanged, this, &DesktopView::usedInAccentColorChanged); 0083 connect(this, &DesktopView::usedInAccentColorChanged, this, [this] { 0084 if (!usedInAccentColor()) { 0085 resetAccentColor(); 0086 } 0087 }); 0088 connect(this, &ContainmentView::containmentChanged, this, &DesktopView::slotContainmentChanged); 0089 } 0090 0091 DesktopView::~DesktopView() 0092 { 0093 } 0094 0095 void DesktopView::showEvent(QShowEvent *e) 0096 { 0097 QQuickWindow::showEvent(e); 0098 adaptToScreen(); 0099 } 0100 0101 void DesktopView::setScreenToFollow(QScreen *screen) 0102 { 0103 Q_ASSERT(screen); 0104 if (screen == m_screenToFollow) { 0105 return; 0106 } 0107 0108 if (m_screenToFollow) { 0109 disconnect(m_screenToFollow.data(), &QScreen::geometryChanged, this, &DesktopView::screenGeometryChanged); 0110 } 0111 m_screenToFollow = screen; 0112 setScreen(screen); 0113 connect(m_screenToFollow.data(), &QScreen::geometryChanged, this, &DesktopView::screenGeometryChanged); 0114 0115 QString rectString; 0116 QDebug(&rectString) << screen->geometry(); 0117 setTitle(QStringLiteral("%1 @ %2").arg(corona()->kPackage().metadata().name()).arg(rectString)); 0118 adaptToScreen(); 0119 } 0120 0121 QScreen *DesktopView::screenToFollow() const 0122 { 0123 return m_screenToFollow; 0124 } 0125 0126 void DesktopView::adaptToScreen() 0127 { 0128 ensureWindowType(); 0129 0130 // This happens sometimes, when shutting down the process 0131 if (!m_screenToFollow) { 0132 return; 0133 } 0134 0135 if (m_windowType == Desktop || m_windowType == WindowedDesktop) { 0136 screenGeometryChanged(); 0137 } 0138 } 0139 0140 bool DesktopView::usedInAccentColor() const 0141 { 0142 if (!m_containment) { 0143 return false; 0144 } 0145 0146 const bool notPrimaryDisplay = m_containment->screen() != 0; 0147 if (notPrimaryDisplay) { 0148 return false; 0149 } 0150 0151 return static_cast<ShellCorona *>(corona())->accentColorFromWallpaperEnabled(); 0152 } 0153 0154 QColor DesktopView::accentColor() const 0155 { 0156 return m_accentColor.value_or(QColor(Qt::transparent)); 0157 } 0158 0159 void DesktopView::setAccentColor(const QColor &accentColor) 0160 { 0161 if (accentColor == m_accentColor) { 0162 return; 0163 } 0164 0165 m_accentColor = accentColor; 0166 Q_EMIT accentColorChanged(accentColor); 0167 if (usedInAccentColor()) { 0168 Q_EMIT static_cast<ShellCorona *>(corona())->colorChanged(accentColor); 0169 } 0170 0171 setAccentColorFromWallpaper(accentColor); 0172 } 0173 0174 void DesktopView::resetAccentColor() 0175 { 0176 if (!m_accentColor.has_value()) { 0177 return; 0178 } 0179 0180 m_accentColor.reset(); 0181 Q_EMIT accentColorChanged(Qt::transparent); 0182 } 0183 0184 DesktopView::WindowType DesktopView::windowType() const 0185 { 0186 return m_windowType; 0187 } 0188 0189 void DesktopView::setWindowType(DesktopView::WindowType type) 0190 { 0191 if (m_windowType == type) { 0192 return; 0193 } 0194 0195 m_windowType = type; 0196 0197 adaptToScreen(); 0198 0199 Q_EMIT windowTypeChanged(); 0200 } 0201 0202 void DesktopView::ensureWindowType() 0203 { 0204 // This happens sometimes, when shutting down the process 0205 if (!screen()) { 0206 return; 0207 } 0208 0209 if (m_windowType == Window) { 0210 setFlags(Qt::Window); 0211 KWindowSystem::setType(winId(), NET::Normal); 0212 KWindowSystem::clearState(winId(), NET::FullScreen); 0213 if (m_shellSurface) { 0214 m_shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Normal); 0215 m_shellSurface->setSkipTaskbar(false); 0216 } 0217 0218 } else if (m_windowType == Desktop) { 0219 setFlags(Qt::Window | Qt::FramelessWindowHint); 0220 KWindowSystem::setType(winId(), NET::Desktop); 0221 KWindowSystem::setState(winId(), NET::KeepBelow); 0222 if (m_shellSurface) { 0223 m_shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Desktop); 0224 m_shellSurface->setSkipTaskbar(true); 0225 } 0226 0227 } else if (m_windowType == WindowedDesktop) { 0228 KWindowSystem::setType(winId(), NET::Normal); 0229 KWindowSystem::clearState(winId(), NET::FullScreen); 0230 setFlags(Qt::FramelessWindowHint | flags()); 0231 if (m_shellSurface) { 0232 m_shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Normal); 0233 m_shellSurface->setSkipTaskbar(false); 0234 } 0235 0236 } else if (m_windowType == FullScreen) { 0237 setFlags(Qt::Window); 0238 KWindowSystem::setType(winId(), NET::Normal); 0239 KWindowSystem::setState(winId(), NET::FullScreen); 0240 if (m_shellSurface) { 0241 m_shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Normal); 0242 m_shellSurface->setSkipTaskbar(false); 0243 } 0244 } 0245 } 0246 0247 DesktopView::SessionType DesktopView::sessionType() const 0248 { 0249 if (qobject_cast<ShellCorona *>(corona())) { 0250 return ShellSession; 0251 } else { 0252 return ApplicationSession; 0253 } 0254 } 0255 0256 QVariantMap DesktopView::candidateContainmentsGraphicItems() const 0257 { 0258 QVariantMap map; 0259 if (!containment()) { 0260 return map; 0261 } 0262 0263 for (auto cont : corona()->containmentsForScreen(containment()->screen())) { 0264 map[cont->activity()] = cont->property("_plasma_graphicObject"); 0265 } 0266 return map; 0267 } 0268 0269 Q_INVOKABLE QString DesktopView::fileFromPackage(const QString &key, const QString &fileName) 0270 { 0271 return corona()->kPackage().filePath(key.toUtf8(), fileName); 0272 } 0273 0274 bool DesktopView::event(QEvent *e) 0275 { 0276 if (e->type() == QEvent::PlatformSurface) { 0277 switch (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType()) { 0278 case QPlatformSurfaceEvent::SurfaceCreated: 0279 setupWaylandIntegration(); 0280 ensureWindowType(); 0281 break; 0282 case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: 0283 delete m_shellSurface; 0284 m_shellSurface = nullptr; 0285 break; 0286 } 0287 } else if (e->type() == QEvent::FocusOut) { 0288 m_krunnerText.clear(); 0289 } 0290 0291 return PlasmaQuick::ContainmentView::event(e); 0292 } 0293 0294 bool DesktopView::handleKRunnerTextInput(QKeyEvent *e) 0295 { 0296 // allow only Shift and GroupSwitch modifiers 0297 if (e->modifiers() & ~Qt::ShiftModifier & ~Qt::GroupSwitchModifier) { 0298 return false; 0299 } 0300 bool krunnerTextChanged = false; 0301 const QString eventText = e->text(); 0302 for (const QChar ch : eventText) { 0303 if (!ch.isPrint()) { 0304 continue; 0305 } 0306 if (ch.isSpace() && m_krunnerText.isEmpty()) { 0307 continue; 0308 } 0309 m_krunnerText += ch; 0310 krunnerTextChanged = true; 0311 } 0312 if (krunnerTextChanged) { 0313 const QString interface(QStringLiteral("org.kde.krunner")); 0314 if (!KAuthorized::authorize(QStringLiteral("run_command"))) { 0315 return false; 0316 } 0317 org::kde::krunner::App krunner(interface, QStringLiteral("/App"), QDBusConnection::sessionBus()); 0318 krunner.query(m_krunnerText); 0319 return true; 0320 } 0321 return false; 0322 } 0323 0324 void DesktopView::keyPressEvent(QKeyEvent *e) 0325 { 0326 ContainmentView::keyPressEvent(e); 0327 0328 if (e->isAccepted()) { 0329 return; 0330 } 0331 0332 if (e->key() == Qt::Key_Escape && KWindowSystem::showingDesktop()) { 0333 KWindowSystem::setShowingDesktop(false); 0334 e->accept(); 0335 return; 0336 } 0337 0338 if (!m_activateKRunnerWhenTypingOnDesktop) { 0339 return; 0340 } 0341 0342 // When a key is pressed on desktop when nothing else is active forward the key to krunner 0343 if (handleKRunnerTextInput(e)) { 0344 e->accept(); 0345 return; 0346 } 0347 } 0348 0349 void DesktopView::showConfigurationInterface(Plasma::Applet *applet) 0350 { 0351 if (m_configView) { 0352 if (m_configView->applet() != applet) { 0353 m_configView->hide(); 0354 m_configView->deleteLater(); 0355 } else { 0356 m_configView->show(); 0357 auto window = qobject_cast<QWindow *>(m_configView); 0358 if (window && QX11Info::isPlatformX11()) { 0359 KStartupInfo::setNewStartupId(window, QX11Info::nextStartupId()); 0360 } 0361 m_configView->requestActivate(); 0362 return; 0363 } 0364 } 0365 0366 if (!applet || !applet->containment()) { 0367 return; 0368 } 0369 0370 Plasma::Containment *cont = qobject_cast<Plasma::Containment *>(applet); 0371 0372 if (cont && cont->isContainment() && cont->containmentType() == Plasma::Types::DesktopContainment) { 0373 m_configView = new ContainmentConfigView(cont); 0374 // if we changed containment with the config open, relaunch the config dialog but for the new containment 0375 // third arg is used to disconnect when the config closes 0376 connect(this, &ContainmentView::containmentChanged, m_configView.data(), [this]() { 0377 showConfigurationInterface(containment()); 0378 }); 0379 } else { 0380 m_configView = new PlasmaQuick::ConfigView(applet); 0381 } 0382 m_configView->init(); 0383 m_configView->setTransientParent(this); 0384 m_configView->show(); 0385 m_configView->requestActivate(); 0386 0387 auto window = qobject_cast<QWindow *>(m_configView); 0388 if (window && QX11Info::isPlatformX11()) { 0389 KStartupInfo::setNewStartupId(window, QX11Info::nextStartupId()); 0390 } 0391 m_configView->requestActivate(); 0392 } 0393 0394 void DesktopView::slotContainmentChanged() 0395 { 0396 if (m_containment) { 0397 disconnect(m_containment, &Plasma::Containment::screenChanged, this, &DesktopView::slotScreenChanged); 0398 } 0399 0400 m_containment = containment(); 0401 0402 if (m_containment) { 0403 connect(m_containment, &Plasma::Containment::screenChanged, this, &DesktopView::slotScreenChanged); 0404 slotScreenChanged(m_containment->screen()); 0405 } 0406 } 0407 0408 void DesktopView::slotScreenChanged(int newId) 0409 { 0410 if (m_containmentScreenId == newId) { 0411 return; 0412 } 0413 0414 m_containmentScreenId = newId; 0415 Q_EMIT usedInAccentColorChanged(); 0416 } 0417 0418 void DesktopView::screenGeometryChanged() 0419 { 0420 const QRect geo = m_screenToFollow->geometry(); 0421 // qDebug() << "newGeometry" << this << geo << geometry(); 0422 setGeometry(geo); 0423 if (m_shellSurface) { 0424 m_shellSurface->setPosition(geo.topLeft()); 0425 } 0426 Q_EMIT geometryChanged(); 0427 } 0428 0429 void DesktopView::coronaPackageChanged(const KPackage::Package &package) 0430 { 0431 setContainment(nullptr); 0432 setSource(package.fileUrl("views", QStringLiteral("Desktop.qml"))); 0433 } 0434 0435 void DesktopView::setupWaylandIntegration() 0436 { 0437 if (m_shellSurface) { 0438 // already setup 0439 return; 0440 } 0441 if (ShellCorona *c = qobject_cast<ShellCorona *>(corona())) { 0442 using namespace KWayland::Client; 0443 PlasmaShell *interface = c->waylandPlasmaShellInterface(); 0444 if (!interface) { 0445 return; 0446 } 0447 Surface *s = Surface::fromWindow(this); 0448 if (!s) { 0449 return; 0450 } 0451 m_shellSurface = interface->createSurface(s, this); 0452 m_shellSurface->setPosition(m_screenToFollow->geometry().topLeft()); 0453 } 0454 } 0455 0456 void DesktopView::setAccentColorFromWallpaper(const QColor &accentColor) 0457 { 0458 if (!usedInAccentColor()) { 0459 return; 0460 } 0461 QDBusMessage applyAccentColor = QDBusMessage::createMethodCall("org.kde.plasmashell.accentColor", "/AccentColor", "", "setAccentColor"); 0462 applyAccentColor << accentColor.rgba(); 0463 QDBusConnection::sessionBus().send(applyAccentColor); 0464 }