File indexing completed on 2024-12-15 05:06:52

0001 /*
0002  *   SPDX-FileCopyrightText: 2010 Dario Freddi <drf@kde.org>
0003  *   SPDX-FileCopyrightText: 2015 Kai Uwe Broulik <kde@privat.broulik.de>
0004  *
0005  *   SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "handlebuttonevents.h"
0009 #include "handlebuttoneventsadaptor.h"
0010 
0011 #include <PowerDevilProfileSettings.h>
0012 #include <powerdevil_debug.h>
0013 #include <powerdevilcore.h>
0014 #include <powerdevilenums.h>
0015 
0016 #include <QAction>
0017 
0018 #include <KActionCollection>
0019 #include <KIdleTime>
0020 #include <KLocalizedString>
0021 
0022 #include <KScreen/Config>
0023 #include <KScreen/ConfigMonitor>
0024 #include <KScreen/GetConfigOperation>
0025 #include <KScreen/Output>
0026 
0027 #include <KGlobalAccel>
0028 #include <Kirigami/Platform/TabletModeWatcher>
0029 
0030 namespace PowerDevil::BundledActions
0031 {
0032 HandleButtonEvents::HandleButtonEvents(QObject *parent)
0033     : Action(parent)
0034     , m_screenConfiguration(nullptr)
0035 {
0036     new HandleButtonEventsAdaptor(this);
0037     // We enforce no policies here - after all, we just call other actions - which have their policies.
0038     setRequiredPolicies(PowerDevil::PolicyAgent::None);
0039     connect(core()->lidController(), &LidController::lidClosedChanged, this, &HandleButtonEvents::onLidClosedChanged);
0040 
0041     KActionCollection *actionCollection = new KActionCollection(this);
0042     actionCollection->setComponentDisplayName(i18nc("Name for powerdevil shortcuts category", "Power Management"));
0043 
0044     KGlobalAccel *accel = KGlobalAccel::self();
0045 
0046     QAction *globalAction = actionCollection->addAction("Sleep");
0047     globalAction->setText(i18nc("@action:inmenu Global shortcut", "Suspend"));
0048     accel->setGlobalShortcut(globalAction, Qt::Key_Sleep);
0049     connect(globalAction, &QAction::triggered, this, &HandleButtonEvents::sleep);
0050 
0051     globalAction = actionCollection->addAction("Hibernate");
0052     globalAction->setText(i18nc("@action:inmenu Global shortcut", "Hibernate"));
0053     accel->setGlobalShortcut(globalAction, Qt::Key_Hibernate);
0054     connect(globalAction, &QAction::triggered, this, &HandleButtonEvents::hibernate);
0055 
0056     globalAction = actionCollection->addAction("PowerOff");
0057     globalAction->setText(i18nc("@action:inmenu Global shortcut", "Power Off"));
0058     auto powerButtonMode = [globalAction](bool isTablet) {
0059         if (!isTablet) {
0060             KGlobalAccel::self()->setGlobalShortcut(globalAction, Qt::Key_PowerOff);
0061         } else {
0062             KGlobalAccel::self()->setGlobalShortcut(globalAction, QList<QKeySequence>());
0063         }
0064     };
0065     auto interface = Kirigami::Platform::TabletModeWatcher::self();
0066     connect(interface, &Kirigami::Platform::TabletModeWatcher::tabletModeChanged, globalAction, powerButtonMode);
0067     powerButtonMode(interface->isTabletMode());
0068     connect(globalAction, &QAction::triggered, this, &HandleButtonEvents::powerOffButtonTriggered);
0069 
0070     globalAction = actionCollection->addAction("PowerDown");
0071     globalAction->setText(i18nc("@action:inmenu Global shortcut, used for long presses of the power button", "Power Down"));
0072     accel->setGlobalShortcut(globalAction, Qt::Key_PowerDown);
0073     connect(globalAction, &QAction::triggered, this, &HandleButtonEvents::powerDownButtonTriggered);
0074 
0075     connect(new KScreen::GetConfigOperation(KScreen::GetConfigOperation::NoEDID),
0076             &KScreen::ConfigOperation::finished,
0077             this,
0078             [this](KScreen::ConfigOperation *op) {
0079                 m_screenConfiguration = qobject_cast<KScreen::GetConfigOperation *>(op)->config();
0080                 checkOutputs();
0081 
0082                 KScreen::ConfigMonitor::instance()->addConfig(m_screenConfiguration);
0083                 connect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged, this, &HandleButtonEvents::checkOutputs);
0084             });
0085 
0086     if (!core()->lidController()->isLidClosed()) {
0087         m_oldKeyboardBrightness = core()->keyboardBrightnessController()->keyboardBrightness();
0088     }
0089     connect(core(), &PowerDevil::Core::keyboardBrightnessChanged, this, [this](const BrightnessLogic::BrightnessInfo &brightnessInfo) {
0090         // By the time the lid close is processed, the backend brightness will already be updated.
0091         // That's why we track the brightness here as long as the lid is open.
0092         m_oldKeyboardBrightness = brightnessInfo.value;
0093     });
0094 }
0095 
0096 HandleButtonEvents::~HandleButtonEvents()
0097 {
0098 }
0099 
0100 bool HandleButtonEvents::isSupported()
0101 {
0102     // we handles keyboard shortcuts in our button handling, users always have a keyboard
0103     return true;
0104 }
0105 
0106 void HandleButtonEvents::onProfileUnload()
0107 {
0108     m_lidAction = PowerDevil::PowerButtonAction::NoAction;
0109     m_powerButtonAction = PowerDevil::PowerButtonAction::NoAction;
0110 }
0111 
0112 void HandleButtonEvents::onIdleTimeout(std::chrono::milliseconds timeout)
0113 {
0114     Q_UNUSED(timeout)
0115 }
0116 
0117 void HandleButtonEvents::onLidClosedChanged(bool closed)
0118 {
0119     if (closed) {
0120         if (m_oldKeyboardBrightness.has_value()) {
0121             core()->keyboardBrightnessController()->setKeyboardBrightness(0);
0122         }
0123 
0124         if (!m_screenConfiguration) {
0125             // triggering the lid closed action now might suspend the system even though it just started up
0126             // once there's a screen configuration, checkOutputs will call this method again if needed
0127             return;
0128         }
0129         if (!triggersLidAction()) {
0130             qCWarning(POWERDEVIL) << "Lid action was suppressed because an external monitor is present";
0131             return;
0132         }
0133 
0134         processAction(m_lidAction);
0135     } else {
0136         // When we restore the keyboard brightness before waking up, we shouldn't conflict
0137         // with dimdisplay or dpms also messing with the keyboard.
0138         if (m_oldKeyboardBrightness.has_value() && m_oldKeyboardBrightness > 0) {
0139             core()->keyboardBrightnessController()->setKeyboardBrightness(m_oldKeyboardBrightness.value());
0140         }
0141 
0142         // In this case, let's send a wakeup event
0143         KIdleTime::instance()->simulateUserActivity();
0144     }
0145 }
0146 
0147 void HandleButtonEvents::processAction(PowerDevil::PowerButtonAction action)
0148 {
0149     // Basically, we simply trigger other actions :)
0150     switch (action) {
0151     case PowerDevil::PowerButtonAction::TurnOffScreen:
0152         // Turn off screen
0153         triggerAction("DPMSControl", QStringLiteral("TurnOff"));
0154         break;
0155     case PowerDevil::PowerButtonAction::ToggleScreenOnOff:
0156         // Toggle screen on/off
0157         triggerAction("DPMSControl", QStringLiteral("ToggleOnOff"));
0158         break;
0159     default:
0160         triggerAction("SuspendSession", qToUnderlying(action));
0161         break;
0162     }
0163 }
0164 
0165 void HandleButtonEvents::triggerAction(const QString &action, const QVariant &type)
0166 {
0167     PowerDevil::Action *helperAction = core()->action(action);
0168     if (helperAction) {
0169         helperAction->trigger({
0170             {QStringLiteral("Type"), type},
0171             {QStringLiteral("Explicit"), true},
0172         });
0173     }
0174 }
0175 
0176 void HandleButtonEvents::triggerImpl(const QVariantMap & /*args*/)
0177 {
0178 }
0179 
0180 bool HandleButtonEvents::loadAction(const PowerDevil::ProfileSettings &profileSettings)
0181 {
0182     m_lidAction = static_cast<PowerDevil::PowerButtonAction>(profileSettings.lidAction());
0183     m_triggerLidActionWhenExternalMonitorPresent = !profileSettings.inhibitLidActionWhenExternalMonitorPresent();
0184     m_powerButtonAction = static_cast<PowerDevil::PowerButtonAction>(profileSettings.powerButtonAction());
0185     m_powerDownButtonAction = static_cast<PowerDevil::PowerButtonAction>(profileSettings.powerDownAction());
0186 
0187     constexpr auto NoAction = PowerDevil::PowerButtonAction::NoAction;
0188     if (m_lidAction == NoAction || m_powerButtonAction == NoAction || m_powerDownButtonAction == NoAction) {
0189         return false;
0190     }
0191 
0192     checkOutputs();
0193 
0194     return true;
0195 }
0196 
0197 int HandleButtonEvents::lidAction() const
0198 {
0199     return qToUnderlying(m_lidAction);
0200 }
0201 
0202 bool HandleButtonEvents::triggersLidAction() const
0203 {
0204     return m_triggerLidActionWhenExternalMonitorPresent || !m_externalMonitorPresent.value_or(false);
0205 }
0206 
0207 void HandleButtonEvents::powerOffButtonTriggered()
0208 {
0209     processAction(m_powerButtonAction);
0210 }
0211 
0212 void HandleButtonEvents::powerDownButtonTriggered()
0213 {
0214     processAction(m_powerDownButtonAction);
0215 }
0216 
0217 void HandleButtonEvents::hibernate()
0218 {
0219     processAction(m_hibernateButtonAction);
0220 }
0221 
0222 void HandleButtonEvents::sleep()
0223 {
0224     processAction(m_sleepButtonAction);
0225 }
0226 
0227 void HandleButtonEvents::checkOutputs()
0228 {
0229     if (!m_screenConfiguration) {
0230         qCWarning(POWERDEVIL) << "Handle button events action could not check for screen configuration";
0231         return;
0232     }
0233 
0234     const bool old_triggersLidAction = triggersLidAction();
0235     const std::optional<bool> oldExternalMonitorPresent = m_externalMonitorPresent;
0236 
0237     const auto outputs = m_screenConfiguration->outputs();
0238     m_externalMonitorPresent = std::any_of(outputs.begin(), outputs.end(), [](const KScreen::OutputPtr &output) {
0239         return output->isConnected() && output->isEnabled() && output->type() != KScreen::Output::Panel && output->type() != KScreen::Output::Unknown;
0240     });
0241 
0242     if (old_triggersLidAction != triggersLidAction() || !oldExternalMonitorPresent.has_value()) {
0243         Q_EMIT triggersLidActionChanged(triggersLidAction());
0244 
0245         // when the lid is closed but we don't suspend because of an external monitor but we then
0246         // unplug said monitor, re-trigger the lid action (Bug 379265)
0247         if (triggersLidAction() && core()->lidController()->isLidClosed()) {
0248             qCDebug(POWERDEVIL) << "External monitor that kept us from suspending is gone and lid is closed, re-triggering lid action";
0249             onLidClosedChanged(true);
0250         }
0251     }
0252 }
0253 
0254 }
0255 
0256 #include "moc_handlebuttonevents.cpp"