From 1bf0ab2609d87db0782a98cdbb410ff56e02e826 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Fri, 13 May 2022 15:36:29 +0200 Subject: [PATCH 01/46] Initial proposition for Gnome theme --- theme/gnome.go | 350 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 theme/gnome.go diff --git a/theme/gnome.go b/theme/gnome.go new file mode 100644 index 00000000..a45f5a20 --- /dev/null +++ b/theme/gnome.go @@ -0,0 +1,350 @@ +package theme + +import ( + "encoding/json" + "image/color" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "sync" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" + ft "fyne.io/fyne/v2/theme" +) + +const gjsScript = ` +let gtkVersion = Number(ARGV[0] || 4); +imports.gi.versions.Gtk = gtkVersion + ".0"; + +const { Gtk } = imports.gi; +if (gtkVersion === 3) { + Gtk.init(null); +} else { + Gtk.init(); +} + +const colors = { + viewbg: [], + viewfg: [], + background: [], + foreground: [], + borders: [], +}; + +const win = new Gtk.Window(); +const ctx = win.get_style_context(); + +let [ok, bg] = [false, null]; + +[ok, bg] = ctx.lookup_color("theme_base_color"); +if (!ok) { + [ok, bg] = ctx.lookup_color("view_bg_color"); +} +colors.viewbg = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok, bg] = ctx.lookup_color("theme_text_color"); +if (!ok) { + [ok, bg] = ctx.lookup_color("view_fg_color"); +} +colors.viewfg = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok, bg] = ctx.lookup_color("theme_bg_color"); +if (!ok) { + [ok, bg] = ctx.lookup_color("window_bg_color"); +} +colors.background = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok, bg] = ctx.lookup_color("theme_fg_color"); +if (!ok) { + [ok, bg] = ctx.lookup_color("window_fg_color"); +} +colors.foreground = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok, bg] = ctx.lookup_color("borders"); +if (!ok) { + [ok, bg] = ctx.lookup_color("unfocused_borders"); +} +colors.borders = [bg.red, bg.green, bg.blue, bg.alpha]; + +print(JSON.stringify(colors)); +` + +// Gnome theme, based on the Gnome desktop environment preferences. +type Gnome struct { + bgColor color.Color + fgColor color.Color + viewBgColor color.Color + viewFgColor color.Color + borderColor color.Color + fontScaleFactor float32 + font fyne.Resource + fontSize float32 + variant fyne.ThemeVariant +} + +// NewGnomeTheme returns a new Gnome theme based on the given gtk version. If gtkVersion is -1, +// the theme will try to determine the higher Gtk version available for the current GtkTheme. +func NewGnomeTheme(gtkVersion int) fyne.Theme { + gnome := &Gnome{} + if gtkVersion == -1 { + gtkVersion = gnome.getGTKVersion() + } + gnome.decodeTheme(gtkVersion, theme.VariantDark) + + return gnome +} + +// Color returns the color for the given color name +// +// Implements: fyne.Theme +func (gnome *Gnome) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color { + + switch name { + case theme.ColorNameBackground: + return gnome.bgColor + case theme.ColorNameForeground: + return gnome.fgColor + case theme.ColorNameButton, theme.ColorNameInputBackground: + return gnome.viewBgColor + default: + return ft.DefaultTheme().Color(name, gnome.variant) + } +} + +// Icon returns the icon for the given name. +// +// Implements: fyne.Theme +func (g *Gnome) Icon(i fyne.ThemeIconName) fyne.Resource { + return ft.DefaultTheme().Icon(i) +} + +// Font returns the font for the given name. +// +// Implements: fyne.Theme +func (g *Gnome) Font(s fyne.TextStyle) fyne.Resource { + return ft.DefaultTheme().Font(s) +} + +// Size returns the size for the given name. It will scale the detected Gnome font size +// by the Gnome font factor. +// +// Implements: fyne.Theme +func (g *Gnome) Size(s fyne.ThemeSizeName) float32 { + switch s { + case theme.SizeNameText: + return g.fontScaleFactor * g.fontSize + } + return ft.DefaultTheme().Size(s) +} + +func (gnome *Gnome) decodeTheme(gtkVersion int, variant fyne.ThemeVariant) { + // default + gnome.bgColor = theme.DefaultTheme().Color(theme.ColorNameBackground, variant) + gnome.fgColor = theme.DefaultTheme().Color(theme.ColorNameForeground, variant) + gnome.fontSize = theme.DefaultTheme().Size(theme.SizeNameText) + wg := sync.WaitGroup{} + + // make things faster in concurrent mode + wg.Add(3) + go func() { + gnome.getColors(gtkVersion) + gnome.setVariant() + wg.Done() + }() + go func() { + gnome.getFont() + wg.Done() + }() + go func() { + gnome.fontScale() + wg.Done() + }() + wg.Wait() +} + +func (gnome *Gnome) getColors(gtkVersion int) { + + // we will call gjs to get the colors + gjs, err := exec.LookPath("gjs") + if err != nil { + log.Println(err) + return + } + + // create a temp file to store the colors + f, err := ioutil.TempFile("", "fyne-theme-gnome-") + if err != nil { + log.Println(err) + return + } + defer os.Remove(f.Name()) + + // write the script to the temp file + _, err = f.WriteString(gjsScript) + if err != nil { + log.Println(err) + return + } + + // run the script + cmd := exec.Command(gjs, f.Name(), strconv.Itoa(gtkVersion)) + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err, string(out)) + return + } + + // decode json to apply to the gnome theme + colors := struct { + WindowBGcolor []float32 `json:"background,-"` + WindowFGcolor []float32 `json:"foreground,-"` + ViewBGcolor []float32 `json:"viewbg,-"` + ViewFGcolor []float32 `json:"viewfg,-"` + Borders []float32 `json:"borders,-"` + }{} + err = json.Unmarshal(out, &colors) + if err != nil { + log.Println(err) + return + } + + // convert the colors to fyne colors + gnome.bgColor = color.RGBA{ + R: uint8(colors.WindowBGcolor[0] * 255), + G: uint8(colors.WindowBGcolor[1] * 255), + B: uint8(colors.WindowBGcolor[2] * 255), + A: uint8(colors.WindowBGcolor[3] * 255)} + + gnome.fgColor = color.RGBA{ + R: uint8(colors.WindowFGcolor[0] * 255), + G: uint8(colors.WindowFGcolor[1] * 255), + B: uint8(colors.WindowFGcolor[2] * 255), + A: uint8(colors.WindowFGcolor[3] * 255)} + + gnome.borderColor = color.RGBA{ + R: uint8(colors.Borders[0] * 255), + G: uint8(colors.Borders[1] * 255), + B: uint8(colors.Borders[2] * 255), + A: uint8(colors.Borders[3] * 255)} + + gnome.viewBgColor = color.RGBA{ + R: uint8(colors.ViewBGcolor[0] * 255), + G: uint8(colors.ViewBGcolor[1] * 255), + B: uint8(colors.ViewBGcolor[2] * 255), + A: uint8(colors.ViewBGcolor[3] * 255)} + + gnome.viewFgColor = color.RGBA{ + R: uint8(colors.ViewFGcolor[0] * 255), + G: uint8(colors.ViewFGcolor[1] * 255), + B: uint8(colors.ViewFGcolor[2] * 255), + A: uint8(colors.ViewFGcolor[3] * 255)} + +} + +func (gnome *Gnome) fontScale() { + + // for any error below, we will use the default + gnome.fontScaleFactor = 1 + + // call gsettings get org.gnome.desktop.interface text-scaling-factor + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor") + out, err := cmd.CombinedOutput() + if err != nil { + return + } + + // get the text scaling factor + ts := strings.TrimSpace(string(out)) + textScale, err := strconv.ParseFloat(ts, 32) + if err != nil { + return + } + + // return the text scaling factor + gnome.fontScaleFactor = float32(textScale) +} + +func (gnome *Gnome) getFont() { + + gnome.font = theme.TextFont() + // call gsettings get org.gnome.desktop.interface font-name + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "font-name") + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err) + log.Println(string(out)) + return + } + // try to get the font as a TTF file + fontFile := strings.TrimSpace(string(out)) + fontFile = strings.Trim(fontFile, "'") + // the fontFile string is in the format: Name size, eg: "Sans Bold 12", so get the size + parts := strings.Split(fontFile, " ") + fontSize := parts[len(parts)-1] + // convert the size to a float + size, err := strconv.ParseFloat(fontSize, 32) + // apply this to the fontScaleFactor + gnome.fontSize = float32(size) +} + +func (gnome *Gnome) setVariant() { + // using the bgColor, detect if the theme is dark or light + // if it is dark, set the variant to dark + // if it is light, set the variant to light + r, g, b, _ := gnome.bgColor.RGBA() + + brightness := (r/255*299 + g/255*587 + b/255*114) / 1000 + if brightness > 125 { + gnome.variant = theme.VariantLight + } else { + gnome.variant = theme.VariantDark + } +} + +func (gnome *Gnome) getGTKVersion() int { + // call gsettings get org.gnome.desktop.interface gtk-theme + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "gtk-theme") + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err) + log.Println(string(out)) + return 3 // default to Gtk 3 + } + themename := strings.TrimSpace(string(out)) + themename = strings.Trim(themename, "'") + + // ok so now, find if the theme is gtk4, either fallback to gtk3 + home, err := os.UserHomeDir() + if err != nil { + log.Println(err) + return 3 // default to Gtk 3 + } + + possiblePaths := []string{ + home + "/.local/share/themes/", + home + "/.themes/", + `/usr/local/share/themes/`, + `/usr/share/themes/`, + } + + for _, path := range possiblePaths { + path = filepath.Join(path, themename) + if _, err := os.Stat(path); err == nil { + // found the theme directory + // now check if it is gtk4 + if _, err := os.Stat(path + "gtk-4.0/gtk.css"); err == nil { + // it is gtk4 + return 4 + } else { + // it is gtk3 + return 3 + } + } + } + return 3 +} From 396f841eda3d480ca43a89fe99d2e06ce06646e9 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Sun, 15 May 2022 07:01:31 +0200 Subject: [PATCH 02/46] Add font detection and conversion --- theme/gnome.go | 92 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/theme/gnome.go b/theme/gnome.go index a45f5a20..43836440 100644 --- a/theme/gnome.go +++ b/theme/gnome.go @@ -2,6 +2,7 @@ package theme import ( "encoding/json" + "fmt" "image/color" "io/ioutil" "log" @@ -17,6 +18,7 @@ import ( ft "fyne.io/fyne/v2/theme" ) +// FONTFORGE_LANGUAGE=ff fontforge -c 'Open("/usr/share/fonts/cantarell/Cantarell-Bold.otf");Generate("example.ttf");' const gjsScript = ` let gtkVersion = Number(ARGV[0] || 4); imports.gi.versions.Gtk = gtkVersion + ".0"; @@ -119,15 +121,18 @@ func (gnome *Gnome) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.C // Icon returns the icon for the given name. // // Implements: fyne.Theme -func (g *Gnome) Icon(i fyne.ThemeIconName) fyne.Resource { +func (gnome *Gnome) Icon(i fyne.ThemeIconName) fyne.Resource { return ft.DefaultTheme().Icon(i) } // Font returns the font for the given name. // // Implements: fyne.Theme -func (g *Gnome) Font(s fyne.TextStyle) fyne.Resource { - return ft.DefaultTheme().Font(s) +func (gnome *Gnome) Font(s fyne.TextStyle) fyne.Resource { + if gnome.font == nil { + return ft.DefaultTheme().Font(s) + } + return gnome.font } // Size returns the size for the given name. It will scale the detected Gnome font size @@ -290,6 +295,9 @@ func (gnome *Gnome) getFont() { size, err := strconv.ParseFloat(fontSize, 32) // apply this to the fontScaleFactor gnome.fontSize = float32(size) + + // try to get the font as a TTF file + gnome.setFont(strings.Join(parts[:len(parts)-1], "-")) } func (gnome *Gnome) setVariant() { @@ -335,16 +343,84 @@ func (gnome *Gnome) getGTKVersion() int { for _, path := range possiblePaths { path = filepath.Join(path, themename) if _, err := os.Stat(path); err == nil { - // found the theme directory - // now check if it is gtk4 + // now check if it is gtk4 compatible if _, err := os.Stat(path + "gtk-4.0/gtk.css"); err == nil { // it is gtk4 return 4 - } else { - // it is gtk3 + } + if _, err := os.Stat(path + "gtk-3.0/gtk.css"); err == nil { return 3 } } } - return 3 + return 3 // default, but that may be a false positive now +} + +func (gnome *Gnome) setFont(fontname string) { + + fontpath, err := gnome.getFontPath(fontname) + if err != nil { + log.Println(err) + return + } + + ext := filepath.Ext(fontpath) + if ext == ".otf" { + font, err := gnome.convertOTF2TTF(fontpath) + if err != nil { + log.Println(err) + return + } + gnome.font = fyne.NewStaticResource(fontpath, font) + } + if ext == ".ttf" { + font, err := ioutil.ReadFile(fontpath) + if err != nil { + log.Println(err) + return + } + gnome.font = fyne.NewStaticResource(fontpath, font) + } + +} + +func (*Gnome) getFontPath(fontname string) (string, error) { + + // get the font path + cmd := exec.Command("fc-match", "-f", "%{file}", fontname) + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err) + log.Println(string(out)) + return "", err + } + log.Println("Detected font path", string(out)) + + // get the font path with fc-list command + fontpath := strings.TrimSpace(string(out)) + return fontpath, nil +} + +func (*Gnome) convertOTF2TTF(fontpath string) ([]byte, error) { + + // convert the font to a ttf file + basename := filepath.Base(fontpath) + tempTTF := filepath.Join(os.TempDir(), "fyne-"+basename+".ttf") + + ffScript := `Open("%s");Generate("%s")` + script := fmt.Sprintf(ffScript, fontpath, tempTTF) + cmd := exec.Command("fontforge", "-c", script) + cmd.Env = append(cmd.Env, "FONTFORGE_LANGUAGE=ff") + + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err) + log.Println(string(out)) + return nil, err + } + defer os.Remove(tempTTF) + log.Println("TTF font generated: ", tempTTF) + + // read the temporary ttf file + return ioutil.ReadFile(tempTTF) } From 2041d9d0524dcd13153db06c1bcd5290d502bbe1 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Sun, 15 May 2022 14:01:46 +0200 Subject: [PATCH 03/46] Make a more complete theme manager for desktop --- theme/desktop/doc.go | 7 + theme/desktop/gnome.go | 383 +++++++++++++++++++++++++++++++++++++++++ theme/desktop/kde.go | 170 ++++++++++++++++++ theme/desktop/utils.go | 94 ++++++++++ theme/windowManager.go | 32 ++++ 5 files changed, 686 insertions(+) create mode 100644 theme/desktop/doc.go create mode 100644 theme/desktop/gnome.go create mode 100644 theme/desktop/kde.go create mode 100644 theme/desktop/utils.go create mode 100644 theme/windowManager.go diff --git a/theme/desktop/doc.go b/theme/desktop/doc.go new file mode 100644 index 00000000..c56115cd --- /dev/null +++ b/theme/desktop/doc.go @@ -0,0 +1,7 @@ +// desktop package provides theme for desktop manager like Gnome, KDE, Plasma... +// To be fully used, the system need to have gsettings and gjs for all GTK/Gnome based desktop. +// KDE/Plasma theme only works when the user has already initialize a session to create ~/.config/kdeglobals +// +// For all desktop, we also need fontconfig package installed to have "fc-match" and "fontconfif" commands. +// This is not required but recommended to be able to generate TTF font if the user as configure a non TTF font. +package desktop diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go new file mode 100644 index 00000000..a95493f5 --- /dev/null +++ b/theme/desktop/gnome.go @@ -0,0 +1,383 @@ +package desktop + +import ( + "encoding/json" + "image/color" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "sync" + + "fyne.io/fyne/v2" + ft "fyne.io/fyne/v2/theme" +) + +// Script to get the colors from the Gnome GTK/Adwaita theme. +const gjsScript = ` +let gtkVersion = Number(ARGV[0] || 4); +imports.gi.versions.Gtk = gtkVersion + ".0"; + +const { Gtk } = imports.gi; +if (gtkVersion === 3) { + Gtk.init(null); +} else { + Gtk.init(); +} + +const colors = { + viewbg: [], + viewfg: [], + background: [], + foreground: [], + borders: [], +}; + +const win = new Gtk.Window(); +const ctx = win.get_style_context(); + +let [ok, bg] = [false, null]; + +[ok, bg] = ctx.lookup_color("theme_base_color"); +if (!ok) { + [ok, bg] = ctx.lookup_color("view_bg_color"); +} +colors.viewbg = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok, bg] = ctx.lookup_color("theme_text_color"); +if (!ok) { + [ok, bg] = ctx.lookup_color("view_fg_color"); +} +colors.viewfg = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok, bg] = ctx.lookup_color("theme_bg_color"); +if (!ok) { + [ok, bg] = ctx.lookup_color("window_bg_color"); +} +colors.background = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok, bg] = ctx.lookup_color("theme_fg_color"); +if (!ok) { + [ok, bg] = ctx.lookup_color("window_fg_color"); +} +colors.foreground = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok, bg] = ctx.lookup_color("borders"); +if (!ok) { + [ok, bg] = ctx.lookup_color("unfocused_borders"); +} +colors.borders = [bg.red, bg.green, bg.blue, bg.alpha]; + +print(JSON.stringify(colors)); +` + +// GnomeTheme theme, based on the Gnome desktop manager. This theme uses GJS and gsettings to get +// the colors and font from the Gnome desktop. +type GnomeTheme struct { + bgColor color.Color + fgColor color.Color + viewBgColor color.Color + viewFgColor color.Color + borderColor color.Color + fontScaleFactor float32 + font fyne.Resource + fontSize float32 + variant fyne.ThemeVariant +} + +// NewGnomeTheme returns a new Gnome theme based on the given gtk version. If gtkVersion is -1, +// the theme will try to determine the higher Gtk version available for the current GtkTheme. +func NewGnomeTheme(gtkVersion int) fyne.Theme { + gnome := &GnomeTheme{} + if gtkVersion == -1 { + gtkVersion = gnome.getGTKVersion() + } + gnome.decodeTheme(gtkVersion, ft.VariantDark) + + return gnome +} + +// Color returns the color for the given color name +// +// Implements: fyne.Theme +func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color { + + switch name { + case ft.ColorNameBackground: + return gnome.bgColor + case ft.ColorNameForeground: + return gnome.fgColor + case ft.ColorNameButton, ft.ColorNameInputBackground: + return gnome.viewBgColor + default: + return ft.DefaultTheme().Color(name, gnome.variant) + } +} + +// Icon returns the icon for the given name. +// +// Implements: fyne.Theme +func (gnome *GnomeTheme) Icon(i fyne.ThemeIconName) fyne.Resource { + return ft.DefaultTheme().Icon(i) +} + +// Font returns the font for the given name. +// +// Implements: fyne.Theme +func (gnome *GnomeTheme) Font(s fyne.TextStyle) fyne.Resource { + if gnome.font == nil { + return ft.DefaultTheme().Font(s) + } + return gnome.font +} + +// Size returns the size for the given name. It will scale the detected Gnome font size +// by the Gnome font factor. +// +// Implements: fyne.Theme +func (g *GnomeTheme) Size(s fyne.ThemeSizeName) float32 { + switch s { + case ft.SizeNameText: + return g.fontScaleFactor * g.fontSize + } + return ft.DefaultTheme().Size(s) +} + +func (gnome *GnomeTheme) decodeTheme(gtkVersion int, variant fyne.ThemeVariant) { + // default + gnome.bgColor = ft.DefaultTheme().Color(ft.ColorNameBackground, variant) + gnome.fgColor = ft.DefaultTheme().Color(ft.ColorNameForeground, variant) + gnome.fontSize = ft.DefaultTheme().Size(ft.SizeNameText) + wg := sync.WaitGroup{} + + // make things faster in concurrent mode + wg.Add(3) + go func() { + gnome.getColors(gtkVersion) + gnome.setVariant() + wg.Done() + }() + go func() { + gnome.getFont() + wg.Done() + }() + go func() { + gnome.fontScale() + wg.Done() + }() + wg.Wait() +} + +func (gnome *GnomeTheme) getColors(gtkVersion int) { + + // we will call gjs to get the colors + gjs, err := exec.LookPath("gjs") + if err != nil { + log.Println(err) + return + } + + // create a temp file to store the colors + f, err := ioutil.TempFile("", "fyne-theme-gnome-") + if err != nil { + log.Println(err) + return + } + defer os.Remove(f.Name()) + + // write the script to the temp file + _, err = f.WriteString(gjsScript) + if err != nil { + log.Println(err) + return + } + + // run the script + cmd := exec.Command(gjs, f.Name(), strconv.Itoa(gtkVersion)) + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err, string(out)) + return + } + + // decode json to apply to the gnome theme + colors := struct { + WindowBGcolor []float32 `json:"background,-"` + WindowFGcolor []float32 `json:"foreground,-"` + ViewBGcolor []float32 `json:"viewbg,-"` + ViewFGcolor []float32 `json:"viewfg,-"` + Borders []float32 `json:"borders,-"` + }{} + err = json.Unmarshal(out, &colors) + if err != nil { + log.Println(err) + return + } + + // convert the colors to fyne colors + gnome.bgColor = color.RGBA{ + R: uint8(colors.WindowBGcolor[0] * 255), + G: uint8(colors.WindowBGcolor[1] * 255), + B: uint8(colors.WindowBGcolor[2] * 255), + A: uint8(colors.WindowBGcolor[3] * 255)} + + gnome.fgColor = color.RGBA{ + R: uint8(colors.WindowFGcolor[0] * 255), + G: uint8(colors.WindowFGcolor[1] * 255), + B: uint8(colors.WindowFGcolor[2] * 255), + A: uint8(colors.WindowFGcolor[3] * 255)} + + gnome.borderColor = color.RGBA{ + R: uint8(colors.Borders[0] * 255), + G: uint8(colors.Borders[1] * 255), + B: uint8(colors.Borders[2] * 255), + A: uint8(colors.Borders[3] * 255)} + + gnome.viewBgColor = color.RGBA{ + R: uint8(colors.ViewBGcolor[0] * 255), + G: uint8(colors.ViewBGcolor[1] * 255), + B: uint8(colors.ViewBGcolor[2] * 255), + A: uint8(colors.ViewBGcolor[3] * 255)} + + gnome.viewFgColor = color.RGBA{ + R: uint8(colors.ViewFGcolor[0] * 255), + G: uint8(colors.ViewFGcolor[1] * 255), + B: uint8(colors.ViewFGcolor[2] * 255), + A: uint8(colors.ViewFGcolor[3] * 255)} + +} + +func (gnome *GnomeTheme) fontScale() { + + // for any error below, we will use the default + gnome.fontScaleFactor = 1 + + // call gsettings get org.gnome.desktop.interface text-scaling-factor + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor") + out, err := cmd.CombinedOutput() + if err != nil { + return + } + + // get the text scaling factor + ts := strings.TrimSpace(string(out)) + textScale, err := strconv.ParseFloat(ts, 32) + if err != nil { + return + } + + // return the text scaling factor + gnome.fontScaleFactor = float32(textScale) +} + +func (gnome *GnomeTheme) getFont() { + + gnome.font = ft.TextFont() + // call gsettings get org.gnome.desktop.interface font-name + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "font-name") + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err) + log.Println(string(out)) + return + } + // try to get the font as a TTF file + fontFile := strings.TrimSpace(string(out)) + fontFile = strings.Trim(fontFile, "'") + // the fontFile string is in the format: Name size, eg: "Sans Bold 12", so get the size + parts := strings.Split(fontFile, " ") + fontSize := parts[len(parts)-1] + // convert the size to a float + size, err := strconv.ParseFloat(fontSize, 32) + // apply this to the fontScaleFactor + gnome.fontSize = float32(size) + + // try to get the font as a TTF file + gnome.setFont(strings.Join(parts[:len(parts)-1], " ")) +} + +func (gnome *GnomeTheme) setVariant() { + // using the bgColor, detect if the theme is dark or light + // if it is dark, set the variant to dark + // if it is light, set the variant to light + r, g, b, _ := gnome.bgColor.RGBA() + + brightness := (r/255*299 + g/255*587 + b/255*114) / 1000 + if brightness > 125 { + gnome.variant = ft.VariantLight + } else { + gnome.variant = ft.VariantDark + } +} + +func (gnome *GnomeTheme) getGTKVersion() int { + // call gsettings get org.gnome.desktop.interface gtk-theme + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "gtk-theme") + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err) + log.Println(string(out)) + return 3 // default to Gtk 3 + } + themename := strings.TrimSpace(string(out)) + themename = strings.Trim(themename, "'") + + // ok so now, find if the theme is gtk4, either fallback to gtk3 + home, err := os.UserHomeDir() + if err != nil { + log.Println(err) + return 3 // default to Gtk 3 + } + + possiblePaths := []string{ + home + "/.local/share/themes/", + home + "/.themes/", + `/usr/local/share/themes/`, + `/usr/share/themes/`, + } + + for _, path := range possiblePaths { + path = filepath.Join(path, themename) + if _, err := os.Stat(path); err == nil { + // now check if it is gtk4 compatible + if _, err := os.Stat(path + "gtk-4.0/gtk.css"); err == nil { + // it is gtk4 + return 4 + } + if _, err := os.Stat(path + "gtk-3.0/gtk.css"); err == nil { + return 3 + } + } + } + return 3 // default, but that may be a false positive now +} + +func (gnome *GnomeTheme) setFont(fontname string) { + + fontpath, err := getFontPath(fontname) + if err != nil { + log.Println(err) + return + } + + ext := filepath.Ext(fontpath) + if ext != ".ttf" { + font, err := converToTTF(fontpath) + if err != nil { + log.Println(err) + return + } + gnome.font = fyne.NewStaticResource(fontpath, font) + } else { + font, err := ioutil.ReadFile(fontpath) + if err != nil { + log.Println(err) + return + } + gnome.font = fyne.NewStaticResource(fontpath, font) + } + +} diff --git a/theme/desktop/kde.go b/theme/desktop/kde.go new file mode 100644 index 00000000..d8d41640 --- /dev/null +++ b/theme/desktop/kde.go @@ -0,0 +1,170 @@ +package desktop + +import ( + "errors" + "image/color" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" + "strings" + + "fyne.io/fyne/v2" + ft "fyne.io/fyne/v2/theme" +) + +// KDE theme is based on the KDE or Plasma theme. +type KDE struct { + variant fyne.ThemeVariant + bgColor color.Color + fgColor color.Color + fontConfig string + fontSize float32 + font fyne.Resource +} + +// NewKDETheme returns a new KDE theme. +func NewKDETheme() fyne.Theme { + kde := &KDE{ + variant: ft.VariantDark, + } + kde.decodeTheme() + log.Println(kde) + + return kde +} + +// Color returns the color for the specified name. +// +// Implements: fyne.Theme +func (k *KDE) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color { + switch name { + case ft.ColorNameBackground: + return k.bgColor + case ft.ColorNameForeground: + return k.fgColor + } + return ft.DefaultTheme().Color(name, k.variant) +} + +// Icon returns the icon for the specified name. +// +// Implements: fyne.Theme +func (k *KDE) Icon(i fyne.ThemeIconName) fyne.Resource { + return ft.DefaultTheme().Icon(i) +} + +// Font returns the font for the specified name. +// +// Implements: fyne.Theme +func (k *KDE) Font(s fyne.TextStyle) fyne.Resource { + if k.font != nil { + return k.font + } + return ft.DefaultTheme().Font(s) +} + +// Size returns the size of the font for the specified text style. +// +// Implements: fyne.Theme +func (k *KDE) Size(s fyne.ThemeSizeName) float32 { + if s == ft.SizeNameText { + return k.fontSize + } + return ft.DefaultTheme().Size(s) +} + +// decodeTheme initialize the theme. +func (k *KDE) decodeTheme() { + k.loadScheme() + k.setFont() +} + +func (k *KDE) loadScheme() error { + // the theme name is declared in ~/.config/kdedefaults/kdeglobals + // in the ini section [General] as "ColorScheme" entry + homedir, err := os.UserHomeDir() + if err != nil { + return err + } + content, err := ioutil.ReadFile(filepath.Join(homedir, ".config/kdeglobals")) + if err != nil { + return err + } + + section := "" + lines := strings.Split(string(content), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "[") { + section = strings.ReplaceAll(line, "[", "") + section = strings.ReplaceAll(section, "]", "") + } + if section == "Colors:Window" { + if strings.HasPrefix(line, "BackgroundNormal=") { + k.bgColor = k.parseColor(strings.ReplaceAll(line, "BackgroundNormal=", "")) + } + if strings.HasPrefix(line, "ForegroundNormal=") { + k.fgColor = k.parseColor(strings.ReplaceAll(line, "ForegroundNormal=", "")) + } + } + if section == "General" { + if strings.HasPrefix(line, "font=") { + k.fontConfig = strings.ReplaceAll(line, "font=", "") + } + } + } + + return errors.New("Unable to find the KDE color scheme") +} + +func (k *KDE) parseColor(col string) color.Color { + // the color is in the form r,g,b, + // we need to convert it to a color.Color + + // split the string + cols := strings.Split(col, ",") + // convert the string to int + r, _ := strconv.Atoi(cols[0]) + g, _ := strconv.Atoi(cols[1]) + b, _ := strconv.Atoi(cols[2]) + + // convert the int to a color.Color + return color.RGBA{uint8(r), uint8(g), uint8(b), 0xff} +} + +func (k *KDE) setFont() { + + if k.fontConfig == "" { + log.Println("WTF") + return + } + // the font is in the form "fontname,size,...", so we can split it + font := strings.Split(k.fontConfig, ",") + name := font[0] + size, _ := strconv.ParseFloat(font[1], 32) + k.fontSize = float32(size) + + // we need to load the font, Gnome struct has got some nice methods + fontpath, err := getFontPath(name) + if err != nil { + log.Println(err) + return + } + log.Println(fontpath) + if filepath.Ext(fontpath) == ".ttf" { + font, err := ioutil.ReadFile(fontpath) + if err != nil { + log.Println(err) + return + } + k.font = fyne.NewStaticResource(fontpath, font) + } else { + font, err := converToTTF(fontpath) + if err != nil { + log.Println(err) + return + } + k.font = fyne.NewStaticResource(fontpath, font) + } +} diff --git a/theme/desktop/utils.go b/theme/desktop/utils.go new file mode 100644 index 00000000..b984f0f0 --- /dev/null +++ b/theme/desktop/utils.go @@ -0,0 +1,94 @@ +package desktop + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" +) + +// getFontPath will detect the font path from the font name taken from gsettings. As the font is not exactly +// the one that fc-match can find, we need to do some extra work to rebuild the name with style. +func getFontPath(fontname string) (string, error) { + + // This to transoform CamelCase to Camel-Case + camelRegExp := regexp.MustCompile(`([a-z\-])([A-Z])`) + + // get all possible styles in fc-list + allstyles := []string{} + cmd := exec.Command("fc-list", "--format", "%{style}\n") + out, err := cmd.CombinedOutput() + if err != nil { + return "", err + } + styles := strings.Split(string(out), "\n") + for _, style := range styles { + if style != "" { + split := strings.Split(style, ",") + for _, s := range split { + allstyles = append(allstyles, s) + // we also need to add a "-" for camel cases + s = camelRegExp.ReplaceAllString(s, "$1-$2") + allstyles = append(allstyles, s) + } + } + } + + // Find the styles, remove it from the nmae, this make a correct fc-match query + fontstyle := []string{} + for _, style := range allstyles { + // remove the style, we add a "space" to avoid this case: Semi-Condensed contains Condensed + // and there is always a space before the style (because the font name prefixes the string) + if strings.Contains(fontname, " "+style) { + fontstyle = append(fontstyle, style) + fontname = strings.ReplaceAll(fontname, style, "") + } + } + + // we can now search + // fc-math ... "Font Name:Font Style + var fontpath string + cmd = exec.Command("fc-match", "-f", "%{file}", fontname+":"+strings.Join(fontstyle, " ")) + out, err = cmd.CombinedOutput() + log.Println(string(out), fontname) + if err != nil { + log.Println(err) + log.Println(string(out)) + return "", err + } + + // get the font path with fc-list command + fontpath = string(out) + fontpath = strings.TrimSpace(fontpath) + return fontpath, nil +} + +// converToTTF will convert a font to a ttf file. This requires the fontconfig package. +func converToTTF(fontpath string) ([]byte, error) { + + // convert the font to a ttf file + basename := filepath.Base(fontpath) + tempTTF := filepath.Join(os.TempDir(), "fyne-"+basename+".ttf") + + // Convert to TTF + ffScript := `Open("%s");Generate("%s")` + script := fmt.Sprintf(ffScript, fontpath, tempTTF) + cmd := exec.Command("fontforge", "-c", script) + cmd.Env = append(cmd.Env, "FONTFORGE_LANGUAGE=ff") + + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err) + log.Println(string(out)) + return nil, err + } + defer os.Remove(tempTTF) + log.Println("TTF font generated: ", tempTTF) + + // read the temporary ttf file + return ioutil.ReadFile(tempTTF) +} diff --git a/theme/windowManager.go b/theme/windowManager.go new file mode 100644 index 00000000..0d710739 --- /dev/null +++ b/theme/windowManager.go @@ -0,0 +1,32 @@ +package theme + +import ( + "log" + "os" + "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" + "fyne.io/x/fyne/theme/desktop" +) + +// NewWindowManagerTheme returns a new WindowManagerTheme instance for the current desktop session. If the desktop manager is +// not supported or if it is not found, return the default theme +func NewWindowManagerTheme() fyne.Theme { + wm := os.Getenv("XDG_CURRENT_DESKTOP") + if wm == "" { + wm = os.Getenv("DESKTOP_SESSION") + } + wm = strings.ToUpper(wm) + + switch wm { + case "GNOME", "XFCE", "UNITY", "GNOME-SHELL", "GNOME-CLASSIC", "MATE", "GNOME-MATE": + return desktop.NewGnomeTheme(-1) + case "KDE", "KDE-PLASMA", "PLASMA": + return desktop.NewKDETheme() + + } + + log.Println("Window manager not supported:", wm, "using default theme") + return theme.DefaultTheme() +} From f977a9195199017ad2e103b7a7af97511da7c4b5 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Sun, 15 May 2022 14:04:16 +0200 Subject: [PATCH 04/46] Remove gnome.go, now in desktop package --- theme/gnome.go | 426 ------------------------------------------------- 1 file changed, 426 deletions(-) delete mode 100644 theme/gnome.go diff --git a/theme/gnome.go b/theme/gnome.go deleted file mode 100644 index 43836440..00000000 --- a/theme/gnome.go +++ /dev/null @@ -1,426 +0,0 @@ -package theme - -import ( - "encoding/json" - "fmt" - "image/color" - "io/ioutil" - "log" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - "sync" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/theme" - ft "fyne.io/fyne/v2/theme" -) - -// FONTFORGE_LANGUAGE=ff fontforge -c 'Open("/usr/share/fonts/cantarell/Cantarell-Bold.otf");Generate("example.ttf");' -const gjsScript = ` -let gtkVersion = Number(ARGV[0] || 4); -imports.gi.versions.Gtk = gtkVersion + ".0"; - -const { Gtk } = imports.gi; -if (gtkVersion === 3) { - Gtk.init(null); -} else { - Gtk.init(); -} - -const colors = { - viewbg: [], - viewfg: [], - background: [], - foreground: [], - borders: [], -}; - -const win = new Gtk.Window(); -const ctx = win.get_style_context(); - -let [ok, bg] = [false, null]; - -[ok, bg] = ctx.lookup_color("theme_base_color"); -if (!ok) { - [ok, bg] = ctx.lookup_color("view_bg_color"); -} -colors.viewbg = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("theme_text_color"); -if (!ok) { - [ok, bg] = ctx.lookup_color("view_fg_color"); -} -colors.viewfg = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("theme_bg_color"); -if (!ok) { - [ok, bg] = ctx.lookup_color("window_bg_color"); -} -colors.background = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("theme_fg_color"); -if (!ok) { - [ok, bg] = ctx.lookup_color("window_fg_color"); -} -colors.foreground = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("borders"); -if (!ok) { - [ok, bg] = ctx.lookup_color("unfocused_borders"); -} -colors.borders = [bg.red, bg.green, bg.blue, bg.alpha]; - -print(JSON.stringify(colors)); -` - -// Gnome theme, based on the Gnome desktop environment preferences. -type Gnome struct { - bgColor color.Color - fgColor color.Color - viewBgColor color.Color - viewFgColor color.Color - borderColor color.Color - fontScaleFactor float32 - font fyne.Resource - fontSize float32 - variant fyne.ThemeVariant -} - -// NewGnomeTheme returns a new Gnome theme based on the given gtk version. If gtkVersion is -1, -// the theme will try to determine the higher Gtk version available for the current GtkTheme. -func NewGnomeTheme(gtkVersion int) fyne.Theme { - gnome := &Gnome{} - if gtkVersion == -1 { - gtkVersion = gnome.getGTKVersion() - } - gnome.decodeTheme(gtkVersion, theme.VariantDark) - - return gnome -} - -// Color returns the color for the given color name -// -// Implements: fyne.Theme -func (gnome *Gnome) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color { - - switch name { - case theme.ColorNameBackground: - return gnome.bgColor - case theme.ColorNameForeground: - return gnome.fgColor - case theme.ColorNameButton, theme.ColorNameInputBackground: - return gnome.viewBgColor - default: - return ft.DefaultTheme().Color(name, gnome.variant) - } -} - -// Icon returns the icon for the given name. -// -// Implements: fyne.Theme -func (gnome *Gnome) Icon(i fyne.ThemeIconName) fyne.Resource { - return ft.DefaultTheme().Icon(i) -} - -// Font returns the font for the given name. -// -// Implements: fyne.Theme -func (gnome *Gnome) Font(s fyne.TextStyle) fyne.Resource { - if gnome.font == nil { - return ft.DefaultTheme().Font(s) - } - return gnome.font -} - -// Size returns the size for the given name. It will scale the detected Gnome font size -// by the Gnome font factor. -// -// Implements: fyne.Theme -func (g *Gnome) Size(s fyne.ThemeSizeName) float32 { - switch s { - case theme.SizeNameText: - return g.fontScaleFactor * g.fontSize - } - return ft.DefaultTheme().Size(s) -} - -func (gnome *Gnome) decodeTheme(gtkVersion int, variant fyne.ThemeVariant) { - // default - gnome.bgColor = theme.DefaultTheme().Color(theme.ColorNameBackground, variant) - gnome.fgColor = theme.DefaultTheme().Color(theme.ColorNameForeground, variant) - gnome.fontSize = theme.DefaultTheme().Size(theme.SizeNameText) - wg := sync.WaitGroup{} - - // make things faster in concurrent mode - wg.Add(3) - go func() { - gnome.getColors(gtkVersion) - gnome.setVariant() - wg.Done() - }() - go func() { - gnome.getFont() - wg.Done() - }() - go func() { - gnome.fontScale() - wg.Done() - }() - wg.Wait() -} - -func (gnome *Gnome) getColors(gtkVersion int) { - - // we will call gjs to get the colors - gjs, err := exec.LookPath("gjs") - if err != nil { - log.Println(err) - return - } - - // create a temp file to store the colors - f, err := ioutil.TempFile("", "fyne-theme-gnome-") - if err != nil { - log.Println(err) - return - } - defer os.Remove(f.Name()) - - // write the script to the temp file - _, err = f.WriteString(gjsScript) - if err != nil { - log.Println(err) - return - } - - // run the script - cmd := exec.Command(gjs, f.Name(), strconv.Itoa(gtkVersion)) - out, err := cmd.CombinedOutput() - if err != nil { - log.Println(err, string(out)) - return - } - - // decode json to apply to the gnome theme - colors := struct { - WindowBGcolor []float32 `json:"background,-"` - WindowFGcolor []float32 `json:"foreground,-"` - ViewBGcolor []float32 `json:"viewbg,-"` - ViewFGcolor []float32 `json:"viewfg,-"` - Borders []float32 `json:"borders,-"` - }{} - err = json.Unmarshal(out, &colors) - if err != nil { - log.Println(err) - return - } - - // convert the colors to fyne colors - gnome.bgColor = color.RGBA{ - R: uint8(colors.WindowBGcolor[0] * 255), - G: uint8(colors.WindowBGcolor[1] * 255), - B: uint8(colors.WindowBGcolor[2] * 255), - A: uint8(colors.WindowBGcolor[3] * 255)} - - gnome.fgColor = color.RGBA{ - R: uint8(colors.WindowFGcolor[0] * 255), - G: uint8(colors.WindowFGcolor[1] * 255), - B: uint8(colors.WindowFGcolor[2] * 255), - A: uint8(colors.WindowFGcolor[3] * 255)} - - gnome.borderColor = color.RGBA{ - R: uint8(colors.Borders[0] * 255), - G: uint8(colors.Borders[1] * 255), - B: uint8(colors.Borders[2] * 255), - A: uint8(colors.Borders[3] * 255)} - - gnome.viewBgColor = color.RGBA{ - R: uint8(colors.ViewBGcolor[0] * 255), - G: uint8(colors.ViewBGcolor[1] * 255), - B: uint8(colors.ViewBGcolor[2] * 255), - A: uint8(colors.ViewBGcolor[3] * 255)} - - gnome.viewFgColor = color.RGBA{ - R: uint8(colors.ViewFGcolor[0] * 255), - G: uint8(colors.ViewFGcolor[1] * 255), - B: uint8(colors.ViewFGcolor[2] * 255), - A: uint8(colors.ViewFGcolor[3] * 255)} - -} - -func (gnome *Gnome) fontScale() { - - // for any error below, we will use the default - gnome.fontScaleFactor = 1 - - // call gsettings get org.gnome.desktop.interface text-scaling-factor - cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor") - out, err := cmd.CombinedOutput() - if err != nil { - return - } - - // get the text scaling factor - ts := strings.TrimSpace(string(out)) - textScale, err := strconv.ParseFloat(ts, 32) - if err != nil { - return - } - - // return the text scaling factor - gnome.fontScaleFactor = float32(textScale) -} - -func (gnome *Gnome) getFont() { - - gnome.font = theme.TextFont() - // call gsettings get org.gnome.desktop.interface font-name - cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "font-name") - out, err := cmd.CombinedOutput() - if err != nil { - log.Println(err) - log.Println(string(out)) - return - } - // try to get the font as a TTF file - fontFile := strings.TrimSpace(string(out)) - fontFile = strings.Trim(fontFile, "'") - // the fontFile string is in the format: Name size, eg: "Sans Bold 12", so get the size - parts := strings.Split(fontFile, " ") - fontSize := parts[len(parts)-1] - // convert the size to a float - size, err := strconv.ParseFloat(fontSize, 32) - // apply this to the fontScaleFactor - gnome.fontSize = float32(size) - - // try to get the font as a TTF file - gnome.setFont(strings.Join(parts[:len(parts)-1], "-")) -} - -func (gnome *Gnome) setVariant() { - // using the bgColor, detect if the theme is dark or light - // if it is dark, set the variant to dark - // if it is light, set the variant to light - r, g, b, _ := gnome.bgColor.RGBA() - - brightness := (r/255*299 + g/255*587 + b/255*114) / 1000 - if brightness > 125 { - gnome.variant = theme.VariantLight - } else { - gnome.variant = theme.VariantDark - } -} - -func (gnome *Gnome) getGTKVersion() int { - // call gsettings get org.gnome.desktop.interface gtk-theme - cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "gtk-theme") - out, err := cmd.CombinedOutput() - if err != nil { - log.Println(err) - log.Println(string(out)) - return 3 // default to Gtk 3 - } - themename := strings.TrimSpace(string(out)) - themename = strings.Trim(themename, "'") - - // ok so now, find if the theme is gtk4, either fallback to gtk3 - home, err := os.UserHomeDir() - if err != nil { - log.Println(err) - return 3 // default to Gtk 3 - } - - possiblePaths := []string{ - home + "/.local/share/themes/", - home + "/.themes/", - `/usr/local/share/themes/`, - `/usr/share/themes/`, - } - - for _, path := range possiblePaths { - path = filepath.Join(path, themename) - if _, err := os.Stat(path); err == nil { - // now check if it is gtk4 compatible - if _, err := os.Stat(path + "gtk-4.0/gtk.css"); err == nil { - // it is gtk4 - return 4 - } - if _, err := os.Stat(path + "gtk-3.0/gtk.css"); err == nil { - return 3 - } - } - } - return 3 // default, but that may be a false positive now -} - -func (gnome *Gnome) setFont(fontname string) { - - fontpath, err := gnome.getFontPath(fontname) - if err != nil { - log.Println(err) - return - } - - ext := filepath.Ext(fontpath) - if ext == ".otf" { - font, err := gnome.convertOTF2TTF(fontpath) - if err != nil { - log.Println(err) - return - } - gnome.font = fyne.NewStaticResource(fontpath, font) - } - if ext == ".ttf" { - font, err := ioutil.ReadFile(fontpath) - if err != nil { - log.Println(err) - return - } - gnome.font = fyne.NewStaticResource(fontpath, font) - } - -} - -func (*Gnome) getFontPath(fontname string) (string, error) { - - // get the font path - cmd := exec.Command("fc-match", "-f", "%{file}", fontname) - out, err := cmd.CombinedOutput() - if err != nil { - log.Println(err) - log.Println(string(out)) - return "", err - } - log.Println("Detected font path", string(out)) - - // get the font path with fc-list command - fontpath := strings.TrimSpace(string(out)) - return fontpath, nil -} - -func (*Gnome) convertOTF2TTF(fontpath string) ([]byte, error) { - - // convert the font to a ttf file - basename := filepath.Base(fontpath) - tempTTF := filepath.Join(os.TempDir(), "fyne-"+basename+".ttf") - - ffScript := `Open("%s");Generate("%s")` - script := fmt.Sprintf(ffScript, fontpath, tempTTF) - cmd := exec.Command("fontforge", "-c", script) - cmd.Env = append(cmd.Env, "FONTFORGE_LANGUAGE=ff") - - out, err := cmd.CombinedOutput() - if err != nil { - log.Println(err) - log.Println(string(out)) - return nil, err - } - defer os.Remove(tempTTF) - log.Println("TTF font generated: ", tempTTF) - - // read the temporary ttf file - return ioutil.ReadFile(tempTTF) -} From 8a9b79ad3722552addc78eb7658d0a1ccbfa669b Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Mon, 16 May 2022 11:47:44 +0200 Subject: [PATCH 05/46] Fixes some colors and more optims --- theme/desktop/gnome.go | 156 ++++++++++++++++++++++++++--------------- theme/desktop/kde.go | 80 +++++++++++++-------- theme/desktop/utils.go | 38 +++++++--- 3 files changed, 177 insertions(+), 97 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index a95493f5..59aaaa7e 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -34,6 +34,11 @@ const colors = { background: [], foreground: [], borders: [], + successColor: [], + warningColor: [], + errorColor: [], + accentColor: [], + card_bg_color: [], }; const win = new Gtk.Window(); @@ -71,28 +76,56 @@ if (!ok) { } colors.borders = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok, bg] = ctx.lookup_color("success_color"); +colors.successColor = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok ,bg] = ctx.lookup_color("warning_color"); +colors.warningColor = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok, bg] = ctx.lookup_color("error_color"); +colors.errorColor = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok, bg] = ctx.lookup_color("accent_color"); +colors.accentColor = [bg.red, bg.green, bg.blue, bg.alpha]; + +[ok, bg] = ctx.lookup_color("card_bg_color"); +if (!ok) { + bg = colors.background; +} +colors.card_bg_color = [bg.red, bg.blue, bg.green, bg.alpha]; + print(JSON.stringify(colors)); ` // GnomeTheme theme, based on the Gnome desktop manager. This theme uses GJS and gsettings to get // the colors and font from the Gnome desktop. type GnomeTheme struct { - bgColor color.Color - fgColor color.Color - viewBgColor color.Color - viewFgColor color.Color - borderColor color.Color + bgColor color.Color + fgColor color.Color + viewBgColor color.Color + viewFgColor color.Color + cardBgColor color.Color + borderColor color.Color + successColor color.Color + warningColor color.Color + errorColor color.Color + accentColor color.Color + fontScaleFactor float32 font fyne.Resource fontSize float32 variant fyne.ThemeVariant } -// NewGnomeTheme returns a new Gnome theme based on the given gtk version. If gtkVersion is -1, +// NewGnomeTheme returns a new Gnome theme based on the given gtk version. If gtkVersion is <= 0, // the theme will try to determine the higher Gtk version available for the current GtkTheme. func NewGnomeTheme(gtkVersion int) fyne.Theme { - gnome := &GnomeTheme{} - if gtkVersion == -1 { + gnome := &GnomeTheme{ + variant: ft.VariantDark, + } + if gtkVersion <= 0 { + // detect gtkVersion gtkVersion = gnome.getGTKVersion() } gnome.decodeTheme(gtkVersion, ft.VariantDark) @@ -107,11 +140,17 @@ func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) co switch name { case ft.ColorNameBackground: - return gnome.bgColor + return gnome.viewBgColor case ft.ColorNameForeground: return gnome.fgColor case ft.ColorNameButton, ft.ColorNameInputBackground: - return gnome.viewBgColor + return gnome.bgColor + case ft.ColorNamePrimary: + return gnome.successColor + case ft.ColorNameError: + return gnome.errorColor + case ft.ColorNameFocus: + return gnome.successColor default: return ft.DefaultTheme().Color(name, gnome.variant) } @@ -155,28 +194,21 @@ func (gnome *GnomeTheme) decodeTheme(gtkVersion int, variant fyne.ThemeVariant) // make things faster in concurrent mode wg.Add(3) - go func() { - gnome.getColors(gtkVersion) - gnome.setVariant() - wg.Done() - }() - go func() { - gnome.getFont() - wg.Done() - }() - go func() { - gnome.fontScale() - wg.Done() - }() + go gnome.getColors(gtkVersion, &wg) + go gnome.getFont(&wg) + go gnome.fontScale(&wg) wg.Wait() } -func (gnome *GnomeTheme) getColors(gtkVersion int) { +func (gnome *GnomeTheme) getColors(gtkVersion int, wg *sync.WaitGroup) { + if wg != nil { + defer wg.Done() + } // we will call gjs to get the colors gjs, err := exec.LookPath("gjs") if err != nil { - log.Println(err) + log.Println("To activate the theme, please install gjs", err) return } @@ -209,7 +241,12 @@ func (gnome *GnomeTheme) getColors(gtkVersion int) { WindowFGcolor []float32 `json:"foreground,-"` ViewBGcolor []float32 `json:"viewbg,-"` ViewFGcolor []float32 `json:"viewfg,-"` + CardBGColor []float32 `json:"card_bg_color,-"` Borders []float32 `json:"borders,-"` + SuccessColor []float32 `json:"successColor,-"` + WarningColor []float32 `json:"warningColor,-"` + ErrorColor []float32 `json:"errorColor,-"` + AccentColor []float32 `json:"accentColor,-"` }{} err = json.Unmarshal(out, &colors) if err != nil { @@ -218,40 +255,37 @@ func (gnome *GnomeTheme) getColors(gtkVersion int) { } // convert the colors to fyne colors - gnome.bgColor = color.RGBA{ - R: uint8(colors.WindowBGcolor[0] * 255), - G: uint8(colors.WindowBGcolor[1] * 255), - B: uint8(colors.WindowBGcolor[2] * 255), - A: uint8(colors.WindowBGcolor[3] * 255)} - - gnome.fgColor = color.RGBA{ - R: uint8(colors.WindowFGcolor[0] * 255), - G: uint8(colors.WindowFGcolor[1] * 255), - B: uint8(colors.WindowFGcolor[2] * 255), - A: uint8(colors.WindowFGcolor[3] * 255)} - - gnome.borderColor = color.RGBA{ - R: uint8(colors.Borders[0] * 255), - G: uint8(colors.Borders[1] * 255), - B: uint8(colors.Borders[2] * 255), - A: uint8(colors.Borders[3] * 255)} - - gnome.viewBgColor = color.RGBA{ - R: uint8(colors.ViewBGcolor[0] * 255), - G: uint8(colors.ViewBGcolor[1] * 255), - B: uint8(colors.ViewBGcolor[2] * 255), - A: uint8(colors.ViewBGcolor[3] * 255)} - - gnome.viewFgColor = color.RGBA{ - R: uint8(colors.ViewFGcolor[0] * 255), - G: uint8(colors.ViewFGcolor[1] * 255), - B: uint8(colors.ViewFGcolor[2] * 255), - A: uint8(colors.ViewFGcolor[3] * 255)} + gnome.bgColor = gnome.parseColor(colors.WindowBGcolor) + gnome.fgColor = gnome.parseColor(colors.WindowFGcolor) + gnome.borderColor = gnome.parseColor(colors.Borders) + gnome.viewBgColor = gnome.parseColor(colors.ViewBGcolor) + gnome.viewFgColor = gnome.parseColor(colors.ViewFGcolor) + gnome.cardBgColor = gnome.parseColor(colors.CardBGColor) + gnome.successColor = gnome.parseColor(colors.SuccessColor) + gnome.warningColor = gnome.parseColor(colors.WarningColor) + gnome.errorColor = gnome.parseColor(colors.ErrorColor) + gnome.accentColor = gnome.parseColor(colors.AccentColor) + + gnome.setVariant() } -func (gnome *GnomeTheme) fontScale() { +// parseColor converts a float32 array to color.Color. +func (*GnomeTheme) parseColor(col []float32) color.Color { + return color.RGBA{ + R: uint8(col[0] * 255), + G: uint8(col[1] * 255), + B: uint8(col[2] * 255), + A: uint8(col[3] * 255), + } +} + +// fontScale find the font scaling factor in settings. +func (gnome *GnomeTheme) fontScale(wg *sync.WaitGroup) { + if wg != nil { + defer wg.Done() + } // for any error below, we will use the default gnome.fontScaleFactor = 1 @@ -273,7 +307,13 @@ func (gnome *GnomeTheme) fontScale() { gnome.fontScaleFactor = float32(textScale) } -func (gnome *GnomeTheme) getFont() { +// getFont gets the font name from gsettings and set the font size. This also calls +// setFont() to set the font. +func (gnome *GnomeTheme) getFont(wg *sync.WaitGroup) { + + if wg != nil { + defer wg.Done() + } gnome.font = ft.TextFont() // call gsettings get org.gnome.desktop.interface font-name @@ -313,6 +353,8 @@ func (gnome *GnomeTheme) setVariant() { } } +// getGTKVersion gets the available GTK version for the given theme. If the version cannot be +// determine, it will return 3 wich is the most common used version. func (gnome *GnomeTheme) getGTKVersion() int { // call gsettings get org.gnome.desktop.interface gtk-theme cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "gtk-theme") @@ -355,6 +397,8 @@ func (gnome *GnomeTheme) getGTKVersion() int { return 3 // default, but that may be a false positive now } +// setFont sets the font for the theme - this method calls getFontPath() and converToTTF +// if needed. func (gnome *GnomeTheme) setFont(fontname string) { fontpath, err := getFontPath(fontname) diff --git a/theme/desktop/kde.go b/theme/desktop/kde.go index d8d41640..b81651b9 100644 --- a/theme/desktop/kde.go +++ b/theme/desktop/kde.go @@ -1,7 +1,6 @@ package desktop import ( - "errors" "image/color" "io/ioutil" "log" @@ -14,23 +13,25 @@ import ( ft "fyne.io/fyne/v2/theme" ) -// KDE theme is based on the KDE or Plasma theme. -type KDE struct { - variant fyne.ThemeVariant - bgColor color.Color - fgColor color.Color - fontConfig string - fontSize float32 - font fyne.Resource +// KDETheme theme is based on the KDETheme or Plasma theme. +type KDETheme struct { + variant fyne.ThemeVariant + bgColor color.Color + fgColor color.Color + viewColor color.Color + buttonColor color.Color + buttonAlternate color.Color + fontConfig string + fontSize float32 + font fyne.Resource } // NewKDETheme returns a new KDE theme. func NewKDETheme() fyne.Theme { - kde := &KDE{ + kde := &KDETheme{ variant: ft.VariantDark, } kde.decodeTheme() - log.Println(kde) return kde } @@ -38,12 +39,19 @@ func NewKDETheme() fyne.Theme { // Color returns the color for the specified name. // // Implements: fyne.Theme -func (k *KDE) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color { +func (k *KDETheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color { switch name { case ft.ColorNameBackground: return k.bgColor case ft.ColorNameForeground: return k.fgColor + case ft.ColorNameButton: + return k.buttonColor + case ft.ColorNameDisabledButton: + return k.buttonAlternate + case ft.ColorNameInputBackground: + return k.viewColor + } return ft.DefaultTheme().Color(name, k.variant) } @@ -51,14 +59,14 @@ func (k *KDE) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color { // Icon returns the icon for the specified name. // // Implements: fyne.Theme -func (k *KDE) Icon(i fyne.ThemeIconName) fyne.Resource { +func (k *KDETheme) Icon(i fyne.ThemeIconName) fyne.Resource { return ft.DefaultTheme().Icon(i) } // Font returns the font for the specified name. // // Implements: fyne.Theme -func (k *KDE) Font(s fyne.TextStyle) fyne.Resource { +func (k *KDETheme) Font(s fyne.TextStyle) fyne.Resource { if k.font != nil { return k.font } @@ -68,7 +76,7 @@ func (k *KDE) Font(s fyne.TextStyle) fyne.Resource { // Size returns the size of the font for the specified text style. // // Implements: fyne.Theme -func (k *KDE) Size(s fyne.ThemeSizeName) float32 { +func (k *KDETheme) Size(s fyne.ThemeSizeName) float32 { if s == ft.SizeNameText { return k.fontSize } @@ -76,12 +84,12 @@ func (k *KDE) Size(s fyne.ThemeSizeName) float32 { } // decodeTheme initialize the theme. -func (k *KDE) decodeTheme() { +func (k *KDETheme) decodeTheme() { k.loadScheme() k.setFont() } -func (k *KDE) loadScheme() error { +func (k *KDETheme) loadScheme() error { // the theme name is declared in ~/.config/kdedefaults/kdeglobals // in the ini section [General] as "ColorScheme" entry homedir, err := os.UserHomeDir() @@ -108,6 +116,19 @@ func (k *KDE) loadScheme() error { k.fgColor = k.parseColor(strings.ReplaceAll(line, "ForegroundNormal=", "")) } } + if section == "Colors:Button" { + if strings.HasPrefix(line, "BackgroundNormal=") { + k.buttonColor = k.parseColor(strings.ReplaceAll(line, "BackgroundNormal=", "")) + } + if strings.HasPrefix(line, "BackgroundAlternate=") { + k.buttonAlternate = k.parseColor(strings.ReplaceAll(line, "BackgroundAlternate=", "")) + } + } + if section == "Colors:View" { + if strings.HasPrefix(line, "BackgroundNormal=") { + k.viewColor = k.parseColor(strings.ReplaceAll(line, "BackgroundNormal=", "")) + } + } if section == "General" { if strings.HasPrefix(line, "font=") { k.fontConfig = strings.ReplaceAll(line, "font=", "") @@ -115,10 +136,10 @@ func (k *KDE) loadScheme() error { } } - return errors.New("Unable to find the KDE color scheme") + return nil } -func (k *KDE) parseColor(col string) color.Color { +func (k *KDETheme) parseColor(col string) color.Color { // the color is in the form r,g,b, // we need to convert it to a color.Color @@ -133,16 +154,15 @@ func (k *KDE) parseColor(col string) color.Color { return color.RGBA{uint8(r), uint8(g), uint8(b), 0xff} } -func (k *KDE) setFont() { +func (k *KDETheme) setFont() { if k.fontConfig == "" { - log.Println("WTF") return } - // the font is in the form "fontname,size,...", so we can split it - font := strings.Split(k.fontConfig, ",") - name := font[0] - size, _ := strconv.ParseFloat(font[1], 32) + // the fontline is in the form "fontline,size,...", so we can split it + fontline := strings.Split(k.fontConfig, ",") + name := fontline[0] + size, _ := strconv.ParseFloat(fontline[1], 32) k.fontSize = float32(size) // we need to load the font, Gnome struct has got some nice methods @@ -151,20 +171,20 @@ func (k *KDE) setFont() { log.Println(err) return } - log.Println(fontpath) + + var font []byte if filepath.Ext(fontpath) == ".ttf" { - font, err := ioutil.ReadFile(fontpath) + font, err = ioutil.ReadFile(fontpath) if err != nil { log.Println(err) return } - k.font = fyne.NewStaticResource(fontpath, font) } else { - font, err := converToTTF(fontpath) + font, err = converToTTF(fontpath) if err != nil { log.Println(err) return } - k.font = fyne.NewStaticResource(fontpath, font) } + k.font = fyne.NewStaticResource(fontpath, font) } diff --git a/theme/desktop/utils.go b/theme/desktop/utils.go index b984f0f0..3755651b 100644 --- a/theme/desktop/utils.go +++ b/theme/desktop/utils.go @@ -11,16 +11,28 @@ import ( "strings" ) -// getFontPath will detect the font path from the font name taken from gsettings. As the font is not exactly -// the one that fc-match can find, we need to do some extra work to rebuild the name with style. +// getFontPath will detect the font path from the font name taken from gsettings. +// As the font is not exactly the one that fc-match can find, we need to do some +// extra work to rebuild the name with style. func getFontPath(fontname string) (string, error) { + // check if fc-list and fc-match are installed + fcList, err := exec.LookPath("fc-list") + if err != nil { + return "", err + } + + fcMatch, err := exec.LookPath("fc-match") + if err != nil { + return "", err + } + // This to transoform CamelCase to Camel-Case camelRegExp := regexp.MustCompile(`([a-z\-])([A-Z])`) // get all possible styles in fc-list allstyles := []string{} - cmd := exec.Command("fc-list", "--format", "%{style}\n") + cmd := exec.Command(fcList, "--format", "%{style}\n") out, err := cmd.CombinedOutput() if err != nil { return "", err @@ -41,8 +53,6 @@ func getFontPath(fontname string) (string, error) { // Find the styles, remove it from the nmae, this make a correct fc-match query fontstyle := []string{} for _, style := range allstyles { - // remove the style, we add a "space" to avoid this case: Semi-Condensed contains Condensed - // and there is always a space before the style (because the font name prefixes the string) if strings.Contains(fontname, " "+style) { fontstyle = append(fontstyle, style) fontname = strings.ReplaceAll(fontname, style, "") @@ -52,9 +62,8 @@ func getFontPath(fontname string) (string, error) { // we can now search // fc-math ... "Font Name:Font Style var fontpath string - cmd = exec.Command("fc-match", "-f", "%{file}", fontname+":"+strings.Join(fontstyle, " ")) + cmd = exec.Command(fcMatch, "-f", "%{file}", fontname+":"+strings.Join(fontstyle, " ")) out, err = cmd.CombinedOutput() - log.Println(string(out), fontname) if err != nil { log.Println(err) log.Println(string(out)) @@ -67,17 +76,25 @@ func getFontPath(fontname string) (string, error) { return fontpath, nil } -// converToTTF will convert a font to a ttf file. This requires the fontconfig package. +// converToTTF will convert a font to a ttf file. This requires the fontforge package. func converToTTF(fontpath string) ([]byte, error) { + // check if fontforge is installed + fontforge, err := exec.LookPath("fontforge") + if err != nil { + return nil, err + } + // convert the font to a ttf file basename := filepath.Base(fontpath) tempTTF := filepath.Join(os.TempDir(), "fyne-"+basename+".ttf") - // Convert to TTF + // Convert to TTF, this is the FF script to call ffScript := `Open("%s");Generate("%s")` script := fmt.Sprintf(ffScript, fontpath, tempTTF) - cmd := exec.Command("fontforge", "-c", script) + + // call fontforge + cmd := exec.Command(fontforge, "-c", script) cmd.Env = append(cmd.Env, "FONTFORGE_LANGUAGE=ff") out, err := cmd.CombinedOutput() @@ -87,7 +104,6 @@ func converToTTF(fontpath string) ([]byte, error) { return nil, err } defer os.Remove(tempTTF) - log.Println("TTF font generated: ", tempTTF) // read the temporary ttf file return ioutil.ReadFile(tempTTF) From f35b29705aae101c7225a05cfa41429420b29bb8 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 17 May 2022 11:32:34 +0200 Subject: [PATCH 06/46] Fix a lot of things... --- theme/desktop/doc.go | 2 +- theme/desktop/gnome.go | 379 ++++++++++++++++++++++++++++++++---- theme/desktop/kde.go | 18 +- theme/desktopEnvironment.go | 32 +++ theme/windowManager.go | 32 --- 5 files changed, 384 insertions(+), 79 deletions(-) create mode 100644 theme/desktopEnvironment.go delete mode 100644 theme/windowManager.go diff --git a/theme/desktop/doc.go b/theme/desktop/doc.go index c56115cd..bf6fee25 100644 --- a/theme/desktop/doc.go +++ b/theme/desktop/doc.go @@ -1,4 +1,4 @@ -// desktop package provides theme for desktop manager like Gnome, KDE, Plasma... +// desktop package provides theme for desktop environment like Gnome, KDE, Plasma... // To be fully used, the system need to have gsettings and gjs for all GTK/Gnome based desktop. // KDE/Plasma theme only works when the user has already initialize a session to create ~/.config/kdeglobals // diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 59aaaa7e..ca28d74f 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -1,7 +1,9 @@ package desktop import ( + "bytes" "encoding/json" + "fmt" "image/color" "io/ioutil" "log" @@ -11,17 +13,71 @@ import ( "strconv" "strings" "sync" + "time" "fyne.io/fyne/v2" ft "fyne.io/fyne/v2/theme" + "github.com/srwiley/oksvg" ) +type GnomeFlag uint8 + +const ( + GnomeFlagAutoReload GnomeFlag = iota +) + +// mapping to gnome/gtk icon names. +var gnomeIconMaps = map[fyne.ThemeIconName]string{ + ft.IconNameInfo: "dialog-information", + ft.IconNameError: "dialog-error", + ft.IconNameQuestion: "dialog-question", + + ft.IconNameFolder: "folder", + ft.IconNameFolderNew: "folder-new", + ft.IconNameFolderOpen: "folder-open", + ft.IconNameHome: "go-home", + ft.IconNameDownload: "download", + + ft.IconNameDocument: "document", + ft.IconNameFileImage: "image", + ft.IconNameFileApplication: "binary", + ft.IconNameFileText: "text", + ft.IconNameFileVideo: "video", + ft.IconNameFileAudio: "audio", + ft.IconNameComputer: "computer", + ft.IconNameMediaPhoto: "photo", + ft.IconNameMediaVideo: "video", + ft.IconNameMediaMusic: "music", + + ft.IconNameConfirm: "dialog-apply", + ft.IconNameCancel: "cancel", + + ft.IconNameCheckButton: "checkbox-symbolic", + ft.IconNameCheckButtonChecked: "checkbox-checked-symbolic", + ft.IconNameRadioButton: "radio-symbolic", + ft.IconNameRadioButtonChecked: "radio-checked-symbolic", + + ft.IconNameArrowDropDown: "arrow-down", + ft.IconNameArrowDropUp: "arrow-up", + ft.IconNameNavigateNext: "go-right", + ft.IconNameNavigateBack: "go-left", + ft.IconNameMoveDown: "go-down", + ft.IconNameMoveUp: "go-up", + ft.IconNameSettings: "document-properties", + ft.IconNameHistory: "history-view", + ft.IconNameList: "view-list", + ft.IconNameGrid: "view-grid", + ft.IconNameColorPalette: "color-select", + ft.IconNameColorChromatic: "color-select", + ft.IconNameColorAchromatic: "color-picker-grey", +} + // Script to get the colors from the Gnome GTK/Adwaita theme. const gjsScript = ` let gtkVersion = Number(ARGV[0] || 4); imports.gi.versions.Gtk = gtkVersion + ".0"; -const { Gtk } = imports.gi; +const { Gtk, Gdk } = imports.gi; if (gtkVersion === 3) { Gtk.init(null); } else { @@ -76,28 +132,73 @@ if (!ok) { } colors.borders = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("success_color"); +[ok, bg] = ctx.lookup_color("success_bg_color"); +if (!ok) { + [ok, bg] = ctx.lookup_color("success_color"); +} colors.successColor = [bg.red, bg.green, bg.blue, bg.alpha]; -[ok ,bg] = ctx.lookup_color("warning_color"); +[ok, bg] = ctx.lookup_color("warning_color"); colors.warningColor = [bg.red, bg.green, bg.blue, bg.alpha]; [ok, bg] = ctx.lookup_color("error_color"); colors.errorColor = [bg.red, bg.green, bg.blue, bg.alpha]; [ok, bg] = ctx.lookup_color("accent_color"); +if (!ok) { + [ok, bg] = ctx.lookup_color("success_color"); +} colors.accentColor = [bg.red, bg.green, bg.blue, bg.alpha]; [ok, bg] = ctx.lookup_color("card_bg_color"); if (!ok) { - bg = colors.background; + bg = colors.background; } colors.card_bg_color = [bg.red, bg.blue, bg.green, bg.alpha]; print(JSON.stringify(colors)); ` +// script to get icons from theme. +const gjsIcons = ` +let gtkVersion = Number(ARGV[0] || 4); +imports.gi.versions.Gtk = gtkVersion + ".0"; +const iconSize = 32; // can be 8, 16, 24, 32, 48, 64, 96 + +const { Gtk, Gdk } = imports.gi; +if (gtkVersion === 3) { + Gtk.init(null); +} else { + Gtk.init(); +} + +let iconTheme = null; +const icons = %s; // the icon list to get +const iconset = {}; + +if (gtkVersion === 3) { + iconTheme = Gtk.IconTheme.get_default(); +} else { + iconTheme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()); +} + +icons.forEach((name) => { + if (gtkVersion === 3) { + try { + const icon = iconTheme.lookup_icon(name, iconSize, 0); + iconset[name] = icon.get_filename(); + } catch (e) { + iconset[name] = null; + } + } else { + const icon = iconTheme.lookup_icon(name, null, null, iconSize, null, 0); + iconset[name] = icon.file.get_path(); + } +}); + +print(JSON.stringify(iconset)); +` + // GnomeTheme theme, based on the Gnome desktop manager. This theme uses GJS and gsettings to get // the colors and font from the Gnome desktop. type GnomeTheme struct { @@ -112,24 +213,57 @@ type GnomeTheme struct { errorColor color.Color accentColor color.Color + icons map[string]string + fontScaleFactor float32 font fyne.Resource fontSize float32 variant fyne.ThemeVariant + iconCache map[string]fyne.Resource } // NewGnomeTheme returns a new Gnome theme based on the given gtk version. If gtkVersion is <= 0, // the theme will try to determine the higher Gtk version available for the current GtkTheme. -func NewGnomeTheme(gtkVersion int) fyne.Theme { +func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { gnome := &GnomeTheme{ - variant: ft.VariantDark, + bgColor: ft.DefaultTheme().Color(ft.ColorNameBackground, ft.VariantDark), + fgColor: ft.DefaultTheme().Color(ft.ColorNameForeground, ft.VariantDark), + fontSize: ft.DefaultTheme().Size(ft.SizeNameText), + variant: ft.VariantDark, + iconCache: map[string]fyne.Resource{}, + icons: map[string]string{}, } + if gtkVersion <= 0 { // detect gtkVersion gtkVersion = gnome.getGTKVersion() } gnome.decodeTheme(gtkVersion, ft.VariantDark) + for _, flag := range flags { + switch flag { + case GnomeFlagAutoReload: + go func() { + currentTheme := gnome.getTheme() + iconThem := gnome.getIconThemeName() + for { + current := fyne.CurrentApp().Settings().Theme() + if _, ok := current.(*GnomeTheme); !ok { + break + } + time.Sleep(1 * time.Second) + newTheme := gnome.getTheme() + newIconTheme := gnome.getIconThemeName() + if currentTheme != newTheme || iconThem != newIconTheme { + // ensure that the current theme is still a GnomeTheme + log.Println("Gnome or icon them changed, reloading theme") + fyne.CurrentApp().Settings().SetTheme(NewGnomeTheme(gtkVersion, flags...)) + break + } + } + }() + } + } return gnome } @@ -146,11 +280,9 @@ func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) co case ft.ColorNameButton, ft.ColorNameInputBackground: return gnome.bgColor case ft.ColorNamePrimary: - return gnome.successColor + return gnome.accentColor case ft.ColorNameError: return gnome.errorColor - case ft.ColorNameFocus: - return gnome.successColor default: return ft.DefaultTheme().Color(name, gnome.variant) } @@ -160,6 +292,13 @@ func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) co // // Implements: fyne.Theme func (gnome *GnomeTheme) Icon(i fyne.ThemeIconName) fyne.Resource { + + if icon, found := gnomeIconMaps[i]; found { + if resource := gnome.loadIcon(icon); resource != nil { + return resource + } + } + //log.Println(i) return ft.DefaultTheme().Icon(i) } @@ -186,21 +325,17 @@ func (g *GnomeTheme) Size(s fyne.ThemeSizeName) float32 { } func (gnome *GnomeTheme) decodeTheme(gtkVersion int, variant fyne.ThemeVariant) { - // default - gnome.bgColor = ft.DefaultTheme().Color(ft.ColorNameBackground, variant) - gnome.fgColor = ft.DefaultTheme().Color(ft.ColorNameForeground, variant) - gnome.fontSize = ft.DefaultTheme().Size(ft.SizeNameText) - wg := sync.WaitGroup{} - // make things faster in concurrent mode - wg.Add(3) - go gnome.getColors(gtkVersion, &wg) - go gnome.getFont(&wg) - go gnome.fontScale(&wg) + wg := sync.WaitGroup{} + wg.Add(4) + go gnome.applyColors(gtkVersion, &wg) + go gnome.applyIcons(gtkVersion, &wg) + go gnome.applyFont(&wg) + go gnome.applyFontScale(&wg) wg.Wait() } -func (gnome *GnomeTheme) getColors(gtkVersion int, wg *sync.WaitGroup) { +func (gnome *GnomeTheme) applyColors(gtkVersion int, wg *sync.WaitGroup) { if wg != nil { defer wg.Done() @@ -213,7 +348,7 @@ func (gnome *GnomeTheme) getColors(gtkVersion int, wg *sync.WaitGroup) { } // create a temp file to store the colors - f, err := ioutil.TempFile("", "fyne-theme-gnome-") + f, err := ioutil.TempFile("", "fyne-theme-gnome-*.js") if err != nil { log.Println(err) return @@ -228,10 +363,13 @@ func (gnome *GnomeTheme) getColors(gtkVersion int, wg *sync.WaitGroup) { } // run the script - cmd := exec.Command(gjs, f.Name(), strconv.Itoa(gtkVersion)) + cmd := exec.Command(gjs, + f.Name(), strconv.Itoa(gtkVersion), + fmt.Sprintf("%0.2f", 1.0), + ) out, err := cmd.CombinedOutput() if err != nil { - log.Println(err, string(out)) + log.Println("gjs error:", err, string(out)) return } @@ -266,8 +404,78 @@ func (gnome *GnomeTheme) getColors(gtkVersion int, wg *sync.WaitGroup) { gnome.errorColor = gnome.parseColor(colors.ErrorColor) gnome.accentColor = gnome.parseColor(colors.AccentColor) - gnome.setVariant() + gnome.calculateVariant() + +} + +func (gnome *GnomeTheme) applyIcons(gtkVersion int, wg *sync.WaitGroup) { + + if wg != nil { + defer wg.Done() + } + + gjs, err := exec.LookPath("gjs") + if err != nil { + log.Println("To activate the theme, please install gjs", err) + return + } + // create the list of icon to get + var icons []string + for _, icon := range gnomeIconMaps { + icons = append(icons, icon) + } + iconSet := "[\n" + for _, icon := range icons { + iconSet += fmt.Sprintf(` "%s",`+"\n", icon) + } + iconSet += "]" + + gjsIconList := fmt.Sprintf(gjsIcons, iconSet) + + // write the script to a temp file + f, err := ioutil.TempFile("", "fyne-theme-gnome-*.js") + if err != nil { + log.Println(err) + return + } + defer os.Remove(f.Name()) + + // write the script to the temp file + _, err = f.WriteString(gjsIconList) + if err != nil { + log.Println(err) + return + } + + // Call gjs with 2 version, 3 and 4 to complete the icon, this because + // gtk version is sometimes not available or icon is not fully completed... + // It's a bit tricky but it works. + for _, gtkVersion := range []string{"3", "4"} { + // run the script + cmd := exec.Command(gjs, + f.Name(), gtkVersion, + ) + out, err := cmd.CombinedOutput() + if err != nil { + log.Println("gjs error:", err, string(out)) + return + } + tmpicons := map[string]*string{} + // decode json to apply to the gnome theme + err = json.Unmarshal(out, &tmpicons) + if err != nil { + log.Println(err) + return + } + for k, v := range tmpicons { + if _, ok := gnome.icons[k]; !ok { + if v != nil && *v != "" { + gnome.icons[k] = *v + } + } + } + } } // parseColor converts a float32 array to color.Color. @@ -278,11 +486,10 @@ func (*GnomeTheme) parseColor(col []float32) color.Color { B: uint8(col[2] * 255), A: uint8(col[3] * 255), } - } -// fontScale find the font scaling factor in settings. -func (gnome *GnomeTheme) fontScale(wg *sync.WaitGroup) { +// applyFontScale find the font scaling factor in settings. +func (gnome *GnomeTheme) applyFontScale(wg *sync.WaitGroup) { if wg != nil { defer wg.Done() } @@ -307,9 +514,9 @@ func (gnome *GnomeTheme) fontScale(wg *sync.WaitGroup) { gnome.fontScaleFactor = float32(textScale) } -// getFont gets the font name from gsettings and set the font size. This also calls +// applyFont gets the font name from gsettings and set the font size. This also calls // setFont() to set the font. -func (gnome *GnomeTheme) getFont(wg *sync.WaitGroup) { +func (gnome *GnomeTheme) applyFont(wg *sync.WaitGroup) { if wg != nil { defer wg.Done() @@ -339,10 +546,8 @@ func (gnome *GnomeTheme) getFont(wg *sync.WaitGroup) { gnome.setFont(strings.Join(parts[:len(parts)-1], " ")) } -func (gnome *GnomeTheme) setVariant() { +func (gnome *GnomeTheme) calculateVariant() { // using the bgColor, detect if the theme is dark or light - // if it is dark, set the variant to dark - // if it is light, set the variant to light r, g, b, _ := gnome.bgColor.RGBA() brightness := (r/255*299 + g/255*587 + b/255*114) / 1000 @@ -356,16 +561,11 @@ func (gnome *GnomeTheme) setVariant() { // getGTKVersion gets the available GTK version for the given theme. If the version cannot be // determine, it will return 3 wich is the most common used version. func (gnome *GnomeTheme) getGTKVersion() int { - // call gsettings get org.gnome.desktop.interface gtk-theme - cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "gtk-theme") - out, err := cmd.CombinedOutput() - if err != nil { - log.Println(err) - log.Println(string(out)) - return 3 // default to Gtk 3 + + themename := gnome.getTheme() + if themename == "" { + return 3 // default to 3 } - themename := strings.TrimSpace(string(out)) - themename = strings.Trim(themename, "'") // ok so now, find if the theme is gtk4, either fallback to gtk3 home, err := os.UserHomeDir() @@ -423,5 +623,100 @@ func (gnome *GnomeTheme) setFont(fontname string) { } gnome.font = fyne.NewStaticResource(fontpath, font) } +} + +func (gnome *GnomeTheme) loadIcon(name string) (resource fyne.Resource) { + var ok bool + + if resource, ok = gnome.iconCache[name]; ok { + return + } + + defer func() { + // whatever the result is, cache it + // even if it is nil + gnome.iconCache[name] = resource + }() + + if filename, ok := gnome.icons[name]; ok { + content, err := ioutil.ReadFile(filename) + if err != nil { + return + } + if strings.HasSuffix(filename, ".svg") { + // we need to ensure that the svg can be opened by Fyne + buff := bytes.NewBuffer(content) + _, err := oksvg.ReadIconStream(buff) + if err != nil { + // try to convert it to png with imageMagik + log.Println("Cannot load file", filename, err, "try to convert") + resource, err = gnome.convertSVGtoPNG(filename) + if err != nil { + log.Println("Cannot convert file", filename, err) + return + } + return + } + } + resource = fyne.NewStaticResource(filename, content) + return + } + return +} +func (gnome *GnomeTheme) convertSVGtoPNG(filename string) (fyne.Resource, error) { + // use "convert" from imageMagik to convert svg to png + + convert, err := exec.LookPath("convert") + if err != nil { + return nil, err + } + + tmpfile, err := ioutil.TempFile("", "fyne-theme-gnome-*.png") + if err != nil { + return nil, err + } + + // convert the svg to png, no background + cmd := exec.Command(convert, filename, "-background", "none", "-flatten", tmpfile.Name()) + log.Println("Converting", filename, "to", tmpfile.Name()) + err = cmd.Run() + if err != nil { + return nil, err + } + + content, err := ioutil.ReadFile(tmpfile.Name()) + if err != nil { + return nil, err + } + + return fyne.NewStaticResource(filename, content), nil +} + +func (gnome *GnomeTheme) getTheme() string { + // call gsettings get org.gnome.desktop.interface gtk-theme + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "gtk-theme") + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err) + log.Println(string(out)) + return "" // default to Gtk 3 + } + themename := strings.TrimSpace(string(out)) + themename = strings.Trim(themename, "'") + return themename +} + +func (gnome *GnomeTheme) getIconThemeName() string { + // call gsettings get org.gnome.desktop.interface icon-theme + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "icon-theme") + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err) + log.Println(string(out)) + return "" + } + themename := strings.TrimSpace(string(out)) + themename = strings.Trim(themename, "'") + return themename } diff --git a/theme/desktop/kde.go b/theme/desktop/kde.go index b81651b9..146b341e 100644 --- a/theme/desktop/kde.go +++ b/theme/desktop/kde.go @@ -31,7 +31,10 @@ func NewKDETheme() fyne.Theme { kde := &KDETheme{ variant: ft.VariantDark, } - kde.decodeTheme() + if err := kde.decodeTheme(); err != nil { + log.Println(err) + return ft.DefaultTheme() + } return kde } @@ -84,9 +87,12 @@ func (k *KDETheme) Size(s fyne.ThemeSizeName) float32 { } // decodeTheme initialize the theme. -func (k *KDETheme) decodeTheme() { - k.loadScheme() +func (k *KDETheme) decodeTheme() error { + if err := k.loadScheme(); err != nil { + return err + } k.setFont() + return nil } func (k *KDETheme) loadScheme() error { @@ -149,9 +155,13 @@ func (k *KDETheme) parseColor(col string) color.Color { r, _ := strconv.Atoi(cols[0]) g, _ := strconv.Atoi(cols[1]) b, _ := strconv.Atoi(cols[2]) + a := 0xff + if len(cols) > 3 { + a, _ = strconv.Atoi(cols[3]) + } // convert the int to a color.Color - return color.RGBA{uint8(r), uint8(g), uint8(b), 0xff} + return color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)} } func (k *KDETheme) setFont() { diff --git a/theme/desktopEnvironment.go b/theme/desktopEnvironment.go new file mode 100644 index 00000000..8fab16bb --- /dev/null +++ b/theme/desktopEnvironment.go @@ -0,0 +1,32 @@ +package theme + +import ( + "log" + "os" + "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" + "fyne.io/x/fyne/theme/desktop" +) + +// FromDesktopEnvironment returns a new WindowManagerTheme instance for the current desktop session. +// If the desktop manager is not supported or if it is not found, return the default theme +func FromDesktopEnvironment() fyne.Theme { + wm := os.Getenv("XDG_CURRENT_DESKTOP") + if wm == "" { + wm = os.Getenv("DESKTOP_SESSION") + } + wm = strings.ToLower(wm) + + switch wm { + case "gnome", "xfce", "unity", "gnome-shell", "gnome-classic", "mate", "gnome-mate": + return desktop.NewGnomeTheme(-1, desktop.GnomeFlagAutoReload) + case "kde", "kde-plasma", "plasma": + return desktop.NewKDETheme() + + } + + log.Println("Window manager not supported:", wm, "using default theme") + return theme.DefaultTheme() +} diff --git a/theme/windowManager.go b/theme/windowManager.go deleted file mode 100644 index 0d710739..00000000 --- a/theme/windowManager.go +++ /dev/null @@ -1,32 +0,0 @@ -package theme - -import ( - "log" - "os" - "strings" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/theme" - "fyne.io/x/fyne/theme/desktop" -) - -// NewWindowManagerTheme returns a new WindowManagerTheme instance for the current desktop session. If the desktop manager is -// not supported or if it is not found, return the default theme -func NewWindowManagerTheme() fyne.Theme { - wm := os.Getenv("XDG_CURRENT_DESKTOP") - if wm == "" { - wm = os.Getenv("DESKTOP_SESSION") - } - wm = strings.ToUpper(wm) - - switch wm { - case "GNOME", "XFCE", "UNITY", "GNOME-SHELL", "GNOME-CLASSIC", "MATE", "GNOME-MATE": - return desktop.NewGnomeTheme(-1) - case "KDE", "KDE-PLASMA", "PLASMA": - return desktop.NewKDETheme() - - } - - log.Println("Window manager not supported:", wm, "using default theme") - return theme.DefaultTheme() -} From 8631afc304b1a862434fa17830aeb42d3943c0fd Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 17 May 2022 11:36:10 +0200 Subject: [PATCH 07/46] Reorder --- theme/desktop/gnome.go | 382 ++++++++++++++++++++--------------------- theme/desktop/kde.go | 40 ++--- 2 files changed, 211 insertions(+), 211 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index ca28d74f..84333ab4 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -222,51 +222,6 @@ type GnomeTheme struct { iconCache map[string]fyne.Resource } -// NewGnomeTheme returns a new Gnome theme based on the given gtk version. If gtkVersion is <= 0, -// the theme will try to determine the higher Gtk version available for the current GtkTheme. -func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { - gnome := &GnomeTheme{ - bgColor: ft.DefaultTheme().Color(ft.ColorNameBackground, ft.VariantDark), - fgColor: ft.DefaultTheme().Color(ft.ColorNameForeground, ft.VariantDark), - fontSize: ft.DefaultTheme().Size(ft.SizeNameText), - variant: ft.VariantDark, - iconCache: map[string]fyne.Resource{}, - icons: map[string]string{}, - } - - if gtkVersion <= 0 { - // detect gtkVersion - gtkVersion = gnome.getGTKVersion() - } - gnome.decodeTheme(gtkVersion, ft.VariantDark) - - for _, flag := range flags { - switch flag { - case GnomeFlagAutoReload: - go func() { - currentTheme := gnome.getTheme() - iconThem := gnome.getIconThemeName() - for { - current := fyne.CurrentApp().Settings().Theme() - if _, ok := current.(*GnomeTheme); !ok { - break - } - time.Sleep(1 * time.Second) - newTheme := gnome.getTheme() - newIconTheme := gnome.getIconThemeName() - if currentTheme != newTheme || iconThem != newIconTheme { - // ensure that the current theme is still a GnomeTheme - log.Println("Gnome or icon them changed, reloading theme") - fyne.CurrentApp().Settings().SetTheme(NewGnomeTheme(gtkVersion, flags...)) - break - } - } - }() - } - } - return gnome -} - // Color returns the color for the given color name // // Implements: fyne.Theme @@ -288,6 +243,16 @@ func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) co } } +// Font returns the font for the given name. +// +// Implements: fyne.Theme +func (gnome *GnomeTheme) Font(s fyne.TextStyle) fyne.Resource { + if gnome.font == nil { + return ft.DefaultTheme().Font(s) + } + return gnome.font +} + // Icon returns the icon for the given name. // // Implements: fyne.Theme @@ -302,16 +267,6 @@ func (gnome *GnomeTheme) Icon(i fyne.ThemeIconName) fyne.Resource { return ft.DefaultTheme().Icon(i) } -// Font returns the font for the given name. -// -// Implements: fyne.Theme -func (gnome *GnomeTheme) Font(s fyne.TextStyle) fyne.Resource { - if gnome.font == nil { - return ft.DefaultTheme().Font(s) - } - return gnome.font -} - // Size returns the size for the given name. It will scale the detected Gnome font size // by the Gnome font factor. // @@ -324,17 +279,6 @@ func (g *GnomeTheme) Size(s fyne.ThemeSizeName) float32 { return ft.DefaultTheme().Size(s) } -func (gnome *GnomeTheme) decodeTheme(gtkVersion int, variant fyne.ThemeVariant) { - // make things faster in concurrent mode - wg := sync.WaitGroup{} - wg.Add(4) - go gnome.applyColors(gtkVersion, &wg) - go gnome.applyIcons(gtkVersion, &wg) - go gnome.applyFont(&wg) - go gnome.applyFontScale(&wg) - wg.Wait() -} - func (gnome *GnomeTheme) applyColors(gtkVersion int, wg *sync.WaitGroup) { if wg != nil { @@ -408,6 +352,64 @@ func (gnome *GnomeTheme) applyColors(gtkVersion int, wg *sync.WaitGroup) { } +// applyFont gets the font name from gsettings and set the font size. This also calls +// setFont() to set the font. +func (gnome *GnomeTheme) applyFont(wg *sync.WaitGroup) { + + if wg != nil { + defer wg.Done() + } + + gnome.font = ft.TextFont() + // call gsettings get org.gnome.desktop.interface font-name + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "font-name") + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err) + log.Println(string(out)) + return + } + // try to get the font as a TTF file + fontFile := strings.TrimSpace(string(out)) + fontFile = strings.Trim(fontFile, "'") + // the fontFile string is in the format: Name size, eg: "Sans Bold 12", so get the size + parts := strings.Split(fontFile, " ") + fontSize := parts[len(parts)-1] + // convert the size to a float + size, err := strconv.ParseFloat(fontSize, 32) + // apply this to the fontScaleFactor + gnome.fontSize = float32(size) + + // try to get the font as a TTF file + gnome.setFont(strings.Join(parts[:len(parts)-1], " ")) +} + +// applyFontScale find the font scaling factor in settings. +func (gnome *GnomeTheme) applyFontScale(wg *sync.WaitGroup) { + if wg != nil { + defer wg.Done() + } + // for any error below, we will use the default + gnome.fontScaleFactor = 1 + + // call gsettings get org.gnome.desktop.interface text-scaling-factor + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor") + out, err := cmd.CombinedOutput() + if err != nil { + return + } + + // get the text scaling factor + ts := strings.TrimSpace(string(out)) + textScale, err := strconv.ParseFloat(ts, 32) + if err != nil { + return + } + + // return the text scaling factor + gnome.fontScaleFactor = float32(textScale) +} + func (gnome *GnomeTheme) applyIcons(gtkVersion int, wg *sync.WaitGroup) { if wg != nil { @@ -478,84 +480,56 @@ func (gnome *GnomeTheme) applyIcons(gtkVersion int, wg *sync.WaitGroup) { } } -// parseColor converts a float32 array to color.Color. -func (*GnomeTheme) parseColor(col []float32) color.Color { - return color.RGBA{ - R: uint8(col[0] * 255), - G: uint8(col[1] * 255), - B: uint8(col[2] * 255), - A: uint8(col[3] * 255), +func (gnome *GnomeTheme) calculateVariant() { + // using the bgColor, detect if the theme is dark or light + r, g, b, _ := gnome.bgColor.RGBA() + + brightness := (r/255*299 + g/255*587 + b/255*114) / 1000 + if brightness > 125 { + gnome.variant = ft.VariantLight + } else { + gnome.variant = ft.VariantDark } } -// applyFontScale find the font scaling factor in settings. -func (gnome *GnomeTheme) applyFontScale(wg *sync.WaitGroup) { - if wg != nil { - defer wg.Done() - } - // for any error below, we will use the default - gnome.fontScaleFactor = 1 +func (gnome *GnomeTheme) convertSVGtoPNG(filename string) (fyne.Resource, error) { + // use "convert" from imageMagik to convert svg to png - // call gsettings get org.gnome.desktop.interface text-scaling-factor - cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor") - out, err := cmd.CombinedOutput() + convert, err := exec.LookPath("convert") if err != nil { - return + return nil, err } - // get the text scaling factor - ts := strings.TrimSpace(string(out)) - textScale, err := strconv.ParseFloat(ts, 32) + tmpfile, err := ioutil.TempFile("", "fyne-theme-gnome-*.png") if err != nil { - return + return nil, err } - // return the text scaling factor - gnome.fontScaleFactor = float32(textScale) -} - -// applyFont gets the font name from gsettings and set the font size. This also calls -// setFont() to set the font. -func (gnome *GnomeTheme) applyFont(wg *sync.WaitGroup) { - - if wg != nil { - defer wg.Done() + // convert the svg to png, no background + cmd := exec.Command(convert, filename, "-background", "none", "-flatten", tmpfile.Name()) + log.Println("Converting", filename, "to", tmpfile.Name()) + err = cmd.Run() + if err != nil { + return nil, err } - gnome.font = ft.TextFont() - // call gsettings get org.gnome.desktop.interface font-name - cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "font-name") - out, err := cmd.CombinedOutput() + content, err := ioutil.ReadFile(tmpfile.Name()) if err != nil { - log.Println(err) - log.Println(string(out)) - return + return nil, err } - // try to get the font as a TTF file - fontFile := strings.TrimSpace(string(out)) - fontFile = strings.Trim(fontFile, "'") - // the fontFile string is in the format: Name size, eg: "Sans Bold 12", so get the size - parts := strings.Split(fontFile, " ") - fontSize := parts[len(parts)-1] - // convert the size to a float - size, err := strconv.ParseFloat(fontSize, 32) - // apply this to the fontScaleFactor - gnome.fontSize = float32(size) - // try to get the font as a TTF file - gnome.setFont(strings.Join(parts[:len(parts)-1], " ")) + return fyne.NewStaticResource(filename, content), nil } -func (gnome *GnomeTheme) calculateVariant() { - // using the bgColor, detect if the theme is dark or light - r, g, b, _ := gnome.bgColor.RGBA() - - brightness := (r/255*299 + g/255*587 + b/255*114) / 1000 - if brightness > 125 { - gnome.variant = ft.VariantLight - } else { - gnome.variant = ft.VariantDark - } +func (gnome *GnomeTheme) decodeTheme(gtkVersion int, variant fyne.ThemeVariant) { + // make things faster in concurrent mode + wg := sync.WaitGroup{} + wg.Add(4) + go gnome.applyColors(gtkVersion, &wg) + go gnome.applyIcons(gtkVersion, &wg) + go gnome.applyFont(&wg) + go gnome.applyFontScale(&wg) + wg.Wait() } // getGTKVersion gets the available GTK version for the given theme. If the version cannot be @@ -597,32 +571,32 @@ func (gnome *GnomeTheme) getGTKVersion() int { return 3 // default, but that may be a false positive now } -// setFont sets the font for the theme - this method calls getFontPath() and converToTTF -// if needed. -func (gnome *GnomeTheme) setFont(fontname string) { - - fontpath, err := getFontPath(fontname) +func (gnome *GnomeTheme) getIconThemeName() string { + // call gsettings get org.gnome.desktop.interface icon-theme + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "icon-theme") + out, err := cmd.CombinedOutput() if err != nil { log.Println(err) - return + log.Println(string(out)) + return "" } + themename := strings.TrimSpace(string(out)) + themename = strings.Trim(themename, "'") + return themename +} - ext := filepath.Ext(fontpath) - if ext != ".ttf" { - font, err := converToTTF(fontpath) - if err != nil { - log.Println(err) - return - } - gnome.font = fyne.NewStaticResource(fontpath, font) - } else { - font, err := ioutil.ReadFile(fontpath) - if err != nil { - log.Println(err) - return - } - gnome.font = fyne.NewStaticResource(fontpath, font) +func (gnome *GnomeTheme) getTheme() string { + // call gsettings get org.gnome.desktop.interface gtk-theme + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "gtk-theme") + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err) + log.Println(string(out)) + return "" // default to Gtk 3 } + themename := strings.TrimSpace(string(out)) + themename = strings.Trim(themename, "'") + return themename } func (gnome *GnomeTheme) loadIcon(name string) (resource fyne.Resource) { @@ -664,59 +638,85 @@ func (gnome *GnomeTheme) loadIcon(name string) (resource fyne.Resource) { return } -func (gnome *GnomeTheme) convertSVGtoPNG(filename string) (fyne.Resource, error) { - // use "convert" from imageMagik to convert svg to png - - convert, err := exec.LookPath("convert") - if err != nil { - return nil, err +// parseColor converts a float32 array to color.Color. +func (*GnomeTheme) parseColor(col []float32) color.Color { + return color.RGBA{ + R: uint8(col[0] * 255), + G: uint8(col[1] * 255), + B: uint8(col[2] * 255), + A: uint8(col[3] * 255), } +} - tmpfile, err := ioutil.TempFile("", "fyne-theme-gnome-*.png") - if err != nil { - return nil, err - } +// setFont sets the font for the theme - this method calls getFontPath() and converToTTF +// if needed. +func (gnome *GnomeTheme) setFont(fontname string) { - // convert the svg to png, no background - cmd := exec.Command(convert, filename, "-background", "none", "-flatten", tmpfile.Name()) - log.Println("Converting", filename, "to", tmpfile.Name()) - err = cmd.Run() + fontpath, err := getFontPath(fontname) if err != nil { - return nil, err + log.Println(err) + return } - content, err := ioutil.ReadFile(tmpfile.Name()) - if err != nil { - return nil, err + ext := filepath.Ext(fontpath) + if ext != ".ttf" { + font, err := converToTTF(fontpath) + if err != nil { + log.Println(err) + return + } + gnome.font = fyne.NewStaticResource(fontpath, font) + } else { + font, err := ioutil.ReadFile(fontpath) + if err != nil { + log.Println(err) + return + } + gnome.font = fyne.NewStaticResource(fontpath, font) } - - return fyne.NewStaticResource(filename, content), nil } -func (gnome *GnomeTheme) getTheme() string { - // call gsettings get org.gnome.desktop.interface gtk-theme - cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "gtk-theme") - out, err := cmd.CombinedOutput() - if err != nil { - log.Println(err) - log.Println(string(out)) - return "" // default to Gtk 3 +// NewGnomeTheme returns a new Gnome theme based on the given gtk version. If gtkVersion is <= 0, +// the theme will try to determine the higher Gtk version available for the current GtkTheme. +func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { + gnome := &GnomeTheme{ + bgColor: ft.DefaultTheme().Color(ft.ColorNameBackground, ft.VariantDark), + fgColor: ft.DefaultTheme().Color(ft.ColorNameForeground, ft.VariantDark), + fontSize: ft.DefaultTheme().Size(ft.SizeNameText), + variant: ft.VariantDark, + iconCache: map[string]fyne.Resource{}, + icons: map[string]string{}, } - themename := strings.TrimSpace(string(out)) - themename = strings.Trim(themename, "'") - return themename -} -func (gnome *GnomeTheme) getIconThemeName() string { - // call gsettings get org.gnome.desktop.interface icon-theme - cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "icon-theme") - out, err := cmd.CombinedOutput() - if err != nil { - log.Println(err) - log.Println(string(out)) - return "" + if gtkVersion <= 0 { + // detect gtkVersion + gtkVersion = gnome.getGTKVersion() } - themename := strings.TrimSpace(string(out)) - themename = strings.Trim(themename, "'") - return themename + gnome.decodeTheme(gtkVersion, ft.VariantDark) + + for _, flag := range flags { + switch flag { + case GnomeFlagAutoReload: + go func() { + currentTheme := gnome.getTheme() + iconThem := gnome.getIconThemeName() + for { + current := fyne.CurrentApp().Settings().Theme() + if _, ok := current.(*GnomeTheme); !ok { + break + } + time.Sleep(1 * time.Second) + newTheme := gnome.getTheme() + newIconTheme := gnome.getIconThemeName() + if currentTheme != newTheme || iconThem != newIconTheme { + // ensure that the current theme is still a GnomeTheme + log.Println("Gnome or icon them changed, reloading theme") + fyne.CurrentApp().Settings().SetTheme(NewGnomeTheme(gtkVersion, flags...)) + break + } + } + }() + } + } + return gnome } diff --git a/theme/desktop/kde.go b/theme/desktop/kde.go index 146b341e..3b101838 100644 --- a/theme/desktop/kde.go +++ b/theme/desktop/kde.go @@ -26,19 +26,6 @@ type KDETheme struct { font fyne.Resource } -// NewKDETheme returns a new KDE theme. -func NewKDETheme() fyne.Theme { - kde := &KDETheme{ - variant: ft.VariantDark, - } - if err := kde.decodeTheme(); err != nil { - log.Println(err) - return ft.DefaultTheme() - } - - return kde -} - // Color returns the color for the specified name. // // Implements: fyne.Theme @@ -59,13 +46,6 @@ func (k *KDETheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Co return ft.DefaultTheme().Color(name, k.variant) } -// Icon returns the icon for the specified name. -// -// Implements: fyne.Theme -func (k *KDETheme) Icon(i fyne.ThemeIconName) fyne.Resource { - return ft.DefaultTheme().Icon(i) -} - // Font returns the font for the specified name. // // Implements: fyne.Theme @@ -76,6 +56,13 @@ func (k *KDETheme) Font(s fyne.TextStyle) fyne.Resource { return ft.DefaultTheme().Font(s) } +// Icon returns the icon for the specified name. +// +// Implements: fyne.Theme +func (k *KDETheme) Icon(i fyne.ThemeIconName) fyne.Resource { + return ft.DefaultTheme().Icon(i) +} + // Size returns the size of the font for the specified text style. // // Implements: fyne.Theme @@ -198,3 +185,16 @@ func (k *KDETheme) setFont() { } k.font = fyne.NewStaticResource(fontpath, font) } + +// NewKDETheme returns a new KDE theme. +func NewKDETheme() fyne.Theme { + kde := &KDETheme{ + variant: ft.VariantDark, + } + if err := kde.decodeTheme(); err != nil { + log.Println(err) + return ft.DefaultTheme() + } + + return kde +} From f2a1cb737bd940ce2771ef2e78b486c54680b6ef Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 17 May 2022 11:38:32 +0200 Subject: [PATCH 08/46] Optimize gjs script --- theme/desktop/gnome.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 84333ab4..33d31432 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -183,16 +183,16 @@ if (gtkVersion === 3) { } icons.forEach((name) => { - if (gtkVersion === 3) { - try { + try { + if (gtkVersion === 3) { const icon = iconTheme.lookup_icon(name, iconSize, 0); iconset[name] = icon.get_filename(); - } catch (e) { - iconset[name] = null; + } else { + const icon = iconTheme.lookup_icon(name, null, null, iconSize, null, 0); + iconset[name] = icon.file.get_path(); } - } else { - const icon = iconTheme.lookup_icon(name, null, null, iconSize, null, 0); - iconset[name] = icon.file.get_path(); + } catch (e) { + iconset[name] = null; } }); From ccf227e2d312b81e84d910db8f896794d1ac3a46 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 17 May 2022 11:59:10 +0200 Subject: [PATCH 09/46] Doc, reorder, needed defers... --- theme/desktop/gnome.go | 50 ++++++-------------- theme/desktop/kde.go | 3 ++ theme/desktop/utils.go | 103 +++++++++++++++++++++++++++-------------- 3 files changed, 87 insertions(+), 69 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 33d31432..f2ce2863 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -23,6 +23,8 @@ import ( type GnomeFlag uint8 const ( + // GnomeFlagAutoReload is a flag that indicates that the theme should be reloaded when + // the gtk theme or icon theme changes. GnomeFlagAutoReload GnomeFlag = iota ) @@ -279,6 +281,7 @@ func (g *GnomeTheme) Size(s fyne.ThemeSizeName) float32 { return ft.DefaultTheme().Size(s) } +// applyColors sets the colors for the Gnome theme. Colors are defined by a GJS script. func (gnome *GnomeTheme) applyColors(gtkVersion int, wg *sync.WaitGroup) { if wg != nil { @@ -410,6 +413,7 @@ func (gnome *GnomeTheme) applyFontScale(wg *sync.WaitGroup) { gnome.fontScaleFactor = float32(textScale) } +// applyIcons gets the icon theme from gsettings and call GJS script to get the icon set. func (gnome *GnomeTheme) applyIcons(gtkVersion int, wg *sync.WaitGroup) { if wg != nil { @@ -480,6 +484,7 @@ func (gnome *GnomeTheme) applyIcons(gtkVersion int, wg *sync.WaitGroup) { } } +// calculateVariant calculates the variant of the theme using the background color. func (gnome *GnomeTheme) calculateVariant() { // using the bgColor, detect if the theme is dark or light r, g, b, _ := gnome.bgColor.RGBA() @@ -492,35 +497,7 @@ func (gnome *GnomeTheme) calculateVariant() { } } -func (gnome *GnomeTheme) convertSVGtoPNG(filename string) (fyne.Resource, error) { - // use "convert" from imageMagik to convert svg to png - - convert, err := exec.LookPath("convert") - if err != nil { - return nil, err - } - - tmpfile, err := ioutil.TempFile("", "fyne-theme-gnome-*.png") - if err != nil { - return nil, err - } - - // convert the svg to png, no background - cmd := exec.Command(convert, filename, "-background", "none", "-flatten", tmpfile.Name()) - log.Println("Converting", filename, "to", tmpfile.Name()) - err = cmd.Run() - if err != nil { - return nil, err - } - - content, err := ioutil.ReadFile(tmpfile.Name()) - if err != nil { - return nil, err - } - - return fyne.NewStaticResource(filename, content), nil -} - +// decodeTheme decodes the theme from the gsettings and Gtk API. func (gnome *GnomeTheme) decodeTheme(gtkVersion int, variant fyne.ThemeVariant) { // make things faster in concurrent mode wg := sync.WaitGroup{} @@ -536,7 +513,7 @@ func (gnome *GnomeTheme) decodeTheme(gtkVersion int, variant fyne.ThemeVariant) // determine, it will return 3 wich is the most common used version. func (gnome *GnomeTheme) getGTKVersion() int { - themename := gnome.getTheme() + themename := gnome.getThemeName() if themename == "" { return 3 // default to 3 } @@ -571,6 +548,7 @@ func (gnome *GnomeTheme) getGTKVersion() int { return 3 // default, but that may be a false positive now } +// getIconThemeName return the current icon theme name. func (gnome *GnomeTheme) getIconThemeName() string { // call gsettings get org.gnome.desktop.interface icon-theme cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "icon-theme") @@ -585,20 +563,22 @@ func (gnome *GnomeTheme) getIconThemeName() string { return themename } -func (gnome *GnomeTheme) getTheme() string { +// getThemeName gets the current theme name. +func (gnome *GnomeTheme) getThemeName() string { // call gsettings get org.gnome.desktop.interface gtk-theme cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "gtk-theme") out, err := cmd.CombinedOutput() if err != nil { log.Println(err) log.Println(string(out)) - return "" // default to Gtk 3 + return "" } themename := strings.TrimSpace(string(out)) themename = strings.Trim(themename, "'") return themename } +// loadIcon loads the icon from gnome theme, if the icon was already loaded, so the cached version is returned. func (gnome *GnomeTheme) loadIcon(name string) (resource fyne.Resource) { var ok bool @@ -624,7 +604,7 @@ func (gnome *GnomeTheme) loadIcon(name string) (resource fyne.Resource) { if err != nil { // try to convert it to png with imageMagik log.Println("Cannot load file", filename, err, "try to convert") - resource, err = gnome.convertSVGtoPNG(filename) + resource, err = convertSVGtoPNG(filename) if err != nil { log.Println("Cannot convert file", filename, err) return @@ -698,7 +678,7 @@ func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { switch flag { case GnomeFlagAutoReload: go func() { - currentTheme := gnome.getTheme() + currentTheme := gnome.getThemeName() iconThem := gnome.getIconThemeName() for { current := fyne.CurrentApp().Settings().Theme() @@ -706,7 +686,7 @@ func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { break } time.Sleep(1 * time.Second) - newTheme := gnome.getTheme() + newTheme := gnome.getThemeName() newIconTheme := gnome.getIconThemeName() if currentTheme != newTheme || iconThem != newIconTheme { // ensure that the current theme is still a GnomeTheme diff --git a/theme/desktop/kde.go b/theme/desktop/kde.go index 3b101838..a859d466 100644 --- a/theme/desktop/kde.go +++ b/theme/desktop/kde.go @@ -82,6 +82,7 @@ func (k *KDETheme) decodeTheme() error { return nil } +// loadScheme loads the KDE theme from kdeglobals if it is found. func (k *KDETheme) loadScheme() error { // the theme name is declared in ~/.config/kdedefaults/kdeglobals // in the ini section [General] as "ColorScheme" entry @@ -132,6 +133,7 @@ func (k *KDETheme) loadScheme() error { return nil } +// parseColor parses a color from a string in form r,g,b or r,g,b,a. func (k *KDETheme) parseColor(col string) color.Color { // the color is in the form r,g,b, // we need to convert it to a color.Color @@ -151,6 +153,7 @@ func (k *KDETheme) parseColor(col string) color.Color { return color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)} } +// setFont sets the font for the theme. func (k *KDETheme) setFont() { if k.fontConfig == "" { diff --git a/theme/desktop/utils.go b/theme/desktop/utils.go index 3755651b..762d1c1a 100644 --- a/theme/desktop/utils.go +++ b/theme/desktop/utils.go @@ -9,8 +9,76 @@ import ( "path/filepath" "regexp" "strings" + + "fyne.io/fyne/v2" ) +// convertSVGtoPNG will convert a svg file to a png file using convert (imageMagik) if the +// binary exists. +func convertSVGtoPNG(filename string) (fyne.Resource, error) { + // use "convert" from imageMagik to convert svg to png + + convert, err := exec.LookPath("convert") + if err != nil { + return nil, err + } + + tmpfile, err := ioutil.TempFile("", "fyne-theme-gnome-*.png") + if err != nil { + return nil, err + } + + // convert the svg to png, no background + log.Println("Converting", filename, "to", tmpfile.Name()) + cmd := exec.Command(convert, filename, "-background", "none", "-flatten", tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + + err = cmd.Run() + if err != nil { + return nil, err + } + + content, err := ioutil.ReadFile(tmpfile.Name()) + if err != nil { + return nil, err + } + + return fyne.NewStaticResource(filename, content), nil +} + +// converToTTF will convert a font to a ttf file. This requires the fontforge package. +func converToTTF(fontpath string) ([]byte, error) { + + // check if fontforge is installed + fontforge, err := exec.LookPath("fontforge") + if err != nil { + return nil, err + } + + // convert the font to a ttf file + basename := filepath.Base(fontpath) + tempTTF := filepath.Join(os.TempDir(), "fyne-"+basename+".ttf") + + // Convert to TTF, this is the FF script to call + ffScript := `Open("%s");Generate("%s")` + script := fmt.Sprintf(ffScript, fontpath, tempTTF) + + // call fontforge + cmd := exec.Command(fontforge, "-c", script) + cmd.Env = append(cmd.Env, "FONTFORGE_LANGUAGE=ff") + + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(err) + log.Println(string(out)) + return nil, err + } + defer os.Remove(tempTTF) + + // read the temporary ttf file + return ioutil.ReadFile(tempTTF) +} + // getFontPath will detect the font path from the font name taken from gsettings. // As the font is not exactly the one that fc-match can find, we need to do some // extra work to rebuild the name with style. @@ -60,7 +128,7 @@ func getFontPath(fontname string) (string, error) { } // we can now search - // fc-math ... "Font Name:Font Style + // fc-match ... "Font Name:Font Style var fontpath string cmd = exec.Command(fcMatch, "-f", "%{file}", fontname+":"+strings.Join(fontstyle, " ")) out, err = cmd.CombinedOutput() @@ -75,36 +143,3 @@ func getFontPath(fontname string) (string, error) { fontpath = strings.TrimSpace(fontpath) return fontpath, nil } - -// converToTTF will convert a font to a ttf file. This requires the fontforge package. -func converToTTF(fontpath string) ([]byte, error) { - - // check if fontforge is installed - fontforge, err := exec.LookPath("fontforge") - if err != nil { - return nil, err - } - - // convert the font to a ttf file - basename := filepath.Base(fontpath) - tempTTF := filepath.Join(os.TempDir(), "fyne-"+basename+".ttf") - - // Convert to TTF, this is the FF script to call - ffScript := `Open("%s");Generate("%s")` - script := fmt.Sprintf(ffScript, fontpath, tempTTF) - - // call fontforge - cmd := exec.Command(fontforge, "-c", script) - cmd.Env = append(cmd.Env, "FONTFORGE_LANGUAGE=ff") - - out, err := cmd.CombinedOutput() - if err != nil { - log.Println(err) - log.Println(string(out)) - return nil, err - } - defer os.Remove(tempTTF) - - // read the temporary ttf file - return ioutil.ReadFile(tempTTF) -} From b6c3d5c166c16809c28b5096af13715657dd08ee Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 17 May 2022 16:14:34 +0200 Subject: [PATCH 10/46] Detect theme changes with dbus --- go.mod | 1 + theme/desktop/gnome.go | 54 +++++++++++++++++++++++++++--------------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index e8834a43..4c037249 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( fyne.io/fyne/v2 v2.2.4 github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 github.com/eclipse/paho.mqtt.golang v1.3.5 + github.com/godbus/dbus/v5 v5.1.0 github.com/gorilla/websocket v1.4.2 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/stretchr/testify v1.7.2 diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index f2ce2863..6cd8b115 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -13,10 +13,10 @@ import ( "strconv" "strings" "sync" - "time" "fyne.io/fyne/v2" ft "fyne.io/fyne/v2/theme" + "github.com/godbus/dbus/v5" "github.com/srwiley/oksvg" ) @@ -497,8 +497,8 @@ func (gnome *GnomeTheme) calculateVariant() { } } -// decodeTheme decodes the theme from the gsettings and Gtk API. -func (gnome *GnomeTheme) decodeTheme(gtkVersion int, variant fyne.ThemeVariant) { +// findThemeInformation decodes the theme from the gsettings and Gtk API. +func (gnome *GnomeTheme) findThemeInformation(gtkVersion int, variant fyne.ThemeVariant) { // make things faster in concurrent mode wg := sync.WaitGroup{} wg.Add(4) @@ -672,27 +672,43 @@ func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { // detect gtkVersion gtkVersion = gnome.getGTKVersion() } - gnome.decodeTheme(gtkVersion, ft.VariantDark) + gnome.findThemeInformation(gtkVersion, ft.VariantDark) for _, flag := range flags { switch flag { case GnomeFlagAutoReload: go func() { - currentTheme := gnome.getThemeName() - iconThem := gnome.getIconThemeName() - for { - current := fyne.CurrentApp().Settings().Theme() - if _, ok := current.(*GnomeTheme); !ok { - break - } - time.Sleep(1 * time.Second) - newTheme := gnome.getThemeName() - newIconTheme := gnome.getIconThemeName() - if currentTheme != newTheme || iconThem != newIconTheme { - // ensure that the current theme is still a GnomeTheme - log.Println("Gnome or icon them changed, reloading theme") - fyne.CurrentApp().Settings().SetTheme(NewGnomeTheme(gtkVersion, flags...)) - break + // connect to dbus to detect theme/icon them changes + conn, err := dbus.SessionBus() + if err != nil { + log.Println(err) + return + } + if err := conn.AddMatchSignal( + dbus.WithMatchObjectPath("/org/freedesktop/portal/desktop"), + dbus.WithMatchInterface("org.freedesktop.portal.Settings"), + dbus.WithMatchMember("SettingChanged"), + ); err != nil { + log.Println(err) + return + } + defer conn.Close() + c := make(chan *dbus.Signal, 1) + conn.Signal(c) + + // wait for theme change event + sig := <-c + + // break if the current theme is not typed as GnomeTheme + currentTheme := fyne.CurrentApp().Settings().Theme() + if _, ok := currentTheme.(*GnomeTheme); !ok { + return + } + + for _, v := range sig.Body { + if v == "gtk-theme" || v == "icon-theme" { + go fyne.CurrentApp().Settings().SetTheme(NewGnomeTheme(gtkVersion, flags...)) + return } } }() From 76850a910910d4d3c532e492534a2f9a37839d4a Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 17 May 2022 18:27:26 +0200 Subject: [PATCH 11/46] fixed some color and add examples --- theme/desktop/gnome.go | 9 +++++---- theme/desktop/gnome_test.go | 21 +++++++++++++++++++++ theme/desktopEnvironment_test.go | 12 ++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 theme/desktop/gnome_test.go create mode 100644 theme/desktopEnvironment_test.go diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 6cd8b115..3fa3c065 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -231,11 +231,11 @@ func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) co switch name { case ft.ColorNameBackground: - return gnome.viewBgColor + return gnome.bgColor case ft.ColorNameForeground: return gnome.fgColor case ft.ColorNameButton, ft.ColorNameInputBackground: - return gnome.bgColor + return gnome.viewBgColor case ft.ColorNamePrimary: return gnome.accentColor case ft.ColorNameError: @@ -693,10 +693,11 @@ func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { return } defer conn.Close() - c := make(chan *dbus.Signal, 1) + c := make(chan *dbus.Signal, 5) conn.Signal(c) - // wait for theme change event + // wait for theme change event, only once because we will create + // a new theme instance anyway. sig := <-c // break if the current theme is not typed as GnomeTheme diff --git a/theme/desktop/gnome_test.go b/theme/desktop/gnome_test.go new file mode 100644 index 00000000..f5689d71 --- /dev/null +++ b/theme/desktop/gnome_test.go @@ -0,0 +1,21 @@ +package desktop + +import "fyne.io/fyne/v2/app" + +func ExampleNewGnomeTheme() { + app := app.New() + app.Settings().SetTheme(NewGnomeTheme(0)) +} + +// Force GTK version to 3 +func ExampleNewGnomeTheme_forceGtkVersion() { + app := app.New() + app.Settings().SetTheme(NewGnomeTheme(3)) +} + +// Will reload theme when it changes in Gnome (or other GTK environment) +// connecting to DBus signal. +func ExampleNewGnomeTheme_autoReload() { + app := app.New() + app.Settings().SetTheme(NewGnomeTheme(0, GnomeFlagAutoReload)) +} diff --git a/theme/desktopEnvironment_test.go b/theme/desktopEnvironment_test.go new file mode 100644 index 00000000..1e570a94 --- /dev/null +++ b/theme/desktopEnvironment_test.go @@ -0,0 +1,12 @@ +package theme + +import ( + "fyne.io/fyne/v2/app" +) + +// ExampleFromDesktopEnvironment_simple demonstrates how to use the FromDesktopEnvironment function. +func ExampleFromDesktopEnvironment_simple() { + app := app.New() + theme := FromDesktopEnvironment() + app.Settings().SetTheme(theme) +} From 4e4d801367b1e8c3dcd9536b86ccdd1b84735b0a Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 17 May 2022 19:16:24 +0200 Subject: [PATCH 12/46] Make color selection easier to tweak --- theme/desktop/gnome.go | 201 +++++++++++++---------------------------- 1 file changed, 64 insertions(+), 137 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 3fa3c065..5c4b0b6f 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -29,7 +29,7 @@ const ( ) // mapping to gnome/gtk icon names. -var gnomeIconMaps = map[fyne.ThemeIconName]string{ +var gnomeIconMap = map[fyne.ThemeIconName]string{ ft.IconNameInfo: "dialog-information", ft.IconNameError: "dialog-error", ft.IconNameQuestion: "dialog-question", @@ -74,8 +74,19 @@ var gnomeIconMaps = map[fyne.ThemeIconName]string{ ft.IconNameColorAchromatic: "color-picker-grey", } +// Map Fyne colorname to Adwaita/GTK color names +// See https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/named-colors.html +var gnomeColorMap = map[fyne.ThemeColorName]string{ + ft.ColorNameBackground: "theme_bg_color,window_bg_color", + ft.ColorNameForeground: "theme_text_color,view_fg_color", + ft.ColorNameButton: "theme_base_color,window_bg_color", + ft.ColorNameInputBackground: "theme_base_color,window_bg_color", + ft.ColorNamePrimary: "accent_color,success_color", + ft.ColorNameError: "error_color", +} + // Script to get the colors from the Gnome GTK/Adwaita theme. -const gjsScript = ` +const gjsColorScript = ` let gtkVersion = Number(ARGV[0] || 4); imports.gi.versions.Gtk = gtkVersion + ".0"; @@ -86,83 +97,28 @@ if (gtkVersion === 3) { Gtk.init(); } -const colors = { - viewbg: [], - viewfg: [], - background: [], - foreground: [], - borders: [], - successColor: [], - warningColor: [], - errorColor: [], - accentColor: [], - card_bg_color: [], -}; - +const colors = {}; const win = new Gtk.Window(); const ctx = win.get_style_context(); - -let [ok, bg] = [false, null]; - -[ok, bg] = ctx.lookup_color("theme_base_color"); -if (!ok) { - [ok, bg] = ctx.lookup_color("view_bg_color"); -} -colors.viewbg = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("theme_text_color"); -if (!ok) { - [ok, bg] = ctx.lookup_color("view_fg_color"); -} -colors.viewfg = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("theme_bg_color"); -if (!ok) { - [ok, bg] = ctx.lookup_color("window_bg_color"); -} -colors.background = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("theme_fg_color"); -if (!ok) { - [ok, bg] = ctx.lookup_color("window_fg_color"); -} -colors.foreground = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("borders"); -if (!ok) { - [ok, bg] = ctx.lookup_color("unfocused_borders"); -} -colors.borders = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("success_bg_color"); -if (!ok) { - [ok, bg] = ctx.lookup_color("success_color"); -} -colors.successColor = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("warning_color"); -colors.warningColor = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("error_color"); -colors.errorColor = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("accent_color"); -if (!ok) { - [ok, bg] = ctx.lookup_color("success_color"); -} -colors.accentColor = [bg.red, bg.green, bg.blue, bg.alpha]; - -[ok, bg] = ctx.lookup_color("card_bg_color"); -if (!ok) { - bg = colors.background; +const colorMap = %s; + +for (let col in colorMap) { + let [ok, bg] = [false, null]; + let found = false; + colorMap[col].split(",").forEach((fetch) => { + [ok, bg] = ctx.lookup_color(fetch); + if (ok && !found) { + found = true; + colors[col] = [bg.red, bg.green, bg.blue, bg.alpha]; + } + }); } -colors.card_bg_color = [bg.red, bg.blue, bg.green, bg.alpha]; print(JSON.stringify(colors)); ` -// script to get icons from theme. -const gjsIcons = ` +// Script to get icons from theme. +const gjsIconsScript = ` let gtkVersion = Number(ARGV[0] || 4); imports.gi.versions.Gtk = gtkVersion + ".0"; const iconSize = 32; // can be 8, 16, 24, 32, 48, 64, 96 @@ -204,45 +160,29 @@ print(JSON.stringify(iconset)); // GnomeTheme theme, based on the Gnome desktop manager. This theme uses GJS and gsettings to get // the colors and font from the Gnome desktop. type GnomeTheme struct { - bgColor color.Color - fgColor color.Color - viewBgColor color.Color - viewFgColor color.Color - cardBgColor color.Color - borderColor color.Color - successColor color.Color - warningColor color.Color - errorColor color.Color - accentColor color.Color - - icons map[string]string + colors map[fyne.ThemeColorName]color.Color + icons map[string]string fontScaleFactor float32 font fyne.Resource fontSize float32 - variant fyne.ThemeVariant + variant *fyne.ThemeVariant iconCache map[string]fyne.Resource } // Color returns the color for the given color name // // Implements: fyne.Theme -func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color { - - switch name { - case ft.ColorNameBackground: - return gnome.bgColor - case ft.ColorNameForeground: - return gnome.fgColor - case ft.ColorNameButton, ft.ColorNameInputBackground: - return gnome.viewBgColor - case ft.ColorNamePrimary: - return gnome.accentColor - case ft.ColorNameError: - return gnome.errorColor - default: - return ft.DefaultTheme().Color(name, gnome.variant) +func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color { + if col, ok := gnome.colors[name]; ok { + return col + } + + if gnome.variant == nil { + return ft.DefaultTheme().Color(name, *gnome.variant) } + + return ft.DefaultTheme().Color(name, variant) } // Font returns the font for the given name. @@ -260,7 +200,7 @@ func (gnome *GnomeTheme) Font(s fyne.TextStyle) fyne.Resource { // Implements: fyne.Theme func (gnome *GnomeTheme) Icon(i fyne.ThemeIconName) fyne.Resource { - if icon, found := gnomeIconMaps[i]; found { + if icon, found := gnomeIconMap[i]; found { if resource := gnome.loadIcon(icon); resource != nil { return resource } @@ -302,8 +242,16 @@ func (gnome *GnomeTheme) applyColors(gtkVersion int, wg *sync.WaitGroup) { } defer os.Remove(f.Name()) + // generate the js object from gnomeColorMap + colormap := "{\n" + for col, fetch := range gnomeColorMap { + colormap += fmt.Sprintf(` "%s": "%s",`+"\n", col, fetch) + } + colormap += "}" + // write the script to the temp file - _, err = f.WriteString(gjsScript) + script := fmt.Sprintf(gjsColorScript, colormap) + _, err = f.WriteString(script) if err != nil { log.Println(err) return @@ -320,36 +268,17 @@ func (gnome *GnomeTheme) applyColors(gtkVersion int, wg *sync.WaitGroup) { return } - // decode json to apply to the gnome theme - colors := struct { - WindowBGcolor []float32 `json:"background,-"` - WindowFGcolor []float32 `json:"foreground,-"` - ViewBGcolor []float32 `json:"viewbg,-"` - ViewFGcolor []float32 `json:"viewfg,-"` - CardBGColor []float32 `json:"card_bg_color,-"` - Borders []float32 `json:"borders,-"` - SuccessColor []float32 `json:"successColor,-"` - WarningColor []float32 `json:"warningColor,-"` - ErrorColor []float32 `json:"errorColor,-"` - AccentColor []float32 `json:"accentColor,-"` - }{} + // decode json + var colors map[fyne.ThemeColorName][]float32 err = json.Unmarshal(out, &colors) if err != nil { - log.Println(err) + log.Println("gjs error:", err, string(out)) return } - - // convert the colors to fyne colors - gnome.bgColor = gnome.parseColor(colors.WindowBGcolor) - gnome.fgColor = gnome.parseColor(colors.WindowFGcolor) - gnome.borderColor = gnome.parseColor(colors.Borders) - gnome.viewBgColor = gnome.parseColor(colors.ViewBGcolor) - gnome.viewFgColor = gnome.parseColor(colors.ViewFGcolor) - gnome.cardBgColor = gnome.parseColor(colors.CardBGColor) - gnome.successColor = gnome.parseColor(colors.SuccessColor) - gnome.warningColor = gnome.parseColor(colors.WarningColor) - gnome.errorColor = gnome.parseColor(colors.ErrorColor) - gnome.accentColor = gnome.parseColor(colors.AccentColor) + for name, rgba := range colors { + // convert string arry to colors + gnome.colors[name] = gnome.parseColor(rgba) + } gnome.calculateVariant() @@ -427,7 +356,7 @@ func (gnome *GnomeTheme) applyIcons(gtkVersion int, wg *sync.WaitGroup) { } // create the list of icon to get var icons []string - for _, icon := range gnomeIconMaps { + for _, icon := range gnomeIconMap { icons = append(icons, icon) } iconSet := "[\n" @@ -436,7 +365,7 @@ func (gnome *GnomeTheme) applyIcons(gtkVersion int, wg *sync.WaitGroup) { } iconSet += "]" - gjsIconList := fmt.Sprintf(gjsIcons, iconSet) + gjsIconList := fmt.Sprintf(gjsIconsScript, iconSet) // write the script to a temp file f, err := ioutil.TempFile("", "fyne-theme-gnome-*.js") @@ -486,14 +415,14 @@ func (gnome *GnomeTheme) applyIcons(gtkVersion int, wg *sync.WaitGroup) { // calculateVariant calculates the variant of the theme using the background color. func (gnome *GnomeTheme) calculateVariant() { - // using the bgColor, detect if the theme is dark or light - r, g, b, _ := gnome.bgColor.RGBA() + r, g, b, _ := gnome.Color(ft.ColorNameBackground, 0).RGBA() brightness := (r/255*299 + g/255*587 + b/255*114) / 1000 + gnome.variant = new(fyne.ThemeVariant) if brightness > 125 { - gnome.variant = ft.VariantLight + *gnome.variant = ft.VariantLight } else { - gnome.variant = ft.VariantDark + *gnome.variant = ft.VariantDark } } @@ -660,12 +589,10 @@ func (gnome *GnomeTheme) setFont(fontname string) { // the theme will try to determine the higher Gtk version available for the current GtkTheme. func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { gnome := &GnomeTheme{ - bgColor: ft.DefaultTheme().Color(ft.ColorNameBackground, ft.VariantDark), - fgColor: ft.DefaultTheme().Color(ft.ColorNameForeground, ft.VariantDark), fontSize: ft.DefaultTheme().Size(ft.SizeNameText), - variant: ft.VariantDark, iconCache: map[string]fyne.Resource{}, icons: map[string]string{}, + colors: map[fyne.ThemeColorName]color.Color{}, } if gtkVersion <= 0 { From a0004f4da397f0dbe3474f7d39167506868a63ab Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Wed, 18 May 2022 07:50:58 +0200 Subject: [PATCH 13/46] React on settings also --- theme/desktop/gnome.go | 46 ++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 5c4b0b6f..9bc8062f 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -605,6 +605,11 @@ func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { switch flag { case GnomeFlagAutoReload: go func() { + // connect to setting changes to not reload the theme if the new selected is + // not a gnome theme + settingChan := make(chan fyne.Settings) + fyne.CurrentApp().Settings().AddChangeListener(settingChan) + // connect to dbus to detect theme/icon them changes conn, err := dbus.SessionBus() if err != nil { @@ -620,23 +625,30 @@ func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { return } defer conn.Close() - c := make(chan *dbus.Signal, 5) - conn.Signal(c) - - // wait for theme change event, only once because we will create - // a new theme instance anyway. - sig := <-c - - // break if the current theme is not typed as GnomeTheme - currentTheme := fyne.CurrentApp().Settings().Theme() - if _, ok := currentTheme.(*GnomeTheme); !ok { - return - } - - for _, v := range sig.Body { - if v == "gtk-theme" || v == "icon-theme" { - go fyne.CurrentApp().Settings().SetTheme(NewGnomeTheme(gtkVersion, flags...)) - return + dbusChan := make(chan *dbus.Signal, 5) + conn.Signal(dbusChan) + + for { + select { + case sig := <-dbusChan: + // break if the current theme is not typed as GnomeTheme + currentTheme := fyne.CurrentApp().Settings().Theme() + if _, ok := currentTheme.(*GnomeTheme); !ok { + return + } + // reload the theme if the changed setting is the Gtk theme + for _, v := range sig.Body { + switch v { + case "gtk-theme", "icon-theme", "text-scaling-factor", "font-name": + go fyne.CurrentApp().Settings().SetTheme(NewGnomeTheme(gtkVersion, flags...)) + return + } + } + case s := <-settingChan: + // leave the loop if the new theme is not a Gnome theme + if _, isGnome := s.Theme().(*GnomeTheme); !isGnome { + return + } } } }() From 6817a400a1c5657d909fd729ff9891dccc6fd438 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Wed, 18 May 2022 08:33:32 +0200 Subject: [PATCH 14/46] Improve image convertion This now can use inkscape or convert. Also, fixed the name provided to the resource to leave Fyne knowing the correct mimetype. --- theme/desktop/gnome.go | 4 ++-- theme/desktop/utils.go | 28 ++++++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 9bc8062f..34544046 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -532,10 +532,10 @@ func (gnome *GnomeTheme) loadIcon(name string) (resource fyne.Resource) { _, err := oksvg.ReadIconStream(buff) if err != nil { // try to convert it to png with imageMagik - log.Println("Cannot load file", filename, err, "try to convert") + log.Println("Cannot load file", filename, err, ", try to convert with tools") resource, err = convertSVGtoPNG(filename) if err != nil { - log.Println("Cannot convert file", filename, err) + log.Println("Cannot convert file", filename, ":", err) return } return diff --git a/theme/desktop/utils.go b/theme/desktop/utils.go index 762d1c1a..c1b9bd16 100644 --- a/theme/desktop/utils.go +++ b/theme/desktop/utils.go @@ -13,24 +13,32 @@ import ( "fyne.io/fyne/v2" ) -// convertSVGtoPNG will convert a svg file to a png file using convert (imageMagik) if the -// binary exists. +// convertSVGtoPNG will convert a SVG file to a PNG file. It will try to detect if some common tools to convert SVG exists, +// like inkscape or ImageMagik "convert". If not, the resource is not converted. func convertSVGtoPNG(filename string) (fyne.Resource, error) { - // use "convert" from imageMagik to convert svg to png - - convert, err := exec.LookPath("convert") + tmpfile, err := ioutil.TempFile("", "fyne-theme-gnome-*.png") if err != nil { return nil, err } - tmpfile, err := ioutil.TempFile("", "fyne-theme-gnome-*.png") - if err != nil { - return nil, err + pngConverterOptions := map[string][]string{ + "inkscape": {"--without-gui", "--export-to-png", tmpfile.Name(), "--export-background-opacity=0", filename}, + "convert": {"-background", "transparent", "-flatten", filename, tmpfile.Name()}, + } + + var commandName string + var opts []string + for binary, options := range pngConverterOptions { + if path, err := exec.LookPath(binary); err == nil { + commandName = path + opts = options + break + } } // convert the svg to png, no background log.Println("Converting", filename, "to", tmpfile.Name()) - cmd := exec.Command(convert, filename, "-background", "none", "-flatten", tmpfile.Name()) + cmd := exec.Command(commandName, opts...) defer os.Remove(tmpfile.Name()) err = cmd.Run() @@ -43,7 +51,7 @@ func convertSVGtoPNG(filename string) (fyne.Resource, error) { return nil, err } - return fyne.NewStaticResource(filename, content), nil + return fyne.NewStaticResource(tmpfile.Name(), content), nil } // converToTTF will convert a font to a ttf file. This requires the fontforge package. From a60bfd7a428e35f662e184798b43fb2f1812aeb9 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Wed, 18 May 2022 08:37:02 +0200 Subject: [PATCH 15/46] The temp file were not removed in certain cases --- theme/desktop/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/desktop/utils.go b/theme/desktop/utils.go index c1b9bd16..03ddc5f2 100644 --- a/theme/desktop/utils.go +++ b/theme/desktop/utils.go @@ -20,6 +20,7 @@ func convertSVGtoPNG(filename string) (fyne.Resource, error) { if err != nil { return nil, err } + defer os.Remove(tmpfile.Name()) pngConverterOptions := map[string][]string{ "inkscape": {"--without-gui", "--export-to-png", tmpfile.Name(), "--export-background-opacity=0", filename}, @@ -39,7 +40,6 @@ func convertSVGtoPNG(filename string) (fyne.Resource, error) { // convert the svg to png, no background log.Println("Converting", filename, "to", tmpfile.Name()) cmd := exec.Command(commandName, opts...) - defer os.Remove(tmpfile.Name()) err = cmd.Run() if err != nil { From af8c864d89e4fd6dee059fcfe7d86caee06a615a Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Wed, 18 May 2022 08:42:51 +0200 Subject: [PATCH 16/46] Cleanup --- theme/desktop/gnome.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 34544046..9c3a8b13 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -199,13 +199,11 @@ func (gnome *GnomeTheme) Font(s fyne.TextStyle) fyne.Resource { // // Implements: fyne.Theme func (gnome *GnomeTheme) Icon(i fyne.ThemeIconName) fyne.Resource { - if icon, found := gnomeIconMap[i]; found { if resource := gnome.loadIcon(icon); resource != nil { return resource } } - //log.Println(i) return ft.DefaultTheme().Icon(i) } @@ -536,7 +534,6 @@ func (gnome *GnomeTheme) loadIcon(name string) (resource fyne.Resource) { resource, err = convertSVGtoPNG(filename) if err != nil { log.Println("Cannot convert file", filename, ":", err) - return } return } From d3c2689b214fb28f859b1a585bd34267bd2eef7c Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 24 May 2022 06:20:56 +0200 Subject: [PATCH 17/46] Add a demo for desktop integration --- cmd/desktop_demo/main.go | 69 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 cmd/desktop_demo/main.go diff --git a/cmd/desktop_demo/main.go b/cmd/desktop_demo/main.go new file mode 100644 index 00000000..bba1b962 --- /dev/null +++ b/cmd/desktop_demo/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + xtheme "fyne.io/x/fyne/theme" + "fyne.io/x/fyne/theme/desktop" +) + +func main() { + app := app.New() + app.Settings().SetTheme(xtheme.FromDesktopEnvironment()) + win := app.NewWindow("Desktop integration demo") + win.Resize(fyne.NewSize(550, 390)) + win.CenterOnScreen() + + entry := widget.NewEntry() + entry.SetPlaceHolder("Type something here") + win.SetContent(container.NewBorder( + nil, + container.NewHBox( + widget.NewButtonWithIcon("Home icon button", theme.HomeIcon(), nil), + widget.NewButtonWithIcon("Info icon button", theme.InfoIcon(), nil), + widget.NewButtonWithIcon("Example file dialog", theme.FolderIcon(), func() { + dialog.ShowFileSave(func(fyne.URIWriteCloser, error) {}, win) + }), + ), + nil, + nil, + container.NewVBox( + createExplanationLabel(app), + entry, + ), + )) + + win.ShowAndRun() +} + +func createExplanationLabel(app fyne.App) fyne.CanvasObject { + + var current string + + switch app.Settings().Theme().(type) { + case *desktop.GnomeTheme: + current = "Gnome / GTK" + case *desktop.KDETheme: + current = "KDE / Plasma" + default: + current = "This window manager is not supported for now" + } + + text := "Current theme: " + current + "\n" + text += ` + +This window should be styled to look like a desktop application. It works with GTK/Gnome based desktops and KDE/Plasma at this time +For the others desktops, the application will look like a normal window with default theme. + +You may try to change icon theme or GTK/KDE theme in your desktop settings, as font, font scaling... + +Note that you need to have fontforge package to make Fyne able to convert non-ttf fonts to ttf. +` + label := widget.NewLabel(text) + label.Wrapping = fyne.TextWrapWord + return label +} From 50967b59d6431dbdfb89d4935cd856c24780b181 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 24 May 2022 06:44:45 +0200 Subject: [PATCH 18/46] Add invert mode Some GTK application uses "view" inside window that are actually what we get with `view_bg_color`. This can be useful to use this color mode if we have a "framed" mode of views (work in progress). --- theme/desktop/gnome.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 9c3a8b13..c899d1d7 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -79,8 +79,8 @@ var gnomeIconMap = map[fyne.ThemeIconName]string{ var gnomeColorMap = map[fyne.ThemeColorName]string{ ft.ColorNameBackground: "theme_bg_color,window_bg_color", ft.ColorNameForeground: "theme_text_color,view_fg_color", - ft.ColorNameButton: "theme_base_color,window_bg_color", - ft.ColorNameInputBackground: "theme_base_color,window_bg_color", + ft.ColorNameButton: "theme_base_color,view_bg_color", + ft.ColorNameInputBackground: "theme_base_color,view_bg_color", ft.ColorNamePrimary: "accent_color,success_color", ft.ColorNameError: "error_color", } @@ -207,6 +207,18 @@ func (gnome *GnomeTheme) Icon(i fyne.ThemeIconName) fyne.Resource { return ft.DefaultTheme().Icon(i) } +// Invert is a specific Gnome/GTK option to invert the theme color for background of window and some input +// widget. This to help to imitate some GTK application with "views" inside the window. +func (gnome *GnomeTheme) Invert() { + + gnome.colors[ft.ColorNameBackground], + gnome.colors[ft.ColorNameInputBackground], + gnome.colors[ft.ColorNameButton] = + gnome.colors[ft.ColorNameButton], + gnome.colors[ft.ColorNameBackground], + gnome.colors[ft.ColorNameBackground] +} + // Size returns the size for the given name. It will scale the detected Gnome font size // by the Gnome font factor. // From bd8aef8f50597ac1c1808daace5f2e882e05e0f9 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 24 May 2022 06:47:28 +0200 Subject: [PATCH 19/46] Add more widgets to play --- cmd/desktop_demo/main.go | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/cmd/desktop_demo/main.go b/cmd/desktop_demo/main.go index bba1b962..a49ef6b3 100644 --- a/cmd/desktop_demo/main.go +++ b/cmd/desktop_demo/main.go @@ -18,8 +18,31 @@ func main() { win.Resize(fyne.NewSize(550, 390)) win.CenterOnScreen() + invertButton := widget.NewButton("Invert Gnome theme", func() { + if t, ok := app.Settings().Theme().(*desktop.GnomeTheme); ok { + t.Invert() + win.Content().Refresh() + } + }) + if _, ok := app.Settings().Theme().(*desktop.GnomeTheme); !ok { + invertButton.Disable() + invertButton.SetText("Invert only works on Gnome/GTK") + } + + var switched bool + switchThemeButton := widget.NewButton("Switch theme", func() { + + if switched { + app.Settings().SetTheme(xtheme.FromDesktopEnvironment()) + } else { + app.Settings().SetTheme(theme.DefaultTheme()) + } + switched = !switched + win.Content().Refresh() + }) + entry := widget.NewEntry() - entry.SetPlaceHolder("Type something here") + entry.SetPlaceHolder("Example of text entry...") win.SetContent(container.NewBorder( nil, container.NewHBox( @@ -28,12 +51,15 @@ func main() { widget.NewButtonWithIcon("Example file dialog", theme.FolderIcon(), func() { dialog.ShowFileSave(func(fyne.URIWriteCloser, error) {}, win) }), + invertButton, ), nil, nil, container.NewVBox( createExplanationLabel(app), entry, + widget.NewLabel("Try to switch theme"), + switchThemeButton, ), )) @@ -53,7 +79,7 @@ func createExplanationLabel(app fyne.App) fyne.CanvasObject { current = "This window manager is not supported for now" } - text := "Current theme: " + current + "\n" + text := "Current Desktop: " + current + "\n" text += ` This window should be styled to look like a desktop application. It works with GTK/Gnome based desktops and KDE/Plasma at this time From 8f1ac473ffd1f80e125465aad47ef1abe597912d Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 24 May 2022 06:50:03 +0200 Subject: [PATCH 20/46] Add a bit more doc --- cmd/desktop_demo/main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/desktop_demo/main.go b/cmd/desktop_demo/main.go index a49ef6b3..9ab81480 100644 --- a/cmd/desktop_demo/main.go +++ b/cmd/desktop_demo/main.go @@ -18,12 +18,15 @@ func main() { win.Resize(fyne.NewSize(550, 390)) win.CenterOnScreen() + // Gnome/GTK theme invertion invertButton := widget.NewButton("Invert Gnome theme", func() { if t, ok := app.Settings().Theme().(*desktop.GnomeTheme); ok { t.Invert() win.Content().Refresh() } }) + + // the invertButton can only work on Gnome / GTK theme. if _, ok := app.Settings().Theme().(*desktop.GnomeTheme); !ok { invertButton.Disable() invertButton.SetText("Invert only works on Gnome/GTK") @@ -31,7 +34,6 @@ func main() { var switched bool switchThemeButton := widget.NewButton("Switch theme", func() { - if switched { app.Settings().SetTheme(xtheme.FromDesktopEnvironment()) } else { From 24c19916dc7af0ec9987a0b816d169a5c949e20e Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 24 May 2022 06:51:49 +0200 Subject: [PATCH 21/46] Prefer alias for fyne-x --- cmd/desktop_demo/main.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/desktop_demo/main.go b/cmd/desktop_demo/main.go index 9ab81480..facd1bf2 100644 --- a/cmd/desktop_demo/main.go +++ b/cmd/desktop_demo/main.go @@ -8,7 +8,7 @@ import ( "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" xtheme "fyne.io/x/fyne/theme" - "fyne.io/x/fyne/theme/desktop" + xdesktop "fyne.io/x/fyne/theme/desktop" ) func main() { @@ -20,14 +20,14 @@ func main() { // Gnome/GTK theme invertion invertButton := widget.NewButton("Invert Gnome theme", func() { - if t, ok := app.Settings().Theme().(*desktop.GnomeTheme); ok { + if t, ok := app.Settings().Theme().(*xdesktop.GnomeTheme); ok { t.Invert() win.Content().Refresh() } }) // the invertButton can only work on Gnome / GTK theme. - if _, ok := app.Settings().Theme().(*desktop.GnomeTheme); !ok { + if _, ok := app.Settings().Theme().(*xdesktop.GnomeTheme); !ok { invertButton.Disable() invertButton.SetText("Invert only works on Gnome/GTK") } @@ -73,9 +73,9 @@ func createExplanationLabel(app fyne.App) fyne.CanvasObject { var current string switch app.Settings().Theme().(type) { - case *desktop.GnomeTheme: + case *xdesktop.GnomeTheme: current = "Gnome / GTK" - case *desktop.KDETheme: + case *xdesktop.KDETheme: current = "KDE / Plasma" default: current = "This window manager is not supported for now" From 9dcbcdd99d6818a6eaa3151b32d06777162c439a Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Sun, 6 Nov 2022 11:54:28 +0100 Subject: [PATCH 22/46] Get theme variant from Gnome 42 interface --- theme/desktop/gnome.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index c899d1d7..07ac1f01 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -425,10 +425,25 @@ func (gnome *GnomeTheme) applyIcons(gtkVersion int, wg *sync.WaitGroup) { // calculateVariant calculates the variant of the theme using the background color. func (gnome *GnomeTheme) calculateVariant() { + + // fetch org.gnome.desktop.interface color-scheme 'prefer-dark' or 'prefer-light' from gsettings + cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "color-scheme") + out, err := cmd.CombinedOutput() + gnome.variant = new(fyne.ThemeVariant) + if err == nil { + if strings.Contains(string(out), "prefer-dark") { + *gnome.variant = ft.VariantDark + return + } + if strings.Contains(string(out), "prefer-light") { + *gnome.variant = ft.VariantLight + return + } + } + r, g, b, _ := gnome.Color(ft.ColorNameBackground, 0).RGBA() brightness := (r/255*299 + g/255*587 + b/255*114) / 1000 - gnome.variant = new(fyne.ThemeVariant) if brightness > 125 { *gnome.variant = ft.VariantLight } else { From 8f098e99eb0dda1a1966d4b9ed4c7f5db6c04cba Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Sun, 6 Nov 2022 15:57:00 +0100 Subject: [PATCH 23/46] Adaptation to use Gnome 42 --- theme/desktop/gnome.go | 45 +++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 07ac1f01..d85fc591 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -431,16 +431,24 @@ func (gnome *GnomeTheme) calculateVariant() { out, err := cmd.CombinedOutput() gnome.variant = new(fyne.ThemeVariant) if err == nil { - if strings.Contains(string(out), "prefer-dark") { + w := strings.TrimSpace(string(out)) + w = strings.Trim(w, "'") + switch w { + case "prefer-dark": *gnome.variant = ft.VariantDark return - } - if strings.Contains(string(out), "prefer-light") { + case "prefer-light": + *gnome.variant = ft.VariantLight + return + case "default": *gnome.variant = ft.VariantLight return } } + // Here, we will try to calculate the variant from the background color + // This is not perfect, but it works in most cases. + // For Gnome < 42 r, g, b, _ := gnome.Color(ft.ColorNameBackground, 0).RGBA() brightness := (r/255*299 + g/255*587 + b/255*114) / 1000 @@ -465,18 +473,35 @@ func (gnome *GnomeTheme) findThemeInformation(gtkVersion int, variant fyne.Theme // getGTKVersion gets the available GTK version for the given theme. If the version cannot be // determine, it will return 3 wich is the most common used version. -func (gnome *GnomeTheme) getGTKVersion() int { +func (gnome *GnomeTheme) getGTKVersion() (version int) { + + defer func() { + log.Println("Detected version", version) + }() + version = 3 + + // get the gnome version + cmd := exec.Command("gnome-shell", "--version") + out, err := cmd.CombinedOutput() + if err == nil { + w := strings.TrimSpace(string(out)) + w = strings.Trim(w, "'") + w = strings.ToLower(w) + if strings.Contains(w, "gnome shell 42") { + version = 4 + } + } themename := gnome.getThemeName() if themename == "" { - return 3 // default to 3 + return } // ok so now, find if the theme is gtk4, either fallback to gtk3 home, err := os.UserHomeDir() if err != nil { log.Println(err) - return 3 // default to Gtk 3 + return } possiblePaths := []string{ @@ -492,14 +517,16 @@ func (gnome *GnomeTheme) getGTKVersion() int { // now check if it is gtk4 compatible if _, err := os.Stat(path + "gtk-4.0/gtk.css"); err == nil { // it is gtk4 - return 4 + version = 3 + return } if _, err := os.Stat(path + "gtk-3.0/gtk.css"); err == nil { - return 3 + version = 3 + return } } } - return 3 // default, but that may be a false positive now + return // default, but that may be a false positive now } // getIconThemeName return the current icon theme name. From bdfeb3b7e8d5b8a363ddd58773b78c1ee46d0e7e Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Mon, 7 Nov 2022 09:10:24 +0100 Subject: [PATCH 24/46] Make some fixes for Adwaita and Gnome >= 42 --- theme/desktop/gnome.go | 74 ++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index d85fc591..dd759ab1 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -168,12 +168,21 @@ type GnomeTheme struct { fontSize float32 variant *fyne.ThemeVariant iconCache map[string]fyne.Resource + + versionNumber int + themeName string } // Color returns the color for the given color name // // Implements: fyne.Theme func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color { + + // Sepcial case for Adwaita on Gnome ><42 -> theme is light or dark, variant correct + if gnome.version() >= 42 && gnome.themeName == "Adwaita" { + return ft.DefaultTheme().Color(name, *gnome.variant) + } + if col, ok := gnome.colors[name]; ok { return col } @@ -434,14 +443,11 @@ func (gnome *GnomeTheme) calculateVariant() { w := strings.TrimSpace(string(out)) w = strings.Trim(w, "'") switch w { - case "prefer-dark": - *gnome.variant = ft.VariantDark - return - case "prefer-light": + case "prefer-light", "default": *gnome.variant = ft.VariantLight return - case "default": - *gnome.variant = ft.VariantLight + case "prefer-dark": + *gnome.variant = ft.VariantDark return } } @@ -462,6 +468,12 @@ func (gnome *GnomeTheme) calculateVariant() { // findThemeInformation decodes the theme from the gsettings and Gtk API. func (gnome *GnomeTheme) findThemeInformation(gtkVersion int, variant fyne.ThemeVariant) { // make things faster in concurrent mode + + themename := gnome.getThemeName() + if themename == "" { + return + } + gnome.themeName = themename wg := sync.WaitGroup{} wg.Add(4) go gnome.applyColors(gtkVersion, &wg) @@ -471,31 +483,41 @@ func (gnome *GnomeTheme) findThemeInformation(gtkVersion int, variant fyne.Theme wg.Wait() } -// getGTKVersion gets the available GTK version for the given theme. If the version cannot be -// determine, it will return 3 wich is the most common used version. -func (gnome *GnomeTheme) getGTKVersion() (version int) { +func (gnome *GnomeTheme) version() int { - defer func() { - log.Println("Detected version", version) - }() - version = 3 + if gnome.versionNumber != 0 { + return gnome.versionNumber + } - // get the gnome version cmd := exec.Command("gnome-shell", "--version") out, err := cmd.CombinedOutput() + version := 40 if err == nil { w := strings.TrimSpace(string(out)) w = strings.Trim(w, "'") w = strings.ToLower(w) - if strings.Contains(w, "gnome shell 42") { - version = 4 + versionNumberParts := strings.Split(w, " ") + if len(versionNumberParts) > 1 { + versionNumber := versionNumberParts[len(versionNumberParts)-1] + releaseParts := strings.Split(versionNumber, ".") + version, err = strconv.Atoi(releaseParts[0]) + if err != nil { + version = 40 // fallback + } } + } else { + log.Println("gnome-shell version not found, fallback to 40", err) + version = 40 // fallback } + gnome.versionNumber = version // int will truncate the float + return gnome.version() +} - themename := gnome.getThemeName() - if themename == "" { - return - } +// getGTKVersion gets the available GTK version for the given theme. If the version cannot be +// determine, it will return 3 wich is the most common used version. +func (gnome *GnomeTheme) getGTKVersion() (version int) { + + version = 3 // ok so now, find if the theme is gtk4, either fallback to gtk3 home, err := os.UserHomeDir() @@ -512,7 +534,7 @@ func (gnome *GnomeTheme) getGTKVersion() (version int) { } for _, path := range possiblePaths { - path = filepath.Join(path, themename) + path = filepath.Join(path, gnome.themeName) if _, err := os.Stat(path); err == nil { // now check if it is gtk4 compatible if _, err := os.Stat(path + "gtk-4.0/gtk.css"); err == nil { @@ -652,6 +674,8 @@ func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { } gnome.findThemeInformation(gtkVersion, ft.VariantDark) + interfaceName := "org.freedesktop.portal.Settings" + for _, flag := range flags { switch flag { case GnomeFlagAutoReload: @@ -669,14 +693,14 @@ func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { } if err := conn.AddMatchSignal( dbus.WithMatchObjectPath("/org/freedesktop/portal/desktop"), - dbus.WithMatchInterface("org.freedesktop.portal.Settings"), + dbus.WithMatchInterface(interfaceName), dbus.WithMatchMember("SettingChanged"), ); err != nil { log.Println(err) return } defer conn.Close() - dbusChan := make(chan *dbus.Signal, 5) + dbusChan := make(chan *dbus.Signal, 10) conn.Signal(dbusChan) for { @@ -690,8 +714,8 @@ func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { // reload the theme if the changed setting is the Gtk theme for _, v := range sig.Body { switch v { - case "gtk-theme", "icon-theme", "text-scaling-factor", "font-name": - go fyne.CurrentApp().Settings().SetTheme(NewGnomeTheme(gtkVersion, flags...)) + case "gtk-theme", "icon-theme", "text-scaling-factor", "font-name", "color-scheme": + fyne.CurrentApp().Settings().SetTheme(NewGnomeTheme(gtkVersion, flags...)) return } } From 3c32385fea28f98c13a06aa1053d517802dc9e03 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Thu, 10 Nov 2022 13:08:09 +0100 Subject: [PATCH 25/46] Fix Adwaita usage We need to find "Adwaita" in the name, not the exact string. For window decoration, it is mandatory for user to set legacy application theme in gnome tweak tools. --- theme/desktop/gnome.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index dd759ab1..c3c9baf1 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -179,7 +179,7 @@ type GnomeTheme struct { func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color { // Sepcial case for Adwaita on Gnome ><42 -> theme is light or dark, variant correct - if gnome.version() >= 42 && gnome.themeName == "Adwaita" { + if gnome.version() >= 42 && strings.HasPrefix(gnome.themeName, "Adwaita") { return ft.DefaultTheme().Color(name, *gnome.variant) } @@ -507,7 +507,6 @@ func (gnome *GnomeTheme) version() int { } } else { log.Println("gnome-shell version not found, fallback to 40", err) - version = 40 // fallback } gnome.versionNumber = version // int will truncate the float return gnome.version() @@ -598,6 +597,7 @@ func (gnome *GnomeTheme) loadIcon(name string) (resource fyne.Resource) { if filename, ok := gnome.icons[name]; ok { content, err := ioutil.ReadFile(filename) if err != nil { + log.Println("Error while loading icon", err) return } if strings.HasSuffix(filename, ".svg") { From 6cba31627bbf1596ed10a0558880fc3f31471fcd Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 15 Nov 2022 18:17:30 +0100 Subject: [PATCH 26/46] Make the scale factor to be applied for everything --- theme/desktop/gnome.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index c3c9baf1..2d1de482 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -237,7 +237,7 @@ func (g *GnomeTheme) Size(s fyne.ThemeSizeName) float32 { case ft.SizeNameText: return g.fontScaleFactor * g.fontSize } - return ft.DefaultTheme().Size(s) + return ft.DefaultTheme().Size(s) * g.fontScaleFactor } // applyColors sets the colors for the Gnome theme. Colors are defined by a GJS script. From de3d6d41813031de806814b5e94d24ba78fbd1c7 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 15 Nov 2022 18:18:18 +0100 Subject: [PATCH 27/46] Add a message in the console for convertion tools --- theme/desktop/utils.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/theme/desktop/utils.go b/theme/desktop/utils.go index 03ddc5f2..c271f770 100644 --- a/theme/desktop/utils.go +++ b/theme/desktop/utils.go @@ -1,6 +1,7 @@ package desktop import ( + "errors" "fmt" "io/ioutil" "log" @@ -37,6 +38,10 @@ func convertSVGtoPNG(filename string) (fyne.Resource, error) { } } + if commandName == "" { + return nil, errors.New("You must install inkscape or imageMagik (convert command) to be able to convert SVG icons to PNG.") + } + // convert the svg to png, no background log.Println("Converting", filename, "to", tmpfile.Name()) cmd := exec.Command(commandName, opts...) From 40626dcadfde037c321bcc78e724739f1dd4c751 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 15 Nov 2022 19:08:16 +0100 Subject: [PATCH 28/46] USe 96x96 icon size to avoid aliasing --- theme/desktop/gnome.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 2d1de482..04583057 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -121,7 +121,7 @@ print(JSON.stringify(colors)); const gjsIconsScript = ` let gtkVersion = Number(ARGV[0] || 4); imports.gi.versions.Gtk = gtkVersion + ".0"; -const iconSize = 32; // can be 8, 16, 24, 32, 48, 64, 96 +const iconSize = 96; // can be 8, 16, 24, 32, 48, 64, 96 const { Gtk, Gdk } = imports.gi; if (gtkVersion === 3) { @@ -605,7 +605,7 @@ func (gnome *GnomeTheme) loadIcon(name string) (resource fyne.Resource) { buff := bytes.NewBuffer(content) _, err := oksvg.ReadIconStream(buff) if err != nil { - // try to convert it to png with imageMagik + // try to convert it to png with a converter log.Println("Cannot load file", filename, err, ", try to convert with tools") resource, err = convertSVGtoPNG(filename) if err != nil { From a11355c3ebd6bea945e6e71615cb39c6f07e0fd4 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 15 Nov 2022 19:09:31 +0100 Subject: [PATCH 29/46] Changed the inkscape command line to the newest --- theme/desktop/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/desktop/utils.go b/theme/desktop/utils.go index c271f770..eaed9bcc 100644 --- a/theme/desktop/utils.go +++ b/theme/desktop/utils.go @@ -24,7 +24,7 @@ func convertSVGtoPNG(filename string) (fyne.Resource, error) { defer os.Remove(tmpfile.Name()) pngConverterOptions := map[string][]string{ - "inkscape": {"--without-gui", "--export-to-png", tmpfile.Name(), "--export-background-opacity=0", filename}, + "inkscape": {"--without-gui", "--export-type=png", "--export-background-opacity=0", filename, "-o", tmpfile.Name()}, "convert": {"-background", "transparent", "-flatten", filename, tmpfile.Name()}, } From 58ddbd9b75ec6e3371b16e839aa0fc3f2b794a4d Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Wed, 16 Nov 2022 07:57:24 +0100 Subject: [PATCH 30/46] Fix go.mod after rebasing --- go.mod | 1 + 1 file changed, 1 insertion(+) diff --git a/go.mod b/go.mod index 4c037249..882a4183 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/godbus/dbus/v5 v5.1.0 github.com/gorilla/websocket v1.4.2 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 + github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 github.com/stretchr/testify v1.7.2 github.com/wagslane/go-password-validator v0.3.0 golang.org/x/image v0.0.0-20220601225756-64ec528b34cd From 95c01c9ad28b2a50c3ffab047cd172ccba814016 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Wed, 16 Nov 2022 16:41:07 +0100 Subject: [PATCH 31/46] wrong varname it's not font scale, it's scale --- theme/desktop/gnome.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 04583057..a185eb4a 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -163,11 +163,11 @@ type GnomeTheme struct { colors map[fyne.ThemeColorName]color.Color icons map[string]string - fontScaleFactor float32 - font fyne.Resource - fontSize float32 - variant *fyne.ThemeVariant - iconCache map[string]fyne.Resource + scaleFactor float32 + font fyne.Resource + fontSize float32 + variant *fyne.ThemeVariant + iconCache map[string]fyne.Resource versionNumber int themeName string @@ -235,9 +235,9 @@ func (gnome *GnomeTheme) Invert() { func (g *GnomeTheme) Size(s fyne.ThemeSizeName) float32 { switch s { case ft.SizeNameText: - return g.fontScaleFactor * g.fontSize + return g.scaleFactor * g.fontSize } - return ft.DefaultTheme().Size(s) * g.fontScaleFactor + return ft.DefaultTheme().Size(s) * g.scaleFactor } // applyColors sets the colors for the Gnome theme. Colors are defined by a GJS script. @@ -341,7 +341,7 @@ func (gnome *GnomeTheme) applyFontScale(wg *sync.WaitGroup) { defer wg.Done() } // for any error below, we will use the default - gnome.fontScaleFactor = 1 + gnome.scaleFactor = 1 // call gsettings get org.gnome.desktop.interface text-scaling-factor cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor") @@ -352,13 +352,13 @@ func (gnome *GnomeTheme) applyFontScale(wg *sync.WaitGroup) { // get the text scaling factor ts := strings.TrimSpace(string(out)) - textScale, err := strconv.ParseFloat(ts, 32) + scaleValue, err := strconv.ParseFloat(ts, 32) if err != nil { return } // return the text scaling factor - gnome.fontScaleFactor = float32(textScale) + gnome.scaleFactor = float32(scaleValue) } // applyIcons gets the icon theme from gsettings and call GJS script to get the icon set. From a5cc1f48ea30f851eb817c0d381bc9ac0b810770 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Fri, 18 Nov 2022 15:02:15 +0100 Subject: [PATCH 32/46] Fix tests and doc... --- theme/desktop/doc.go | 22 ++++++++- theme/desktop/gnome_test.go | 19 +++++++- theme/desktop/kde_test.go | 42 +++++++++++++++++ theme/desktopEnvironment_test.go | 80 ++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 theme/desktop/kde_test.go diff --git a/theme/desktop/doc.go b/theme/desktop/doc.go index bf6fee25..f0b73936 100644 --- a/theme/desktop/doc.go +++ b/theme/desktop/doc.go @@ -1,7 +1,25 @@ -// desktop package provides theme for desktop environment like Gnome, KDE, Plasma... +// desktop package provides theme for Linux (for now) desktop environment like Gnome, KDE, Plasma... +// // To be fully used, the system need to have gsettings and gjs for all GTK/Gnome based desktop. // KDE/Plasma theme only works when the user has already initialize a session to create ~/.config/kdeglobals // -// For all desktop, we also need fontconfig package installed to have "fc-match" and "fontconfif" commands. +// The package will try to use fontconfig ("fc-match" and "fontconfig" commands). // This is not required but recommended to be able to generate TTF font if the user as configure a non TTF font. +// +// The package also tries to use "inkscape" or "convert" command (from ImageMagick) to generate SVG icons when they +// cannot be parsed by Fyne (it happens when oksvg package fails to load icons). +// +// Some recent desktop environment now use Adwaita as default theme. If this theme is applied, the desktop package +// loads the default Fyne theme colors. It only try to change the scaling factor, font and icons of the applications. +// +// The easiest way to use this package is to call the FromDesktopEnvironment function from "theme" package. +// +// Example: +// +// app := app.New() +// theme := FromDesktopEnvironment() +// app.Settings().SetTheme(theme) +// +// This loads the theme from the current detected desktop environment. For Windows and MacOS, and mobile devices +// it will return the default Fyne theme. package desktop diff --git a/theme/desktop/gnome_test.go b/theme/desktop/gnome_test.go index f5689d71..560fdf86 100644 --- a/theme/desktop/gnome_test.go +++ b/theme/desktop/gnome_test.go @@ -1,6 +1,13 @@ package desktop -import "fyne.io/fyne/v2/app" +import ( + "testing" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/test" + "fyne.io/fyne/v2/widget" +) func ExampleNewGnomeTheme() { app := app.New() @@ -19,3 +26,13 @@ func ExampleNewGnomeTheme_autoReload() { app := app.New() app.Settings().SetTheme(NewGnomeTheme(0, GnomeFlagAutoReload)) } + +// Check if the GnomeTheme can be loaded. +func TestGnomeTheme(t *testing.T) { + app := test.NewApp() + app.Settings().SetTheme(NewGnomeTheme(0)) + win := app.NewWindow("Test") + defer win.Close() + win.Resize(fyne.NewSize(200, 200)) + win.SetContent(widget.NewLabel("Hello")) +} diff --git a/theme/desktop/kde_test.go b/theme/desktop/kde_test.go new file mode 100644 index 00000000..8573e940 --- /dev/null +++ b/theme/desktop/kde_test.go @@ -0,0 +1,42 @@ +package desktop + +import ( + "os" + "testing" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/test" + "fyne.io/fyne/v2/widget" +) + +func setup() (tmp, home string) { + // create a false home directory + var err error + tmp, err = os.MkdirTemp("", "fyne-test-") + if err != nil { + panic(err) + } + home = os.Getenv("HOME") + os.Setenv("HOME", tmp) + + // creat a false KDE configuration + os.MkdirAll(tmp+"/.config", 0755) + os.WriteFile(tmp+"/.config/kdeglobals", []byte("[General]\nwidgetStyle=GTK"), 0644) + + return +} + +func teardown(tmp, home string) { + os.RemoveAll(tmp) + os.Setenv("HOME", home) +} +func TestKDETheme(t *testing.T) { + tmp, home := setup() + defer teardown(tmp, home) + app := test.NewApp() + app.Settings().SetTheme(NewKDETheme()) + win := app.NewWindow("Test") + defer win.Close() + win.Resize(fyne.NewSize(200, 200)) + win.SetContent(widget.NewLabel("Hello")) +} diff --git a/theme/desktopEnvironment_test.go b/theme/desktopEnvironment_test.go index 1e570a94..78ab1a85 100644 --- a/theme/desktopEnvironment_test.go +++ b/theme/desktopEnvironment_test.go @@ -1,12 +1,92 @@ package theme import ( + "os" + "testing" + + "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/test" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "fyne.io/x/fyne/theme/desktop" ) +func setup() (tmp, home string) { + // create a false home directory + var err error + tmp, err = os.MkdirTemp("", "fyne-test-") + if err != nil { + panic(err) + } + home = os.Getenv("HOME") + os.Setenv("HOME", tmp) + + // creat a false KDE configuration + os.MkdirAll(tmp+"/.config", 0755) + os.WriteFile(tmp+"/.config/kdeglobals", []byte("[General]\nwidgetStyle=GTK"), 0644) + + return +} + +func teardown(tmp, home string) { + os.Unsetenv("XDG_CURRENT_DESKTOP") + os.RemoveAll(tmp) + os.Setenv("HOME", home) +} + // ExampleFromDesktopEnvironment_simple demonstrates how to use the FromDesktopEnvironment function. func ExampleFromDesktopEnvironment_simple() { app := app.New() theme := FromDesktopEnvironment() app.Settings().SetTheme(theme) } + +// Test to load from desktop environment. +func TestLoadFromEnvironment(t *testing.T) { + tmp, home := setup() + defer teardown(tmp, home) + + // Set XDG_CURRENT_DESKTOP to "GNOME" + envs := []string{"GNOME", "KDE", "FAKE"} + for _, env := range envs { + // chante desktop environment + os.Setenv("XDG_CURRENT_DESKTOP", env) + app := test.NewApp() + app.Settings().SetTheme(FromDesktopEnvironment()) + win := app.NewWindow("Test") + defer win.Close() + win.Resize(fyne.NewSize(200, 200)) + win.SetContent(widget.NewLabel("Hello")) + + // check if the theme is loaded + current := app.Settings().Theme() + // Check if the type of the theme is correct + if current == nil { + t.Error("Theme is nil") + } + switch env { + case "GNOME": + switch v := current.(type) { + case *desktop.GnomeTheme: + // OK + default: + t.Error("Theme is not GnomeTheme") + t.Logf("Theme is %T\n", v) + } + case "KDE": + switch v := current.(type) { + case *desktop.KDETheme: + // OK + default: + t.Error("Theme is not KDETheme") + t.Logf("Theme is %T\n", v) + } + case "FAKE": + if current != theme.DefaultTheme() { + t.Error("Theme is not DefaultTheme") + } + } + + } +} From a0cceded55e2f3ae2eabdc71cad4f7deceae28c9 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Fri, 18 Nov 2022 15:08:22 +0100 Subject: [PATCH 33/46] io.WriteFile is not working on go 1.14 --- theme/desktop/kde_test.go | 9 ++++++--- theme/desktopEnvironment_test.go | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/theme/desktop/kde_test.go b/theme/desktop/kde_test.go index 8573e940..568e5b48 100644 --- a/theme/desktop/kde_test.go +++ b/theme/desktop/kde_test.go @@ -1,6 +1,7 @@ package desktop import ( + "io/ioutil" "os" "testing" @@ -20,9 +21,11 @@ func setup() (tmp, home string) { os.Setenv("HOME", tmp) // creat a false KDE configuration - os.MkdirAll(tmp+"/.config", 0755) - os.WriteFile(tmp+"/.config/kdeglobals", []byte("[General]\nwidgetStyle=GTK"), 0644) - + if err = os.MkdirAll(tmp+"/.config", 0755); err != nil { + panic(err) + } + content := []byte("[General]\nwidgetStyle=GTK") + ioutil.WriteFile(tmp+"/.config/kdeglobals", content, 0644) return } diff --git a/theme/desktopEnvironment_test.go b/theme/desktopEnvironment_test.go index 78ab1a85..0afdef37 100644 --- a/theme/desktopEnvironment_test.go +++ b/theme/desktopEnvironment_test.go @@ -1,6 +1,7 @@ package theme import ( + "io/ioutil" "os" "testing" @@ -23,8 +24,11 @@ func setup() (tmp, home string) { os.Setenv("HOME", tmp) // creat a false KDE configuration - os.MkdirAll(tmp+"/.config", 0755) - os.WriteFile(tmp+"/.config/kdeglobals", []byte("[General]\nwidgetStyle=GTK"), 0644) + if err = os.MkdirAll(tmp+"/.config", 0755); err != nil { + panic(err) + } + content := []byte("[General]\nwidgetStyle=GTK") + ioutil.WriteFile(tmp+"/.config/kdeglobals", content, 0644) return } From 51a0d614b871c967fe41b67e85eef89deb1c48c0 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Fri, 18 Nov 2022 15:16:37 +0100 Subject: [PATCH 34/46] One more time, fix tests... --- theme/desktop/kde_test.go | 3 ++- theme/desktopEnvironment_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/theme/desktop/kde_test.go b/theme/desktop/kde_test.go index 568e5b48..a796fd7d 100644 --- a/theme/desktop/kde_test.go +++ b/theme/desktop/kde_test.go @@ -13,7 +13,7 @@ import ( func setup() (tmp, home string) { // create a false home directory var err error - tmp, err = os.MkdirTemp("", "fyne-test-") + tmp, err = ioutil.TempDir("", "fyne-test-") if err != nil { panic(err) } @@ -33,6 +33,7 @@ func teardown(tmp, home string) { os.RemoveAll(tmp) os.Setenv("HOME", home) } + func TestKDETheme(t *testing.T) { tmp, home := setup() defer teardown(tmp, home) diff --git a/theme/desktopEnvironment_test.go b/theme/desktopEnvironment_test.go index 0afdef37..8ed2a32d 100644 --- a/theme/desktopEnvironment_test.go +++ b/theme/desktopEnvironment_test.go @@ -16,7 +16,7 @@ import ( func setup() (tmp, home string) { // create a false home directory var err error - tmp, err = os.MkdirTemp("", "fyne-test-") + tmp, err = ioutil.TempDir("", "fyne-test-") if err != nil { panic(err) } From 10a34c29e7a1d13c72480d224a14b1cf4fbb7116 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Fri, 18 Nov 2022 15:26:22 +0100 Subject: [PATCH 35/46] Fix nil pointer + rename theme import --- theme/desktop/gnome.go | 147 +++++++++++++++++++++-------------------- 1 file changed, 74 insertions(+), 73 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index a185eb4a..d7d82164 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -15,7 +15,7 @@ import ( "sync" "fyne.io/fyne/v2" - ft "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/theme" "github.com/godbus/dbus/v5" "github.com/srwiley/oksvg" ) @@ -30,59 +30,59 @@ const ( // mapping to gnome/gtk icon names. var gnomeIconMap = map[fyne.ThemeIconName]string{ - ft.IconNameInfo: "dialog-information", - ft.IconNameError: "dialog-error", - ft.IconNameQuestion: "dialog-question", - - ft.IconNameFolder: "folder", - ft.IconNameFolderNew: "folder-new", - ft.IconNameFolderOpen: "folder-open", - ft.IconNameHome: "go-home", - ft.IconNameDownload: "download", - - ft.IconNameDocument: "document", - ft.IconNameFileImage: "image", - ft.IconNameFileApplication: "binary", - ft.IconNameFileText: "text", - ft.IconNameFileVideo: "video", - ft.IconNameFileAudio: "audio", - ft.IconNameComputer: "computer", - ft.IconNameMediaPhoto: "photo", - ft.IconNameMediaVideo: "video", - ft.IconNameMediaMusic: "music", - - ft.IconNameConfirm: "dialog-apply", - ft.IconNameCancel: "cancel", - - ft.IconNameCheckButton: "checkbox-symbolic", - ft.IconNameCheckButtonChecked: "checkbox-checked-symbolic", - ft.IconNameRadioButton: "radio-symbolic", - ft.IconNameRadioButtonChecked: "radio-checked-symbolic", - - ft.IconNameArrowDropDown: "arrow-down", - ft.IconNameArrowDropUp: "arrow-up", - ft.IconNameNavigateNext: "go-right", - ft.IconNameNavigateBack: "go-left", - ft.IconNameMoveDown: "go-down", - ft.IconNameMoveUp: "go-up", - ft.IconNameSettings: "document-properties", - ft.IconNameHistory: "history-view", - ft.IconNameList: "view-list", - ft.IconNameGrid: "view-grid", - ft.IconNameColorPalette: "color-select", - ft.IconNameColorChromatic: "color-select", - ft.IconNameColorAchromatic: "color-picker-grey", + theme.IconNameInfo: "dialog-information", + theme.IconNameError: "dialog-error", + theme.IconNameQuestion: "dialog-question", + + theme.IconNameFolder: "folder", + theme.IconNameFolderNew: "folder-new", + theme.IconNameFolderOpen: "folder-open", + theme.IconNameHome: "go-home", + theme.IconNameDownload: "download", + + theme.IconNameDocument: "document", + theme.IconNameFileImage: "image", + theme.IconNameFileApplication: "binary", + theme.IconNameFileText: "text", + theme.IconNameFileVideo: "video", + theme.IconNameFileAudio: "audio", + theme.IconNameComputer: "computer", + theme.IconNameMediaPhoto: "photo", + theme.IconNameMediaVideo: "video", + theme.IconNameMediaMusic: "music", + + theme.IconNameConfirm: "dialog-apply", + theme.IconNameCancel: "cancel", + + theme.IconNameCheckButton: "checkbox-symbolic", + theme.IconNameCheckButtonChecked: "checkbox-checked-symbolic", + theme.IconNameRadioButton: "radio-symbolic", + theme.IconNameRadioButtonChecked: "radio-checked-symbolic", + + theme.IconNameArrowDropDown: "arrow-down", + theme.IconNameArrowDropUp: "arrow-up", + theme.IconNameNavigateNext: "go-right", + theme.IconNameNavigateBack: "go-left", + theme.IconNameMoveDown: "go-down", + theme.IconNameMoveUp: "go-up", + theme.IconNameSettings: "document-properties", + theme.IconNameHistory: "history-view", + theme.IconNameList: "view-list", + theme.IconNameGrid: "view-grid", + theme.IconNameColorPalette: "color-select", + theme.IconNameColorChromatic: "color-select", + theme.IconNameColorAchromatic: "color-picker-grey", } // Map Fyne colorname to Adwaita/GTK color names // See https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/named-colors.html var gnomeColorMap = map[fyne.ThemeColorName]string{ - ft.ColorNameBackground: "theme_bg_color,window_bg_color", - ft.ColorNameForeground: "theme_text_color,view_fg_color", - ft.ColorNameButton: "theme_base_color,view_bg_color", - ft.ColorNameInputBackground: "theme_base_color,view_bg_color", - ft.ColorNamePrimary: "accent_color,success_color", - ft.ColorNameError: "error_color", + theme.ColorNameBackground: "theme_bg_color,window_bg_color", + theme.ColorNameForeground: "theme_text_color,view_fg_color", + theme.ColorNameButton: "theme_base_color,view_bg_color", + theme.ColorNameInputBackground: "theme_base_color,view_bg_color", + theme.ColorNamePrimary: "accent_color,success_color", + theme.ColorNameError: "error_color", } // Script to get the colors from the Gnome GTK/Adwaita theme. @@ -180,7 +180,7 @@ func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVaria // Sepcial case for Adwaita on Gnome ><42 -> theme is light or dark, variant correct if gnome.version() >= 42 && strings.HasPrefix(gnome.themeName, "Adwaita") { - return ft.DefaultTheme().Color(name, *gnome.variant) + return theme.DefaultTheme().Color(name, *gnome.variant) } if col, ok := gnome.colors[name]; ok { @@ -188,10 +188,10 @@ func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVaria } if gnome.variant == nil { - return ft.DefaultTheme().Color(name, *gnome.variant) + return theme.DefaultTheme().Color(name, *gnome.variant) } - return ft.DefaultTheme().Color(name, variant) + return theme.DefaultTheme().Color(name, variant) } // Font returns the font for the given name. @@ -199,7 +199,7 @@ func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVaria // Implements: fyne.Theme func (gnome *GnomeTheme) Font(s fyne.TextStyle) fyne.Resource { if gnome.font == nil { - return ft.DefaultTheme().Font(s) + return theme.DefaultTheme().Font(s) } return gnome.font } @@ -213,19 +213,19 @@ func (gnome *GnomeTheme) Icon(i fyne.ThemeIconName) fyne.Resource { return resource } } - return ft.DefaultTheme().Icon(i) + return theme.DefaultTheme().Icon(i) } // Invert is a specific Gnome/GTK option to invert the theme color for background of window and some input // widget. This to help to imitate some GTK application with "views" inside the window. func (gnome *GnomeTheme) Invert() { - gnome.colors[ft.ColorNameBackground], - gnome.colors[ft.ColorNameInputBackground], - gnome.colors[ft.ColorNameButton] = - gnome.colors[ft.ColorNameButton], - gnome.colors[ft.ColorNameBackground], - gnome.colors[ft.ColorNameBackground] + gnome.colors[theme.ColorNameBackground], + gnome.colors[theme.ColorNameInputBackground], + gnome.colors[theme.ColorNameButton] = + gnome.colors[theme.ColorNameButton], + gnome.colors[theme.ColorNameBackground], + gnome.colors[theme.ColorNameBackground] } // Size returns the size for the given name. It will scale the detected Gnome font size @@ -234,15 +234,16 @@ func (gnome *GnomeTheme) Invert() { // Implements: fyne.Theme func (g *GnomeTheme) Size(s fyne.ThemeSizeName) float32 { switch s { - case ft.SizeNameText: + case theme.SizeNameText: return g.scaleFactor * g.fontSize } - return ft.DefaultTheme().Size(s) * g.scaleFactor + return theme.DefaultTheme().Size(s) * g.scaleFactor } // applyColors sets the colors for the Gnome theme. Colors are defined by a GJS script. func (gnome *GnomeTheme) applyColors(gtkVersion int, wg *sync.WaitGroup) { + defer gnome.calculateVariant() if wg != nil { defer wg.Done() } @@ -299,8 +300,6 @@ func (gnome *GnomeTheme) applyColors(gtkVersion int, wg *sync.WaitGroup) { gnome.colors[name] = gnome.parseColor(rgba) } - gnome.calculateVariant() - } // applyFont gets the font name from gsettings and set the font size. This also calls @@ -311,7 +310,7 @@ func (gnome *GnomeTheme) applyFont(wg *sync.WaitGroup) { defer wg.Done() } - gnome.font = ft.TextFont() + gnome.font = theme.TextFont() // call gsettings get org.gnome.desktop.interface font-name cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "font-name") out, err := cmd.CombinedOutput() @@ -438,16 +437,15 @@ func (gnome *GnomeTheme) calculateVariant() { // fetch org.gnome.desktop.interface color-scheme 'prefer-dark' or 'prefer-light' from gsettings cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "color-scheme") out, err := cmd.CombinedOutput() - gnome.variant = new(fyne.ThemeVariant) if err == nil { w := strings.TrimSpace(string(out)) w = strings.Trim(w, "'") switch w { case "prefer-light", "default": - *gnome.variant = ft.VariantLight + *gnome.variant = theme.VariantLight return case "prefer-dark": - *gnome.variant = ft.VariantDark + *gnome.variant = theme.VariantDark return } } @@ -455,13 +453,13 @@ func (gnome *GnomeTheme) calculateVariant() { // Here, we will try to calculate the variant from the background color // This is not perfect, but it works in most cases. // For Gnome < 42 - r, g, b, _ := gnome.Color(ft.ColorNameBackground, 0).RGBA() + r, g, b, _ := gnome.Color(theme.ColorNameBackground, 0).RGBA() brightness := (r/255*299 + g/255*587 + b/255*114) / 1000 if brightness > 125 { - *gnome.variant = ft.VariantLight + *gnome.variant = theme.VariantLight } else { - *gnome.variant = ft.VariantDark + *gnome.variant = theme.VariantDark } } @@ -662,17 +660,20 @@ func (gnome *GnomeTheme) setFont(fontname string) { // the theme will try to determine the higher Gtk version available for the current GtkTheme. func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { gnome := &GnomeTheme{ - fontSize: ft.DefaultTheme().Size(ft.SizeNameText), + fontSize: theme.DefaultTheme().Size(theme.SizeNameText), iconCache: map[string]fyne.Resource{}, icons: map[string]string{}, colors: map[fyne.ThemeColorName]color.Color{}, + variant: new(fyne.ThemeVariant), } + *gnome.variant = theme.VariantDark + if gtkVersion <= 0 { // detect gtkVersion gtkVersion = gnome.getGTKVersion() } - gnome.findThemeInformation(gtkVersion, ft.VariantDark) + gnome.findThemeInformation(gtkVersion, theme.VariantDark) interfaceName := "org.freedesktop.portal.Settings" From 135828a0f16ca162a7b549f57de848d95d248752 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Fri, 18 Nov 2022 15:51:24 +0100 Subject: [PATCH 36/46] Restrict compilation OS --- theme/desktop/doc.go | 2 +- theme/desktop/gnome.go | 10 +++++++--- theme/desktop/gnome_test.go | 3 +++ theme/desktop/kde.go | 3 +++ theme/desktop/kde_test.go | 3 +++ theme/desktop/utils.go | 5 ++++- ...esktopEnvironment.go => desktopEnvironmentLinux.go} | 3 +++ theme/desktopEnvironmentWinAndDarwin.go | 10 ++++++++++ theme/desktopEnvironment_test.go | 3 +++ 9 files changed, 37 insertions(+), 5 deletions(-) rename theme/{desktopEnvironment.go => desktopEnvironmentLinux.go} (96%) create mode 100644 theme/desktopEnvironmentWinAndDarwin.go diff --git a/theme/desktop/doc.go b/theme/desktop/doc.go index f0b73936..c9110cd2 100644 --- a/theme/desktop/doc.go +++ b/theme/desktop/doc.go @@ -1,4 +1,4 @@ -// desktop package provides theme for Linux (for now) desktop environment like Gnome, KDE, Plasma... +// Package desktop provides theme for Linux (for now) desktop environment like Gnome, KDE, Plasma... // // To be fully used, the system need to have gsettings and gjs for all GTK/Gnome based desktop. // KDE/Plasma theme only works when the user has already initialize a session to create ~/.config/kdeglobals diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index d7d82164..53777c48 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package desktop import ( @@ -20,6 +23,7 @@ import ( "github.com/srwiley/oksvg" ) +// GnomeFlag provides options for the Gnome theme. See GnomeFlagAutoReload (the only one at this time). type GnomeFlag uint8 const ( @@ -232,12 +236,12 @@ func (gnome *GnomeTheme) Invert() { // by the Gnome font factor. // // Implements: fyne.Theme -func (g *GnomeTheme) Size(s fyne.ThemeSizeName) float32 { +func (gnome *GnomeTheme) Size(s fyne.ThemeSizeName) float32 { switch s { case theme.SizeNameText: - return g.scaleFactor * g.fontSize + return gnome.scaleFactor * gnome.fontSize } - return theme.DefaultTheme().Size(s) * g.scaleFactor + return theme.DefaultTheme().Size(s) * gnome.scaleFactor } // applyColors sets the colors for the Gnome theme. Colors are defined by a GJS script. diff --git a/theme/desktop/gnome_test.go b/theme/desktop/gnome_test.go index 560fdf86..43367340 100644 --- a/theme/desktop/gnome_test.go +++ b/theme/desktop/gnome_test.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package desktop import ( diff --git a/theme/desktop/kde.go b/theme/desktop/kde.go index a859d466..03458738 100644 --- a/theme/desktop/kde.go +++ b/theme/desktop/kde.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package desktop import ( diff --git a/theme/desktop/kde_test.go b/theme/desktop/kde_test.go index a796fd7d..f248ec1b 100644 --- a/theme/desktop/kde_test.go +++ b/theme/desktop/kde_test.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package desktop import ( diff --git a/theme/desktop/utils.go b/theme/desktop/utils.go index eaed9bcc..5e7f7e51 100644 --- a/theme/desktop/utils.go +++ b/theme/desktop/utils.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package desktop import ( @@ -39,7 +42,7 @@ func convertSVGtoPNG(filename string) (fyne.Resource, error) { } if commandName == "" { - return nil, errors.New("You must install inkscape or imageMagik (convert command) to be able to convert SVG icons to PNG.") + return nil, errors.New("you must install inkscape or imageMagik (convert command) to be able to convert SVG icons to PNG") } // convert the svg to png, no background diff --git a/theme/desktopEnvironment.go b/theme/desktopEnvironmentLinux.go similarity index 96% rename from theme/desktopEnvironment.go rename to theme/desktopEnvironmentLinux.go index 8fab16bb..4965312e 100644 --- a/theme/desktopEnvironment.go +++ b/theme/desktopEnvironmentLinux.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package theme import ( diff --git a/theme/desktopEnvironmentWinAndDarwin.go b/theme/desktopEnvironmentWinAndDarwin.go new file mode 100644 index 00000000..ba0d3289 --- /dev/null +++ b/theme/desktopEnvironmentWinAndDarwin.go @@ -0,0 +1,10 @@ +//go:build !linux +// +build !linux + +package theme + +import fynetheme "fyne.io/x/fyne/v2/theme" + +func FromDesktopEnvironment() fyne.Theme { + return fynetheme.DefaultTheme() +} diff --git a/theme/desktopEnvironment_test.go b/theme/desktopEnvironment_test.go index 8ed2a32d..454c4be8 100644 --- a/theme/desktopEnvironment_test.go +++ b/theme/desktopEnvironment_test.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package theme import ( From e26af0a6bcb3c58813b553dd59732adf886d0d11 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Fri, 18 Nov 2022 15:58:17 +0100 Subject: [PATCH 37/46] Fixed imports --- theme/desktopEnvironmentWinAndDarwin.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/theme/desktopEnvironmentWinAndDarwin.go b/theme/desktopEnvironmentWinAndDarwin.go index ba0d3289..120b4ea8 100644 --- a/theme/desktopEnvironmentWinAndDarwin.go +++ b/theme/desktopEnvironmentWinAndDarwin.go @@ -3,7 +3,10 @@ package theme -import fynetheme "fyne.io/x/fyne/v2/theme" +import ( + "fyne.io/fyne/v2" + fynetheme "fyne.io/fyne/v2/theme" +) func FromDesktopEnvironment() fyne.Theme { return fynetheme.DefaultTheme() From 759b8943ef9c178b762fdb418893d9c9e4d91976 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Fri, 18 Nov 2022 16:00:25 +0100 Subject: [PATCH 38/46] Restrict OS on Linux at this time --- cmd/desktop_demo/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/desktop_demo/main.go b/cmd/desktop_demo/main.go index facd1bf2..7f61aa53 100644 --- a/cmd/desktop_demo/main.go +++ b/cmd/desktop_demo/main.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package main import ( From 29373dc2fe63f469ed1590a98719dc700548bb71 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Fri, 18 Nov 2022 16:12:50 +0100 Subject: [PATCH 39/46] Fix OS restriction --- theme/desktop/gnome.go | 15 --------------- theme/desktop/gnomeNotLinux.go | 14 ++++++++++++++ theme/desktop/kdeNotLinux.go | 14 ++++++++++++++ ...AndDarwin.go => desktopEnvironmentNotLinux.go} | 0 4 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 theme/desktop/gnomeNotLinux.go create mode 100644 theme/desktop/kdeNotLinux.go rename theme/{desktopEnvironmentWinAndDarwin.go => desktopEnvironmentNotLinux.go} (100%) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 53777c48..02afeeb3 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -552,21 +552,6 @@ func (gnome *GnomeTheme) getGTKVersion() (version int) { return // default, but that may be a false positive now } -// getIconThemeName return the current icon theme name. -func (gnome *GnomeTheme) getIconThemeName() string { - // call gsettings get org.gnome.desktop.interface icon-theme - cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "icon-theme") - out, err := cmd.CombinedOutput() - if err != nil { - log.Println(err) - log.Println(string(out)) - return "" - } - themename := strings.TrimSpace(string(out)) - themename = strings.Trim(themename, "'") - return themename -} - // getThemeName gets the current theme name. func (gnome *GnomeTheme) getThemeName() string { // call gsettings get org.gnome.desktop.interface gtk-theme diff --git a/theme/desktop/gnomeNotLinux.go b/theme/desktop/gnomeNotLinux.go new file mode 100644 index 00000000..c8324254 --- /dev/null +++ b/theme/desktop/gnomeNotLinux.go @@ -0,0 +1,14 @@ +//go:build !linux +// +build !linux + +package desktop + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" +) + +// NewGnomeTheme returns the GNOME theme. If the current OS is not Linux, it returns the default theme. +func NewGnomeTheme() fyne.Theme { + return theme.DefaultTheme() +} diff --git a/theme/desktop/kdeNotLinux.go b/theme/desktop/kdeNotLinux.go new file mode 100644 index 00000000..3ecb4d9e --- /dev/null +++ b/theme/desktop/kdeNotLinux.go @@ -0,0 +1,14 @@ +//go:build !linux +// +build !linux + +package desktop + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" +) + +// NewKdeTheme returns the KDE theme. If the current OS is not Linux, it returns the default theme. +func NewKDETheme() fyne.Theme { + return theme.DefaultTheme() +} diff --git a/theme/desktopEnvironmentWinAndDarwin.go b/theme/desktopEnvironmentNotLinux.go similarity index 100% rename from theme/desktopEnvironmentWinAndDarwin.go rename to theme/desktopEnvironmentNotLinux.go From 2696bf306bc90163ed70a80155eebea8c66e810f Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Fri, 18 Nov 2022 16:22:03 +0100 Subject: [PATCH 40/46] Fix unused and not initialized variables --- theme/desktop/gnome.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 02afeeb3..a7633169 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -331,6 +331,10 @@ func (gnome *GnomeTheme) applyFont(wg *sync.WaitGroup) { fontSize := parts[len(parts)-1] // convert the size to a float size, err := strconv.ParseFloat(fontSize, 32) + if err != nil { + log.Println(err) + return + } // apply this to the fontScaleFactor gnome.fontSize = float32(size) @@ -649,11 +653,14 @@ func (gnome *GnomeTheme) setFont(fontname string) { // the theme will try to determine the higher Gtk version available for the current GtkTheme. func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { gnome := &GnomeTheme{ - fontSize: theme.DefaultTheme().Size(theme.SizeNameText), - iconCache: map[string]fyne.Resource{}, - icons: map[string]string{}, - colors: map[fyne.ThemeColorName]color.Color{}, - variant: new(fyne.ThemeVariant), + fontSize: theme.DefaultTheme().Size(theme.SizeNameText), + iconCache: map[string]fyne.Resource{}, + icons: map[string]string{}, + colors: map[fyne.ThemeColorName]color.Color{}, + variant: new(fyne.ThemeVariant), + font: theme.DefaultTextFont(), + scaleFactor: 1.0, + versionNumber: 40, } *gnome.variant = theme.VariantDark From 94b21de30d4bed1d9688068b2b1202124ace9fa2 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 22 Nov 2022 08:34:55 +0100 Subject: [PATCH 41/46] Simplify the integration, see details The variant should be calculated by the work made at https://github.com/fyne-io/fyne/pull/3414 So, we can now only get colors and icons from the applied theme in Gnome or KDE. --- theme/desktop/gnome.go | 46 ------------------------------------------ 1 file changed, 46 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index a7633169..1def2341 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -170,7 +170,6 @@ type GnomeTheme struct { scaleFactor float32 font fyne.Resource fontSize float32 - variant *fyne.ThemeVariant iconCache map[string]fyne.Resource versionNumber int @@ -182,19 +181,10 @@ type GnomeTheme struct { // Implements: fyne.Theme func (gnome *GnomeTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color { - // Sepcial case for Adwaita on Gnome ><42 -> theme is light or dark, variant correct - if gnome.version() >= 42 && strings.HasPrefix(gnome.themeName, "Adwaita") { - return theme.DefaultTheme().Color(name, *gnome.variant) - } - if col, ok := gnome.colors[name]; ok { return col } - if gnome.variant == nil { - return theme.DefaultTheme().Color(name, *gnome.variant) - } - return theme.DefaultTheme().Color(name, variant) } @@ -247,7 +237,6 @@ func (gnome *GnomeTheme) Size(s fyne.ThemeSizeName) float32 { // applyColors sets the colors for the Gnome theme. Colors are defined by a GJS script. func (gnome *GnomeTheme) applyColors(gtkVersion int, wg *sync.WaitGroup) { - defer gnome.calculateVariant() if wg != nil { defer wg.Done() } @@ -439,38 +428,6 @@ func (gnome *GnomeTheme) applyIcons(gtkVersion int, wg *sync.WaitGroup) { } } -// calculateVariant calculates the variant of the theme using the background color. -func (gnome *GnomeTheme) calculateVariant() { - - // fetch org.gnome.desktop.interface color-scheme 'prefer-dark' or 'prefer-light' from gsettings - cmd := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "color-scheme") - out, err := cmd.CombinedOutput() - if err == nil { - w := strings.TrimSpace(string(out)) - w = strings.Trim(w, "'") - switch w { - case "prefer-light", "default": - *gnome.variant = theme.VariantLight - return - case "prefer-dark": - *gnome.variant = theme.VariantDark - return - } - } - - // Here, we will try to calculate the variant from the background color - // This is not perfect, but it works in most cases. - // For Gnome < 42 - r, g, b, _ := gnome.Color(theme.ColorNameBackground, 0).RGBA() - - brightness := (r/255*299 + g/255*587 + b/255*114) / 1000 - if brightness > 125 { - *gnome.variant = theme.VariantLight - } else { - *gnome.variant = theme.VariantDark - } -} - // findThemeInformation decodes the theme from the gsettings and Gtk API. func (gnome *GnomeTheme) findThemeInformation(gtkVersion int, variant fyne.ThemeVariant) { // make things faster in concurrent mode @@ -657,14 +614,11 @@ func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { iconCache: map[string]fyne.Resource{}, icons: map[string]string{}, colors: map[fyne.ThemeColorName]color.Color{}, - variant: new(fyne.ThemeVariant), font: theme.DefaultTextFont(), scaleFactor: 1.0, versionNumber: 40, } - *gnome.variant = theme.VariantDark - if gtkVersion <= 0 { // detect gtkVersion gtkVersion = gnome.getGTKVersion() From 1a1c34fc80dbf99d363dfa19b370e7f63340e5cb Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 22 Nov 2022 08:51:11 +0100 Subject: [PATCH 42/46] Cleanup, no need to get Gnome Version --- go.mod | 1 - theme/desktop/gnome.go | 111 +++---------------------------- theme/desktopEnvironmentLinux.go | 2 +- 3 files changed, 9 insertions(+), 105 deletions(-) diff --git a/go.mod b/go.mod index 882a4183..b2efd0f8 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( fyne.io/fyne/v2 v2.2.4 github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 github.com/eclipse/paho.mqtt.golang v1.3.5 - github.com/godbus/dbus/v5 v5.1.0 github.com/gorilla/websocket v1.4.2 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 1def2341..9160849b 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -19,19 +19,9 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/theme" - "github.com/godbus/dbus/v5" "github.com/srwiley/oksvg" ) -// GnomeFlag provides options for the Gnome theme. See GnomeFlagAutoReload (the only one at this time). -type GnomeFlag uint8 - -const ( - // GnomeFlagAutoReload is a flag that indicates that the theme should be reloaded when - // the gtk theme or icon theme changes. - GnomeFlagAutoReload GnomeFlag = iota -) - // mapping to gnome/gtk icon names. var gnomeIconMap = map[fyne.ThemeIconName]string{ theme.IconNameInfo: "dialog-information", @@ -446,35 +436,6 @@ func (gnome *GnomeTheme) findThemeInformation(gtkVersion int, variant fyne.Theme wg.Wait() } -func (gnome *GnomeTheme) version() int { - - if gnome.versionNumber != 0 { - return gnome.versionNumber - } - - cmd := exec.Command("gnome-shell", "--version") - out, err := cmd.CombinedOutput() - version := 40 - if err == nil { - w := strings.TrimSpace(string(out)) - w = strings.Trim(w, "'") - w = strings.ToLower(w) - versionNumberParts := strings.Split(w, " ") - if len(versionNumberParts) > 1 { - versionNumber := versionNumberParts[len(versionNumberParts)-1] - releaseParts := strings.Split(versionNumber, ".") - version, err = strconv.Atoi(releaseParts[0]) - if err != nil { - version = 40 // fallback - } - } - } else { - log.Println("gnome-shell version not found, fallback to 40", err) - } - gnome.versionNumber = version // int will truncate the float - return gnome.version() -} - // getGTKVersion gets the available GTK version for the given theme. If the version cannot be // determine, it will return 3 wich is the most common used version. func (gnome *GnomeTheme) getGTKVersion() (version int) { @@ -608,77 +569,21 @@ func (gnome *GnomeTheme) setFont(fontname string) { // NewGnomeTheme returns a new Gnome theme based on the given gtk version. If gtkVersion is <= 0, // the theme will try to determine the higher Gtk version available for the current GtkTheme. -func NewGnomeTheme(gtkVersion int, flags ...GnomeFlag) fyne.Theme { +func NewGnomeTheme(gtkVersion int) fyne.Theme { gnome := &GnomeTheme{ - fontSize: theme.DefaultTheme().Size(theme.SizeNameText), - iconCache: map[string]fyne.Resource{}, - icons: map[string]string{}, - colors: map[fyne.ThemeColorName]color.Color{}, - font: theme.DefaultTextFont(), - scaleFactor: 1.0, - versionNumber: 40, + fontSize: theme.DefaultTheme().Size(theme.SizeNameText), + iconCache: map[string]fyne.Resource{}, + icons: map[string]string{}, + colors: map[fyne.ThemeColorName]color.Color{}, + font: theme.DefaultTextFont(), + scaleFactor: 1.0, } if gtkVersion <= 0 { // detect gtkVersion gtkVersion = gnome.getGTKVersion() } - gnome.findThemeInformation(gtkVersion, theme.VariantDark) - interfaceName := "org.freedesktop.portal.Settings" - - for _, flag := range flags { - switch flag { - case GnomeFlagAutoReload: - go func() { - // connect to setting changes to not reload the theme if the new selected is - // not a gnome theme - settingChan := make(chan fyne.Settings) - fyne.CurrentApp().Settings().AddChangeListener(settingChan) - - // connect to dbus to detect theme/icon them changes - conn, err := dbus.SessionBus() - if err != nil { - log.Println(err) - return - } - if err := conn.AddMatchSignal( - dbus.WithMatchObjectPath("/org/freedesktop/portal/desktop"), - dbus.WithMatchInterface(interfaceName), - dbus.WithMatchMember("SettingChanged"), - ); err != nil { - log.Println(err) - return - } - defer conn.Close() - dbusChan := make(chan *dbus.Signal, 10) - conn.Signal(dbusChan) - - for { - select { - case sig := <-dbusChan: - // break if the current theme is not typed as GnomeTheme - currentTheme := fyne.CurrentApp().Settings().Theme() - if _, ok := currentTheme.(*GnomeTheme); !ok { - return - } - // reload the theme if the changed setting is the Gtk theme - for _, v := range sig.Body { - switch v { - case "gtk-theme", "icon-theme", "text-scaling-factor", "font-name", "color-scheme": - fyne.CurrentApp().Settings().SetTheme(NewGnomeTheme(gtkVersion, flags...)) - return - } - } - case s := <-settingChan: - // leave the loop if the new theme is not a Gnome theme - if _, isGnome := s.Theme().(*GnomeTheme); !isGnome { - return - } - } - } - }() - } - } + gnome.findThemeInformation(gtkVersion, theme.VariantDark) return gnome } diff --git a/theme/desktopEnvironmentLinux.go b/theme/desktopEnvironmentLinux.go index 4965312e..9399d1ec 100644 --- a/theme/desktopEnvironmentLinux.go +++ b/theme/desktopEnvironmentLinux.go @@ -24,7 +24,7 @@ func FromDesktopEnvironment() fyne.Theme { switch wm { case "gnome", "xfce", "unity", "gnome-shell", "gnome-classic", "mate", "gnome-mate": - return desktop.NewGnomeTheme(-1, desktop.GnomeFlagAutoReload) + return desktop.NewGnomeTheme(-1) case "kde", "kde-plasma", "plasma": return desktop.NewKDETheme() From aafaa769f4a2768e940db9d4da73c19f79b1cd3e Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 22 Nov 2022 08:53:56 +0100 Subject: [PATCH 43/46] Forgotten to remove the flags --- theme/desktop/gnome_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/desktop/gnome_test.go b/theme/desktop/gnome_test.go index 43367340..3647eb6c 100644 --- a/theme/desktop/gnome_test.go +++ b/theme/desktop/gnome_test.go @@ -27,7 +27,7 @@ func ExampleNewGnomeTheme_forceGtkVersion() { // connecting to DBus signal. func ExampleNewGnomeTheme_autoReload() { app := app.New() - app.Settings().SetTheme(NewGnomeTheme(0, GnomeFlagAutoReload)) + app.Settings().SetTheme(NewGnomeTheme(0)) } // Check if the GnomeTheme can be loaded. From 84966caa62aaf1956fcc3cd3912c887754c04ee3 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 22 Nov 2022 08:54:48 +0100 Subject: [PATCH 44/46] Add lxqt desktop environment --- theme/desktopEnvironmentLinux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/desktopEnvironmentLinux.go b/theme/desktopEnvironmentLinux.go index 9399d1ec..d6c72790 100644 --- a/theme/desktopEnvironmentLinux.go +++ b/theme/desktopEnvironmentLinux.go @@ -25,7 +25,7 @@ func FromDesktopEnvironment() fyne.Theme { switch wm { case "gnome", "xfce", "unity", "gnome-shell", "gnome-classic", "mate", "gnome-mate": return desktop.NewGnomeTheme(-1) - case "kde", "kde-plasma", "plasma": + case "kde", "kde-plasma", "plasma", "lxqt": return desktop.NewKDETheme() } From c842d675c98f53d8ce0565121a092991e3513f8c Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 22 Nov 2022 08:57:02 +0100 Subject: [PATCH 45/46] Optimize string split --- theme/desktop/kde.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/desktop/kde.go b/theme/desktop/kde.go index 03458738..d1da143c 100644 --- a/theme/desktop/kde.go +++ b/theme/desktop/kde.go @@ -163,7 +163,7 @@ func (k *KDETheme) setFont() { return } // the fontline is in the form "fontline,size,...", so we can split it - fontline := strings.Split(k.fontConfig, ",") + fontline := strings.SplitN(k.fontConfig, ",", 2) name := fontline[0] size, _ := strconv.ParseFloat(fontline[1], 32) k.fontSize = float32(size) From db2d88b9b216cd4a543855a7079d7b1195ff34e7 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Tue, 22 Nov 2022 09:05:09 +0100 Subject: [PATCH 46/46] Remove unused versionNumber --- theme/desktop/gnome.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/theme/desktop/gnome.go b/theme/desktop/gnome.go index 9160849b..c0afbafa 100644 --- a/theme/desktop/gnome.go +++ b/theme/desktop/gnome.go @@ -162,8 +162,7 @@ type GnomeTheme struct { fontSize float32 iconCache map[string]fyne.Resource - versionNumber int - themeName string + themeName string } // Color returns the color for the given color name