File indexing completed on 2024-04-28 05:35:58
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 "screenpool.h" 0011 #include "shellcorona.h" 0012 0013 #include <QDBusConnection> 0014 #include <QDBusMessage> 0015 #include <QQmlContext> 0016 #include <QQmlEngine> 0017 #include <QQuickItem> 0018 #include <QScreen> 0019 #include <qopenglshaderprogram.h> 0020 0021 #include <PlasmaQuick/AppletQuickItem> 0022 0023 #include <KAuthorized> 0024 #include <KStartupInfo> 0025 #include <KX11Extras> 0026 #include <klocalizedstring.h> 0027 #include <kwindowsystem.h> 0028 #include <plasmaactivities/controller.h> 0029 0030 #include <KPackage/Package> 0031 0032 #include <LayerShellQt/Window> 0033 0034 #include <private/qtx11extras_p.h> 0035 0036 using namespace Qt::StringLiterals; 0037 0038 DesktopView::DesktopView(Plasma::Corona *corona, QScreen *targetScreen) 0039 : PlasmaQuick::ContainmentView(corona, nullptr) 0040 , m_accentColor(Qt::transparent) 0041 { 0042 QObject::setParent(corona); 0043 0044 setColor(Qt::black); 0045 setFlags(Qt::Window | Qt::FramelessWindowHint); 0046 0047 if (KWindowSystem::isPlatformWayland()) { 0048 m_layerWindow = LayerShellQt::Window::get(this); 0049 m_layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityOnDemand); 0050 m_layerWindow->setExclusiveZone(-1); 0051 m_layerWindow->setLayer(LayerShellQt::Window::LayerBackground); 0052 m_layerWindow->setScope(QStringLiteral("desktop")); 0053 m_layerWindow->setCloseOnDismissed(false); 0054 } else { 0055 KX11Extras::setType(winId(), NET::Desktop); 0056 KX11Extras::setState(winId(), NET::KeepBelow); 0057 } 0058 0059 if (targetScreen) { 0060 setScreenToFollow(targetScreen); 0061 } else { 0062 setTitle(corona->kPackage().metadata().name()); 0063 } 0064 0065 rootContext()->setContextProperty(QStringLiteral("desktop"), this); 0066 setSource(corona->kPackage().fileUrl("views", QStringLiteral("Desktop.qml"))); 0067 connect(this, &ContainmentView::containmentChanged, this, &DesktopView::slotContainmentChanged); 0068 0069 QObject::connect(corona, &Plasma::Corona::kPackageChanged, this, &DesktopView::coronaPackageChanged); 0070 0071 KActivities::Controller *m_activityController = new KActivities::Controller(this); 0072 0073 QObject::connect(m_activityController, &KActivities::Controller::activityAdded, this, &DesktopView::candidateContainmentsChanged); 0074 QObject::connect(m_activityController, &KActivities::Controller::activityRemoved, this, &DesktopView::candidateContainmentsChanged); 0075 0076 // KRunner settings 0077 KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("krunnerrc")); 0078 KConfigGroup configGroup(config, u"General"_s); 0079 m_activateKRunnerWhenTypingOnDesktop = configGroup.readEntry("ActivateWhenTypingOnDesktop", true); 0080 0081 m_configWatcher = KConfigWatcher::create(config); 0082 connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) { 0083 if (names.contains(QByteArray("ActivateWhenTypingOnDesktop"))) { 0084 m_activateKRunnerWhenTypingOnDesktop = group.readEntry("ActivateWhenTypingOnDesktop", true); 0085 } 0086 }); 0087 0088 // Accent color setting 0089 connect(static_cast<ShellCorona *>(corona), &ShellCorona::accentColorFromWallpaperEnabledChanged, this, &DesktopView::usedInAccentColorChanged); 0090 connect(this, &DesktopView::usedInAccentColorChanged, this, [this] { 0091 if (!usedInAccentColor()) { 0092 resetAccentColor(); 0093 } 0094 }); 0095 connect(this, &ContainmentView::containmentChanged, this, &DesktopView::slotContainmentChanged); 0096 } 0097 0098 DesktopView::~DesktopView() 0099 { 0100 } 0101 0102 void DesktopView::showEvent(QShowEvent *e) 0103 { 0104 QQuickWindow::showEvent(e); 0105 adaptToScreen(); 0106 } 0107 0108 void DesktopView::setScreenToFollow(QScreen *screen) 0109 { 0110 Q_ASSERT(screen); 0111 if (screen == m_screenToFollow) { 0112 return; 0113 } 0114 0115 // layer surfaces can't be moved between outputs, so hide and show the window on a new output 0116 const bool remap = m_layerWindow && isVisible(); 0117 if (remap) { 0118 setVisible(false); 0119 } 0120 0121 if (m_screenToFollow) { 0122 disconnect(m_screenToFollow.data(), &QScreen::geometryChanged, this, &DesktopView::screenGeometryChanged); 0123 } 0124 m_screenToFollow = screen; 0125 setScreen(screen); 0126 connect(m_screenToFollow.data(), &QScreen::geometryChanged, this, &DesktopView::screenGeometryChanged); 0127 0128 if (remap) { 0129 setVisible(true); 0130 } 0131 0132 QString rectString; 0133 QDebug(&rectString) << screen->geometry(); 0134 setTitle(QStringLiteral("%1 @ %2").arg(corona()->kPackage().metadata().name()).arg(rectString)); 0135 adaptToScreen(); 0136 } 0137 0138 QScreen *DesktopView::screenToFollow() const 0139 { 0140 return m_screenToFollow; 0141 } 0142 0143 void DesktopView::adaptToScreen() 0144 { 0145 // This happens sometimes, when shutting down the process 0146 if (!m_screenToFollow) { 0147 return; 0148 } 0149 0150 screenGeometryChanged(); 0151 } 0152 0153 bool DesktopView::usedInAccentColor() const 0154 { 0155 if (!m_containment) { 0156 return false; 0157 } 0158 0159 const bool notPrimaryDisplay = m_containment->screen() != 0; 0160 if (notPrimaryDisplay) { 0161 return false; 0162 } 0163 0164 return static_cast<ShellCorona *>(corona())->accentColorFromWallpaperEnabled(); 0165 } 0166 0167 QColor DesktopView::accentColor() const 0168 { 0169 return m_accentColor.value_or(QColor(Qt::transparent)); 0170 } 0171 0172 void DesktopView::setAccentColor(const QColor &accentColor) 0173 { 0174 if (accentColor == m_accentColor) { 0175 return; 0176 } 0177 0178 m_accentColor = accentColor; 0179 Q_EMIT accentColorChanged(accentColor); 0180 if (usedInAccentColor()) { 0181 Q_EMIT static_cast<ShellCorona *>(corona())->colorChanged(accentColor); 0182 } 0183 0184 setAccentColorFromWallpaper(accentColor); 0185 } 0186 0187 void DesktopView::resetAccentColor() 0188 { 0189 if (!m_accentColor.has_value()) { 0190 return; 0191 } 0192 0193 m_accentColor.reset(); 0194 Q_EMIT accentColorChanged(Qt::transparent); 0195 } 0196 0197 #if PROJECT_VERSION_PATCH >= 80 || PROJECT_VERSION_MINOR >= 80 0198 bool DesktopView::showPreviewBanner() const 0199 { 0200 static const bool shouldShowPreviewBanner = 0201 !KConfigGroup(KSharedConfig::openConfig("kdeglobals"), u"General"_s).readEntry("HideDesktopPreviewBanner", false); 0202 return shouldShowPreviewBanner; 0203 } 0204 0205 QString DesktopView::previewBannerTitle() const 0206 { 0207 // Plasma 6 pre-release versions 0208 if constexpr (PROJECT_VERSION_MAJOR == 5 && PROJECT_VERSION_MINOR >= 80) { 0209 if constexpr (PROJECT_VERSION_PATCH == 80) { 0210 // Development 0211 return i18nc("@label %1 is the Plasma version", "KDE Plasma 6.0 Dev"); 0212 } else if constexpr (PROJECT_VERSION_MINOR == 80) { 0213 // Alpha, 5.80.0 0214 return i18nc("@label %1 is the Plasma version", "KDE Plasma 6.0 Alpha"); 0215 } else if constexpr (PROJECT_VERSION_MINOR == 90) { 0216 // Beta 1, 5.90.0 0217 return i18nc("@label %1 is the Plasma version", "KDE Plasma 6.0 Beta 1"); 0218 } else if constexpr (PROJECT_VERSION_MINOR == 91) { 0219 // Beta 2, 5.91.0 0220 return i18nc("@label %1 is the Plasma version", "KDE Plasma 6.0 Beta 2"); 0221 } else if constexpr (PROJECT_VERSION_MINOR == 92) { 0222 // RC1, 5.92.0 0223 return i18nc("@label %1 is the Plasma version, RC meaning Release Candidate", "KDE Plasma 6.0 RC1"); 0224 } else if constexpr (PROJECT_VERSION_MINOR == 93) { 0225 // RC2, 5.93.0 0226 return i18nc("@label %1 is the Plasma version, RC meaning Release Candidate", "KDE Plasma 6.0 RC2"); 0227 } 0228 } 0229 0230 /* 0231 * Versions are reported as follows: 0232 * Development, 5.27.80 -> KDE Plasma 6.0 Dev 0233 * Beta, 5.27.90 -> KDE Plasma 6.0 Beta 0234 * Development, 6.0.80 -> KDE Plasma 6.1 Dev 0235 * Beta, 6.0.90 -> KDE Plasma 6.1 Beta 0236 * Beta, 6.0.91 -> KDE Plasma 6.1 Beta 2 0237 */ 0238 0239 // finalMajor, finalMinor is the final version in the line and 0240 // should be updated after the final Plasma 6 release 0241 constexpr int finalMajor = 5; 0242 constexpr int finalMinor = 27; 0243 0244 // Incremented minor, which is zeroed and major incremented when 0245 // we reach the final version in the major release line 0246 constexpr int major = (PROJECT_VERSION_MAJOR == finalMajor && PROJECT_VERSION_MINOR == finalMinor) ? PROJECT_VERSION_MAJOR + 1 : PROJECT_VERSION_MAJOR; 0247 constexpr int minor = (PROJECT_VERSION_MAJOR == finalMajor && PROJECT_VERSION_MINOR == finalMinor) ? 0 : PROJECT_VERSION_MINOR + 1; 0248 const QString version = QStringLiteral("%1.%2").arg(QString::number(major), QString::number(minor)); 0249 0250 if constexpr (PROJECT_VERSION_PATCH == 80) { 0251 // Development version 0252 return i18nc("@label %1 is the Plasma version", "KDE Plasma %1 Dev", version); 0253 } else if constexpr (PROJECT_VERSION_PATCH >= 90) { 0254 // Beta version 0255 if constexpr (PROJECT_VERSION_PATCH == 90) { 0256 return i18nc("@label %1 is the Plasma version", "KDE Plasma %1 Beta", version); 0257 } else { 0258 constexpr int betaNumber = PROJECT_VERSION_PATCH - 89; 0259 return i18nc("@label %1 is the Plasma version, %2 is the beta release number", "KDE Plasma %1 Beta %2", version, betaNumber); 0260 } 0261 } else { 0262 // Unrecognised version 0263 return i18nc("@label %1 is the Plasma version", "KDE Plasma %1", WORKSPACE_VERSION_STRING); 0264 } 0265 } 0266 0267 QString DesktopView::previewBannerText() const 0268 { 0269 return i18nc("@info:usagetip", "Visit bugs.kde.org to report issues"); 0270 } 0271 #endif 0272 0273 QVariantMap DesktopView::candidateContainmentsGraphicItems() const 0274 { 0275 QVariantMap map; 0276 if (!containment()) { 0277 return map; 0278 } 0279 0280 for (auto cont : corona()->containmentsForScreen(containment()->screen())) { 0281 map[cont->activity()] = QVariant::fromValue(PlasmaQuick::AppletQuickItem::itemForApplet(cont)); 0282 } 0283 return map; 0284 } 0285 0286 Q_INVOKABLE QString DesktopView::fileFromPackage(const QString &key, const QString &fileName) 0287 { 0288 return corona()->kPackage().filePath(key.toUtf8(), fileName); 0289 } 0290 0291 bool DesktopView::event(QEvent *e) 0292 { 0293 if (e->type() == QEvent::FocusOut) { 0294 m_krunnerText.clear(); 0295 } 0296 0297 return PlasmaQuick::ContainmentView::event(e); 0298 } 0299 0300 bool DesktopView::handleKRunnerTextInput(QKeyEvent *e) 0301 { 0302 // allow only Shift and GroupSwitch modifiers 0303 if (e->modifiers() & ~Qt::ShiftModifier & ~Qt::GroupSwitchModifier) { 0304 return false; 0305 } 0306 bool krunnerTextChanged = false; 0307 const QString eventText = e->text(); 0308 for (const QChar ch : eventText) { 0309 if (!ch.isPrint()) { 0310 continue; 0311 } 0312 if (ch.isSpace() && m_krunnerText.isEmpty()) { 0313 continue; 0314 } 0315 m_krunnerText += ch; 0316 krunnerTextChanged = true; 0317 } 0318 if (krunnerTextChanged) { 0319 const QString interface(QStringLiteral("org.kde.krunner")); 0320 if (!KAuthorized::authorize(QStringLiteral("run_command"))) { 0321 return false; 0322 } 0323 org::kde::krunner::App krunner(interface, QStringLiteral("/App"), QDBusConnection::sessionBus()); 0324 krunner.query(m_krunnerText); 0325 return true; 0326 } 0327 return false; 0328 } 0329 0330 void DesktopView::keyPressEvent(QKeyEvent *e) 0331 { 0332 ContainmentView::keyPressEvent(e); 0333 0334 if (e->isAccepted()) { 0335 return; 0336 } 0337 0338 if (e->key() == Qt::Key_Escape && KWindowSystem::showingDesktop()) { 0339 KWindowSystem::setShowingDesktop(false); 0340 e->accept(); 0341 return; 0342 } 0343 0344 if (!m_activateKRunnerWhenTypingOnDesktop) { 0345 return; 0346 } 0347 0348 // When a key is pressed on desktop when nothing else is active forward the key to krunner 0349 if (handleKRunnerTextInput(e)) { 0350 e->accept(); 0351 return; 0352 } 0353 } 0354 0355 void DesktopView::showConfigurationInterface(Plasma::Applet *applet) 0356 { 0357 if (m_configView) { 0358 if (m_configView->applet() != applet) { 0359 m_configView->hide(); 0360 m_configView->deleteLater(); 0361 } else { 0362 m_configView->show(); 0363 auto window = qobject_cast<QWindow *>(m_configView); 0364 if (window && QX11Info::isPlatformX11()) { 0365 KStartupInfo::setNewStartupId(window, QX11Info::nextStartupId()); 0366 } 0367 m_configView->requestActivate(); 0368 return; 0369 } 0370 } 0371 0372 if (!applet || !applet->containment()) { 0373 return; 0374 } 0375 0376 Plasma::Containment *cont = qobject_cast<Plasma::Containment *>(applet); 0377 0378 if (cont && cont->isContainment() && cont->containmentType() == Plasma::Containment::Desktop) { 0379 m_configView = new ContainmentConfigView(cont); 0380 // if we changed containment with the config open, relaunch the config dialog but for the new containment 0381 // third arg is used to disconnect when the config closes 0382 connect(this, &ContainmentView::containmentChanged, m_configView.data(), [this]() { 0383 if (containment()->property("wallpaperGraphicsObject").value<QObject *>()) { 0384 showConfigurationInterface(containment()); 0385 } else { 0386 // BUG 407619: wallpaperConfiguration is invalid after changing layout 0387 connect(containment(), &Plasma::Containment::wallpaperGraphicsObjectChanged, this, [this] { 0388 disconnect(static_cast<Plasma::Containment *>(sender()), &Plasma::Containment::wallpaperGraphicsObjectChanged, this, nullptr); 0389 showConfigurationInterface(static_cast<Plasma::Containment *>(sender())); 0390 }); 0391 } 0392 }); 0393 } else { 0394 m_configView = new PlasmaQuick::ConfigView(applet); 0395 } 0396 m_configView->init(); 0397 m_configView->setTransientParent(this); 0398 m_configView->show(); 0399 m_configView->requestActivate(); 0400 0401 auto window = qobject_cast<QWindow *>(m_configView); 0402 if (window && QX11Info::isPlatformX11()) { 0403 KStartupInfo::setNewStartupId(window, QX11Info::nextStartupId()); 0404 } 0405 m_configView->requestActivate(); 0406 } 0407 0408 void DesktopView::slotContainmentChanged() 0409 { 0410 if (m_containment) { 0411 disconnect(m_containment, &Plasma::Containment::screenChanged, this, &DesktopView::slotScreenChanged); 0412 } 0413 0414 m_containment = containment(); 0415 0416 if (m_containment) { 0417 connect(m_containment, &Plasma::Containment::screenChanged, this, &DesktopView::slotScreenChanged); 0418 slotScreenChanged(m_containment->screen()); 0419 } 0420 } 0421 0422 void DesktopView::slotScreenChanged(int newId) 0423 { 0424 if (m_containmentScreenId == newId) { 0425 return; 0426 } 0427 0428 m_containmentScreenId = newId; 0429 Q_EMIT usedInAccentColorChanged(); 0430 } 0431 0432 void DesktopView::screenGeometryChanged() 0433 { 0434 setGeometry(m_screenToFollow->geometry()); 0435 Q_EMIT geometryChanged(); 0436 } 0437 0438 void DesktopView::coronaPackageChanged(const KPackage::Package &package) 0439 { 0440 setContainment(nullptr); 0441 setSource(package.fileUrl("views", QStringLiteral("Desktop.qml"))); 0442 } 0443 0444 void DesktopView::setAccentColorFromWallpaper(const QColor &accentColor) 0445 { 0446 if (!usedInAccentColor()) { 0447 return; 0448 } 0449 QDBusMessage applyAccentColor = QDBusMessage::createMethodCall("org.kde.plasmashell.accentColor", "/AccentColor", "", "setAccentColor"); 0450 applyAccentColor << accentColor.rgba(); 0451 QDBusConnection::sessionBus().send(applyAccentColor); 0452 }