Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions quickshell/Common/SettingsData.qml
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ Singleton {
property string batteryProfileName: ""
property int batteryPostLockMonitorTimeout: 0
property int batteryChargeLimit: 100
property bool lowerDisplayRefreshRateOnBattery: false
property bool lockBeforeSuspend: false
property bool loginctlLockIntegration: true
property bool fadeToLockEnabled: true
Expand Down Expand Up @@ -735,6 +736,7 @@ Singleton {
property var hyprlandOutputSettings: ({})
property var displayProfiles: ({})
property var activeDisplayProfile: ({})
property var activeDisplayProfileModes: ({})
property bool displayProfileAutoSelect: false
property bool displayShowDisconnected: false
property bool displaySnapToEdge: true
Expand Down
1 change: 1 addition & 0 deletions quickshell/Common/settings/SettingsSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ var SPEC = {
batteryProfileName: { def: "" },
batteryPostLockMonitorTimeout: { def: 0 },
batteryChargeLimit: { def: 100 },
lowerDisplayRefreshRateOnBattery: { def: false },
lockBeforeSuspend: { def: false },
loginctlLockIntegration: { def: true },
fadeToLockEnabled: { def: true },
Expand Down
40 changes: 40 additions & 0 deletions quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,26 @@ 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
};
}

const updated = JSON.parse(JSON.stringify(SettingsData.activeDisplayProfileModes || {}));
updated[compositor] = modes;
SettingsData.activeDisplayProfileModes = updated;
}

function generateProfileId() {
return "profile_" + Date.now() + "_" + Math.random().toString(36).slice(2, 9);
}
Expand Down Expand Up @@ -356,6 +376,7 @@ Singleton {
};
validatedProfiles = updated;
matchedProfile = findMatchingProfile();
publishActiveProfileModes();
profileSaved(profileId, profileName);
});
});
Expand Down Expand Up @@ -495,6 +516,7 @@ Singleton {
};
const onWriteSuccess = () => {
SettingsData.setActiveDisplayProfile(CompositorService.compositor, configId);
publishActiveProfileModes();
if (isManual) {
profilesLoading = false;
profileActivated(configId, profileName);
Expand Down Expand Up @@ -569,6 +591,7 @@ Singleton {
writeMonitorsJson(data, null);
validatedProfiles = validated;
matchedProfile = findMatchingProfile();
publishActiveProfileModes();
if (!profilesReady) {
profilesReady = true;
applyAutoConfig();
Expand Down Expand Up @@ -616,6 +639,7 @@ Singleton {
currentOutputSet = buildCurrentOutputSet();
matchedProfile = findMatchingProfile();
SettingsData.setActiveDisplayProfile(CompositorService.compositor, id);
publishActiveProfileModes();
profileSaved(id, profileName);
});
});
Expand Down Expand Up @@ -661,6 +685,7 @@ Singleton {
delete updated[profileId];
validatedProfiles = updated;
matchedProfile = findMatchingProfile();
publishActiveProfileModes();
profileDeleted(profileId);
});
});
Expand Down Expand Up @@ -838,6 +863,14 @@ Singleton {
target: CompositorService
function onCompositorChanged() {
root.checkIncludeStatus();
root.publishActiveProfileModes();
}
}

Connections {
target: SettingsData
function onActiveDisplayProfileChanged() {
root.publishActiveProfileModes();
}
}

Expand Down Expand Up @@ -2031,6 +2064,13 @@ Singleton {
};

if (profileId) {
const updated = JSON.parse(JSON.stringify(validatedProfiles));
if (updated[profileId]) {
updated[profileId].outputs = outputConfigs;
validatedProfiles = updated;
publishActiveProfileModes();
}

readMonitorsJson(data => {
const match = findConfigEntryById(data, profileId);
if (match) {
Expand Down
10 changes: 10 additions & 0 deletions quickshell/Modules/Settings/PowerSleepTab.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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")
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
Expand Down
36 changes: 35 additions & 1 deletion quickshell/Services/BatteryService.qml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ Singleton {
interval: 500
repeat: false
running: true
onTriggered: root.suppressSound = false
onTriggered: {
root.suppressSound = false;
DisplayService.syncRefreshRates(root.isPluggedIn, "startup");
}
}

readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY")
Expand Down Expand Up @@ -83,9 +86,40 @@ Singleton {
}
}

DisplayService.syncRefreshRates(root.isPluggedIn, "power-change");

previousPluggedState = isPluggedIn;
}

Connections {
target: SettingsData
function onLowerDisplayRefreshRateOnBatteryChanged() {
DisplayService.syncRefreshRates(root.isPluggedIn, "setting-change");
}

function onActiveDisplayProfileChanged() {
DisplayService.syncRefreshRates(root.isPluggedIn, "profile-change");
}

function onActiveDisplayProfileModesChanged() {
DisplayService.syncRefreshRates(root.isPluggedIn, "profile-change");
}
}

Connections {
target: NiriService
function onOutputsChanged() {
DisplayService.syncRefreshRates(root.isPluggedIn, "output-change");
}
}

Connections {
target: WlrOutputService
function onStateChanged() {
DisplayService.syncRefreshRates(root.isPluggedIn, "output-change");
}
}

// Aggregated charge/discharge rate
readonly property real changeRate: {
if (!batteryAvailable)
Expand Down
Loading