mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-29 22:47:47 -04:00
theme: write desktop settings through dconf instead of gsettings
fixes #3280 port 1.6
This commit is contained in:
1 parent
d57e32c5cb
commit
a665ba8ca5
9 files changed
+185
-80
No files matched your search
@@ -2,6 +2,7 @@ package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
@@ -10,22 +11,35 @@ func dconfPath(schema, key string) string {
|
||||
return "/" + strings.ReplaceAll(schema, ".", "/") + "/" + key
|
||||
}
|
||||
|
||||
// GsettingsGet reads a gsettings value, falling back to dconf read.
|
||||
// gsettings reaches whichever backend its own GLib links, not necessarily the
|
||||
// dconf store GTK apps read, and it exits 0 either way.
|
||||
func dconfSelected() bool {
|
||||
backend := os.Getenv("GSETTINGS_BACKEND")
|
||||
return backend == "" || backend == "dconf"
|
||||
}
|
||||
|
||||
// GsettingsGet reads a desktop setting from dconf, falling back to gsettings.
|
||||
func GsettingsGet(schema, key string) (string, error) {
|
||||
if out, err := exec.Command("gsettings", "get", schema, key).Output(); err == nil {
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
if dconfSelected() {
|
||||
if out, err := exec.Command("dconf", "read", dconfPath(schema, key)).Output(); err == nil {
|
||||
if value := strings.TrimSpace(string(out)); value != "" {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
out, err := exec.Command("dconf", "read", dconfPath(schema, key)).Output()
|
||||
out, err := exec.Command("gsettings", "get", schema, key).Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("gsettings/dconf get failed for %s %s: %w", schema, key, err)
|
||||
return "", fmt.Errorf("dconf/gsettings get failed for %s %s: %w", schema, key, err)
|
||||
}
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
}
|
||||
|
||||
// GsettingsSet writes a gsettings value, falling back to dconf write.
|
||||
// GsettingsSet writes a desktop setting to dconf, falling back to gsettings.
|
||||
func GsettingsSet(schema, key, value string) error {
|
||||
if err := exec.Command("gsettings", "set", schema, key, value).Run(); err == nil {
|
||||
return nil
|
||||
if dconfSelected() {
|
||||
if err := exec.Command("dconf", "write", dconfPath(schema, key), "'"+value+"'").Run(); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return exec.Command("dconf", "write", dconfPath(schema, key), "'"+value+"'").Run()
|
||||
return exec.Command("gsettings", "set", schema, key, value).Run()
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fakeTool struct {
|
||||
name string
|
||||
stdout string
|
||||
exit int
|
||||
}
|
||||
|
||||
func fakeTools(t *testing.T, tools ...fakeTool) string {
|
||||
t.Helper()
|
||||
binDir := t.TempDir()
|
||||
logPath := filepath.Join(binDir, "calls.log")
|
||||
for _, tool := range tools {
|
||||
script := fmt.Sprintf("#!/bin/sh\necho \"%s $*\" >> %q\nprintf '%%s' %q\nexit %d\n", tool.name, logPath, tool.stdout, tool.exit)
|
||||
require.NoError(t, os.WriteFile(filepath.Join(binDir, tool.name), []byte(script), 0o755))
|
||||
}
|
||||
t.Setenv("PATH", binDir)
|
||||
return logPath
|
||||
}
|
||||
|
||||
func toolCalls(t *testing.T, logPath string) string {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(logPath)
|
||||
if os.IsNotExist(err) {
|
||||
return ""
|
||||
}
|
||||
require.NoError(t, err)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func TestGsettingsSetWritesDconf(t *testing.T) {
|
||||
t.Setenv("GSETTINGS_BACKEND", "")
|
||||
logPath := fakeTools(t,
|
||||
fakeTool{name: "dconf"},
|
||||
fakeTool{name: "gsettings"},
|
||||
)
|
||||
|
||||
require.NoError(t, GsettingsSet("org.gnome.desktop.interface", "gtk-theme", "adw-gtk3"))
|
||||
|
||||
calls := toolCalls(t, logPath)
|
||||
assert.Contains(t, calls, "dconf write /org/gnome/desktop/interface/gtk-theme 'adw-gtk3'")
|
||||
assert.NotContains(t, calls, "gsettings")
|
||||
}
|
||||
|
||||
func TestGsettingsSetFallsBackWhenDconfFails(t *testing.T) {
|
||||
t.Setenv("GSETTINGS_BACKEND", "")
|
||||
logPath := fakeTools(t,
|
||||
fakeTool{name: "dconf", exit: 1},
|
||||
fakeTool{name: "gsettings"},
|
||||
)
|
||||
|
||||
require.NoError(t, GsettingsSet("org.gnome.desktop.interface", "gtk-theme", "adw-gtk3"))
|
||||
|
||||
assert.Contains(t, toolCalls(t, logPath), "gsettings set org.gnome.desktop.interface gtk-theme adw-gtk3")
|
||||
}
|
||||
|
||||
func TestGsettingsSetHonorsBackendOverride(t *testing.T) {
|
||||
t.Setenv("GSETTINGS_BACKEND", "keyfile")
|
||||
logPath := fakeTools(t,
|
||||
fakeTool{name: "dconf"},
|
||||
fakeTool{name: "gsettings"},
|
||||
)
|
||||
|
||||
require.NoError(t, GsettingsSet("org.gnome.desktop.interface", "gtk-theme", "adw-gtk3"))
|
||||
|
||||
calls := toolCalls(t, logPath)
|
||||
assert.Contains(t, calls, "gsettings set org.gnome.desktop.interface gtk-theme adw-gtk3")
|
||||
assert.NotContains(t, calls, "dconf write")
|
||||
}
|
||||
|
||||
func TestGsettingsGetReadsDconf(t *testing.T) {
|
||||
t.Setenv("GSETTINGS_BACKEND", "")
|
||||
fakeTools(t,
|
||||
fakeTool{name: "dconf", stdout: "'adw-gtk3-dark'"},
|
||||
fakeTool{name: "gsettings", stdout: "'Adwaita'"},
|
||||
)
|
||||
|
||||
value, err := GsettingsGet("org.gnome.desktop.interface", "gtk-theme")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "'adw-gtk3-dark'", value)
|
||||
}
|
||||
|
||||
func TestGsettingsGetFallsBackWhenDconfUnset(t *testing.T) {
|
||||
t.Setenv("GSETTINGS_BACKEND", "")
|
||||
fakeTools(t,
|
||||
fakeTool{name: "dconf"},
|
||||
fakeTool{name: "gsettings", stdout: "'Adwaita'"},
|
||||
)
|
||||
|
||||
value, err := GsettingsGet("org.gnome.desktop.interface", "gtk-theme")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "'Adwaita'", value)
|
||||
}
|
||||
|
||||
func TestGsettingsGetFailsWhenNoToolWorks(t *testing.T) {
|
||||
t.Setenv("GSETTINGS_BACKEND", "")
|
||||
fakeTools(t,
|
||||
fakeTool{name: "dconf", exit: 1},
|
||||
fakeTool{name: "gsettings", exit: 1},
|
||||
)
|
||||
|
||||
_, err := GsettingsGet("org.gnome.desktop.interface", "gtk-theme")
|
||||
|
||||
require.ErrorContains(t, err, "gtk-theme")
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.pragma library
|
||||
|
||||
function path(schema, key) {
|
||||
return "/" + schema.replace(/\./g, "/") + "/" + key;
|
||||
}
|
||||
|
||||
// gsettings reaches whichever backend its own GLib links, not necessarily the
|
||||
// dconf store GTK apps read, and it exits 0 either way.
|
||||
function backendIsDconf() {
|
||||
return "[ \"${GSETTINGS_BACKEND:-dconf}\" = dconf ]";
|
||||
}
|
||||
|
||||
function getCmd(schema, key) {
|
||||
return "{ { " + backendIsDconf() + " && dconf read " + path(schema, key) + " 2>/dev/null | grep .; } || gsettings get " + schema + " " + key + " 2>/dev/null; } | tr -d \"'\"";
|
||||
}
|
||||
|
||||
function setCmd(schema, key, value) {
|
||||
return "{ " + backendIsDconf() + " && dconf write " + path(schema, key) + " \"'" + value + "'\" 2>/dev/null; } || gsettings set " + schema + " " + key + " '" + value + "'";
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Common.settings
|
||||
import qs.Services
|
||||
import "GSettings.js" as GSettings
|
||||
import "settings/SettingsSpec.js" as Spec
|
||||
import "settings/SettingsStore.js" as Store
|
||||
|
||||
@@ -1524,11 +1525,7 @@ Singleton {
|
||||
return;
|
||||
if (!SessionData.lastAppliedIconTheme)
|
||||
return;
|
||||
const script = `if command -v gsettings >/dev/null 2>&1; then
|
||||
gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed "s/'//g"
|
||||
elif command -v dconf >/dev/null 2>&1; then
|
||||
dconf read /org/gnome/desktop/interface/icon-theme 2>/dev/null | sed "s/'//g"
|
||||
fi`;
|
||||
const script = GSettings.getCmd("org.gnome.desktop.interface", "icon-theme");
|
||||
|
||||
Proc.runCommand("iconThemeDriftCheck", ["sh", "-c", script], (output, exitCode) => {
|
||||
const platform = (output || "").trim();
|
||||
@@ -1566,11 +1563,7 @@ Singleton {
|
||||
const resolved = resolveIconTheme();
|
||||
let cosmicThemeName = (resolved === "System Default") ? systemDefaultIconTheme : resolved;
|
||||
if (!cosmicThemeName || cosmicThemeName === "System Default") {
|
||||
const detectScript = `if command -v gsettings >/dev/null 2>&1; then
|
||||
gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed "s/'//g"
|
||||
elif command -v dconf >/dev/null 2>&1; then
|
||||
dconf read /org/gnome/desktop/interface/icon-theme 2>/dev/null | sed "s/'//g"
|
||||
fi`;
|
||||
const detectScript = GSettings.getCmd("org.gnome.desktop.interface", "icon-theme");
|
||||
|
||||
Proc.runCommand("detectCosmicIconTheme", ["sh", "-c", detectScript], (output, exitCode) => {
|
||||
if (exitCode !== 0)
|
||||
@@ -1632,11 +1625,7 @@ Singleton {
|
||||
fi
|
||||
done
|
||||
|
||||
if command -v gsettings >/dev/null 2>&1; then
|
||||
gsettings set org.gnome.desktop.interface icon-theme '${gtkThemeName}' 2>/dev/null || true
|
||||
elif command -v dconf >/dev/null 2>&1; then
|
||||
dconf write /org/gnome/desktop/interface/icon-theme "'${gtkThemeName}'" 2>/dev/null || true
|
||||
fi
|
||||
${GSettings.setCmd("org.gnome.desktop.interface", "icon-theme", gtkThemeName)} || true
|
||||
|
||||
pkill -HUP -f 'gtk' 2>/dev/null || true`;
|
||||
|
||||
@@ -2132,7 +2121,7 @@ Singleton {
|
||||
const pathsArg = iconPaths.join(" ");
|
||||
|
||||
const script = `
|
||||
echo "SYSDEFAULT:$(gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed "s/'//g" || echo '')"
|
||||
echo "SYSDEFAULT:$(${GSettings.getCmd("org.gnome.desktop.interface", "icon-theme")})"
|
||||
for dir in ${pathsArg}; do
|
||||
[ -d "$dir" ] || continue
|
||||
for theme in "$dir"/*/; do
|
||||
@@ -2171,7 +2160,7 @@ Singleton {
|
||||
const pathsArg = cursorPaths.join(" ");
|
||||
|
||||
const script = `
|
||||
echo "SYSDEFAULT:$(gsettings get org.gnome.desktop.interface cursor-theme 2>/dev/null | sed "s/'//g" || echo '')"
|
||||
echo "SYSDEFAULT:$(${GSettings.getCmd("org.gnome.desktop.interface", "cursor-theme")})"
|
||||
for dir in ${pathsArg}; do
|
||||
[ -d "$dir" ] || continue
|
||||
for theme in "$dir"/*/; do
|
||||
|
||||
@@ -10,6 +10,7 @@ import qs.DankCommon.Common as DankCommon
|
||||
import qs.Services
|
||||
import qs.Modules.Greetd
|
||||
import "StockThemes.js" as StockThemes
|
||||
import "GSettings.js" as GSettings
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -1918,23 +1919,12 @@ Singleton {
|
||||
const theme = isLight ? "adw-gtk3" : "adw-gtk3-dark";
|
||||
const schema = "org.gnome.desktop.interface";
|
||||
const key = "gtk-theme";
|
||||
const reset = GSettings.setCmd(schema, key, "");
|
||||
const apply = GSettings.setCmd(schema, key, theme);
|
||||
|
||||
const makeCmd = (tool, schema, val) => {
|
||||
if (tool === "gsettings") {
|
||||
return `gsettings set ${schema} ${key} '' && gsettings set ${schema} ${key} ${val}`;
|
||||
} else {
|
||||
const dconfPath = `/${schema.replace(/\./g, "/")}`;
|
||||
return `dconf write ${dconfPath}/${key} "''" && dconf write ${dconfPath}/${key} "'${val}'"`;
|
||||
}
|
||||
};
|
||||
|
||||
Proc.runCommand("gtkRefresher", ["sh", "-c", makeCmd("gsettings", schema, theme)], (output, exitCode) => {
|
||||
Proc.runCommand("gtkRefresher", ["sh", "-c", `${reset}; ${apply}`], (output, exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
Proc.runCommand("gtkRefreshFallback", ["sh", "-c", makeCmd("dconf", schema, theme)], (output, exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
log.warn("Failed to refresh gtk-theme");
|
||||
}
|
||||
});
|
||||
log.warn("Failed to refresh gtk-theme");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ Item {
|
||||
tab: "sounds"
|
||||
tags: ["sound", "theme", "system"]
|
||||
settingKey: "useSystemSoundTheme"
|
||||
visible: AudioService.gsettingsAvailable
|
||||
visible: AudioService.soundThemeSupported
|
||||
text: I18n.tr("Use System Theme")
|
||||
description: I18n.tr("Use sound theme from system settings")
|
||||
checked: SettingsData.useSystemSoundTheme
|
||||
@@ -123,7 +123,7 @@ Item {
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.2
|
||||
visible: AudioService.gsettingsAvailable
|
||||
visible: AudioService.soundThemeSupported
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
|
||||
@@ -8,6 +8,7 @@ import Quickshell.Io
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import "../Common/GSettings.js" as GSettings
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -18,7 +19,7 @@ Singleton {
|
||||
|
||||
readonly property bool soundsAvailable: MultimediaService.available
|
||||
property bool playersRequested: false
|
||||
property bool gsettingsAvailable: false
|
||||
property bool soundThemeSupported: false
|
||||
property var availableSoundThemes: []
|
||||
property string currentSoundTheme: ""
|
||||
property var soundFilePaths: ({})
|
||||
@@ -545,10 +546,10 @@ EOFCONFIG
|
||||
}
|
||||
}
|
||||
|
||||
function checkGsettings() {
|
||||
Proc.runCommand("checkGsettings", ["sh", "-c", "gsettings get org.gnome.desktop.sound theme-name 2>/dev/null"], (output, exitCode) => {
|
||||
gsettingsAvailable = (exitCode === 0);
|
||||
if (gsettingsAvailable) {
|
||||
function checkSoundThemeSupport() {
|
||||
Proc.runCommand("checkSoundThemeSupport", ["sh", "-c", GSettings.getCmd("org.gnome.desktop.sound", "theme-name")], (output, exitCode) => {
|
||||
soundThemeSupported = (output || "").trim().length > 0;
|
||||
if (soundThemeSupported) {
|
||||
scanSoundThemes();
|
||||
getCurrentSoundTheme();
|
||||
}
|
||||
@@ -581,8 +582,8 @@ EOFCONFIG
|
||||
}
|
||||
|
||||
function getCurrentSoundTheme() {
|
||||
Proc.runCommand("getCurrentSoundTheme", ["sh", "-c", "gsettings get org.gnome.desktop.sound theme-name 2>/dev/null | sed \"s/'//g\""], (output, exitCode) => {
|
||||
if (exitCode === 0 && output.trim()) {
|
||||
Proc.runCommand("getCurrentSoundTheme", ["sh", "-c", GSettings.getCmd("org.gnome.desktop.sound", "theme-name")], (output, exitCode) => {
|
||||
if (output.trim()) {
|
||||
currentSoundTheme = output.trim();
|
||||
log.debug("Current system sound theme:", currentSoundTheme);
|
||||
if (SettingsData.useSystemSoundTheme) {
|
||||
@@ -600,7 +601,7 @@ EOFCONFIG
|
||||
return;
|
||||
}
|
||||
|
||||
Proc.runCommand("setSoundTheme", ["sh", "-c", `gsettings set org.gnome.desktop.sound theme-name '${themeName}'`], (output, exitCode) => {
|
||||
Proc.runCommand("setSoundTheme", ["sh", "-c", GSettings.setCmd("org.gnome.desktop.sound", "theme-name", themeName)], (output, exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
currentSoundTheme = themeName;
|
||||
if (SettingsData.useSystemSoundTheme) {
|
||||
@@ -1171,7 +1172,7 @@ EOFCONFIG
|
||||
onSoundsAvailableChanged: {
|
||||
if (!soundsAvailable)
|
||||
return;
|
||||
checkGsettings();
|
||||
checkSoundThemeSupport();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
@@ -84,7 +84,7 @@ Singleton {
|
||||
if (Quickshell.env("QS_ICON_THEME"))
|
||||
return;
|
||||
const script = `v=$(sed -n 's/^gtk-icon-theme-name *= *//p' "\${XDG_CONFIG_HOME:-$HOME/.config}/gtk-3.0/settings.ini" 2>/dev/null | head -1)
|
||||
[ -z "$v" ] && command -v gsettings >/dev/null 2>&1 && v=$(gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null)
|
||||
[ -z "$v" ] && v=$(${GSettings.getCmd("org.gnome.desktop.interface", "icon-theme")})
|
||||
printf '%s' "$v" | tr -d "'\\""`;
|
||||
|
||||
Proc.runCommand("iconThemeProbe", ["sh", "-c", script], (out, code) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import "../Common/GSettings.js" as GSettings
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -19,7 +20,6 @@ Singleton {
|
||||
property bool colorSchemeInitialized: false
|
||||
|
||||
property bool freedeskAvailable: false
|
||||
property string colorSchemeCommand: ""
|
||||
property string pendingProfileImage: ""
|
||||
|
||||
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
|
||||
@@ -146,14 +146,7 @@ Singleton {
|
||||
const preferLight = isLightMode && systemColorScheme === 2;
|
||||
const targetScheme = isLightMode ? (preferLight ? "prefer-light" : "default") : "prefer-dark";
|
||||
|
||||
switch (colorSchemeCommand) {
|
||||
case "gsettings":
|
||||
Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", targetScheme]);
|
||||
break;
|
||||
case "dconf":
|
||||
Quickshell.execDetached(["dconf", "write", "/org/gnome/desktop/interface/color-scheme", `'${targetScheme}'`]);
|
||||
break;
|
||||
}
|
||||
Quickshell.execDetached(["sh", "-c", GSettings.setCmd("org.gnome.desktop.interface", "color-scheme", targetScheme)]);
|
||||
}
|
||||
|
||||
function setSystemIconTheme(themeName) {
|
||||
@@ -207,7 +200,6 @@ Singleton {
|
||||
} else {
|
||||
log.info("DMS_SOCKET not set");
|
||||
}
|
||||
colorSchemeDetector.running = true;
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -348,23 +340,6 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: colorSchemeDetector
|
||||
command: ["bash", "-c", "command -v gsettings || command -v dconf"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const cmd = text.trim();
|
||||
if (cmd.includes("gsettings")) {
|
||||
root.colorSchemeCommand = "gsettings";
|
||||
} else if (cmd.includes("dconf")) {
|
||||
root.colorSchemeCommand = "dconf";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "profile"
|
||||
|
||||
|
||||
Reference in new issue
Block a user