File indexing completed on 2024-04-28 16:45:12

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