mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-20 18:15:24 -04:00
fix(shell): don't treat DPMS off/on as a screen-reconnect recovery storm (#2654)
A DPMS off/on cycle removes an output from Quickshell.screens and re-adds it, which DMSShell's onScreensChanged cannot distinguish from a hotplug. It fired triggerSurfaceRecovery() on every such event; on hardware where recreating layer-shell surfaces re-wakes the just-powered-down output, this drives an endless recovery storm that visibly power-cycles the monitor. Route the screen-reconnect path through a 450 ms debounce (collapsing the output-remove + re-add pair into a single pass) followed by a 4 s cooldown, so repeated flaps trigger at most one recovery per window. Recovery still runs once per resume, so the partial-DPMS-resume recovery added for #2579 is preserved. The session-resume path runs its own recovery directly and now clears any queued screen-reconnect recovery to avoid a redundant follow-up. Fixes #2642
This commit is contained in:
+58
-2
@@ -323,6 +323,9 @@ Item {
|
|||||||
|
|
||||||
property bool hadRealScreen: true
|
property bool hadRealScreen: true
|
||||||
property var previousRealScreenNames: []
|
property var previousRealScreenNames: []
|
||||||
|
// Guards for the screen-reconnect recovery path (see scheduleScreenReconnectRecovery).
|
||||||
|
property bool _screenRecoveryCooldown: false
|
||||||
|
property bool _screenRecoveryPending: false
|
||||||
|
|
||||||
function _getRealScreenNames() {
|
function _getRealScreenNames() {
|
||||||
const names = [];
|
const names = [];
|
||||||
@@ -365,15 +368,60 @@ Item {
|
|||||||
const partialReconnect = root.previousRealScreenNames.length > 0
|
const partialReconnect = root.previousRealScreenNames.length > 0
|
||||||
&& currentNames.some(name => !root.previousRealScreenNames.includes(name));
|
&& currentNames.some(name => !root.previousRealScreenNames.includes(name));
|
||||||
if (fullReconnect || partialReconnect) {
|
if (fullReconnect || partialReconnect) {
|
||||||
log.info("Screen reconnect detected, triggering surface recovery",
|
log.info("Screen reconnect detected, scheduling surface recovery",
|
||||||
"full:", fullReconnect, "partial:", partialReconnect);
|
"full:", fullReconnect, "partial:", partialReconnect);
|
||||||
root.triggerSurfaceRecovery("screen-reconnect");
|
root.scheduleScreenReconnectRecovery();
|
||||||
}
|
}
|
||||||
root.hadRealScreen = hasReal;
|
root.hadRealScreen = hasReal;
|
||||||
root.previousRealScreenNames = currentNames;
|
root.previousRealScreenNames = currentNames;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A DPMS off/on cycle removes an output from the screen list and re-adds it,
|
||||||
|
// which is indistinguishable here from a hotplug. Recovering immediately on
|
||||||
|
// every such event lets a flapping monitor (or a recovery that itself perturbs
|
||||||
|
// the output) drive an endless recovery storm that power-cycles the display
|
||||||
|
// (#2642). Debounce a burst of changes into a single pass, then hold a cooldown
|
||||||
|
// so repeated flaps trigger at most one recovery per window. Recovery still runs
|
||||||
|
// once per resume, so a partial DPMS resume keeps redrawing its surfaces (#2579).
|
||||||
|
function scheduleScreenReconnectRecovery() {
|
||||||
|
if (root._screenRecoveryCooldown) {
|
||||||
|
root._screenRecoveryPending = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
screenReconnectDebounce.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: screenReconnectDebounce
|
||||||
|
// Wide enough to collapse the output-remove + output-re-add pair that one
|
||||||
|
// DPMS off/on cycle emits as two near-simultaneous events into one recovery.
|
||||||
|
interval: 450
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
root._screenRecoveryCooldown = true;
|
||||||
|
root._screenRecoveryPending = false;
|
||||||
|
screenReconnectCooldown.restart();
|
||||||
|
root.triggerSurfaceRecovery("screen-reconnect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: screenReconnectCooldown
|
||||||
|
// Must exceed the full two-pass surfaceResumeRecoveryTimer sequence
|
||||||
|
// (800 + 2000 ms) so the cooldown still covers an in-flight recovery;
|
||||||
|
// raise this if those passes are lengthened.
|
||||||
|
interval: 4000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
root._screenRecoveryCooldown = false;
|
||||||
|
if (root._screenRecoveryPending) {
|
||||||
|
root._screenRecoveryPending = false;
|
||||||
|
screenReconnectDebounce.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: surfaceResumeRecoveryTimer
|
id: surfaceResumeRecoveryTimer
|
||||||
interval: 800
|
interval: 800
|
||||||
@@ -1003,6 +1051,14 @@ Item {
|
|||||||
osdResumeRecreateTimer.interval = 400;
|
osdResumeRecreateTimer.interval = 400;
|
||||||
osdResumeRecreateTimer.restart();
|
osdResumeRecreateTimer.restart();
|
||||||
|
|
||||||
|
// This path runs its own recovery directly, so drop any queued or
|
||||||
|
// in-flight screen-reconnect recovery to avoid a redundant pass once
|
||||||
|
// its cooldown expires.
|
||||||
|
screenReconnectDebounce.stop();
|
||||||
|
screenReconnectCooldown.stop();
|
||||||
|
root._screenRecoveryCooldown = false;
|
||||||
|
root._screenRecoveryPending = false;
|
||||||
|
|
||||||
root.triggerSurfaceRecovery("sessionResumed");
|
root.triggerSurfaceRecovery("sessionResumed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user