1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-23 11:35:25 -04:00

portal: add bidirectional syncing with freedesktop color-scheme, read in

addition to the existing write
This commit is contained in:
bbedward
2026-06-22 14:52:10 -04:00
parent b5e2e68a22
commit 5b28a63f75
4 changed files with 133 additions and 41 deletions
+72 -2
View File
@@ -6,6 +6,7 @@ import (
"os" "os"
"sync" "sync"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil" "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
) )
@@ -94,9 +95,65 @@ func (m *Manager) initializeSettings() error {
return fmt.Errorf("failed to update settings state: %w", err) return fmt.Errorf("failed to update settings state: %w", err)
} }
go m.watchSettingsChanges()
return nil 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 { func (m *Manager) updateAccountsState() error {
if !m.state.Accounts.Available || m.accountsObj == nil { if !m.state.Accounts.Available || m.accountsObj == nil {
return fmt.Errorf("accounts service not available") return fmt.Errorf("accounts service not available")
@@ -134,10 +191,23 @@ func (m *Manager) updateSettingsState() error {
var variant dbus.Variant var variant dbus.Variant
err := m.settingsObj.Call(dbusPortalSettingsInterface+".ReadOne", 0, "org.freedesktop.appearance", "color-scheme").Store(&variant) err := m.settingsObj.Call(dbusPortalSettingsInterface+".ReadOne", 0, "org.freedesktop.appearance", "color-scheme").Store(&variant)
if err != nil { 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.stateMutex.Lock()
m.state.Settings.ColorScheme = colorScheme m.state.Settings.ColorScheme = colorScheme
m.stateMutex.Unlock() m.stateMutex.Unlock()
+12 -27
View File
@@ -175,8 +175,7 @@ Item {
property bool barSurfacesLoaded: true property bool barSurfacesLoaded: true
function recreateBarSurfaces() { function recreateBarSurfaces() {
log.info("Recreating bar surfaces, screens:", Quickshell.screens.length, log.info("Recreating bar surfaces, screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","));
Quickshell.screens.map(s => s.name).join(","));
if (barSurfacesLoaded) if (barSurfacesLoaded)
barSurfacesLoaded = false; barSurfacesLoaded = false;
barSurfaceReloadAction.schedule(); barSurfaceReloadAction.schedule();
@@ -345,12 +344,7 @@ Item {
} }
function triggerSurfaceRecovery(source) { function triggerSurfaceRecovery(source) {
log.info("Surface recovery triggered by:", 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);
"screens:", Quickshell.screens.length,
Quickshell.screens.map(s => s.name).join(","),
"barLoaded:", root.barSurfacesLoaded,
"frameLoaded:", root.frameSurfacesLoaded,
"dockEnabled:", root.dockEnabled);
surfaceResumeRecoveryTimer.pass = 0; surfaceResumeRecoveryTimer.pass = 0;
surfaceResumeRecoveryTimer.interval = 800; surfaceResumeRecoveryTimer.interval = 800;
surfaceResumeRecoveryTimer.restart(); surfaceResumeRecoveryTimer.restart();
@@ -361,15 +355,11 @@ Item {
function onScreensChanged() { function onScreensChanged() {
const hasReal = root._hasRealScreen(); const hasReal = root._hasRealScreen();
const currentNames = root._getRealScreenNames(); const currentNames = root._getRealScreenNames();
log.info("Screens changed:", Quickshell.screens.length, log.info("Screens changed:", Quickshell.screens.length, Quickshell.screens.map(s => "'" + s.name + "'").join(","), "hasReal:", hasReal, "hadReal:", root.hadRealScreen);
Quickshell.screens.map(s => "'" + s.name + "'").join(","),
"hasReal:", hasReal, "hadReal:", root.hadRealScreen);
const fullReconnect = !root.hadRealScreen && hasReal; const fullReconnect = !root.hadRealScreen && hasReal;
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, scheduling surface recovery", log.info("Screen reconnect detected, scheduling surface recovery", "full:", fullReconnect, "partial:", partialReconnect);
"full:", fullReconnect, "partial:", partialReconnect);
root.scheduleScreenReconnectRecovery(); root.scheduleScreenReconnectRecovery();
} }
root.hadRealScreen = hasReal; root.hadRealScreen = hasReal;
@@ -429,9 +419,7 @@ Item {
property int pass: 0 property int pass: 0
onTriggered: { onTriggered: {
pass++; pass++;
log.info("Surface recovery pass", pass, log.info("Surface recovery pass", pass, "screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","));
"screens:", Quickshell.screens.length,
Quickshell.screens.map(s => s.name).join(","));
root.recreateBarSurfaces(); root.recreateBarSurfaces();
@@ -457,11 +445,12 @@ Item {
Component.onCompleted: { Component.onCompleted: {
dockRecreateDebounce.start(); dockRecreateDebounce.start();
// Force PolkitService singleton to initialize
PolkitService.polkitAvailable;
// Force DisplayConfigState singleton to initialize so auto-config runs at startup
DisplayConfigState.hasOutputBackend;
loginSoundTimer.start(); loginSoundTimer.start();
// These are dummy references just to trigger the singletons onCompleted to trigger
PolkitService.polkitAvailable;
DisplayConfigState.hasOutputBackend;
PortalService.systemColorScheme;
} }
Loader { Loader {
@@ -1041,11 +1030,7 @@ Item {
target: SessionService target: SessionService
function onSessionResumed() { function onSessionResumed() {
log.info("Session resumed: screens:", Quickshell.screens.length, log.info("Session resumed: screens:", Quickshell.screens.length, Quickshell.screens.map(s => s.name).join(","), "barLoaded:", root.barSurfacesLoaded, "frameLoaded:", root.frameSurfacesLoaded, "dockEnabled:", root.dockEnabled);
Quickshell.screens.map(s => s.name).join(","),
"barLoaded:", root.barSurfacesLoaded,
"frameLoaded:", root.frameSurfacesLoaded,
"dockEnabled:", root.dockEnabled);
root.pendingOsdResumeReloads = 2; root.pendingOsdResumeReloads = 2;
osdResumeRecreateTimer.interval = 400; osdResumeRecreateTimer.interval = 400;
+3
View File
@@ -58,6 +58,7 @@ Singleton {
signal openUrlRequested(string url) signal openUrlRequested(string url)
signal appPickerRequested(var data) signal appPickerRequested(var data)
signal screensaverStateUpdate(var data) signal screensaverStateUpdate(var data)
signal freedesktopStateUpdate(var data)
signal clipboardStateUpdate(var data) signal clipboardStateUpdate(var data)
signal locationStateUpdate(var data) signal locationStateUpdate(var data)
signal sysupdateStateUpdate(var data) signal sysupdateStateUpdate(var data)
@@ -384,6 +385,8 @@ Singleton {
screensaverInhibited = data.inhibited || false; screensaverInhibited = data.inhibited || false;
screensaverInhibitors = data.inhibitors || []; screensaverInhibitors = data.inhibitors || [];
screensaverStateUpdate(data); screensaverStateUpdate(data);
} else if (service === "freedesktop") {
freedesktopStateUpdate(data);
} else if (service === "dbus") { } else if (service === "dbus") {
dbusSignalReceived(data.subscriptionId || "", data); dbusSignalReceived(data.subscriptionId || "", data);
} else if (service === "clipboard") { } else if (service === "clipboard") {
+46 -12
View File
@@ -87,17 +87,22 @@ Singleton {
} }
} }
function getSystemColorScheme() { function evaluateColorScheme() {
if (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal === false) { if (typeof SettingsData === "undefined" || !SettingsData.syncModeWithPortal)
return; return;
} if (!settingsPortalAvailable)
if (!freedeskAvailable)
return; return;
DMSService.sendRequest("freedesktop.settings.getColorScheme", null, response => { if (typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled)
if (response.result) { return;
systemColorScheme = response.result.value || 0; 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) { function setLightMode(isLightMode) {
@@ -176,6 +181,36 @@ Singleton {
colorSchemeDetector.running = true; 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 { Connections {
target: DMSService target: DMSService
@@ -232,9 +267,8 @@ Singleton {
DMSService.sendRequest("freedesktop.getState", null, response => { DMSService.sendRequest("freedesktop.getState", null, response => {
if (response.result && response.result.settings) { if (response.result && response.result.settings) {
settingsPortalAvailable = response.result.settings.available || false; settingsPortalAvailable = response.result.settings.available || false;
if (settingsPortalAvailable && SettingsData.syncModeWithPortal) { systemColorScheme = response.result.settings.colorScheme || 0;
getSystemColorScheme(); evaluateColorScheme();
}
} }
}); });
} }