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"