File indexing completed on 2024-05-12 05:38:20
0001 /* 0002 SPDX-FileCopyrightText: 2008 Dario Freddi <drf@kdemod.ath.cx> 0003 SPDX-FileCopyrightText: 2008 Sebastian Kügler <sebas@kde.org> 0004 SPDX-FileCopyrightText: 2023 Natalie Clarius <natalie_clarius@yahoo.de> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "PowerDevilRunner.h" 0010 0011 #include <sessionmanagement.h> 0012 0013 #include <QDBusConnectionInterface> 0014 #include <QDBusInterface> 0015 #include <QDBusReply> 0016 #include <QDebug> 0017 #include <QIcon> 0018 0019 #include <KConfig> 0020 #include <KConfigGroup> 0021 0022 #include <cmath> 0023 0024 using namespace Qt::StringLiterals; 0025 0026 K_PLUGIN_CLASS_WITH_JSON(PowerDevilRunner, "plasma-runner-powerdevil.json") 0027 0028 PowerDevilRunner::PowerDevilRunner(QObject *parent, const KPluginMetaData &metaData) 0029 : KRunner::AbstractRunner(parent, metaData) 0030 , m_session(new SessionManagement(this)) 0031 , m_power(ki18nc("Note this is a KRunner keyword; 'power' as in 'power saving mode'", "power")) 0032 , m_suspend(ki18nc("Note this is a KRunner keyword", "suspend")) 0033 , m_toRam(ki18nc("Note this is a KRunner keyword", "to ram"), false) 0034 , m_sleep(ki18nc("Note this is a KRunner keyword", "sleep")) 0035 , m_hibernate(ki18nc("Note this is a KRunner keyword", "hibernate")) 0036 , m_toDisk(ki18nc("Note this is a KRunner keyword", "to disk"), false) 0037 , m_hybridSuspend(ki18nc("Note this is a KRunner keyword", "hybrid sleep"), false) 0038 , m_hybrid(ki18nc("Note this is a KRunner keyword", "hybrid"), false) 0039 , m_dimScreen(ki18nc("Note this is a KRunner keyword", "dim screen")) 0040 , m_screenBrightness(ki18nc("Note this is a KRunner keyword", "dim screen")) 0041 { 0042 setMinLetterCount(3); 0043 updateSyntaxes(); 0044 } 0045 0046 void PowerDevilRunner::updateSyntaxes() 0047 { 0048 setSyntaxes({}); // Clear the existing ones 0049 addSyntaxForKeyword({m_suspend}, 0050 i18n("Lists system suspend (e.g. sleep, hibernate) options " 0051 "and allows them to be activated")); 0052 0053 if (m_session->canSuspend()) { 0054 addSyntaxForKeyword({m_sleep, m_toRam}, i18n("Suspends the system to RAM")); 0055 } 0056 0057 if (m_session->canHibernate()) { 0058 addSyntaxForKeyword({m_hibernate, m_toDisk}, i18n("Suspends the system to disk")); 0059 } 0060 0061 if (m_session->canHybridSuspend()) { 0062 addSyntaxForKeyword({m_hybrid, m_hybridSuspend}, i18n("Sleeps now and falls back to hibernate")); 0063 } 0064 0065 addSyntax(QStringList{i18nc("Note this is a KRunner keyword, <> is a placeholder and should be at the end", "screen brightness <percent value>"), 0066 i18nc("Note this is a KRunner keyword", "dim screen")}, 0067 // xgettext:no-c-format 0068 i18n("Lists screen brightness options or sets it to the brightness defined by the search term; " 0069 "e.g. screen brightness 50 would dim the screen to 50% maximum brightness")); 0070 } 0071 0072 enum SleepState { SuspendState, HibernateState, HybridSuspendState }; 0073 0074 void PowerDevilRunner::match(KRunner::RunnerContext &context) 0075 { 0076 const QString term = context.query(); 0077 KRunner::QueryMatch::CategoryRelevance categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Highest; 0078 QList<KRunner::QueryMatch> matches; 0079 0080 int screenBrightnessResults = matchesScreenBrightnessKeywords(term); 0081 if (screenBrightnessResults != -1) { 0082 KRunner::QueryMatch match(this); 0083 match.setCategoryRelevance(categoryRelevance); 0084 match.setIconName(u"video-display-brightness"_s); 0085 match.setText(i18n("Set Brightness to %1%", screenBrightnessResults)); 0086 match.setData(screenBrightnessResults); 0087 match.setRelevance(1); 0088 match.setId(u"BrightnessChange"_s); 0089 matches.append(match); 0090 } else if (matchesRunnerKeywords({m_screenBrightness, m_dimScreen}, categoryRelevance, term)) { 0091 KRunner::QueryMatch match1(this); 0092 match1.setCategoryRelevance(KRunner::QueryMatch::CategoryRelevance::Highest); 0093 match1.setIconName(u"video-display-brightness"_s); 0094 match1.setText(i18n("Dim screen totally")); 0095 match1.setRelevance(1); 0096 match1.setId(u"DimTotal"_s); 0097 matches.append(match1); 0098 0099 KRunner::QueryMatch match2(this); 0100 match2.setCategoryRelevance(categoryRelevance); 0101 match2.setIconName(u"video-display-brightness"_s); 0102 match2.setText(i18n("Dim screen by half")); 0103 match2.setRelevance(1); 0104 match2.setId(u"DimHalf"_s); 0105 matches.append(match2); 0106 } else if (matchesRunnerKeywords({m_power, m_sleep, m_suspend, m_toRam}, categoryRelevance, term)) { 0107 if (m_session->canSuspend()) { 0108 addSuspendMatch(SuspendState, matches, categoryRelevance); 0109 } 0110 } else if (matchesRunnerKeywords({m_power, m_sleep, m_suspend, m_hibernate, m_toDisk}, categoryRelevance, term)) { 0111 if (m_session->canHibernate()) { 0112 addSuspendMatch(HibernateState, matches, categoryRelevance); 0113 } 0114 } else if (matchesRunnerKeywords({m_power, m_sleep, m_suspend, m_hybrid, m_hybridSuspend}, categoryRelevance, term)) { 0115 if (m_session->canHybridSuspend()) { 0116 addSuspendMatch(HybridSuspendState, matches, categoryRelevance); 0117 } 0118 } 0119 0120 context.addMatches(matches); 0121 } 0122 0123 void PowerDevilRunner::addSuspendMatch(int value, QList<KRunner::QueryMatch> &matches, KRunner::QueryMatch::CategoryRelevance categoryRelevance) 0124 { 0125 KRunner::QueryMatch match(this); 0126 match.setCategoryRelevance(categoryRelevance); 0127 0128 switch ((SleepState)value) { 0129 case SuspendState: 0130 match.setIconName(u"system-suspend"_s); 0131 match.setText(i18nc("Suspend to RAM", "Sleep")); 0132 match.setSubtext(i18n("Suspend to RAM")); 0133 match.setRelevance(1); 0134 break; 0135 case HibernateState: 0136 match.setIconName(u"system-suspend-hibernate"_s); 0137 match.setText(i18nc("Suspend to disk", "Hibernate")); 0138 match.setSubtext(i18n("Suspend to disk")); 0139 match.setRelevance(0.99); 0140 break; 0141 case HybridSuspendState: 0142 match.setIconName(u"system-suspend-hybrid"_s); 0143 match.setText(i18nc("Suspend to both RAM and disk", "Hybrid sleep")); 0144 match.setSubtext(i18n("Sleep now and fall back to hibernate")); 0145 match.setRelevance(0.98); 0146 break; 0147 } 0148 0149 match.setData(value); 0150 match.setId(u"Sleep"_s); 0151 matches.append(match); 0152 } 0153 0154 void PowerDevilRunner::run(const KRunner::RunnerContext & /*context*/, const KRunner::QueryMatch &match) 0155 { 0156 QDBusInterface iface(u"org.kde.Solid.PowerManagement"_s, u"/org/kde/Solid/PowerManagement"_s, u"org.kde.Solid.PowerManagement"_s); 0157 QDBusInterface brightnessIface(u"org.kde.Solid.PowerManagement"_s, 0158 u"/org/kde/Solid/PowerManagement/Actions/BrightnessControl"_s, 0159 u"org.kde.Solid.PowerManagement.Actions.BrightnessControl"_s); 0160 const QString action = match.id().remove(AbstractRunner::id() + QLatin1Char('_')); 0161 if (action == QLatin1String("BrightnessChange")) { 0162 QDBusReply<int> max = brightnessIface.call("brightnessMax"); 0163 const int value = max.isValid() ? std::round(match.data().toInt() * max / 100.0) : match.data().toInt(); 0164 brightnessIface.asyncCall("setBrightness", value); 0165 } else if (action == QLatin1String("DimTotal")) { 0166 brightnessIface.asyncCall(u"setBrightness"_s, 0); 0167 } else if (action == QLatin1String("DimHalf")) { 0168 QDBusReply<int> brightness = brightnessIface.asyncCall(u"brightness"_s); 0169 brightnessIface.asyncCall(u"setBrightness"_s, static_cast<int>(brightness / 2)); 0170 } else if (action == QLatin1String("Sleep")) { 0171 switch ((SleepState)match.data().toInt()) { 0172 case SuspendState: { 0173 if (m_session->canSuspendThenHibernate()) { 0174 const QDBusReply<QString> currProfile = iface.call(u"currentProfile"_s); 0175 const KConfigGroup config(KConfig(u"powermanagementprofilesrc"_s).group(currProfile).group(u"SuspendSession"_s)); 0176 if (config.readEntry<bool>("suspendThenHibernate", false)) { 0177 m_session->suspendThenHibernate(); 0178 break; 0179 } 0180 } 0181 m_session->suspend(); 0182 break; 0183 } 0184 case HibernateState: 0185 m_session->hibernate(); 0186 break; 0187 case HybridSuspendState: 0188 m_session->hybridSuspend(); 0189 break; 0190 } 0191 } 0192 } 0193 0194 bool PowerDevilRunner::matchesRunnerKeywords(const QList<RunnerKeyword> &keywords, 0195 KRunner::QueryMatch::CategoryRelevance &categoryRelevance, 0196 const QString &query) const 0197 { 0198 return std::any_of(keywords.begin(), keywords.end(), [&query, &categoryRelevance](const RunnerKeyword &keyword) { 0199 bool exactMatch = 0200 keyword.triggerWord.compare(query, Qt::CaseInsensitive) == 0 || keyword.translatedTriggerWord.compare(query, Qt::CaseInsensitive) == 0; 0201 categoryRelevance = exactMatch ? KRunner::QueryMatch::CategoryRelevance::Highest : KRunner::QueryMatch::CategoryRelevance::Low; 0202 if (!exactMatch && keyword.supportPartialMatch) { 0203 return keyword.triggerWord.startsWith(query, Qt::CaseInsensitive) || keyword.translatedTriggerWord.startsWith(query, Qt::CaseInsensitive); 0204 } 0205 return exactMatch; 0206 }); 0207 } 0208 0209 void PowerDevilRunner::addSyntaxForKeyword(const QList<RunnerKeyword> &keywords, const QString &description) 0210 { 0211 QStringList exampleQueries; 0212 for (const auto &keyword : keywords) { 0213 exampleQueries << keyword.translatedTriggerWord; 0214 } 0215 addSyntax(exampleQueries, description); 0216 } 0217 0218 int PowerDevilRunner::matchesScreenBrightnessKeywords(const QString &query) const 0219 { 0220 const static QStringList expressions = { 0221 u"screen brightness "_s, 0222 i18nc("Note this is a KRunner keyword, it should end with a space", "screen brightness "), 0223 u"dim screen "_s, 0224 i18nc("Note this is a KRunner keyword, it should end with a space", "dim screen "), 0225 }; 0226 0227 for (const QString &expression : expressions) { 0228 if (query.startsWith(expression)) { 0229 const QString number = query.mid(expression.size()); 0230 bool ok; 0231 int result = qBound(0, number.toInt(&ok), 100); 0232 return ok ? result : -1; 0233 } 0234 } 0235 return -1; 0236 } 0237 0238 #include "PowerDevilRunner.moc"