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

portal: prevent feedback loop with the gtk4 hack to make it react to

color scheme changes
This commit is contained in:
bbedward
2026-07-04 20:42:33 -04:00
parent 30ceda97a0
commit 8a85ab06a1
6 changed files with 136 additions and 11 deletions
+14
View File
@@ -912,6 +912,18 @@ func refreshGTK(mode ColorMode) {
} }
} }
var colorSchemeEchoHook func(scheme string)
func SetColorSchemeEchoHook(hook func(scheme string)) {
colorSchemeEchoHook = hook
}
func expectColorSchemeEcho(scheme string) {
if colorSchemeEchoHook != nil {
colorSchemeEchoHook(scheme)
}
}
func refreshGTK4() { func refreshGTK4() {
output, err := utils.GsettingsGet("org.gnome.desktop.interface", "color-scheme") output, err := utils.GsettingsGet("org.gnome.desktop.interface", "color-scheme")
if err != nil { if err != nil {
@@ -926,11 +938,13 @@ func refreshGTK4() {
toggle = "prefer-dark" toggle = "prefer-dark"
} }
expectColorSchemeEcho(toggle)
if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", toggle); err != nil { if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", toggle); err != nil {
log.Warnf("Failed to toggle color-scheme for GTK4 refresh: %v", err) log.Warnf("Failed to toggle color-scheme for GTK4 refresh: %v", err)
return return
} }
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
expectColorSchemeEcho(current)
if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", current); err != nil { if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", current); err != nil {
log.Warnf("Failed to restore color-scheme for GTK4 refresh: %v", err) log.Warnf("Failed to restore color-scheme for GTK4 refresh: %v", err)
} }
+39 -1
View File
@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"sync" "sync"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil" "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil"
@@ -100,6 +101,41 @@ func (m *Manager) initializeSettings() error {
return nil return nil
} }
// ExpectColorSchemeEcho registers a self-write so the watcher swallows its SettingChanged echo instead of forwarding it as an external change.
func (m *Manager) ExpectColorSchemeEcho(scheme string) {
var value uint32
switch scheme {
case "prefer-dark":
value = 1
case "prefer-light":
value = 2
}
m.selfEchoMu.Lock()
m.selfEchoes = append(m.selfEchoes, colorSchemeEcho{value: value, expires: time.Now().Add(10 * time.Second)})
m.selfEchoMu.Unlock()
}
func (m *Manager) consumeSelfEcho(value uint32) bool {
m.selfEchoMu.Lock()
defer m.selfEchoMu.Unlock()
now := time.Now()
kept := m.selfEchoes[:0]
consumed := false
for _, echo := range m.selfEchoes {
if now.After(echo.expires) {
continue
}
if !consumed && echo.value == value {
consumed = true
continue
}
kept = append(kept, echo)
}
m.selfEchoes = kept
return consumed
}
func (m *Manager) watchSettingsChanges() { func (m *Manager) watchSettingsChanges() {
conn, err := dbus.ConnectSessionBus() conn, err := dbus.ConnectSessionBus()
if err != nil { if err != nil {
@@ -142,13 +178,15 @@ func (m *Manager) watchSettingsChanges() {
continue continue
} }
selfInflicted := m.consumeSelfEcho(colorScheme)
m.stateMutex.Lock() m.stateMutex.Lock()
changed := m.state.Settings.ColorScheme != colorScheme || !m.state.Settings.Available changed := m.state.Settings.ColorScheme != colorScheme || !m.state.Settings.Available
m.state.Settings.ColorScheme = colorScheme m.state.Settings.ColorScheme = colorScheme
m.state.Settings.Available = true m.state.Settings.Available = true
m.stateMutex.Unlock() m.stateMutex.Unlock()
if changed { if changed && !selfInflicted {
m.NotifySubscribers() m.NotifySubscribers()
} }
} }
@@ -141,3 +141,45 @@ func TestManager_SettingsState_Modification(t *testing.T) {
original := manager.GetState() original := manager.GetState()
assert.Equal(t, uint32(0), original.Settings.ColorScheme) assert.Equal(t, uint32(0), original.Settings.ColorScheme)
} }
func TestManager_SelfEcho_ConsumesRegisteredWrites(t *testing.T) {
manager := &Manager{state: &FreedeskState{}}
manager.ExpectColorSchemeEcho("prefer-dark")
manager.ExpectColorSchemeEcho("default")
assert.True(t, manager.consumeSelfEcho(1))
assert.True(t, manager.consumeSelfEcho(0))
assert.False(t, manager.consumeSelfEcho(1))
assert.False(t, manager.consumeSelfEcho(0))
}
func TestManager_SelfEcho_ExternalChangePassesThrough(t *testing.T) {
manager := &Manager{state: &FreedeskState{}}
manager.ExpectColorSchemeEcho("prefer-dark")
assert.False(t, manager.consumeSelfEcho(2))
assert.True(t, manager.consumeSelfEcho(1))
}
func TestManager_SelfEcho_ConsumesOnePerRegistration(t *testing.T) {
manager := &Manager{state: &FreedeskState{}}
manager.ExpectColorSchemeEcho("prefer-dark")
manager.ExpectColorSchemeEcho("prefer-dark")
assert.True(t, manager.consumeSelfEcho(1))
assert.True(t, manager.consumeSelfEcho(1))
assert.False(t, manager.consumeSelfEcho(1))
}
func TestManager_SelfEcho_SchemeMapping(t *testing.T) {
manager := &Manager{state: &FreedeskState{}}
manager.ExpectColorSchemeEcho("prefer-light")
assert.True(t, manager.consumeSelfEcho(2))
manager.ExpectColorSchemeEcho("default")
assert.True(t, manager.consumeSelfEcho(0))
}
@@ -2,11 +2,17 @@ package freedesktop
import ( import (
"sync" "sync"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
) )
type colorSchemeEcho struct {
value uint32
expires time.Time
}
type AccountsState struct { type AccountsState struct {
Available bool `json:"available"` Available bool `json:"available"`
UserPath string `json:"userPath"` UserPath string `json:"userPath"`
@@ -63,4 +69,6 @@ type Manager struct {
screensaverCookieCounter uint32 screensaverCookieCounter uint32
screensaverFreedesktopClaimed bool screensaverFreedesktopClaimed bool
screensaverGnomeClaimed bool screensaverGnomeClaimed bool
selfEchoMu sync.Mutex
selfEchoes []colorSchemeEcho
} }
+2
View File
@@ -17,6 +17,7 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation" "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/core/internal/matugen"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/apppicker" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/apppicker"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/bluez" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/bluez"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
@@ -190,6 +191,7 @@ func InitializeFreedeskManager() error {
} }
freedesktopManager = manager freedesktopManager = manager
matugen.SetColorSchemeEchoHook(manager.ExpectColorSchemeEcho)
log.Info("Freedesktop manager initialized") log.Info("Freedesktop manager initialized")
return nil return nil
+31 -10
View File
@@ -87,22 +87,43 @@ Singleton {
} }
} }
function evaluateColorScheme() { function canSyncColorScheme() {
if (typeof SettingsData === "undefined" || !SettingsData.syncModeWithPortal) if (typeof SettingsData === "undefined" || !SettingsData.syncModeWithPortal)
return; return false;
if (!settingsPortalAvailable) if (!settingsPortalAvailable)
return; return false;
if (typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled) if (typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled)
return; return false;
if (typeof Theme === "undefined") return 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) // Only follow values stable for the settle window — the GTK4-refresh toggle reverts within ~50ms and following it would loop.
function evaluateColorScheme() {
if (!canSyncColorScheme())
return; return;
const shouldBeLight = systemColorScheme !== 1; const shouldBeLight = systemColorScheme !== 1;
if (Theme.isLightMode === shouldBeLight) if (Theme.isLightMode === shouldBeLight) {
colorSchemeSettleTimer.stop();
return; return;
Theme.setLightMode(shouldBeLight, true, false); }
colorSchemeSettleTimer.restart();
}
Timer {
id: colorSchemeSettleTimer
interval: 750
onTriggered: {
if (!root.canSyncColorScheme())
return;
const shouldBeLight = root.systemColorScheme !== 1;
if (Theme.isLightMode === shouldBeLight)
return;
if (Theme.workerRunning) {
restart();
return;
}
Theme.setLightMode(shouldBeLight, true, false);
}
} }
function setLightMode(isLightMode) { function setLightMode(isLightMode) {