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