File indexing completed on 2024-04-21 05:27:33

0001 /*
0002 SPDX-FileCopyrightText: 2004 Chris Howells <howells@kde.org>
0003 SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
0004 
0005 SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 #include "greeterapp.h"
0008 #include "kscreensaversettingsbase.h"
0009 #include "lnf_integration.h"
0010 #include "noaccessnetworkaccessmanagerfactory.h"
0011 #include "powermanagement.h"
0012 #include "wallpaper_integration.h"
0013 
0014 #include <config-kscreenlocker.h>
0015 #include <iostream>
0016 #include <kscreenlocker_greet_logging.h>
0017 
0018 #include <LayerShellQt/Window>
0019 
0020 // KDE
0021 #include <KAuthorized>
0022 #include <KConfigPropertyMap>
0023 #include <KCrash>
0024 #include <KLocalizedContext>
0025 #include <KScreenDpms/Dpms>
0026 #include <KWindowSystem>
0027 #include <PlasmaQuick/QuickViewSharedEngine>
0028 
0029 #include <KUser>
0030 // Plasma
0031 #include <KPackage/Package>
0032 #include <KPackage/PackageLoader>
0033 // Qt
0034 #include <QAbstractNativeEventFilter>
0035 #include <QClipboard>
0036 #include <QDBusConnection>
0037 #include <QKeyEvent>
0038 #include <QMimeData>
0039 #include <QSocketNotifier>
0040 #include <QThread>
0041 #include <QTimer>
0042 #include <qscreen.h>
0043 
0044 #include <QQmlContext>
0045 #include <QQmlEngine>
0046 #include <QQmlExpression>
0047 #include <QQmlProperty>
0048 #include <QQuickItem>
0049 #include <QQuickView>
0050 
0051 #include <private/qtx11extras_p.h>
0052 // Wayland
0053 #include <wayland-client.h>
0054 #include <wayland-ksld-client-protocol.h>
0055 // X11
0056 #include <X11/Xatom.h>
0057 #include <X11/Xlib.h>
0058 #include <fixx11h.h>
0059 //
0060 #include <xcb/xcb.h>
0061 
0062 #include "pamauthenticator.h"
0063 #include "pamauthenticators.h"
0064 
0065 // this is usable to fake a "screensaver" installation for testing
0066 // *must* be "0" for every public commit!
0067 #define TEST_SCREENSAVER 0
0068 
0069 static const QString s_plasmaShellService = QStringLiteral("org.kde.plasmashell");
0070 static const QString s_osdServicePath = QStringLiteral("/org/kde/osdService");
0071 static const QString s_osdServiceInterface = QStringLiteral("org.kde.osdService");
0072 
0073 using namespace Qt::Literals;
0074 
0075 namespace ScreenLocker
0076 {
0077 // disable DrKonqi as the crash dialog blocks the restart of the locker
0078 void disableDrKonqi()
0079 {
0080     KCrash::setDrKonqiEnabled(false);
0081 }
0082 // run immediately, before Q_CORE_STARTUP functions
0083 // that would enable drkonqi
0084 Q_CONSTRUCTOR_FUNCTION(disableDrKonqi)
0085 
0086 // Verify that a package or its fallback is using the right API
0087 bool verifyPackageApi(const KPackage::Package &package)
0088 {
0089     if (package.metadata().value("X-Plasma-APIVersion", QStringLiteral("1")).toInt() >= 2) {
0090         return true;
0091     }
0092 
0093     if (!package.filePath("lockscreenmainscript").contains(package.path())) {
0094         // The current package does not contain the lock screen and we are
0095         // using the fallback package. So check to see if that package has
0096         // the right version instead.
0097         if (package.fallbackPackage().metadata().value("X-Plasma-APIVersion", QStringLiteral("1")).toInt() >= 2) {
0098             return true;
0099         }
0100     }
0101 
0102     return false;
0103 }
0104 
0105 class FocusOutEventFilter : public QAbstractNativeEventFilter
0106 {
0107 public:
0108     bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override
0109     {
0110         Q_UNUSED(result)
0111         if (qstrcmp(eventType, "xcb_generic_event_t") != 0) {
0112             return false;
0113         }
0114         xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(message);
0115         if ((event->response_type & ~0x80) == XCB_FOCUS_OUT) {
0116             return true;
0117         }
0118         return false;
0119     }
0120 };
0121 
0122 class WallpaperItem : public WallpaperIntegration
0123 {
0124     Q_OBJECT
0125 public:
0126     explicit WallpaperItem(QQuickItem *parent = nullptr)
0127         : WallpaperIntegration(parent)
0128     {
0129         setConfig(KScreenSaverSettingsBase::self()->sharedConfig());
0130         setPluginName(KScreenSaverSettingsBase::self()->wallpaperPluginId());
0131         init();
0132     }
0133 };
0134 
0135 // App
0136 UnlockApp::UnlockApp(int &argc, char **argv)
0137     : QGuiApplication(argc, argv)
0138     , m_resetRequestIgnoreTimer(new QTimer(this))
0139     , m_delayedLockTimer(nullptr)
0140     , m_testing(false)
0141     , m_ignoreRequests(false)
0142     , m_immediateLock(false)
0143     , m_graceTime(0)
0144     , m_noLock(false)
0145     , m_defaultToSwitchUser(false)
0146     , m_lnfIntegration(new LnFIntegration(this))
0147 {
0148     auto interactive = std::make_unique<PamAuthenticator>(KSCREENLOCKER_PAM_SERVICE, KUser().loginName());
0149     std::vector<std::unique_ptr<PamAuthenticator>> noninteractive;
0150     noninteractive.push_back(std::make_unique<PamAuthenticator>(KSCREENLOCKER_PAM_FINGERPRINT_SERVICE, KUser().loginName(), PamAuthenticator::Fingerprint));
0151     noninteractive.push_back(std::make_unique<PamAuthenticator>(KSCREENLOCKER_PAM_SMARTCARD_SERVICE, KUser().loginName(), PamAuthenticator::Smartcard));
0152     m_authenticators = new PamAuthenticators(std::move(interactive), std::move(noninteractive), this);
0153     initialize();
0154 
0155     if (QX11Info::isPlatformX11()) {
0156         installNativeEventFilter(new FocusOutEventFilter);
0157     }
0158 }
0159 
0160 UnlockApp::~UnlockApp()
0161 {
0162     // workaround QTBUG-55460
0163     // will be fixed when themes port to QQC2
0164     for (auto view : std::as_const(m_views)) {
0165         if (QQuickItem *focusItem = view->activeFocusItem()) {
0166             focusItem->setFocus(false);
0167         }
0168     }
0169     qDeleteAll(m_views);
0170 
0171     if (m_ksldInterface) {
0172         org_kde_ksld_destroy(m_ksldInterface);
0173     }
0174     if (m_display) {
0175         wl_display_disconnect(m_display);
0176     }
0177 }
0178 
0179 void UnlockApp::initialize()
0180 {
0181     // set up the request ignore timeout, so that multiple requests to sleep/suspend/shutdown
0182     // are not processed in quick (and confusing) succession)
0183     m_resetRequestIgnoreTimer->setSingleShot(true);
0184     m_resetRequestIgnoreTimer->setInterval(2000);
0185     connect(m_resetRequestIgnoreTimer, &QTimer::timeout, this, &UnlockApp::resetRequestIgnore);
0186 
0187     KScreenSaverSettingsBase::self()->load();
0188     KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
0189     KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE");
0190     m_packageName = cg.readEntry("LookAndFeelPackage", QString());
0191     if (!m_packageName.isEmpty()) {
0192         package.setPath(m_packageName);
0193     }
0194     if (!KScreenSaverSettingsBase::theme().isEmpty()) {
0195         package.setPath(KScreenSaverSettingsBase::theme());
0196     }
0197     if (!verifyPackageApi(package)) {
0198         qCWarning(KSCREENLOCKER_GREET) << "Lockscreen QML outdated, falling back to default";
0199         package.setPath(QStringLiteral("org.kde.breeze.desktop"));
0200     }
0201 
0202     m_mainQmlPath = package.fileUrl("lockscreenmainscript");
0203 
0204     // The root of wallpaper packages will be a WallpaperItem we provide the same API via WallpaperIntegration
0205     // although with most things nooping
0206     constexpr const char *uri = "org.kde.plasma.plasmoid";
0207     qmlRegisterType<WallpaperItem>(uri, 2, 0, "WallpaperItem");
0208 
0209     m_wallpaperPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Wallpaper"));
0210     m_wallpaperPackage.setPath(KScreenSaverSettingsBase::self()->wallpaperPluginId());
0211 
0212     m_lnfIntegration->setPackage(package);
0213     m_lnfIntegration->setConfig(KScreenSaverSettingsBase::self()->sharedConfig());
0214     m_lnfIntegration->init();
0215 
0216     const KUser user;
0217     const QString fullName = user.property(KUser::FullName).toString();
0218 
0219     m_userName = fullName.isEmpty() ? user.loginName() : fullName;
0220     m_userImage = user.faceIconPath();
0221 
0222     installEventFilter(this);
0223 
0224     QDBusConnection::sessionBus().connect(s_plasmaShellService,
0225                                           s_osdServicePath,
0226                                           s_osdServiceInterface,
0227                                           QStringLiteral("osdProgress"),
0228                                           this,
0229                                           SLOT(osdProgress(QString, int, int, QString)));
0230     QDBusConnection::sessionBus()
0231         .connect(s_plasmaShellService, s_osdServicePath, s_osdServiceInterface, QStringLiteral("osdText"), this, SLOT(osdText(QString, QString)));
0232 
0233     connect(PowerManagement::instance(), &PowerManagement::canSuspendChanged, this, &UnlockApp::updateCanSuspend);
0234     connect(PowerManagement::instance(), &PowerManagement::canHibernateChanged, this, &UnlockApp::updateCanHibernate);
0235 }
0236 
0237 QWindow *UnlockApp::getActiveScreen()
0238 {
0239     QWindow *activeScreen = nullptr;
0240 
0241     if (m_views.isEmpty()) {
0242         return activeScreen;
0243     }
0244 
0245     for (PlasmaQuick::QuickViewSharedEngine *view : std::as_const(m_views)) {
0246         if (view->geometry().contains(QCursor::pos())) {
0247             activeScreen = view;
0248             break;
0249         }
0250     }
0251     if (!activeScreen) {
0252         activeScreen = m_views.first();
0253     }
0254 
0255     return activeScreen;
0256 }
0257 
0258 PlasmaQuick::SharedQmlEngine *UnlockApp::loadWallpaperPlugin(PlasmaQuick::QuickViewSharedEngine *view)
0259 {
0260     if (!m_wallpaperPackage.isValid()) {
0261         qCWarning(KSCREENLOCKER_GREET) << "Error loading the wallpaper, no valid package loaded";
0262         return nullptr;
0263     }
0264 
0265     auto qmlObject = new PlasmaQuick::SharedQmlEngine(view);
0266     qmlObject->setInitializationDelayed(true);
0267     qmlObject->setSource(QUrl::fromLocalFile(m_wallpaperPackage.filePath("mainscript")));
0268     view->setProperty("wallpaperGraphicsObject", QVariant::fromValue(qmlObject));
0269     connect(qmlObject, &PlasmaQuick::SharedQmlEngine::finished, this, [this, qmlObject, view] {
0270         auto item = qobject_cast<WallpaperItem *>(qmlObject->rootObject());
0271         if (!item) {
0272             qCWarning(KSCREENLOCKER_GREET) << "Root item not a WallpaperItem";
0273             return;
0274         };
0275         qmlObject->rootContext()->setContextProperty(QStringLiteral("wallpaper"), item);
0276         view->rootContext()->setContextProperty(QStringLiteral("wallpaper"), item);
0277         view->rootContext()->setContextProperty(QStringLiteral("wallpaperIntegration"), item);
0278     });
0279     return qmlObject;
0280 }
0281 
0282 void UnlockApp::setWallpaperItemProperties(PlasmaQuick::SharedQmlEngine *wallpaperObject, PlasmaQuick::QuickViewSharedEngine *view)
0283 {
0284     if (!wallpaperObject) {
0285         return;
0286     }
0287 
0288     auto item = qobject_cast<QQuickItem *>(wallpaperObject->rootObject());
0289     if (!item) {
0290         qCWarning(KSCREENLOCKER_GREET) << "Wallpaper needs to be a QtQuick Item";
0291         return;
0292     }
0293     item->setParentItem(view->rootObject());
0294     item->setZ(-1000);
0295 
0296     // set anchors
0297     QQmlExpression expr(wallpaperObject->engine()->rootContext(), item, QStringLiteral("parent"));
0298     QQmlProperty prop(item, QStringLiteral("anchors.fill"));
0299     prop.write(expr.evaluate());
0300 }
0301 
0302 void UnlockApp::initialViewSetup()
0303 {
0304     qmlRegisterUncreatableType<PamAuthenticator>("org.kde.kscreenlocker",
0305                                                  1,
0306                                                  0,
0307                                                  "Authenticator",
0308                                                  QStringLiteral("authenticators must be obtained from the context"));
0309     qmlRegisterUncreatableType<PamAuthenticators>("org.kde.kscreenlocker",
0310                                                   1,
0311                                                   0,
0312                                                   "Authenticators",
0313                                                   QStringLiteral("authenticators must be obtained from the context"));
0314     for (QScreen *screen : screens()) {
0315         handleScreen(screen);
0316     }
0317     connect(this, &UnlockApp::screenAdded, this, &UnlockApp::handleScreen);
0318 }
0319 
0320 void UnlockApp::handleScreen(QScreen *screen)
0321 {
0322     if (screen->geometry().isNull()) {
0323         return;
0324     }
0325     auto *view = createViewForScreen(screen);
0326     m_views << view;
0327     connect(this, &QGuiApplication::screenRemoved, view, [this, view, screen](QScreen *removedScreen) {
0328         if (removedScreen != screen) {
0329             return;
0330         }
0331         m_views.removeOne(view);
0332         delete view;
0333     });
0334 }
0335 
0336 PlasmaQuick::QuickViewSharedEngine *UnlockApp::createViewForScreen(QScreen *screen)
0337 {
0338     // create the view
0339     auto *view = new PlasmaQuick::QuickViewSharedEngine();
0340 
0341     view->setColor(Qt::black);
0342     view->setScreen(screen);
0343     view->setGeometry(screen->geometry());
0344 
0345     connect(screen, &QScreen::geometryChanged, view, [view](const QRect &geo) {
0346         view->setGeometry(geo);
0347     });
0348 
0349     view->engine()->setProperty("_kirigamiTheme", QStringLiteral("KirigamiPlasmaStyle"));
0350     view->engine()->rootContext()->setContextObject(new KLocalizedContext(view->engine().get()));
0351     auto oldFactory = view->engine()->networkAccessManagerFactory();
0352     view->engine()->setNetworkAccessManagerFactory(nullptr);
0353     delete oldFactory;
0354     view->engine()->setNetworkAccessManagerFactory(new NoAccessNetworkAccessManagerFactory);
0355 
0356     if (!m_testing) {
0357         if (QX11Info::isPlatformX11()) {
0358             view->setFlags(Qt::X11BypassWindowManagerHint);
0359         } else {
0360             view->setFlags(Qt::FramelessWindowHint);
0361         }
0362     }
0363 
0364     if (m_ksldInterface) {
0365         view->create();
0366         org_kde_ksld_x11window(m_ksldInterface, view->winId());
0367         wl_display_flush(m_display);
0368     }
0369 
0370     // engine stuff
0371     QQmlContext *context = view->engine()->rootContext();
0372     connect(view->engine().get(), &QQmlEngine::quit, this, [this]() {
0373         if (m_authenticators->isUnlocked()) {
0374             std::cout << "Unlocked" << std::endl;
0375             QCoreApplication::quit();
0376         } else {
0377             qCWarning(KSCREENLOCKER_GREET) << "Greeter tried to quit without being unlocked";
0378         }
0379     });
0380 
0381     context->setContextProperty(QStringLiteral("kscreenlocker_userName"), m_userName);
0382     context->setContextProperty(QStringLiteral("kscreenlocker_userImage"), m_userImage);
0383     context->setContextProperty(QStringLiteral("authenticator"), m_authenticators);
0384     context->setContextProperty(QStringLiteral("org_kde_plasma_screenlocker_greeter_interfaceVersion"), 2);
0385     context->setContextProperty(QStringLiteral("org_kde_plasma_screenlocker_greeter_view"), view);
0386     context->setContextProperty(QStringLiteral("defaultToSwitchUser"), m_defaultToSwitchUser);
0387     context->setContextProperty(QStringLiteral("config"), m_lnfIntegration->configuration());
0388 
0389     auto wallpaperObj = loadWallpaperPlugin(view);
0390     if (auto object = view->property("wallpaperGraphicsObject").value<PlasmaQuick::SharedQmlEngine *>()) {
0391         // initialize with our size to avoid as much resize events as possible
0392         object->completeInitialization({
0393             {QStringLiteral("width"), view->width()},
0394             {QStringLiteral("height"), view->height()},
0395         });
0396     }
0397 
0398     view->setSource(m_mainQmlPath);
0399     // on error, load the fallback lockscreen to not lock the user out of the system
0400     if (view->status() != QQmlComponent::Ready) {
0401         static const QUrl fallbackUrl(QUrl(QStringLiteral("qrc:/fallbacktheme/LockScreen.qml")));
0402 
0403         qCWarning(KSCREENLOCKER_GREET) << "Failed to load lockscreen QML, falling back to built-in locker";
0404         for (const auto &error : view->errors()) {
0405             qCWarning(KSCREENLOCKER_GREET) << error;
0406         }
0407 
0408         m_mainQmlPath = fallbackUrl;
0409         view->setSource(fallbackUrl);
0410 
0411         if (view->status() != QQmlComponent::Ready) {
0412             qCWarning(KSCREENLOCKER_GREET) << "Failed to load the fallback lockscreen QML, something went really wrong! Terminating...";
0413             for (const auto &error : view->errors()) {
0414                 qCWarning(KSCREENLOCKER_GREET) << error;
0415             }
0416             std::terminate();
0417         }
0418     }
0419     view->setResizeMode(PlasmaQuick::QuickViewSharedEngine::SizeRootObjectToView);
0420 
0421     // we need to set this wallpaper properties separately after the lockscreen QML is loaded
0422     // this is because we need to anchor to the view that gets loaded
0423     setWallpaperItemProperties(wallpaperObj, view);
0424 
0425     QQmlProperty lockProperty(view->rootObject(), QStringLiteral("locked"));
0426     lockProperty.write(m_immediateLock || (!m_noLock && !m_delayedLockTimer));
0427 
0428     QQmlProperty sleepProperty(view->rootObject(), QStringLiteral("suspendToRamSupported"));
0429     sleepProperty.write(PowerManagement::instance()->canSuspend());
0430     if (view->rootObject() && view->rootObject()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("suspendToRam()").constData()) != -1) {
0431         connect(view->rootObject(), SIGNAL(suspendToRam()), SLOT(suspendToRam()));
0432     }
0433 
0434     QQmlProperty hibernateProperty(view->rootObject(), QStringLiteral("suspendToDiskSupported"));
0435     hibernateProperty.write(PowerManagement::instance()->canHibernate());
0436     if (view->rootObject() && view->rootObject()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("suspendToDisk()").constData()) != -1) {
0437         connect(view->rootObject(), SIGNAL(suspendToDisk()), SLOT(suspendToDisk()));
0438     }
0439 
0440     // verify that the engine's controller didn't change
0441     Q_ASSERT(dynamic_cast<NoAccessNetworkAccessManagerFactory *>(view->engine()->networkAccessManagerFactory()));
0442 
0443     if (KWindowSystem::isPlatformWayland()) {
0444         if (auto layerShellWindow = LayerShellQt::Window::get(view)) {
0445             layerShellWindow->setExclusiveZone(-1);
0446             layerShellWindow->setLayer(LayerShellQt::Window::LayerOverlay);
0447             layerShellWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityExclusive);
0448         }
0449     }
0450 
0451     // showFullScreen is implicit on X11 (through geometry and hints) and Wayland (layer-shell)
0452     view->show();
0453     view->raise();
0454 
0455     auto onFrameSwapped = [this, view] {
0456         markViewsAsVisible(view);
0457     };
0458     connect(view, &QQuickWindow::frameSwapped, this, onFrameSwapped, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
0459 
0460     return view;
0461 }
0462 
0463 void UnlockApp::markViewsAsVisible(PlasmaQuick::QuickViewSharedEngine *view)
0464 {
0465     QQmlProperty showProperty(view->rootObject(), QStringLiteral("viewVisible"));
0466     showProperty.write(true);
0467     // random state update, actually rather required on init only
0468     QMetaObject::invokeMethod(this, "getFocus", Qt::QueuedConnection);
0469 
0470     auto mime1 = new QMimeData;
0471     // Effectively we want to clear the clipboard
0472     // however some clipboard managers (like klipper with it's default settings)
0473     // will prevent an empty clipboard
0474     // we need some non-empty non-text mimeData to replace the clipboard so we don't leak real data to a user pasting into the text field
0475     // as the clipboard is cleared on close, klipper will then put the original text back when we exit
0476     mime1->setData(QStringLiteral("x-kde-lockscreen"), QByteArrayLiteral("empty"));
0477     // ownership is transferred
0478     QGuiApplication::clipboard()->setMimeData(mime1, QClipboard::Clipboard);
0479 
0480     auto mime2 = new QMimeData;
0481     mime2->setData(QStringLiteral("x-kde-lockscreen"), QByteArrayLiteral("empty"));
0482     QGuiApplication::clipboard()->setMimeData(mime2, QClipboard::Selection);
0483 }
0484 
0485 void UnlockApp::getFocus()
0486 {
0487     QWindow *activeScreen = getActiveScreen();
0488 
0489     if (!activeScreen) {
0490         return;
0491     }
0492     // this loop is required to make the qml/graphicsscene properly handle the shared keyboard input
0493     // ie. "type something into the box of every greeter"
0494     for (PlasmaQuick::QuickViewSharedEngine *view : std::as_const(m_views)) {
0495         if (!m_testing) {
0496             view->setKeyboardGrabEnabled(true); // TODO - check whether this still works in master!
0497         }
0498     }
0499     // activate window and grab input to be sure it really ends up there.
0500     // focus setting is still required for proper internal QWidget state (and eg. visual reflection)
0501     if (!m_testing) {
0502         activeScreen->setKeyboardGrabEnabled(true); // TODO - check whether this still works in master!
0503     }
0504     activeScreen->requestActivate();
0505 }
0506 
0507 void UnlockApp::setLockedPropertyOnViews()
0508 {
0509     delete m_delayedLockTimer;
0510     m_delayedLockTimer = nullptr;
0511 
0512     for (PlasmaQuick::QuickViewSharedEngine *view : std::as_const(m_views)) {
0513         QQmlProperty lockProperty(view->rootObject(), QStringLiteral("locked"));
0514         lockProperty.write(true);
0515     }
0516 }
0517 
0518 void UnlockApp::resetRequestIgnore()
0519 {
0520     m_ignoreRequests = false;
0521 }
0522 
0523 void UnlockApp::suspendToRam()
0524 {
0525     if (m_ignoreRequests) {
0526         return;
0527     }
0528 
0529     m_ignoreRequests = true;
0530     m_resetRequestIgnoreTimer->start();
0531     m_authenticators->cancel();
0532 
0533     PowerManagement::instance()->suspend();
0534 }
0535 
0536 void UnlockApp::suspendToDisk()
0537 {
0538     if (m_ignoreRequests) {
0539         return;
0540     }
0541 
0542     m_ignoreRequests = true;
0543     m_resetRequestIgnoreTimer->start();
0544     m_authenticators->cancel();
0545 
0546     PowerManagement::instance()->hibernate();
0547 }
0548 
0549 void UnlockApp::setTesting(bool enable)
0550 {
0551     m_testing = enable;
0552     if (m_views.isEmpty()) {
0553         return;
0554     }
0555     if (enable) {
0556         // remove bypass window manager hint
0557         for (PlasmaQuick::QuickViewSharedEngine *view : std::as_const(m_views)) {
0558             view->setFlags(view->flags() & ~Qt::X11BypassWindowManagerHint);
0559         }
0560     } else {
0561         for (PlasmaQuick::QuickViewSharedEngine *view : std::as_const(m_views)) {
0562             view->setFlags(view->flags() | Qt::X11BypassWindowManagerHint);
0563         }
0564     }
0565 }
0566 
0567 void UnlockApp::setTheme(const QString &theme)
0568 {
0569     if (theme.isEmpty() || !m_testing) {
0570         return;
0571     }
0572 
0573     m_packageName = theme;
0574     KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
0575     package.setPath(m_packageName);
0576 
0577     m_mainQmlPath = package.fileUrl("lockscreenmainscript");
0578 }
0579 
0580 void UnlockApp::setImmediateLock(bool immediate)
0581 {
0582     m_immediateLock = immediate;
0583 }
0584 
0585 void UnlockApp::lockImmediately()
0586 {
0587     setImmediateLock(true);
0588     setLockedPropertyOnViews();
0589 }
0590 
0591 bool UnlockApp::eventFilter(QObject *obj, QEvent *event)
0592 {
0593     if (obj != this && event->type() == QEvent::Show) {
0594         PlasmaQuick::QuickViewSharedEngine *view = nullptr;
0595         for (PlasmaQuick::QuickViewSharedEngine *v : std::as_const(m_views)) {
0596             if (v == obj) {
0597                 view = v;
0598                 break;
0599             }
0600         }
0601         if (view && view->winId() && QX11Info::isPlatformX11()) {
0602             // showing greeter view window, set property
0603             static Atom tag = XInternAtom(QX11Info::display(), "_KDE_SCREEN_LOCKER", False);
0604             XChangeProperty(QX11Info::display(), view->winId(), tag, tag, 32, PropModeReplace, nullptr, 0);
0605         }
0606         // no further processing
0607         return false;
0608     }
0609 
0610     if (event->type() == QEvent::MouseButtonPress && QX11Info::isPlatformX11()) {
0611         if (getActiveScreen()) {
0612             getActiveScreen()->requestActivate();
0613         }
0614         return false;
0615     }
0616 
0617     if (event->type() == QEvent::KeyPress) { // react if saver is visible
0618         shareEvent(event, qobject_cast<PlasmaQuick::QuickViewSharedEngine *>(obj));
0619         return false; // we don't care
0620     } else if (event->type() == QEvent::KeyRelease) { // conditionally reshow the saver
0621         QKeyEvent *ke = static_cast<QKeyEvent *>(event);
0622         if (ke->key() != Qt::Key_Escape) {
0623             shareEvent(event, qobject_cast<PlasmaQuick::QuickViewSharedEngine *>(obj));
0624             return false; // irrelevant
0625         } else {
0626             auto dpms = new KScreen::Dpms(this);
0627             if (dpms->isSupported()) {
0628                 connect(dpms, &KScreen::Dpms::hasPendingChangesChanged, this, [dpms](bool hasPendingChanges) {
0629                     if (!hasPendingChanges) {
0630                         dpms->deleteLater();
0631                     }
0632                 });
0633                 dpms->switchMode(KScreen::Dpms::Off);
0634             } else {
0635                 dpms->deleteLater();
0636             }
0637         }
0638         return true; // don't pass
0639     }
0640 
0641     return false;
0642 }
0643 
0644 /*
0645  * This function forwards an event from one greeter window to all others
0646  * It's used to have the keyboard operate on all greeter windows (on every screen)
0647  * at once so that the user gets visual feedback on the screen he's looking at -
0648  * even if the focus is actually on a powered off screen.
0649  */
0650 
0651 void UnlockApp::shareEvent(QEvent *e, PlasmaQuick::QuickViewSharedEngine *from)
0652 {
0653     // from can be NULL any time (because the parameter is passed as qobject_cast)
0654     // m_views.contains(from) is atm. supposed to be true but required if any further
0655     // QQuickView are added (which are not part of m_views)
0656     // this makes "from" an optimization (nullptr check aversion)
0657     if (from && m_views.contains(from)) {
0658         // NOTICE any recursion in the event sharing will prevent authentication on multiscreen setups!
0659         // Any change in regarded event processing shall be tested thoroughly!
0660         removeEventFilter(this); // prevent recursion!
0661         const bool accepted = e->isAccepted(); // store state
0662         for (PlasmaQuick::QuickViewSharedEngine *view : std::as_const(m_views)) {
0663             if (view != from) {
0664                 QCoreApplication::sendEvent(view, e);
0665                 e->setAccepted(accepted);
0666             }
0667         }
0668         installEventFilter(this);
0669     }
0670 }
0671 
0672 void UnlockApp::setGraceTime(int milliseconds)
0673 {
0674     m_graceTime = milliseconds;
0675     if (milliseconds < 0 || m_delayedLockTimer || m_noLock || m_immediateLock) {
0676         return;
0677     }
0678     m_delayedLockTimer = new QTimer(this);
0679     m_delayedLockTimer->setSingleShot(true);
0680     connect(m_delayedLockTimer, &QTimer::timeout, this, &UnlockApp::setLockedPropertyOnViews);
0681     m_delayedLockTimer->start(m_graceTime);
0682 }
0683 
0684 void UnlockApp::setNoLock(bool noLock)
0685 {
0686     m_noLock = noLock;
0687 }
0688 
0689 void UnlockApp::setDefaultToSwitchUser(bool defaultToSwitchUser)
0690 {
0691     m_defaultToSwitchUser = defaultToSwitchUser;
0692 }
0693 
0694 void UnlockApp::setKsldSocket(int socket)
0695 {
0696     m_display = wl_display_connect_to_fd(socket);
0697     auto socketnotifier = new QSocketNotifier(socket, QSocketNotifier::Read, this);
0698     connect(socketnotifier, &QSocketNotifier::activated, this, [this] {
0699         wl_display_dispatch(m_display);
0700     });
0701     auto registry = wl_display_get_registry(m_display);
0702     auto globalAdded = [](void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
0703         Q_UNUSED(version)
0704         if (interface != "org_kde_ksld"_ba) {
0705             return;
0706         }
0707         auto self = static_cast<UnlockApp *>(data);
0708         // bind version 1 as we dropped all the V2 features
0709         self->m_ksldInterface = static_cast<org_kde_ksld *>(wl_registry_bind(registry, name, &org_kde_ksld_interface, 1));
0710         for (auto v : std::as_const(self->m_views)) {
0711             org_kde_ksld_x11window(self->m_ksldInterface, v->winId());
0712             wl_display_flush(self->m_display);
0713         }
0714     };
0715     auto noopGlobalRemove = [](void *, struct wl_registry *, uint32_t) {};
0716     static const wl_registry_listener registryListener = wl_registry_listener{globalAdded, noopGlobalRemove};
0717     wl_registry_add_listener(registry, &registryListener, this);
0718     wl_display_flush(m_display);
0719 }
0720 
0721 void UnlockApp::osdProgress(const QString &icon, int percent, int maximumPercent, const QString &additionalText)
0722 {
0723     for (auto v : std::as_const(m_views)) {
0724         auto osd = v->rootObject()->findChild<QQuickItem *>(QStringLiteral("onScreenDisplay"));
0725         if (!osd) {
0726             continue;
0727         }
0728         // Update max value first to prevent value from being clamped
0729         osd->setProperty("osdMaxValue", maximumPercent);
0730         osd->setProperty("osdValue", percent);
0731         osd->setProperty("osdAdditionalText", additionalText);
0732         osd->setProperty("showingProgress", true);
0733         osd->setProperty("icon", icon);
0734         QMetaObject::invokeMethod(osd, "show");
0735     }
0736 }
0737 
0738 void UnlockApp::osdText(const QString &icon, const QString &additionalText)
0739 {
0740     for (auto v : std::as_const(m_views)) {
0741         auto osd = v->rootObject()->findChild<QQuickItem *>(QStringLiteral("onScreenDisplay"));
0742         if (!osd) {
0743             continue;
0744         }
0745         osd->setProperty("showingProgress", false);
0746         osd->setProperty("osdValue", additionalText);
0747         osd->setProperty("icon", icon);
0748         QMetaObject::invokeMethod(osd, "show");
0749     }
0750 }
0751 
0752 void UnlockApp::updateCanSuspend()
0753 {
0754     for (auto it = m_views.constBegin(), end = m_views.constEnd(); it != end; ++it) {
0755         QQmlProperty sleepProperty((*it)->rootObject(), QStringLiteral("suspendToRamSupported"));
0756         sleepProperty.write(PowerManagement::instance()->canSuspend());
0757     }
0758 }
0759 
0760 void UnlockApp::updateCanHibernate()
0761 {
0762     for (auto it = m_views.constBegin(), end = m_views.constEnd(); it != end; ++it) {
0763         QQmlProperty hibernateProperty((*it)->rootObject(), QStringLiteral("suspendToDiskSupported"));
0764         hibernateProperty.write(PowerManagement::instance()->canHibernate());
0765     }
0766 }
0767 
0768 } // namespace
0769 
0770 #include "greeterapp.moc"