diff --git a/core/internal/server/freedesktop/manager.go b/core/internal/server/freedesktop/manager.go index 054f859e..1489245e 100644 --- a/core/internal/server/freedesktop/manager.go +++ b/core/internal/server/freedesktop/manager.go @@ -6,6 +6,7 @@ import ( "os" "sync" + "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil" "github.com/godbus/dbus/v5" ) @@ -94,9 +95,65 @@ func (m *Manager) initializeSettings() error { return fmt.Errorf("failed to update settings state: %w", err) } + go m.watchSettingsChanges() + return nil } +func (m *Manager) watchSettingsChanges() { + conn, err := dbus.ConnectSessionBus() + if err != nil { + log.Warnf("color-scheme watcher: session bus connect: %v", err) + return + } + + if err := conn.AddMatchSignal( + dbus.WithMatchInterface(dbusPortalSettingsInterface), + dbus.WithMatchMember("SettingChanged"), + ); err != nil { + log.Warnf("Failed to watch portal settings changes: %v", err) + conn.Close() + return + } + + signals := make(chan *dbus.Signal, 64) + conn.Signal(signals) + + for sig := range signals { + if sig.Name != dbusPortalSettingsInterface+".SettingChanged" { + continue + } + if len(sig.Body) < 3 { + continue + } + + namespace, _ := sig.Body[0].(string) + key, _ := sig.Body[1].(string) + if namespace != "org.freedesktop.appearance" || key != "color-scheme" { + continue + } + + variant, ok := sig.Body[2].(dbus.Variant) + if !ok { + continue + } + colorScheme, ok := dbusutil.As[uint32](variant) + if !ok { + continue + } + + m.stateMutex.Lock() + changed := m.state.Settings.ColorScheme != colorScheme || !m.state.Settings.Available + m.state.Settings.ColorScheme = colorScheme + m.state.Settings.Available = true + m.stateMutex.Unlock() + + if changed { + m.NotifySubscribers() + } + } +} + func (m *Manager) updateAccountsState() error { if !m.state.Accounts.Available || m.accountsObj == nil { return fmt.Errorf("accounts service not available") @@ -134,10 +191,23 @@ func (m *Manager) updateSettingsState() error { var variant dbus.Variant err := m.settingsObj.Call(dbusPortalSettingsInterface+".ReadOne", 0, "org.freedesktop.appearance", "color-scheme").Store(&variant) if err != nil { - return err + // Older xdg-desktop-portal versions only expose the deprecated Read. + var nested dbus.Variant + if rerr := m.settingsObj.Call(dbusPortalSettingsInterface+".Read", 0, "org.freedesktop.appearance", "color-scheme").Store(&nested); rerr != nil { + log.Warnf("color-scheme: ReadOne (%v) and Read (%v) both failed", err, rerr) + return err + } + variant = nested } - if colorScheme, ok := dbusutil.As[uint32](variant); ok { + colorScheme, ok := dbusutil.As[uint32](variant) + if !ok { + // Read double-wraps the value in a variant. + if inner, innerOk := variant.Value().(dbus.Variant); innerOk { + colorScheme, ok = dbusutil.As[uint32](inner) + } + } + if ok { m.stateMutex.Lock() m.state.Settings.ColorScheme = colorScheme m.stateMutex.Unlock() diff --git a/quickshell/DMSShell.qml b/quickshell/DMSShell.qml index 23726af2..16b69744 100644 --- a/quickshell/DMSShell.qml +++ b/quickshell/DMSShell.qml @@ -175,8 +175,7 @@ Item { property bool barSurfacesLoaded: true function recreateBarSurfaces() { - log.info("Recreating bar surfaces, screens:", Quickshell.screens.length, - Quickshell.screens.map(s => s.name).join(",")); + log.info("Recreating bar surfaces, screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(",")); if (barSurfacesLoaded) barSurfacesLoaded = false; barSurfaceReloadAction.schedule(); @@ -345,12 +344,7 @@ Item { } function triggerSurfaceRecovery(source) { - log.info("Surface recovery triggered by:", source, - "screens:", Quickshell.screens.length, - Quickshell.screens.map(s => s.name).join(","), - "barLoaded:", root.barSurfacesLoaded, - "frameLoaded:", root.frameSurfacesLoaded, - "dockEnabled:", root.dockEnabled); + log.info("Surface recovery triggered by:", source, "screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","), "barLoaded:", root.barSurfacesLoaded, "frameLoaded:", root.frameSurfacesLoaded, "dockEnabled:", root.dockEnabled); surfaceResumeRecoveryTimer.pass = 0; surfaceResumeRecoveryTimer.interval = 800; surfaceResumeRecoveryTimer.restart(); @@ -361,15 +355,11 @@ Item { function onScreensChanged() { const hasReal = root._hasRealScreen(); const currentNames = root._getRealScreenNames(); - log.info("Screens changed:", Quickshell.screens.length, - Quickshell.screens.map(s => "'" + s.name + "'").join(","), - "hasReal:", hasReal, "hadReal:", root.hadRealScreen); + log.info("Screens changed:", Quickshell.screens.length, Quickshell.screens.map(s => "'" + s.name + "'").join(","), "hasReal:", hasReal, "hadReal:", root.hadRealScreen); const fullReconnect = !root.hadRealScreen && hasReal; - const partialReconnect = root.previousRealScreenNames.length > 0 - && currentNames.some(name => !root.previousRealScreenNames.includes(name)); + const partialReconnect = root.previousRealScreenNames.length > 0 && currentNames.some(name => !root.previousRealScreenNames.includes(name)); if (fullReconnect || partialReconnect) { - log.info("Screen reconnect detected, scheduling surface recovery", - "full:", fullReconnect, "partial:", partialReconnect); + log.info("Screen reconnect detected, scheduling surface recovery", "full:", fullReconnect, "partial:", partialReconnect); root.scheduleScreenReconnectRecovery(); } root.hadRealScreen = hasReal; @@ -429,9 +419,7 @@ Item { property int pass: 0 onTriggered: { pass++; - log.info("Surface recovery pass", pass, - "screens:", Quickshell.screens.length, - Quickshell.screens.map(s => s.name).join(",")); + log.info("Surface recovery pass", pass, "screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(",")); root.recreateBarSurfaces(); @@ -457,11 +445,12 @@ Item { Component.onCompleted: { dockRecreateDebounce.start(); - // Force PolkitService singleton to initialize - PolkitService.polkitAvailable; - // Force DisplayConfigState singleton to initialize so auto-config runs at startup - DisplayConfigState.hasOutputBackend; loginSoundTimer.start(); + + // These are dummy references just to trigger the singletons onCompleted to trigger + PolkitService.polkitAvailable; + DisplayConfigState.hasOutputBackend; + PortalService.systemColorScheme; } Loader { @@ -1041,11 +1030,7 @@ Item { target: SessionService function onSessionResumed() { - log.info("Session resumed: screens:", Quickshell.screens.length, - Quickshell.screens.map(s => s.name).join(","), - "barLoaded:", root.barSurfacesLoaded, - "frameLoaded:", root.frameSurfacesLoaded, - "dockEnabled:", root.dockEnabled); + log.info("Session resumed: screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","), "barLoaded:", root.barSurfacesLoaded, "frameLoaded:", root.frameSurfacesLoaded, "dockEnabled:", root.dockEnabled); root.pendingOsdResumeReloads = 2; osdResumeRecreateTimer.interval = 400; diff --git a/quickshell/Services/DMSService.qml b/quickshell/Services/DMSService.qml index a3091478..9a75b34f 100644 --- a/quickshell/Services/DMSService.qml +++ b/quickshell/Services/DMSService.qml @@ -58,6 +58,7 @@ Singleton { signal openUrlRequested(string url) signal appPickerRequested(var data) signal screensaverStateUpdate(var data) + signal freedesktopStateUpdate(var data) signal clipboardStateUpdate(var data) signal locationStateUpdate(var data) signal sysupdateStateUpdate(var data) @@ -384,6 +385,8 @@ Singleton { screensaverInhibited = data.inhibited || false; screensaverInhibitors = data.inhibitors || []; screensaverStateUpdate(data); + } else if (service === "freedesktop") { + freedesktopStateUpdate(data); } else if (service === "dbus") { dbusSignalReceived(data.subscriptionId || "", data); } else if (service === "clipboard") { diff --git a/quickshell/Services/PortalService.qml b/quickshell/Services/PortalService.qml index d7bd3512..8011a558 100644 --- a/quickshell/Services/PortalService.qml +++ b/quickshell/Services/PortalService.qml @@ -87,17 +87,22 @@ Singleton { } } - function getSystemColorScheme() { - if (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal === false) { + function evaluateColorScheme() { + if (typeof SettingsData === "undefined" || !SettingsData.syncModeWithPortal) return; - } - if (!freedeskAvailable) + if (!settingsPortalAvailable) return; - DMSService.sendRequest("freedesktop.settings.getColorScheme", null, response => { - if (response.result) { - systemColorScheme = response.result.value || 0; - } - }); + if (typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled) + return; + if (typeof Theme === "undefined") + return; + // Defer mid-generation: setLightMode's regen would be dropped, and DMS's own transient color-scheme toggle would be misread. Re-run on worker completion. + if (Theme.workerRunning) + return; + const shouldBeLight = systemColorScheme !== 1; + if (Theme.isLightMode === shouldBeLight) + return; + Theme.setLightMode(shouldBeLight, true, false); } function setLightMode(isLightMode) { @@ -176,6 +181,36 @@ Singleton { colorSchemeDetector.running = true; } + Connections { + target: typeof SettingsData !== "undefined" ? SettingsData : null + + function onSyncModeWithPortalChanged() { + if (SettingsData.syncModeWithPortal) + root.evaluateColorScheme(); + } + } + + Connections { + target: DMSService + + function onFreedesktopStateUpdate(data) { + if (!data || !data.settings) + return; + root.settingsPortalAvailable = data.settings.available === true; + root.systemColorScheme = data.settings.colorScheme || 0; + root.evaluateColorScheme(); + } + } + + Connections { + target: typeof Theme !== "undefined" ? Theme : null + + function onWorkerRunningChanged() { + if (!Theme.workerRunning) + root.evaluateColorScheme(); + } + } + Connections { target: DMSService @@ -232,9 +267,8 @@ Singleton { DMSService.sendRequest("freedesktop.getState", null, response => { if (response.result && response.result.settings) { settingsPortalAvailable = response.result.settings.available || false; - if (settingsPortalAvailable && SettingsData.syncModeWithPortal) { - getSystemColorScheme(); - } + systemColorScheme = response.result.settings.colorScheme || 0; + evaluateColorScheme(); } }); }