File indexing completed on 2024-05-12 05:38:21

0001 /*
0002     SPDX-FileCopyrightText: 2006 Aaron Seigo <aseigo@kde.org>
0003     SPDX-FileCopyrightText: 2022 Natalie Clarius <natalie_clarius@yahoo.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "sessionrunner.h"
0009 
0010 #include <KGuiItem>
0011 #include <KLocalizedString>
0012 #include <KMessageBox>
0013 #include <KSharedConfig>
0014 
0015 K_PLUGIN_CLASS_WITH_JSON(SessionRunner, "plasma-runner-sessions.json")
0016 
0017 SessionRunner::SessionRunner(QObject *parent, const KPluginMetaData &metaData)
0018     : KRunner::AbstractRunner(parent, metaData)
0019 {
0020     m_logoutKeywords = i18nc("KRunner keywords (split by semicolons without whitespace) to log out of the session", "logout;log out")
0021                            .split(QLatin1Char(';'), Qt::SkipEmptyParts);
0022     if (m_session.canLogout()) {
0023         addSyntax(m_logoutKeywords, i18n("Logs out, exiting the current desktop session"));
0024     }
0025 
0026     m_shutdownKeywords = i18nc("KRunner keywords (split by semicolons without whitespace) to shut down the computer", "shutdown;shut down;power;power off")
0027                              .split(QLatin1Char(';'), Qt::SkipEmptyParts);
0028     if (m_session.canShutdown()) {
0029         addSyntax(m_shutdownKeywords, i18n("Turns off the computer"));
0030     }
0031 
0032     m_restartKeywords = i18nc("KRunner keywords (split by semicolons without whitespace) to restart the computer", "restart;reboot")
0033                             .split(QLatin1Char(';'), Qt::SkipEmptyParts);
0034     if (m_session.canReboot()) {
0035         addSyntax(m_restartKeywords, i18n("Reboots the computer"));
0036     }
0037 
0038     m_lockKeywords =
0039         i18nc("KRunner keywords (split by semicolons without whitespace) to lock the screen", "lock;lock screen").split(QLatin1Char(';'), Qt::SkipEmptyParts);
0040     if (m_session.canLock()) {
0041         addSyntax(m_lockKeywords, i18n("Locks the current sessions and starts the screen saver"));
0042     }
0043 
0044     m_saveKeywords = i18nc("KRunner keywords (split by semicolons without whitespace) to save the desktop session", "save;save session")
0045                          .split(QLatin1Char(';'), Qt::SkipEmptyParts);
0046     if (m_session.canSaveSession()) {
0047         addSyntax(m_saveKeywords, i18n("Saves the current session for session restoration"));
0048     }
0049 
0050     m_usersKeywords = i18nc("KRunner keywords (split by semicolons without whitespace) to switch user sessions", "switch user;new session")
0051                           .split(QLatin1Char(';'), Qt::SkipEmptyParts);
0052     if (m_session.canSwitchUser()) {
0053         addSyntax(m_usersKeywords, i18n("Starts a new session as a different user"));
0054     }
0055 
0056     m_sessionsKeyword = i18nc("KRunner keyword to list user sessions", "sessions");
0057     KRunner::RunnerSyntax sessionsSyntax(m_sessionsKeyword, i18n("Lists all sessions"));
0058     addSyntax(sessionsSyntax);
0059 
0060     m_switchKeyword = i18nc("KRunner keyword to switch user sessions", "switch");
0061     addSyntax(m_switchKeyword + QStringLiteral(" :q:"),
0062               i18n("Switches to the active session for the user :q:, or lists all active sessions if :q: is not provided"));
0063 
0064     setMinLetterCount(3);
0065 }
0066 
0067 static inline bool anyKeywordMatches(const QStringList &keywords, const QString &term)
0068 {
0069     return std::any_of(keywords.cbegin(), keywords.cend(), [&term](const QString &keyword) {
0070         return term.compare(keyword, Qt::CaseInsensitive) == 0;
0071     });
0072 }
0073 
0074 void SessionRunner::matchCommands(QList<KRunner::QueryMatch> &matches, const QString &term)
0075 {
0076     KRunner::QueryMatch match(this);
0077     match.setCategoryRelevance(KRunner::QueryMatch::CategoryRelevance::Highest);
0078     match.setRelevance(0.9);
0079     if (anyKeywordMatches(m_logoutKeywords, term)) {
0080         if (m_session.canLogout()) {
0081             match.setText(i18nc("log out command", "Log Out"));
0082             match.setIconName(QStringLiteral("system-log-out"));
0083             match.setData(LogoutAction);
0084             matches << match;
0085         }
0086     } else if (anyKeywordMatches(m_shutdownKeywords, term)) {
0087         if (m_session.canShutdown()) {
0088             KRunner::QueryMatch match(this);
0089             match.setText(i18nc("turn off computer command", "Shut Down"));
0090             match.setIconName(QStringLiteral("system-shutdown"));
0091             match.setData(ShutdownAction);
0092             matches << match;
0093         }
0094     } else if (anyKeywordMatches(m_restartKeywords, term)) {
0095         if (m_session.canReboot()) {
0096             match.setText(i18nc("restart computer command", "Restart"));
0097             match.setIconName(QStringLiteral("system-reboot"));
0098             match.setData(RestartAction);
0099             matches << match;
0100         }
0101     } else if (anyKeywordMatches(m_lockKeywords, term)) {
0102         if (m_session.canLock()) {
0103             match.setText(i18nc("lock screen command", "Lock"));
0104             match.setIconName(QStringLiteral("system-lock-screen"));
0105             match.setData(LockAction);
0106             matches << match;
0107         }
0108     } else if (anyKeywordMatches(m_saveKeywords, term)) {
0109         if (m_session.canSaveSession()) {
0110             match.setText(i18n("Save Session"));
0111             match.setIconName(QStringLiteral("system-save-session"));
0112             match.setData(SaveAction);
0113             matches << match;
0114         }
0115     }
0116 }
0117 
0118 void SessionRunner::match(KRunner::RunnerContext &context)
0119 {
0120     const QString term = context.query();
0121     QString user;
0122     bool matchUser = false;
0123 
0124     QList<KRunner::QueryMatch> matches;
0125 
0126     // first compare with "sessions" keyword
0127     // "SESSIONS" must *NOT* be translated (i18n)
0128     // as it is used as an internal command trigger (e.g. via d-bus),
0129     // not as a user supplied query. and yes, "Ugh, magic strings"
0130     bool listAll = anyKeywordMatches(QStringList({QStringLiteral("SESSIONS"), m_sessionsKeyword}), term);
0131 
0132     if (!listAll) {
0133         // no luck, try the "switch" user command
0134         if (term.startsWith(m_switchKeyword, Qt::CaseInsensitive)) {
0135             user = term.right(term.size() - m_switchKeyword.length()).trimmed();
0136             listAll = user.isEmpty();
0137             matchUser = !listAll;
0138         } else {
0139             // we know it's not "sessions" or "switch <something>", so let's
0140             // try some other possibilities
0141             matchCommands(matches, term);
0142         }
0143     }
0144 
0145     bool switchUser = listAll || anyKeywordMatches(m_usersKeywords, term);
0146 
0147     if (switchUser && m_session.canSwitchUser() && dm.isSwitchable() && dm.numReserve() >= 0) {
0148         KRunner::QueryMatch match(this);
0149         match.setCategoryRelevance(KRunner::QueryMatch::CategoryRelevance::Highest);
0150         match.setIconName(QStringLiteral("system-switch-user"));
0151         match.setText(i18n("Switch User"));
0152         matches << match;
0153     }
0154 
0155     // now add the active sessions
0156     if (listAll || matchUser) {
0157         SessList sessions;
0158         dm.localSessions(sessions);
0159 
0160         for (const SessEnt &session : std::as_const(sessions)) {
0161             if (!session.vt || session.self) {
0162                 continue;
0163             }
0164 
0165             QString name = KDisplayManager::sess2Str(session);
0166             KRunner::QueryMatch::CategoryRelevance categoryRelevance;
0167             qreal relevance = 0.7;
0168 
0169             if (listAll) {
0170                 categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Highest;
0171                 relevance = 1;
0172             } else if (matchUser) {
0173                 const int nameIdx = name.indexOf(user, Qt::CaseInsensitive);
0174                 if (nameIdx == 0 && name.size() == user.size()) {
0175                     // we need an elif branch here because we don't
0176                     // want the last conditional to be checked if !listAll
0177                     categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Highest;
0178                     relevance = 1;
0179                 } else if (nameIdx == 0) {
0180                     categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Moderate;
0181                 } else {
0182                     continue;
0183                 }
0184             } else {
0185                 continue;
0186             }
0187 
0188             KRunner::QueryMatch match(this);
0189             match.setCategoryRelevance(categoryRelevance);
0190             match.setRelevance(relevance);
0191             match.setIconName(QStringLiteral("user-identity"));
0192             match.setText(name);
0193             match.setData(QString::number(session.vt));
0194             matches << match;
0195         }
0196     }
0197 
0198     context.addMatches(matches);
0199 }
0200 
0201 void SessionRunner::run(const KRunner::RunnerContext & /*context*/, const KRunner::QueryMatch &match)
0202 {
0203     if (match.data().typeId() == QMetaType::Int) {
0204         switch (match.data().toInt()) {
0205         case LogoutAction:
0206             m_session.requestLogout();
0207             break;
0208         case RestartAction:
0209             m_session.requestReboot();
0210             break;
0211         case ShutdownAction:
0212             m_session.requestShutdown();
0213             break;
0214         case LockAction:
0215             m_session.lock();
0216             break;
0217         case SaveAction:
0218             m_session.saveSession();
0219             break;
0220         }
0221         return;
0222     }
0223 
0224     if (!match.data().toString().isEmpty()) {
0225         dm.lockSwitchVT(match.data().toString().toInt());
0226         return;
0227     }
0228 
0229     const auto config = KSharedConfig::openConfig(QStringLiteral("ksmserverrc"));
0230     KMessageBox::setDontShowAgainConfig(config.data());
0231     KGuiItem continueButton = KStandardGuiItem::cont();
0232     continueButton.setText("Enter new session");
0233     KGuiItem cancelButton = KStandardGuiItem::cancel();
0234     cancelButton.setText("Stay in current session");
0235     KMessageBox::ButtonCode confirmNewSession =
0236         KMessageBox::warningContinueCancel(nullptr,
0237                                            i18n("<p>You are about to enter a new desktop session.</p>"
0238                                                 "<p>A login screen will be displayed and the current session will be hidden.</p>"
0239                                                 "<p>You can switch between desktop sessions using:</p>"
0240                                                 "<ul>"
0241                                                 "<li>Ctrl+Alt+F{number of session}</li>"
0242                                                 "<li>Plasma search (type '%1')</li>"
0243                                                 "<li>Plasma widgets (such as the application launcher)</li>"
0244                                                 "</ul>",
0245                                                 m_switchKeyword),
0246                                            i18n("New Desktop Session"),
0247                                            continueButton,
0248                                            cancelButton,
0249                                            QStringLiteral("ConfirmNewSession"));
0250     if (confirmNewSession == KMessageBox::Continue) {
0251         m_session.lock();
0252         dm.startReserve();
0253     }
0254 }
0255 
0256 #include "sessionrunner.moc"