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"