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 }