mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-12 17:59:06 +02:00
* desktop UI light mode * Theme review fixes plus macOS window outline fix * Windows runtime chrome re-theming plus apply serialization * Windows chrome threading and theme event ordering fixes * Darken toggle and setting sidebar text * resolve theme appearance, apply on UI thread * read theme once per window * Re-assert Windows dark opt-in after SetTheme * split app-wide GTK theming from per-window chrome * Update Wails dependency and checksums * KDE tray icon panel fix * Five review fixes: theme ordering, cgo dedup, KDE panel resolution * Path guard hardening, toggle contrast, windows comment * non-vacuous escape tests * Default view edits * Polish settings nav, controls, borders, and disc * Profiles settings boarder, modals, and buttons * Additional edits based on feedback * Switch colors away from slight blue hue * Update missing lang * Fix vertical tab active view
100 lines
3.1 KiB
Go
100 lines
3.1 KiB
Go
//go:build linux && cgo && !android && !ios
|
|
|
|
package services
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// setAppAppearance points GTK at the light or dark variant of the current theme
|
|
// so the decorations match the webview. Without it a forced Light theme keeps
|
|
// dark decorations on a dark desktop, and the reverse.
|
|
//
|
|
// The theme name is switched, not just gtk-application-prefer-dark-theme:
|
|
// desktops such as Ubuntu implement dark mode as a separate theme (Yaru-dark),
|
|
// which that flag cannot lighten. The flag is still set for themes that do
|
|
// carry both variants under one name. Both are per-process settings, so this
|
|
// changes only our own decorations; GTK re-reads the desktop value on a change,
|
|
// which is why Theme.apply re-asserts. Must run on the main thread.
|
|
//
|
|
// GTK styling is app-wide, which is why this is separate from
|
|
// setWindowAppearance: it must be applied even when no window exists yet, since
|
|
// windows created later inherit it rather than carrying it in their options.
|
|
func setAppAppearance(dark bool) {
|
|
target := baseGtkTheme(gtkThemeName())
|
|
if dark {
|
|
if variant, ok := darkGtkVariant(target); ok {
|
|
target = variant
|
|
}
|
|
}
|
|
// An unknown name would leave GTK with no theme at all, so fall back to
|
|
// changing nothing and let the prefer-dark flag do what it can.
|
|
if target != "" && !gtkThemeExists(target) {
|
|
target = ""
|
|
}
|
|
applyGtkTheme(target, dark)
|
|
}
|
|
|
|
// baseGtkTheme strips a dark-variant suffix, so "Yaru-dark" becomes "Yaru".
|
|
func baseGtkTheme(name string) string {
|
|
for _, suffix := range []string{"-dark", "-Dark"} {
|
|
if len(name) > len(suffix) && strings.EqualFold(name[len(name)-len(suffix):], suffix) {
|
|
return name[:len(name)-len(suffix)]
|
|
}
|
|
}
|
|
return name
|
|
}
|
|
|
|
// darkGtkVariant reports the installed dark counterpart of a base theme name.
|
|
// Themes that carry both variants under one name have none, and rely on
|
|
// gtk-application-prefer-dark-theme instead.
|
|
func darkGtkVariant(base string) (string, bool) {
|
|
if base == "" {
|
|
return "", false
|
|
}
|
|
for _, suffix := range []string{"-dark", "-Dark"} {
|
|
if candidate := base + suffix; gtkThemeExists(candidate) {
|
|
return candidate, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// gtkThemeExists reports whether a theme of that name is installed, searching
|
|
// the same locations GTK does.
|
|
func gtkThemeExists(name string) bool {
|
|
if name == "" {
|
|
return false
|
|
}
|
|
for _, dir := range gtkThemeDirs() {
|
|
if info, err := os.Stat(filepath.Join(dir, name)); err == nil && info.IsDir() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func gtkThemeDirs() []string {
|
|
var dirs []string
|
|
if home, err := os.UserHomeDir(); err == nil {
|
|
dirs = append(dirs, filepath.Join(home, ".themes"))
|
|
}
|
|
if dataHome := os.Getenv("XDG_DATA_HOME"); dataHome != "" {
|
|
dirs = append(dirs, filepath.Join(dataHome, "themes"))
|
|
} else if home, err := os.UserHomeDir(); err == nil {
|
|
dirs = append(dirs, filepath.Join(home, ".local", "share", "themes"))
|
|
}
|
|
dataDirs := os.Getenv("XDG_DATA_DIRS")
|
|
if dataDirs == "" {
|
|
dataDirs = "/usr/local/share:/usr/share"
|
|
}
|
|
for _, dir := range strings.Split(dataDirs, ":") {
|
|
if dir != "" {
|
|
dirs = append(dirs, filepath.Join(dir, "themes"))
|
|
}
|
|
}
|
|
return dirs
|
|
}
|