diff --git a/app/app_desktop_darwin.go b/app/app_desktop_darwin.go index 026e823d2b..01aca7c36e 100644 --- a/app/app_desktop_darwin.go +++ b/app/app_desktop_darwin.go @@ -16,7 +16,6 @@ import ( "net/url" "os" "os/exec" - "path/filepath" "fyne.io/fyne/v2" ) @@ -41,13 +40,6 @@ func (a *fyneApp) SetSystemTrayMenu(menu *fyne.Menu) { } } -func rootConfigDir() string { - homeDir, _ := os.UserHomeDir() - - desktopConfig := filepath.Join(filepath.Join(homeDir, "Library"), "Preferences") - return filepath.Join(desktopConfig, "fyne") -} - //export themeChanged func themeChanged() { fyne.CurrentApp().Settings().(*settings).setupTheme() diff --git a/app/app_goxjs.go b/app/app_goxjs.go index 08c2df7611..f3c4a49ec6 100644 --- a/app/app_goxjs.go +++ b/app/app_goxjs.go @@ -50,10 +50,6 @@ func (a *fyneApp) SendNotification(n *fyne.Notification) { } } -func rootConfigDir() string { - return "/data/" -} - var themeChanged = js.FuncOf(func(this js.Value, args []js.Value) interface{} { if len(args) > 0 && args[0].Type() == js.TypeObject { fyne.CurrentApp().Settings().(*settings).setupTheme() diff --git a/app/app_mobile_and.go b/app/app_mobile_and.go index 9abf04745c..fb1d8afa60 100644 --- a/app/app_mobile_and.go +++ b/app/app_mobile_and.go @@ -12,10 +12,7 @@ void sendNotification(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char */ import "C" import ( - "log" "net/url" - "os" - "path/filepath" "unsafe" "fyne.io/fyne/v2" @@ -44,13 +41,3 @@ func (a *fyneApp) SendNotification(n *fyne.Notification) { return nil }) } - -func rootConfigDir() string { - filesDir := os.Getenv("FILESDIR") - if filesDir == "" { - log.Println("FILESDIR env was not set by android native code") - return "/data/data" // probably won't work, but we can't make a better guess - } - - return filepath.Join(filesDir, "fyne") -} diff --git a/app/app_mobile_and_test.go b/app/app_mobile_and_test.go index da0244cb1f..408446dadc 100644 --- a/app/app_mobile_and_test.go +++ b/app/app_mobile_and_test.go @@ -6,6 +6,8 @@ import ( "os" "testing" + "fyne.io/fyne/v2/internal/app" + "github.com/stretchr/testify/assert" ) @@ -13,7 +15,7 @@ func Test_RootConfigDir(t *testing.T) { oldEnv := os.Getenv("FILESPATH") os.Setenv("FILESPATH", "/tmp") - assert.Equal(t, "/tmp", rootConfigDir()) + assert.Equal(t, "/tmp", app.RootConfigDir()) os.Setenv("FILESPATH", oldEnv) } diff --git a/app/app_mobile_ios.go b/app/app_mobile_ios.go index 86b12d1fbb..82b8d25f49 100644 --- a/app/app_mobile_ios.go +++ b/app/app_mobile_ios.go @@ -15,15 +15,9 @@ void sendNotification(char *title, char *content); import "C" import ( "net/url" - "path/filepath" "unsafe" ) -func rootConfigDir() string { - root := C.documentsPath() - return filepath.Join(C.GoString(root), "fyne") -} - func (a *fyneApp) OpenURL(url *url.URL) error { urlStr := C.CString(url.String()) C.openURL(urlStr) diff --git a/app/app_other.go b/app/app_other.go index 83c336defd..72601c7a50 100644 --- a/app/app_other.go +++ b/app/app_other.go @@ -5,16 +5,10 @@ package app import ( "errors" "net/url" - "os" - "path/filepath" "fyne.io/fyne/v2" ) -func rootConfigDir() string { - return filepath.Join(os.TempDir(), "fyne-test") -} - func (a *fyneApp) OpenURL(_ *url.URL) error { return errors.New("Unable to open url for unknown operating system") } diff --git a/app/app_windows.go b/app/app_windows.go index e6eb30966d..ad3a6832db 100644 --- a/app/app_windows.go +++ b/app/app_windows.go @@ -29,13 +29,6 @@ $xml.LoadXml($toastXml.OuterXml) $toast = [Windows.UI.Notifications.ToastNotification]::new($xml) [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("%s").Show($toast);` -func rootConfigDir() string { - homeDir, _ := os.UserHomeDir() - - desktopConfig := filepath.Join(filepath.Join(homeDir, "AppData"), "Roaming") - return filepath.Join(desktopConfig, "fyne") -} - func (a *fyneApp) OpenURL(url *url.URL) error { cmd := exec.Command("rundll32", "url.dll,FileProtocolHandler", url.String()) cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr diff --git a/app/app_xdg.go b/app/app_xdg.go index 71aeae4142..a9068a4f87 100644 --- a/app/app_xdg.go +++ b/app/app_xdg.go @@ -6,7 +6,6 @@ import ( "net/url" "os" "os/exec" - "path/filepath" "sync/atomic" "github.com/godbus/dbus/v5" @@ -114,11 +113,6 @@ func (a *fyneApp) SetSystemTrayIcon(icon fyne.Resource) { } } -func rootConfigDir() string { - desktopConfig, _ := os.UserConfigDir() - return filepath.Join(desktopConfig, "fyne") -} - func watchTheme(s *settings) { go func() { // Theme lookup hangs on some desktops. Update theme variant cache from within goroutine. diff --git a/app/preferences_android.go b/app/preferences_android.go index a2e7e47fbf..ac9f834cd7 100644 --- a/app/preferences_android.go +++ b/app/preferences_android.go @@ -2,7 +2,11 @@ package app -import "path/filepath" +import ( + "path/filepath" + + "fyne.io/fyne/v2/internal/app" +) // storagePath returns the location of the settings storage func (p *preferences) storagePath() string { @@ -12,7 +16,7 @@ func (p *preferences) storagePath() string { // storageRoot returns the location of the app storage func (a *fyneApp) storageRoot() string { - return rootConfigDir() // we are in a sandbox, so no app ID added to this path + return app.RootConfigDir() // we are in a sandbox, so no app ID added to this path } func (p *preferences) watch() { diff --git a/app/preferences_ios.go b/app/preferences_ios.go index 8720dcf331..b861eec475 100644 --- a/app/preferences_ios.go +++ b/app/preferences_ios.go @@ -4,6 +4,8 @@ package app import ( "path/filepath" + + "fyne.io/fyne/v2/internal/app" ) import "C" @@ -15,7 +17,7 @@ func (p *preferences) storagePath() string { // storageRoot returns the location of the app storage func (a *fyneApp) storageRoot() string { - return rootConfigDir() // we are in a sandbox, so no app ID added to this path + return app.RootConfigDir() // we are in a sandbox, so no app ID added to this path } func (p *preferences) watch() { diff --git a/app/preferences_mobile.go b/app/preferences_mobile.go index 0faa122672..1fd186f945 100644 --- a/app/preferences_mobile.go +++ b/app/preferences_mobile.go @@ -2,7 +2,11 @@ package app -import "path/filepath" +import ( + "path/filepath" + + "fyne.io/fyne/v2/internal/app" +) // storagePath returns the location of the settings storage func (p *preferences) storagePath() string { @@ -11,7 +15,7 @@ func (p *preferences) storagePath() string { // storageRoot returns the location of the app storage func (a *fyneApp) storageRoot() string { - return filepath.Join(rootConfigDir(), a.UniqueID()) + return filepath.Join(app.RootConfigDir(), a.UniqueID()) } func (p *preferences) watch() { diff --git a/app/preferences_other.go b/app/preferences_other.go index 472253747c..3e698c2c0a 100644 --- a/app/preferences_other.go +++ b/app/preferences_other.go @@ -2,7 +2,11 @@ package app -import "path/filepath" +import ( + "path/filepath" + + "fyne.io/fyne/v2/internal/app" +) // storagePath returns the location of the settings storage func (p *preferences) storagePath() string { @@ -11,7 +15,7 @@ func (p *preferences) storagePath() string { // storageRoot returns the location of the app storage func (a *fyneApp) storageRoot() string { - return filepath.Join(rootConfigDir(), a.UniqueID()) + return filepath.Join(app.RootConfigDir(), a.UniqueID()) } func (p *preferences) watch() { diff --git a/app/settings.go b/app/settings.go index a297a95f35..80124ec325 100644 --- a/app/settings.go +++ b/app/settings.go @@ -7,7 +7,7 @@ import ( "sync" "fyne.io/fyne/v2" - internalapp "fyne.io/fyne/v2/internal/app" + "fyne.io/fyne/v2/internal/app" "fyne.io/fyne/v2/internal/build" "fyne.io/fyne/v2/theme" ) @@ -25,7 +25,7 @@ type SettingsSchema struct { // StoragePath returns the location of the settings storage func (sc *SettingsSchema) StoragePath() string { - return filepath.Join(rootConfigDir(), "settings.json") + return filepath.Join(app.RootConfigDir(), "settings.json") } // Declare conformity with Settings interface @@ -134,7 +134,7 @@ func (s *settings) fileChanged() { } func (s *settings) loadSystemTheme() fyne.Theme { - path := filepath.Join(rootConfigDir(), "theme.json") + path := filepath.Join(app.RootConfigDir(), "theme.json") data, err := fyne.LoadResourceFromPath(path) if err != nil { if !os.IsNotExist(err) { @@ -158,7 +158,7 @@ func (s *settings) setupTheme() { name = env } - variant := internalapp.DefaultVariant() + variant := app.DefaultVariant() effectiveTheme := s.theme if !s.themeSpecified { effectiveTheme = s.loadSystemTheme() diff --git a/internal/app/config.go b/internal/app/config.go new file mode 100644 index 0000000000..bb31841f92 --- /dev/null +++ b/internal/app/config.go @@ -0,0 +1,5 @@ +package app + +func RootConfigDir() string { + return rootConfigDir() +} diff --git a/internal/app/config_desktop_darwin.go b/internal/app/config_desktop_darwin.go new file mode 100644 index 0000000000..1571b17ee2 --- /dev/null +++ b/internal/app/config_desktop_darwin.go @@ -0,0 +1,15 @@ +//go:build !ci && !ios && !wasm && !test_web_driver && !mobile + +package app + +import ( + "os" + "path/filepath" +) + +func rootConfigDir() string { + homeDir, _ := os.UserHomeDir() + + desktopConfig := filepath.Join(filepath.Join(homeDir, "Library"), "Preferences") + return filepath.Join(desktopConfig, "fyne") +} diff --git a/internal/app/config_goxjs.go b/internal/app/config_goxjs.go new file mode 100644 index 0000000000..c3441d2028 --- /dev/null +++ b/internal/app/config_goxjs.go @@ -0,0 +1,7 @@ +//go:build !ci && (!android || !ios || !mobile) && (wasm || test_web_driver) + +package app + +func rootConfigDir() string { + return "/data/" +} diff --git a/internal/app/config_mobile_and.go b/internal/app/config_mobile_and.go new file mode 100644 index 0000000000..9c3d855b80 --- /dev/null +++ b/internal/app/config_mobile_and.go @@ -0,0 +1,19 @@ +//go:build !ci && android + +package app + +import ( + "log" + "os" + "path/filepath" +) + +func rootConfigDir() string { + filesDir := os.Getenv("FILESDIR") + if filesDir == "" { + log.Println("FILESDIR env was not set by android native code") + return "/data/data" // probably won't work, but we can't make a better guess + } + + return filepath.Join(filesDir, "fyne") +} diff --git a/internal/app/config_mobile_ios.go b/internal/app/config_mobile_ios.go new file mode 100644 index 0000000000..193063e76b --- /dev/null +++ b/internal/app/config_mobile_ios.go @@ -0,0 +1,12 @@ +//go:build !ci && ios && !mobile + +package app + +import ( + "path/filepath" +) + +func rootConfigDir() string { + root := C.documentsPath() + return filepath.Join(C.GoString(root), "fyne") +} diff --git a/internal/app/config_other.go b/internal/app/config_other.go new file mode 100644 index 0000000000..1b45477c2d --- /dev/null +++ b/internal/app/config_other.go @@ -0,0 +1,12 @@ +//go:build ci || (mobile && !android && !ios) || (!linux && !darwin && !windows && !freebsd && !openbsd && !netbsd && !wasm && !test_web_driver) + +package app + +import ( + "os" + "path/filepath" +) + +func rootConfigDir() string { + return filepath.Join(os.TempDir(), "fyne-test") +} diff --git a/internal/app/config_windows.go b/internal/app/config_windows.go new file mode 100644 index 0000000000..c0960036e9 --- /dev/null +++ b/internal/app/config_windows.go @@ -0,0 +1,15 @@ +//go:build !ci && !android && !ios && !wasm && !test_web_driver + +package app + +import ( + "os" + "path/filepath" +) + +func rootConfigDir() string { + homeDir, _ := os.UserHomeDir() + + desktopConfig := filepath.Join(filepath.Join(homeDir, "AppData"), "Roaming") + return filepath.Join(desktopConfig, "fyne") +} diff --git a/internal/app/config_xdg.go b/internal/app/config_xdg.go new file mode 100644 index 0000000000..3a04463c21 --- /dev/null +++ b/internal/app/config_xdg.go @@ -0,0 +1,13 @@ +//go:build !ci && !wasm && !test_web_driver && !android && !ios && !mobile && (linux || openbsd || freebsd || netbsd) + +package app + +import ( + "os" + "path/filepath" +) + +func rootConfigDir() string { + desktopConfig, _ := os.UserConfigDir() + return filepath.Join(desktopConfig, "fyne") +} diff --git a/internal/app/theme_darwin.go b/internal/app/theme_darwin.go index f01ea3ace3..a74eb2ec49 100644 --- a/internal/app/theme_darwin.go +++ b/internal/app/theme_darwin.go @@ -13,7 +13,7 @@ bool isDarkMode(); import "C" import ( "fyne.io/fyne/v2" - "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/internal/theme" ) // DefaultVariant returns the systems default fyne.ThemeVariant. diff --git a/internal/app/theme_other.go b/internal/app/theme_other.go index f2d69da073..9a6cc4d05c 100644 --- a/internal/app/theme_other.go +++ b/internal/app/theme_other.go @@ -4,7 +4,7 @@ package app import ( "fyne.io/fyne/v2" - "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/internal/theme" ) // DefaultVariant returns the systems default fyne.ThemeVariant. diff --git a/internal/app/theme_wasm.go b/internal/app/theme_wasm.go index 427ae3fec6..ca00087285 100644 --- a/internal/app/theme_wasm.go +++ b/internal/app/theme_wasm.go @@ -6,7 +6,7 @@ import ( "syscall/js" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/internal/theme" ) // DefaultVariant returns the systems default fyne.ThemeVariant. diff --git a/internal/app/theme_web.go b/internal/app/theme_web.go index 310317c0c6..fcb86608f3 100644 --- a/internal/app/theme_web.go +++ b/internal/app/theme_web.go @@ -4,7 +4,7 @@ package app import ( "fyne.io/fyne/v2" - "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/internal/theme" ) // DefaultVariant returns the systems default fyne.ThemeVariant. diff --git a/internal/app/theme_windows.go b/internal/app/theme_windows.go index 7bb906bc7b..83a20e2dc1 100644 --- a/internal/app/theme_windows.go +++ b/internal/app/theme_windows.go @@ -8,7 +8,7 @@ import ( "golang.org/x/sys/windows/registry" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/internal/theme" ) const themeRegKey = `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize` diff --git a/theme/color_test.go b/theme/color_test.go index d7af1f21fd..fd9a5c612f 100644 --- a/theme/color_test.go +++ b/theme/color_test.go @@ -12,12 +12,12 @@ import ( func Test_BackgroundColor(t *testing.T) { t.Run("dark theme", func(t *testing.T) { fyne.CurrentApp().Settings().SetTheme(theme.DarkTheme()) - assert.Equal(t, theme.DefaultTheme().Color(theme.ColorNameBackground, theme.VariantDark), theme.BackgroundColor(), "wrong dark theme background color") + assert.Equal(t, theme.Current().Color(theme.ColorNameBackground, theme.VariantDark), theme.BackgroundColor(), "wrong dark theme background color") }) t.Run("light theme", func(t *testing.T) { fyne.CurrentApp().Settings().SetTheme(theme.LightTheme()) bg := theme.BackgroundColor() - assert.Equal(t, theme.DefaultTheme().Color(theme.ColorNameBackground, theme.VariantLight), bg, "wrong light theme background color") + assert.Equal(t, theme.Current().Color(theme.ColorNameBackground, theme.VariantLight), bg, "wrong light theme background color") }) } diff --git a/theme/json.go b/theme/json.go index a0bbf81b67..78d26f1800 100644 --- a/theme/json.go +++ b/theme/json.go @@ -27,12 +27,16 @@ func FromJSON(data string) (fyne.Theme, error) { // // Since: 2.2 func FromJSONReader(r io.Reader) (fyne.Theme, error) { + return fromJSONWithFallback(r, DefaultTheme()) +} + +func fromJSONWithFallback(r io.Reader, fallback fyne.Theme) (fyne.Theme, error) { var th *schema if err := json.NewDecoder(r).Decode(&th); err != nil { - return DefaultTheme(), err + return fallback, err } - return &jsonTheme{data: th, fallback: DefaultTheme()}, nil + return &jsonTheme{data: th, fallback: fallback}, nil } type hexColor string diff --git a/theme/theme.go b/theme/theme.go index c977456626..f59b4b8d9d 100644 --- a/theme/theme.go +++ b/theme/theme.go @@ -2,11 +2,14 @@ package theme // import "fyne.io/fyne/v2/theme" import ( + "bytes" "image/color" "os" + "path/filepath" "strings" "fyne.io/fyne/v2" + internalApp "fyne.io/fyne/v2/internal/app" "fyne.io/fyne/v2/internal/cache" internaltheme "fyne.io/fyne/v2/internal/theme" ) @@ -24,7 +27,7 @@ const ( VariantLight = internaltheme.VariantLight ) -var defaultTheme fyne.Theme +var defaultTheme, systemTheme fyne.Theme // DarkTheme defines the built-in dark theme colors and sizes. // @@ -45,6 +48,11 @@ func DefaultTheme() fyne.Theme { defaultTheme = setupDefaultTheme() } + // check system too + if systemTheme != nil { + return systemTheme + } + return defaultTheme } @@ -376,7 +384,30 @@ func selectionColorNamed(name string) color.NRGBA { func setupDefaultTheme() fyne.Theme { theme := &builtinTheme{variant: internaltheme.VariantNameUserPreference} - theme.initFonts() + + systemTheme = setupSystemTheme(theme) + return theme } + +func setupSystemTheme(fallback fyne.Theme) fyne.Theme { + root := internalApp.RootConfigDir() + + path := filepath.Join(root, "theme.json") + data, err := fyne.LoadResourceFromPath(path) + if err != nil { + if !os.IsNotExist(err) { + fyne.LogError("Failed to load user theme file: "+path, err) + } + return nil + } + if data != nil && data.Content() != nil { + th, err := fromJSONWithFallback(bytes.NewReader(data.Content()), fallback) + if err == nil { + return th + } + fyne.LogError("Failed to parse user theme file: "+path, err) + } + return nil +}