1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-02 03:28:28 -04:00

feat(power): lower refresh rate on battery (#2428)

* feat(power): lower refresh rate on battery (#1203)

Add a Power & Sleep setting that lowers eligible displays to 60 Hz on battery and restores their previous mode on AC power.

Skip displays without an available 60 Hz mode, displays with a single refresh-rate option, current modes at or below 60 Hz, and VRR-enabled outputs.
Apply niri changes at runtime through output commands and use wlr-output-management as fallback.

* fix(quickshell): debounce display refresh sync

* chore: regenerate settings_search_index.json for pre-commit CI fix

* fix: translator context is missing for lowerDisplayRefreshRateOnBattery

* fix: activeDisplayProfileModes is missing from SettingsSpec

* feat: added guard to syncRefreshRates function

* refactor(display): move refresh sync listeners to display service

* fix(display): publish active profile modes after config write

* feat(display): persist previous refresh modes

* fix(display): route refresh mode writes through compositor backends

* fix(display): detect preferred niri refresh modes

* some small cleanups

---------

Co-authored-by: bbedward <bbedward@gmail.com>
This commit is contained in:
Artem Timofeev
2026-07-17 15:41:08 +02:00
committed by GitHub
parent 95e1d6b72e
commit adbbd9b112
6 changed files with 677 additions and 1 deletions
@@ -264,6 +264,24 @@ Singleton {
callback(true);
}
function publishActiveProfileModes() {
const compositor = CompositorService.compositor;
const profileId = SettingsData.getActiveDisplayProfile(compositor);
const profile = profileId ? validatedProfiles[profileId] : null;
const outputs = profile?.outputs || {};
const modes = {};
for (const outputId in outputs) {
const mode = outputs[outputId]?.mode;
if (mode)
modes[outputId] = {
"mode": mode
};
}
SettingsData.setActiveDisplayProfileModes(compositor, modes);
}
function generateProfileId() {
return "profile_" + Date.now() + "_" + Math.random().toString(36).slice(2, 9);
}
@@ -362,6 +380,7 @@ Singleton {
};
validatedProfiles = updated;
matchedProfile = findMatchingProfile();
publishActiveProfileModes();
profileSaved(profileId, profileName);
});
});
@@ -543,6 +562,7 @@ Singleton {
};
const onWriteSuccess = () => {
SettingsData.setActiveDisplayProfile(CompositorService.compositor, configId);
publishActiveProfileModes();
if (isManual) {
profilesLoading = false;
profileActivated(configId, profileName);
@@ -586,6 +606,7 @@ Singleton {
writeMonitorsJson(data, null);
validatedProfiles = validated;
matchedProfile = findMatchingProfile();
publishActiveProfileModes();
if (!profilesReady) {
profilesReady = true;
applyAutoConfig();
@@ -633,6 +654,7 @@ Singleton {
currentOutputSet = buildCurrentOutputSet();
matchedProfile = findMatchingProfile();
SettingsData.setActiveDisplayProfile(CompositorService.compositor, id);
publishActiveProfileModes();
profileSaved(id, profileName);
});
});
@@ -678,6 +700,7 @@ Singleton {
delete updated[profileId];
validatedProfiles = updated;
matchedProfile = findMatchingProfile();
publishActiveProfileModes();
profileDeleted(profileId);
});
});
@@ -890,6 +913,14 @@ Singleton {
target: CompositorService
function onCompositorChanged() {
root.checkIncludeStatus();
root.publishActiveProfileModes();
}
}
Connections {
target: SettingsData
function onActiveDisplayProfileChanged() {
root.publishActiveProfileModes();
}
}
@@ -2251,7 +2282,16 @@ Singleton {
"name": match.entry.name || "",
"outputs": outputConfigs
};
writeMonitorsJson(data, null);
writeMonitorsJson(data, success => {
if (!success || !profileId)
return;
const updated = JSON.parse(JSON.stringify(validatedProfiles));
if (updated[profileId]) {
updated[profileId].outputs = outputConfigs;
validatedProfiles = updated;
publishActiveProfileModes();
}
});
});
clearPendingChanges();
@@ -187,6 +187,16 @@ Item {
}
}
SettingsToggleRow {
settingKey: "lowerDisplayRefreshRateOnBattery"
tags: ["power", "battery", "display", "refresh", "rate", "60hz", "hz"]
text: I18n.tr("Lower display refresh rate on battery", "setting title under power & sleep tab")
description: I18n.tr("Switch displays with an available 60 Hz mode to 60 Hz on battery and restore the previous mode on AC. Skips displays with VRR enabled.")
checked: SettingsData.lowerDisplayRefreshRateOnBattery
visible: BatteryService.batteryAvailable
onToggled: checked => SettingsData.set("lowerDisplayRefreshRateOnBattery", checked)
}
Rectangle {
width: parent.width
height: 1