From 7f577bda26ff333ca7e6db39f469a940113f9d7c Mon Sep 17 00:00:00 2001 From: mutchiko <95985922+mutchiko@users.noreply.github.com> Date: Sun, 15 Mar 2026 22:18:30 +0100 Subject: [PATCH 1/2] Implement charger-based performance mode switching Implement Power Profile-based performance mode switching --- src/i18n/MControlCenter_en.ts | 26 +++ src/mainwindow.cpp | 126 +++++++++++ src/mainwindow.h | 10 + src/mainwindow.ui | 395 +++++++++++++++++++++++----------- src/powermonitor.cpp | 207 ++++++++++++++++++ src/powermonitor.h | 67 ++++++ 6 files changed, 706 insertions(+), 125 deletions(-) create mode 100644 src/powermonitor.cpp create mode 100644 src/powermonitor.h diff --git a/src/i18n/MControlCenter_en.ts b/src/i18n/MControlCenter_en.ts index 30b3272..b587b6a 100644 --- a/src/i18n/MControlCenter_en.ts +++ b/src/i18n/MControlCenter_en.ts @@ -115,6 +115,16 @@ rpm + + Couldn't connect to UPower to get charger status. +Make sure that UPower is installed and running then restart the app. + + + + Couldn't connect to Power Profiles Daemon. +Make sure that either Power Profiles Daemon or TuneD is installed and restart the app. + + Mode @@ -215,6 +225,10 @@ Current fan Mode: + + Automatic Profile Switching + + Keyboard @@ -243,6 +257,10 @@ Maximum performance at the cost of heat and increased power consumption + + Follow system's power profile + + If you mainly use your laptop with the charger plugged most of the time, it is recommended to set the charge capacity at a lower percentage (60% or 80%) to prolong your battery lifecycle. @@ -251,6 +269,14 @@ Charge the battery when under 90%, stop at 100% + + On Charger: + + + + On Battery: + + Keyboard Backlight diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4cf2a3a..c5f29ba 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -24,6 +24,7 @@ #include Operate operate; +PowerMonitor powerMonitor; bool isActive = false; bool isUpdateDataError = false; @@ -136,6 +137,8 @@ MainWindow::MainWindow(QWidget *parent) connect(ui->fanSpeedResetButton, &QPushButton::clicked, this, &MainWindow::updateFanSpeedSettings); connect(ui->fanSpeedApplyButton, &QPushButton::clicked, this, &MainWindow::setFanSpeedSettings); + connect(&powerMonitor, &PowerMonitor::currentChargerState, this, &MainWindow::on_ChargerStateChange); + connect(&powerMonitor, &PowerMonitor::currentPowerProfile, this, &MainWindow::on_PowerProfileChange); connect(qApp, &QGuiApplication::saveStateRequest, this, &MainWindow::saveStateRequest); @@ -174,6 +177,10 @@ MainWindow::MainWindow(QWidget *parent) ui->QtVersionValue->setText(QT_VERSION_STR); ui->versionValueLabel->setText(MControlCenter_VERSION); + ui->autoAcDcProfilesGroupBox->setChecked(s.getValue("Settings/autoAcDcProfilesState").toBool()); + ui->userModeOnBatteryComboBox->setCurrentIndex(s.getValue("Settings/UserModeOnBattery").toInt()); + ui->userModeOnChargerComboBox->setCurrentIndex(s.getValue("Settings/UserModeOnCharger").toInt()); + ui->autoPPDCheckBox->setChecked(s.getValue("Settings/autoPPDstate").toBool()); } MainWindow::~MainWindow() { @@ -679,6 +686,65 @@ void MainWindow::timerSleepTimeout() { } } +void MainWindow::setModeFromSelection(PowerProfile profile) { + switch (profile) { + case PowerProfile::Performance: + setHighPerformanceMode(); + break; + case PowerProfile::Balanced: + setBalancedMode(); + break; + case PowerProfile::Silent: + setSilentMode(); + break; + case PowerProfile::PowerSaver: + setSuperBatteryMode(); + break; + case PowerProfile::Unknown: + default:; + } +} + +void MainWindow::on_ChargerStateChange(bool isCharging) { + if (ui->autoAcDcProfilesGroupBox->isChecked()) { + Settings s; + int SelectedModeOnBattery = s.getValue("Settings/UserModeOnBattery").toInt(); + int SelectedModeOnCharger = s.getValue("Settings/UserModeOnCharger").toInt(); + + PowerProfile batteryProfile = static_cast(SelectedModeOnBattery); + PowerProfile chargerProfile = static_cast(SelectedModeOnCharger); + + if (isCharging) { + setModeFromSelection(chargerProfile); + } else { + setModeFromSelection(batteryProfile); + } + } else { + ui->autoPPDCheckBox->setEnabled(1); + } +} + +void MainWindow::on_PowerProfileChange(const PowerProfile profile) { + if (ui->autoPPDCheckBox->isChecked()) { + switch (profile) { + case PowerProfile::Performance: + setHighPerformanceMode(); + ui->highPerformanceModeRadioButton->setChecked(true); + break; + case PowerProfile::Balanced: + setBalancedMode(); + ui->balancedModeRadioButton->setChecked(true); + break; + case PowerProfile::PowerSaver: + setSuperBatteryMode(); + ui->superBatteryModeRadioButton->setChecked(true); + break; + case PowerProfile::Unknown: + default:; + } + } +} + void MainWindow::on_bestMobilityRadioButton_toggled(bool checked) { if (checked) setBestMobility(); @@ -756,6 +822,66 @@ void MainWindow::on_keyboardBacklightModeComboBox_currentIndexChanged(int index) operate.setKeyboardBacklightMode(index); } +void MainWindow::on_userModeOnBatteryComboBox_currentIndexChanged(int index) const { + Settings::setValue("Settings/UserModeOnBattery", index); + powerMonitor.queryChargerState(); +} + +void MainWindow::on_userModeOnChargerComboBox_currentIndexChanged(int index) const { + Settings::setValue("Settings/UserModeOnCharger", index); + powerMonitor.queryChargerState(); +} + +void MainWindow::on_autoAcDcProfilesGroupBox_toggled(bool checked) { + if(checked) { + if (!powerMonitor.connectToUpower()) { + QMessageBox::critical(nullptr, this->windowTitle(), tr("Couldn't connect to UPower to get charger status.\n" + "Make sure that UPower is installed and running then restart the system.")); + ui->autoAcDcProfilesGroupBox->setChecked(0); + ui->autoAcDcProfilesGroupBox->setEnabled(0); + return; + } + + powerMonitor.disconnectFromPowerProfiles(); + ui->autoPPDCheckBox->setChecked(0); + ui->autoPPDCheckBox->setEnabled(0); + powerMonitor.queryChargerState(); + } else { + ui->autoPPDCheckBox->setEnabled(1); + powerMonitor.disconnectFromUpower(); + } + + Settings::setValue("Settings/autoAcDcProfilesState", checked); +} + +void MainWindow::on_autoPPDCheckBox_toggled(bool checked) { + if (checked) { + + if (!powerMonitor.connectToPowerProfiles()) { + QMessageBox::critical(nullptr, this->windowTitle(), tr("Couldn't connect to Power Profiles Daemon.\n" + "Make sure that either Power Profiles Daemon or TuneD is installed and restart the system.")); + ui->autoPPDCheckBox->setChecked(0); + return; + } + + powerMonitor.disconnectFromUpower(); + ui->highPerformanceModeRadioButton->setEnabled(0); + ui->balancedModeRadioButton->setEnabled(0); + ui->silentModeRadioButton->setEnabled(0); + ui->superBatteryModeRadioButton->setEnabled(0); + ui->autoAcDcProfilesGroupBox->setChecked(0); + ui->autoAcDcProfilesGroupBox->setEnabled(0); + powerMonitor.queryPowerProfile(); + } else { + ui->highPerformanceModeRadioButton->setEnabled(1); + ui->balancedModeRadioButton->setEnabled(1); + ui->silentModeRadioButton->setEnabled(1); + ui->superBatteryModeRadioButton->setEnabled(1); + ui->autoAcDcProfilesGroupBox->setEnabled(1); + } + Settings::setValue("Settings/autoPPDstate", checked); +} + void MainWindow::on_highPerformanceModeRadioButton_toggled(bool checked) { if (checked) setHighPerformanceMode(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 192a809..83019b0 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -19,6 +19,7 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include "powermonitor.h" #include #include #include @@ -45,6 +46,7 @@ Q_OBJECT void startRealtimeUpdate() const; void stopRealtimeUpdate() const; void setUpdateInterval(int msec) const; + void setModeFromSelection(PowerProfile profile); void realtimeUpdate(); void loadConfigs(); @@ -120,6 +122,9 @@ Q_OBJECT QAction *quitAction = nullptr; private slots: + void on_ChargerStateChange(bool isCharging); + void on_PowerProfileChange(const PowerProfile profile); + void on_bestMobilityRadioButton_toggled(bool checked); void on_balancedBatteryRadioButton_toggled(bool checked); void on_bestBatteryRadioButton_toggled(bool checked); @@ -141,6 +146,11 @@ private slots: void on_keyboardBacklightModeComboBox_currentIndexChanged(int index) const; + void on_userModeOnBatteryComboBox_currentIndexChanged(int index) const; + void on_userModeOnChargerComboBox_currentIndexChanged(int index) const; + void on_autoPPDCheckBox_toggled(bool checked); + void on_autoAcDcProfilesGroupBox_toggled(bool active); + void on_highPerformanceModeRadioButton_toggled(bool checked); void on_balancedModeRadioButton_toggled(bool checked); void on_silentModeRadioButton_toggled(bool checked); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 84af2aa..6b5c11c 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -42,8 +42,11 @@ true - + + + true + 0 @@ -105,6 +108,74 @@ + + + + + 35 + 0 + + + + QFrame::Shadow::Sunken + + + Qt::Orientation::Vertical + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Orientation::Horizontal + + + + 58 + 20 + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + @@ -211,61 +282,6 @@ - - - - - 35 - 0 - - - - QFrame::Shadow::Sunken - - - Qt::Orientation::Vertical - - - - - - - Qt::Orientation::Horizontal - - - - 58 - 20 - - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - @@ -358,21 +374,36 @@ - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 20 + + + + QFrame::Shadow::Sunken + + + Qt::Orientation::Horizontal + + + @@ -392,21 +423,21 @@ 45 - 10 + 15 - 10 + 0 - 10 + 0 - 10 + 0 - 10 + 15 - + This mode unlocks Advanced fan mode @@ -416,7 +447,7 @@ - + @@ -435,14 +466,14 @@ - + Balanced - + The middle spot between fan noise and power usage @@ -455,14 +486,14 @@ - + Silent - + Low fan noise and moderate power usage @@ -475,14 +506,14 @@ - + Super Battery - + Limits performance and turns off fans at lower temperatures @@ -495,21 +526,35 @@ + + + + true + + + Follow system's power profile + + + + + + + + + + + Battery + + - - - - 0 - 0 - - + 0 - 30 + 10 @@ -520,16 +565,6 @@ - - - - - - - - Battery - - @@ -552,20 +587,143 @@ - - - + + + + Qt::Orientation::Vertical + + - 0 - 10 + 20 + 195 - - QFrame::Shadow::Sunken + + + + + + true - - Qt::Orientation::Horizontal + + Automatic Profile Switching + + + false + + + true + + + false + + + + + On Charger: + + + Qt::AlignmentFlag::AlignCenter + + + + + + + On Battery: + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 0 + 0 + + + + Balanced + + + 1 + + + + High Performance + + + + + Balanced + + + + + Silent + + + + + Super Battery + + + + + + + + + 10 + 0 + + + + Qt::Orientation::Vertical + + + + + + + + 0 + 0 + + + + Super Battery + + + 3 + + + + High Performance + + + + + Balanced + + + + + Silent + + + + + Super Battery + + + + + @@ -580,7 +738,7 @@ 10 - 0 + 10 10 @@ -725,19 +883,6 @@ - - - - Qt::Orientation::Vertical - - - - 20 - 195 - - - - @@ -758,8 +903,8 @@ 0 0 - 556 - 424 + 544 + 414 diff --git a/src/powermonitor.cpp b/src/powermonitor.cpp new file mode 100644 index 0000000..81e90f2 --- /dev/null +++ b/src/powermonitor.cpp @@ -0,0 +1,207 @@ +/* Copyright (C) 2026 Dmitry Serov + * + * This file is part of MControlCenter. + * + * MControlCenter is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * MControlCenter is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MControlCenter. If not, see . + */ + +#include "powermonitor.h" +#include +#include + +PowerMonitor::PowerMonitor() = default; + +bool PowerMonitor::connectToUpower() { + if(isUPowerConnected) { + return true; + } + + auto bus = QDBusConnection::systemBus(); + + if (!bus.interface()->isServiceRegistered("org.freedesktop.UPower")) { + return false; + } + + bool ok = bus.connect( + "org.freedesktop.UPower", + "/org/freedesktop/UPower/devices/DisplayDevice", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + this, + SLOT(onChargerStateChanged(QString,QVariantMap,QStringList)) + ); + isUPowerConnected = ok; + return ok; +} + +void PowerMonitor::disconnectFromUpower() { + if(isUPowerConnected) { + QDBusConnection::systemBus().disconnect( + "org.freedesktop.UPower", + "/org/freedesktop/UPower/devices/DisplayDevice", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + this, + SLOT(onChargerStateChanged(QString,QVariantMap,QStringList)) + ); + isUPowerConnected = false; + } +} + +void PowerMonitor::queryChargerState() { + if (isUPowerConnected) { + QDBusInterface iface( + "org.freedesktop.UPower", + "/org/freedesktop/UPower/devices/DisplayDevice", + "org.freedesktop.DBus.Properties", + QDBusConnection::systemBus() + ); + + QDBusReply reply = + iface.call("Get", "org.freedesktop.UPower.Device", "State"); + + if (!reply.isValid()) { + return; + } + + uint state = reply.value().toUInt(); + bool connected = parseChargerState(state); + + emit currentChargerState(connected); + } +} + +void PowerMonitor::onChargerStateChanged( + const QString &interface, + const QVariantMap &changedProps, + const QStringList &invalidatedProps) { + Q_UNUSED(invalidatedProps); + + if (interface != "org.freedesktop.UPower.Device") + return; + + if (!changedProps.contains("State")) + return; + + uint state = changedProps.value("State").toUInt(); + bool connected = parseChargerState(state); + + emit currentChargerState(connected); +} + +bool PowerMonitor::parseChargerState(uint state) const { + switch (state) { + case 1: // Charging + case 4: // Fully charged + case 5: // Pending charge + return true; + + case 2: // Discharging + case 3: // Empty + case 6: // Pending discharge + default: + return false; + } +} + +bool PowerMonitor::connectToPowerProfiles() { + if (isPowerProfileConnected) { + return true; + } + + auto bus = QDBusConnection::systemBus(); + + if (!bus.interface()->isServiceRegistered("net.hadess.PowerProfiles")) { + return false; + } + + bool ok = bus.connect( + "net.hadess.PowerProfiles", + "/org/freedesktop/UPower/PowerProfiles", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + this, + SLOT(onPowerProfileChanged(QString,QVariantMap,QStringList)) + ); + + isPowerProfileConnected = ok; + return ok; +} + +void PowerMonitor::disconnectFromPowerProfiles() { + if (isPowerProfileConnected) { + QDBusConnection::systemBus().disconnect( + "net.hadess.PowerProfiles", + "/org/freedesktop/UPower/PowerProfiles", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + this, + SLOT(onPowerProfileChanged(QString,QVariantMap,QStringList)) + ); + isPowerProfileConnected = false; + } +} + +void PowerMonitor::queryPowerProfile() { + if (isPowerProfileConnected) { + QDBusInterface iface( + "net.hadess.PowerProfiles", + "/org/freedesktop/UPower/PowerProfiles", + "org.freedesktop.DBus.Properties", + QDBusConnection::systemBus() + ); + + QDBusReply reply = + iface.call("Get", "org.freedesktop.UPower.PowerProfiles", "ActiveProfile"); + + if (!reply.isValid()) { + return; + } + + const QString ProfileName = reply.value().toString(); + + PowerProfile profile = parsePowerProfile(ProfileName); + + emit currentPowerProfile(profile); + } +} + +void PowerMonitor::onPowerProfileChanged( + const QString &interface, + const QVariantMap &changed, + const QStringList &invalidatedProps) { + Q_UNUSED(invalidatedProps); + + if (interface != "org.freedesktop.UPower.PowerProfiles") + return; + + if (!changed.contains("ActiveProfile")) + return; + + const QString ProfileName = changed.value("ActiveProfile").toString(); + + PowerProfile profile = parsePowerProfile(ProfileName); + + emit currentPowerProfile(profile); +} + +PowerProfile PowerMonitor::parsePowerProfile(const QString &profile) { + if (profile == "performance") + return PowerProfile::Performance; + if (profile == "balanced") + return PowerProfile::Balanced; + if (profile == "power-saver") + return PowerProfile::PowerSaver; + return PowerProfile::Unknown; +} diff --git a/src/powermonitor.h b/src/powermonitor.h new file mode 100644 index 0000000..01f16cc --- /dev/null +++ b/src/powermonitor.h @@ -0,0 +1,67 @@ +/* Copyright (C) 2026 Dmitry Serov + * + * This file is part of MControlCenter. + * + * MControlCenter is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * MControlCenter is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MControlCenter. If not, see . + */ + +#ifndef POWERMONITOR_H +#define POWERMONITOR_H + +#include + +enum class PowerProfile { + Performance, + Balanced, + Silent, + PowerSaver, + Unknown +}; + +class PowerMonitor : public QObject { + Q_OBJECT +public: + PowerMonitor(); + + bool connectToUpower(); + bool connectToPowerProfiles(); + void disconnectFromUpower(); + void disconnectFromPowerProfiles(); + void queryChargerState(); + void queryPowerProfile(); + + Q_ENUM(PowerProfile) + +private: + bool parseChargerState(uint state) const; + PowerProfile parsePowerProfile(const QString &profile); + + bool isUPowerConnected = false; + bool isPowerProfileConnected = false; + +signals: + void currentChargerState(bool isOnline); + void currentPowerProfile(const PowerProfile profile); + +private slots: + void onChargerStateChanged(const QString &interface, + const QVariantMap &changedProps, + const QStringList &invalidatedProps); + + void onPowerProfileChanged(const QString &interface, + const QVariantMap &changed, + const QStringList &invalidatedProps); +}; + +#endif // POWERMONITOR_H From 3911cc0fd9a0ca92a5f7b9ebb49b81cb9369c15e Mon Sep 17 00:00:00 2001 From: mutchiko <95985922+mutchiko@users.noreply.github.com> Date: Mon, 16 Mar 2026 06:50:55 +0100 Subject: [PATCH 2/2] Include new PowerMonitor in CMakeLists --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0072049..ba0ef9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ set(PROJECT_SOURCES src/mainwindow.ui src/mainwindow.cpp src/mainwindow.h src/operate.cpp src/operate.h + src/powermonitor.cpp src/powermonitor.h src/helper.cpp src/helper.h src/msi-ec_helper.cpp src/msi-ec_helper.h src/settings.cpp src/settings.h