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"