File indexing completed on 2024-05-05 05:38:40

0001 /*
0002     SPDX-FileCopyrightText: 2000 Matthias Ettrich <ettrich@kde.org>
0003     SPDX-FileCopyrightText: 2007 Urs Wolfer <uwolfer @ kde.org>
0004 
0005     SPDX-License-Identifier: MIT
0006 */
0007 
0008 #include "shutdowndlg.h"
0009 
0010 #include <QApplication>
0011 #include <QDBusConnection>
0012 #include <QDBusMessage>
0013 #include <QDBusPendingCall>
0014 #include <QDBusPendingCallWatcher>
0015 #include <QDBusPendingReply>
0016 #include <QDBusVariant>
0017 #include <QFile>
0018 #include <QPainter>
0019 #include <QQmlContext>
0020 #include <QQmlEngine>
0021 #include <QQmlPropertyMap>
0022 #include <QQuickItem>
0023 #include <QQuickView>
0024 #include <QStandardPaths>
0025 #include <QTimer>
0026 #include <private/qtx11extras_p.h>
0027 
0028 #include <KAuthorized>
0029 #include <KConfigGroup>
0030 #include <KLocalizedString>
0031 #include <KSharedConfig>
0032 #include <KUser>
0033 #include <KWindowEffects>
0034 #include <KWindowSystem>
0035 #include <KX11Extras>
0036 #include <LayerShellQt/Window>
0037 
0038 #include <netwm.h>
0039 #include <stdio.h>
0040 
0041 #include <X11/Xatom.h>
0042 #include <X11/Xutil.h>
0043 #include <fixx11h.h>
0044 
0045 #include <config-workspace.h>
0046 #include <debug.h>
0047 
0048 static const QString s_login1Service = QStringLiteral("org.freedesktop.login1");
0049 static const QString s_login1Path = QStringLiteral("/org/freedesktop/login1");
0050 static const QString s_dbusPropertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties");
0051 static const QString s_login1ManagerInterface = QStringLiteral("org.freedesktop.login1.Manager");
0052 static const QString s_login1RebootToFirmwareSetup = QStringLiteral("RebootToFirmwareSetup");
0053 
0054 static const QString s_packageKitService = QStringLiteral("org.freedesktop.PackageKit");
0055 static const QString s_packageKitPath = QStringLiteral("/org/freedesktop/PackageKit");
0056 static const QString s_packageKitOfflineInterface = QStringLiteral("org.freedesktop.PackageKit.Offline");
0057 
0058 KSMShutdownDlg::KSMShutdownDlg(QWindow *parent, KWorkSpace::ShutdownType sdtype, QScreen *screen)
0059     : QuickViewSharedEngine(parent)
0060     , m_result(false)
0061 // this is a WType_Popup on purpose. Do not change that! Not
0062 // having a popup here has severe side effects.
0063 {
0064     // window stuff
0065     setColor(QColor(Qt::transparent));
0066     setScreen(screen);
0067 
0068     if (KWindowSystem::isPlatformWayland() && !m_windowed) {
0069         if (auto w = LayerShellQt::Window::get(this)) {
0070             w->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityExclusive);
0071             w->setExclusiveZone(-1);
0072             w->setLayer(LayerShellQt::Window::LayerOverlay);
0073         }
0074     }
0075 
0076     setResizeMode(PlasmaQuick::QuickViewSharedEngine::SizeRootObjectToView);
0077 
0078     // Qt doesn't set this on unmanaged windows
0079     // FIXME: or does it?
0080     if (KWindowSystem::isPlatformX11()) {
0081         XChangeProperty(QX11Info::display(),
0082                         winId(),
0083                         XInternAtom(QX11Info::display(), "WM_WINDOW_ROLE", False),
0084                         XA_STRING,
0085                         8,
0086                         PropModeReplace,
0087                         (unsigned char *)"logoutdialog",
0088                         strlen("logoutdialog"));
0089 
0090         XClassHint classHint;
0091         classHint.res_name = const_cast<char *>("ksmserver-logout-greeter");
0092         classHint.res_class = const_cast<char *>("ksmserver-logout-greeter");
0093         XSetClassHint(QX11Info::display(), winId(), &classHint);
0094     }
0095 
0096     // QQuickView *windowContainer = QQuickView::createWindowContainer(m_view, this);
0097     // windowContainer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0098     QQmlContext *context = rootContext();
0099     context->setContextProperty(QStringLiteral("maysd"), m_session.canShutdown());
0100     context->setContextProperty(QStringLiteral("sdtype"), sdtype);
0101 
0102     QQmlPropertyMap *mapShutdownType = new QQmlPropertyMap(this);
0103     mapShutdownType->insert(QStringLiteral("ShutdownTypeDefault"), QVariant::fromValue<int>(KWorkSpace::ShutdownTypeDefault));
0104     mapShutdownType->insert(QStringLiteral("ShutdownTypeNone"), QVariant::fromValue<int>(KWorkSpace::ShutdownTypeNone));
0105     mapShutdownType->insert(QStringLiteral("ShutdownTypeReboot"), QVariant::fromValue<int>(KWorkSpace::ShutdownTypeReboot));
0106     mapShutdownType->insert(QStringLiteral("ShutdownTypeHalt"), QVariant::fromValue<int>(KWorkSpace::ShutdownTypeHalt));
0107     mapShutdownType->insert(QStringLiteral("ShutdownTypeLogout"), QVariant::fromValue<int>(KWorkSpace::ShutdownTypeLogout));
0108     context->setContextProperty(QStringLiteral("ShutdownType"), mapShutdownType);
0109 
0110     QQmlPropertyMap *mapSpdMethods = new QQmlPropertyMap(this);
0111     mapSpdMethods->insert(QStringLiteral("StandbyState"), m_session.canSuspend());
0112     mapSpdMethods->insert(QStringLiteral("SuspendState"), m_session.canSuspend());
0113     mapSpdMethods->insert(QStringLiteral("HibernateState"), m_session.canHibernate());
0114     context->setContextProperty(QStringLiteral("spdMethods"), mapSpdMethods);
0115     context->setContextProperty(QStringLiteral("canLogout"), m_session.canLogout());
0116 
0117     // Trying to access a non-existent context property throws an error, always create the property and then update it later
0118     context->setContextProperty("rebootToFirmwareSetup", false);
0119 
0120     QDBusMessage message = QDBusMessage::createMethodCall(s_login1Service, s_login1Path, s_dbusPropertiesInterface, QStringLiteral("Get"));
0121     message.setArguments({s_login1ManagerInterface, s_login1RebootToFirmwareSetup});
0122     QDBusPendingReply<QVariant> call = QDBusConnection::systemBus().asyncCall(message);
0123     QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this);
0124     connect(callWatcher, &QDBusPendingCallWatcher::finished, context, [context](QDBusPendingCallWatcher *watcher) {
0125         QDBusPendingReply<QVariant> reply = *watcher;
0126         watcher->deleteLater();
0127 
0128         if (reply.value().toBool()) {
0129             context->setContextProperty("rebootToFirmwareSetup", true);
0130         }
0131     });
0132 
0133     context->setContextProperty("softwareUpdatePending", false);
0134     checkSoftwareUpdatePending();
0135 
0136     // TODO KF6 remove, used to read "BootManager" from kdmrc
0137     context->setContextProperty(QStringLiteral("bootManager"), QStringLiteral("None"));
0138 
0139     // TODO KF6 remove. Unused
0140     context->setContextProperty(QStringLiteral("choose"), false);
0141 
0142     // TODO KF6 remove, used to call KDisplayManager::bootOptions
0143     QStringList rebootOptions;
0144     int def = 0;
0145     QQmlPropertyMap *rebootOptionsMap = new QQmlPropertyMap(this);
0146     rebootOptionsMap->insert(QStringLiteral("options"), QVariant::fromValue(rebootOptions));
0147     rebootOptionsMap->insert(QStringLiteral("default"), QVariant::fromValue(def));
0148     context->setContextProperty(QStringLiteral("rebootOptions"), rebootOptionsMap);
0149 
0150     // engine stuff
0151     engine()->rootContext()->setContextObject(new KLocalizedContext(engine().get()));
0152     engine()->setProperty("_kirigamiTheme", QStringLiteral("KirigamiPlasmaStyle"));
0153 }
0154 
0155 void KSMShutdownDlg::init(const KPackage::Package &package)
0156 {
0157     rootContext()->setContextProperty(QStringLiteral("screenGeometry"), screen()->geometry());
0158 
0159     const QString fileName = package.filePath("logoutmainscript");
0160 
0161     if (QFile::exists(fileName)) {
0162         setSource(package.fileUrl("logoutmainscript"));
0163     } else {
0164         qCWarning(LOGOUT_GREETER) << "Couldn't find a theme for the Shutdown dialog" << fileName;
0165         return;
0166     }
0167 
0168     if (!errors().isEmpty()) {
0169         qCWarning(LOGOUT_GREETER) << errors();
0170     }
0171 
0172     connect(rootObject(), SIGNAL(logoutRequested()), SLOT(slotLogout()));
0173     connect(rootObject(), SIGNAL(haltRequested()), SLOT(slotHalt()));
0174     connect(rootObject(), SIGNAL(suspendRequested(int)), SLOT(slotSuspend(int)));
0175     connect(rootObject(), SIGNAL(rebootRequested()), SLOT(slotReboot()));
0176     connect(rootObject(), SIGNAL(rebootRequested2(int)), SLOT(slotReboot(int)));
0177     connect(rootObject(), SIGNAL(cancelRequested()), SLOT(reject()));
0178     connect(rootObject(), SIGNAL(lockScreenRequested()), SLOT(slotLockScreen()));
0179     connect(rootObject(), SIGNAL(cancelSoftwareUpdateRequested()), SLOT(slotCancelSoftwareUpdate()));
0180 
0181     connect(screen(), &QScreen::geometryChanged, this, [this] {
0182         setGeometry(screen()->geometry());
0183     });
0184 
0185     // decide in backgroundcontrast whether doing things darker or lighter
0186     // set backgroundcontrast here, because in QEvent::PlatformSurface
0187     // is too early and we don't have the root object yet
0188     const QColor backgroundColor = rootObject() ? rootObject()->property("backgroundColor").value<QColor>() : QColor();
0189     KWindowEffects::enableBackgroundContrast(this, true, 0.4, (backgroundColor.value() > 128 ? 1.6 : 0.3), 1.7);
0190     if (m_windowed) {
0191         show();
0192     } else {
0193         showFullScreen();
0194         setFlag(Qt::FramelessWindowHint);
0195     }
0196     requestActivate();
0197 
0198     if (KWindowSystem::isPlatformX11()) {
0199         KX11Extras::setState(winId(), NET::SkipTaskbar | NET::SkipPager);
0200     }
0201 
0202     setKeyboardGrabEnabled(true);
0203     KWindowEffects::enableBlurBehind(this, true);
0204 }
0205 
0206 void KSMShutdownDlg::resizeEvent(QResizeEvent *e)
0207 {
0208     PlasmaQuick::QuickViewSharedEngine::resizeEvent(e);
0209 
0210     if (KX11Extras::compositingActive()) {
0211         // TODO: reenable window mask when we are without composite?
0212         //        clearMask();
0213     } else {
0214         //        setMask(m_view->mask());
0215     }
0216 }
0217 
0218 void KSMShutdownDlg::slotLogout()
0219 {
0220     m_session.requestLogout(SessionManagement::ConfirmationMode::Skip);
0221     accept();
0222 }
0223 
0224 void KSMShutdownDlg::slotReboot()
0225 {
0226     // no boot option selected -> current
0227     m_bootOption.clear();
0228     m_session.requestReboot(SessionManagement::ConfirmationMode::Skip);
0229     accept();
0230 }
0231 
0232 void KSMShutdownDlg::slotReboot(int opt)
0233 {
0234     if (int(rebootOptions.size()) > opt)
0235         m_bootOption = rebootOptions[opt];
0236     m_session.requestReboot(SessionManagement::ConfirmationMode::Skip);
0237     accept();
0238 }
0239 
0240 void KSMShutdownDlg::slotLockScreen()
0241 {
0242     m_bootOption.clear();
0243     m_session.lock();
0244     reject();
0245 }
0246 
0247 void KSMShutdownDlg::slotHalt()
0248 {
0249     m_bootOption.clear();
0250     m_session.requestShutdown(SessionManagement::ConfirmationMode::Skip);
0251     accept();
0252 }
0253 
0254 void KSMShutdownDlg::slotSuspend(int spdMethod)
0255 {
0256     m_bootOption.clear();
0257     switch (spdMethod) {
0258     case 1: // Solid::PowerManagement::StandbyState:
0259     case 2: // Solid::PowerManagement::SuspendState:
0260         m_session.suspend();
0261         break;
0262     case 4: // Solid::PowerManagement::HibernateState:
0263         m_session.hibernate();
0264         break;
0265     }
0266     reject();
0267 }
0268 
0269 void KSMShutdownDlg::slotCancelSoftwareUpdate()
0270 {
0271     QDBusMessage packageKitMessage =
0272         QDBusMessage::createMethodCall(s_packageKitService, s_packageKitPath, s_packageKitOfflineInterface, QStringLiteral("Cancel"));
0273     QDBusPendingReply<> packageKitCall = QDBusConnection::systemBus().asyncCall(packageKitMessage);
0274     QDBusPendingCallWatcher *packageKitCallWatcher = new QDBusPendingCallWatcher(packageKitCall, this);
0275     connect(packageKitCallWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0276         QDBusPendingReply<> reply = *watcher;
0277         watcher->deleteLater();
0278 
0279         if (reply.isError()) {
0280             qWarning() << "Failed to cancel pending software update" << reply.error().message();
0281         } else {
0282             checkSoftwareUpdatePending();
0283         }
0284     });
0285 }
0286 
0287 void KSMShutdownDlg::checkSoftwareUpdatePending()
0288 {
0289     QDBusMessage packageKitMessage = QDBusMessage::createMethodCall(s_packageKitService, s_packageKitPath, s_dbusPropertiesInterface, QStringLiteral("GetAll"));
0290     packageKitMessage.setArguments({s_packageKitOfflineInterface});
0291     QDBusPendingReply<QVariantMap> packageKitCall = QDBusConnection::systemBus().asyncCall(packageKitMessage);
0292     QDBusPendingCallWatcher *packageKitCallWatcher = new QDBusPendingCallWatcher(packageKitCall, this);
0293     connect(packageKitCallWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0294         QDBusPendingReply<QVariantMap> reply = *watcher;
0295         watcher->deleteLater();
0296 
0297         if (reply.isError() || !rootContext()) {
0298             return;
0299         }
0300 
0301         const QVariantMap properties = reply.value();
0302         if (properties.value(QStringLiteral("UpdateTriggered")).toBool() || properties.value(QStringLiteral("UpgradeTriggered")).toBool()) {
0303             rootContext()->setContextProperty("softwareUpdatePending", true);
0304         }
0305     });
0306 }
0307 
0308 void KSMShutdownDlg::accept()
0309 {
0310     Q_EMIT accepted();
0311 }
0312 
0313 void KSMShutdownDlg::reject()
0314 {
0315     Q_EMIT rejected();
0316 }