From b87c36d29e9962f3751747f9e3e41f72a4e5f6d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristijan=20Ribari=C4=87?= <96492763+kristijanribaric@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:08:50 +0200 Subject: [PATCH] fix(quickshell): restore night mode and OSD surfaces after resume (#2254) --- quickshell/Common/Theme.qml | 32 ++++- quickshell/DMSShell.qml | 171 ++++++++++++++++--------- quickshell/Services/DisplayService.qml | 50 ++++++-- quickshell/Services/SessionService.qml | 70 +++++++++- 4 files changed, 250 insertions(+), 73 deletions(-) diff --git a/quickshell/Common/Theme.qml b/quickshell/Common/Theme.qml index 48d9889b..04df0b58 100644 --- a/quickshell/Common/Theme.qml +++ b/quickshell/Common/Theme.qml @@ -346,12 +346,11 @@ Singleton { function onLoginctlEvent(event) { if (!SessionData.themeModeAutoEnabled) return; - if (event.event === "unlock" || event.event === "resume") { - if (!themeAutoBackendAvailable()) { - root.evaluateThemeMode(); - return; - } - DMSService.sendRequest("theme.auto.trigger", {}); + if (typeof SettingsData !== "undefined" && SettingsData.loginctlLockIntegration) + return; + const eventType = String(event?.type || event?.event || "").toLowerCase(); + if (eventType === "unlock") { + root.triggerThemeAutomationRefresh(); } } @@ -414,6 +413,27 @@ Singleton { } } + Connections { + target: SessionService + enabled: typeof SessionService !== "undefined" && typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled + + function onSessionUnlocked() { + root.triggerThemeAutomationRefresh(); + } + + function onSessionResumed() { + root.triggerThemeAutomationRefresh(); + } + } + + function triggerThemeAutomationRefresh() { + if (!themeAutoBackendAvailable()) { + root.evaluateThemeMode(); + return; + } + DMSService.sendRequest("theme.auto.trigger", {}); + } + function applyGreeterTheme(themeName) { switchTheme(themeName, false, false); if (themeName === dynamic && dynamicColorsFileView.path) { diff --git a/quickshell/DMSShell.qml b/quickshell/DMSShell.qml index df3cbb82..21f42775 100644 --- a/quickshell/DMSShell.qml +++ b/quickshell/DMSShell.qml @@ -27,6 +27,15 @@ import qs.Services Item { id: root + property bool osdSurfacesLoaded: true + property int pendingOsdResumeReloads: 0 + + function recreateOsdSurfaces() { + OSDManager.currentOSDsByScreen = ({}); + osdSurfacesLoaded = false; + osdSurfaceReloadTimer.restart(); + } + Instantiator { id: daemonPluginInstantiator asynchronous: true @@ -232,6 +241,32 @@ Item { } } + Timer { + id: osdResumeRecreateTimer + interval: 400 + repeat: false + onTriggered: { + root.recreateOsdSurfaces(); + + if (root.pendingOsdResumeReloads > 1) { + root.pendingOsdResumeReloads--; + interval = 1400; + restart(); + return; + } + + root.pendingOsdResumeReloads = 0; + interval = 400; + } + } + + Timer { + id: osdSurfaceReloadTimer + interval: 120 + repeat: false + onTriggered: root.osdSurfacesLoaded = true + } + Component.onCompleted: { dockRecreateDebounce.start(); // Force PolkitService singleton to initialize @@ -749,6 +784,16 @@ Item { } } + Connections { + target: SessionService + + function onSessionResumed() { + root.pendingOsdResumeReloads = 2; + osdResumeRecreateTimer.interval = 400; + osdResumeRecreateTimer.restart(); + } + } + DankColorPickerModal { id: colorPickerModal @@ -923,51 +968,85 @@ Item { } } - Variants { - model: SettingsData.getFilteredScreens("osd") + Loader { + id: osdSurfacesLoader + active: root.osdSurfacesLoaded + asynchronous: false - delegate: VolumeOSD { - modelData: item - } - } + sourceComponent: Component { + Item { + Variants { + model: SettingsData.getFilteredScreens("osd") - Variants { - model: SettingsData.getFilteredScreens("osd") + delegate: VolumeOSD { + modelData: item + } + } - delegate: MediaVolumeOSD { - modelData: item - } - } + Variants { + model: SettingsData.getFilteredScreens("osd") - Variants { - model: SettingsData.getFilteredScreens("osd") + delegate: MediaVolumeOSD { + modelData: item + } + } - delegate: MediaPlaybackOSD { - modelData: item - } - } + Variants { + model: SettingsData.getFilteredScreens("osd") - Variants { - model: SettingsData.getFilteredScreens("osd") + delegate: MediaPlaybackOSD { + modelData: item + } + } - delegate: MicMuteOSD { - modelData: item - } - } + Variants { + model: SettingsData.getFilteredScreens("osd") - Variants { - model: SettingsData.getFilteredScreens("osd") + delegate: MicMuteOSD { + modelData: item + } + } - delegate: BrightnessOSD { - modelData: item - } - } + Variants { + model: SettingsData.getFilteredScreens("osd") - Variants { - model: SettingsData.getFilteredScreens("osd") + delegate: BrightnessOSD { + modelData: item + } + } - delegate: IdleInhibitorOSD { - modelData: item + Variants { + model: SettingsData.getFilteredScreens("osd") + + delegate: IdleInhibitorOSD { + modelData: item + } + } + + Variants { + model: SettingsData.osdPowerProfileEnabled ? SettingsData.getFilteredScreens("osd") : [] + + delegate: PowerProfileOSD { + modelData: item + } + } + + Variants { + model: SettingsData.getFilteredScreens("osd") + + delegate: CapsLockOSD { + modelData: item + } + } + + Variants { + model: SettingsData.getFilteredScreens("osd") + + delegate: AudioOutputOSD { + modelData: item + } + } + } } } @@ -977,30 +1056,6 @@ Item { source: "Services/PowerProfileWatcher.qml" } - Variants { - model: SettingsData.osdPowerProfileEnabled ? SettingsData.getFilteredScreens("osd") : [] - - delegate: PowerProfileOSD { - modelData: item - } - } - - Variants { - model: SettingsData.getFilteredScreens("osd") - - delegate: CapsLockOSD { - modelData: item - } - } - - Variants { - model: SettingsData.getFilteredScreens("osd") - - delegate: AudioOutputOSD { - modelData: item - } - } - LazyLoader { id: hyprlandOverviewLoader active: CompositorService.isHyprland diff --git a/quickshell/Services/DisplayService.qml b/quickshell/Services/DisplayService.qml index 553dd26b..5fede6af 100644 --- a/quickshell/Services/DisplayService.qml +++ b/quickshell/Services/DisplayService.qml @@ -40,6 +40,7 @@ Singleton { property bool nightModeEnabled: false property bool automationAvailable: false property bool gammaControlAvailable: false + property int resumeRecoveryAttempt: 0 property var gammaState: ({}) property int gammaCurrentTemp: gammaState?.currentTemp ?? 0 @@ -672,6 +673,15 @@ Singleton { } } + function runResumeRecoveryPass() { + checkGammaControlAvailability(); + rescanDevices(); + + if (nightModeEnabled) { + evaluateNightMode(); + } + } + function checkGammaControlAvailability() { if (!DMSService.isConnected) { return; @@ -730,6 +740,26 @@ Singleton { } } + Timer { + id: resumeRecoveryTimer + interval: 400 + repeat: false + + onTriggered: { + runResumeRecoveryPass(); + resumeRecoveryAttempt++; + + if (resumeRecoveryAttempt < 3) { + interval = resumeRecoveryAttempt === 1 ? 1400 : 2600; + restart(); + return; + } + + resumeRecoveryAttempt = 0; + interval = 400; + } + } + function rescanDevices() { if (!DMSService.isConnected) { return; @@ -815,19 +845,23 @@ Singleton { updateSingleDevice(device); } - function onLoginctlEvent(event) { - if (event.event === "unlock" || event.event === "resume") { - suppressOsd = true; - osdSuppressTimer.restart(); - evaluateNightMode(); - } - } - function onGammaStateUpdate(data) { root.gammaState = data; } } + Connections { + target: SessionService + + function onSessionResumed() { + suppressOsd = true; + osdSuppressTimer.restart(); + resumeRecoveryAttempt = 0; + resumeRecoveryTimer.interval = 400; + resumeRecoveryTimer.restart(); + } + } + // Session Data Connections Connections { target: SessionData diff --git a/quickshell/Services/SessionService.qml b/quickshell/Services/SessionService.qml index 5c7a4f2d..90163f1d 100644 --- a/quickshell/Services/SessionService.qml +++ b/quickshell/Services/SessionService.qml @@ -48,6 +48,9 @@ Singleton { signal loginctlStateChanged property bool stateInitialized: false + property string prepareForSleepSubscriptionId: "" + property bool prepareForSleepSubscriptionPending: false + property double lastResumeSignalTimestamp: 0 readonly property string socketPath: Quickshell.env("DMS_SOCKET") @@ -463,10 +466,13 @@ Singleton { function onConnectionStateChanged() { if (DMSService.isConnected) { checkDMSCapabilities(); + } else { + clearPrepareForSleepSubscriptionState(); } } function onCapabilitiesReceived() { + checkDMSCapabilities(); syncSleepInhibitor(); } } @@ -478,6 +484,13 @@ Singleton { function onCapabilitiesChanged() { checkDMSCapabilities(); } + + function onDbusSignalReceived(subscriptionId, data) { + if (subscriptionId !== prepareForSleepSubscriptionId) { + return; + } + handlePrepareForSleepSignal(data); + } } Connections { @@ -539,6 +552,61 @@ Singleton { loginctlAvailable = false; console.log("SessionService: loginctl capability not available in DMS"); } + + if (DMSService.capabilities.includes("dbus")) { + ensurePrepareForSleepSubscription(); + } else { + clearPrepareForSleepSubscriptionState(); + } + } + + function clearPrepareForSleepSubscriptionState() { + prepareForSleepSubscriptionId = ""; + prepareForSleepSubscriptionPending = false; + } + + function ensurePrepareForSleepSubscription() { + if (!DMSService.isConnected || !DMSService.capabilities.includes("dbus")) { + return; + } + + if (prepareForSleepSubscriptionId || prepareForSleepSubscriptionPending) { + return; + } + + prepareForSleepSubscriptionPending = true; + DMSService.dbusSubscribe("system", "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "PrepareForSleep", response => { + prepareForSleepSubscriptionPending = false; + + if (response.error) { + console.warn("SessionService: Failed to subscribe to PrepareForSleep:", response.error); + return; + } + + prepareForSleepSubscriptionId = response.result?.subscriptionId || ""; + }); + } + + function emitSessionResumedOnce() { + const now = Date.now(); + if ((now - lastResumeSignalTimestamp) < 1000) { + return; + } + lastResumeSignalTimestamp = now; + sessionResumed(); + } + + function handlePrepareForSleepSignal(data) { + if (!data?.body || data.body.length === 0) { + return; + } + + const wasSleeping = preparingForSleep; + preparingForSleep = data.body[0] === true; + + if (wasSleeping && !preparingForSleep) { + emitSessionResumedOnce(); + } } function getLoginctlState() { @@ -604,7 +672,7 @@ Singleton { } if (wasSleeping && !preparingForSleep) { - sessionResumed(); + emitSessionResumedOnce(); } loginctlStateChanged();