203 lines
5.0 KiB
Go
203 lines
5.0 KiB
Go
//go:build !ci && !js && !wasm && !test_web_driver && (linux || openbsd || freebsd || netbsd) && !android
|
|
// +build !ci
|
|
// +build !js
|
|
// +build !wasm
|
|
// +build !test_web_driver
|
|
// +build linux openbsd freebsd netbsd
|
|
// +build !android
|
|
|
|
package app
|
|
|
|
import (
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
"golang.org/x/sys/execabs"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/theme"
|
|
)
|
|
|
|
var once sync.Once
|
|
|
|
func defaultVariant() fyne.ThemeVariant {
|
|
return findFreedestktopColorScheme()
|
|
}
|
|
|
|
func (a *fyneApp) OpenURL(url *url.URL) error {
|
|
cmd := execabs.Command("xdg-open", url.String())
|
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
|
return cmd.Start()
|
|
}
|
|
|
|
// fetch color variant from dbus portal desktop settings.
|
|
func findFreedestktopColorScheme() fyne.ThemeVariant {
|
|
dbusConn, err := dbus.SessionBus()
|
|
if err != nil {
|
|
fyne.LogError("Unable to connect to session D-Bus", err)
|
|
return theme.VariantDark
|
|
}
|
|
|
|
dbusObj := dbusConn.Object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop")
|
|
call := dbusObj.Call(
|
|
"org.freedesktop.portal.Settings.Read",
|
|
dbus.FlagNoAutoStart,
|
|
"org.freedesktop.appearance",
|
|
"color-scheme",
|
|
)
|
|
if call.Err != nil {
|
|
// many desktops don't have this exported yet
|
|
return theme.VariantDark
|
|
}
|
|
|
|
var value uint8
|
|
if err = call.Store(&value); err != nil {
|
|
fyne.LogError("failed to read theme variant from D-Bus", err)
|
|
return theme.VariantDark
|
|
}
|
|
|
|
// See: https://github.com/flatpak/xdg-desktop-portal/blob/1.16.0/data/org.freedesktop.impl.portal.Settings.xml#L32-L46
|
|
// 0: No preference
|
|
// 1: Prefer dark appearance
|
|
// 2: Prefer light appearance
|
|
switch value {
|
|
case 2:
|
|
return theme.VariantLight
|
|
case 1:
|
|
return theme.VariantDark
|
|
default:
|
|
// Default to light theme to support Gnome's default see https://github.com/fyne-io/fyne/pull/3561
|
|
return theme.VariantLight
|
|
}
|
|
}
|
|
|
|
func (a *fyneApp) SendNotification(n *fyne.Notification) {
|
|
conn, err := dbus.SessionBus() // shared connection, don't close
|
|
if err != nil {
|
|
fyne.LogError("Unable to connect to session D-Bus", err)
|
|
return
|
|
}
|
|
|
|
appName := fyne.CurrentApp().UniqueID()
|
|
appIcon := a.cachedIconPath()
|
|
timeout := int32(0) // we don't support this yet
|
|
|
|
obj := conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
|
|
call := obj.Call("org.freedesktop.Notifications.Notify", 0, appName, uint32(0),
|
|
appIcon, n.Title, n.Content, []string{}, map[string]dbus.Variant{}, timeout)
|
|
if call.Err != nil {
|
|
fyne.LogError("Failed to send message to bus", call.Err)
|
|
}
|
|
}
|
|
|
|
func (a *fyneApp) saveIconToCache(dirPath, filePath string) error {
|
|
err := os.MkdirAll(dirPath, 0700)
|
|
if err != nil {
|
|
fyne.LogError("Unable to create application cache directory", err)
|
|
return err
|
|
}
|
|
|
|
file, err := os.Create(filePath)
|
|
if err != nil {
|
|
fyne.LogError("Unable to create icon file", err)
|
|
return err
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
if icon := a.Icon(); icon != nil {
|
|
_, err = file.Write(icon.Content())
|
|
if err != nil {
|
|
fyne.LogError("Unable to write icon contents", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *fyneApp) cachedIconPath() string {
|
|
if a.Icon() == nil {
|
|
return ""
|
|
}
|
|
|
|
dirPath := filepath.Join(rootCacheDir(), a.UniqueID())
|
|
filePath := filepath.Join(dirPath, "icon.png")
|
|
once.Do(func() {
|
|
err := a.saveIconToCache(dirPath, filePath)
|
|
if err != nil {
|
|
filePath = ""
|
|
}
|
|
})
|
|
|
|
return filePath
|
|
}
|
|
|
|
// SetSystemTrayMenu creates a system tray item and attaches the specified menu.
|
|
// By default this will use the application icon.
|
|
func (a *fyneApp) SetSystemTrayMenu(menu *fyne.Menu) {
|
|
if desk, ok := a.Driver().(systrayDriver); ok { // don't use this on mobile tag
|
|
desk.SetSystemTrayMenu(menu)
|
|
}
|
|
}
|
|
|
|
// SetSystemTrayIcon sets a custom image for the system tray icon.
|
|
// You should have previously called `SetSystemTrayMenu` to initialise the menu icon.
|
|
func (a *fyneApp) SetSystemTrayIcon(icon fyne.Resource) {
|
|
if desk, ok := a.Driver().(systrayDriver); ok { // don't use this on mobile tag
|
|
desk.SetSystemTrayIcon(icon)
|
|
}
|
|
}
|
|
|
|
func rootConfigDir() string {
|
|
desktopConfig, _ := os.UserConfigDir()
|
|
return filepath.Join(desktopConfig, "fyne")
|
|
}
|
|
|
|
func rootCacheDir() string {
|
|
desktopCache, _ := os.UserCacheDir()
|
|
return filepath.Join(desktopCache, "fyne")
|
|
}
|
|
|
|
func watchTheme() {
|
|
go watchFreedekstopThemeChange()
|
|
}
|
|
|
|
func themeChanged() {
|
|
fyne.CurrentApp().Settings().(*settings).setupTheme()
|
|
}
|
|
|
|
// connect to dbus to detect color-schem theme changes in portal settings.
|
|
func watchFreedekstopThemeChange() {
|
|
conn, err := dbus.SessionBus()
|
|
if err != nil {
|
|
fyne.LogError("Unable to connect to session D-Bus", err)
|
|
return
|
|
}
|
|
|
|
if err := conn.AddMatchSignal(
|
|
dbus.WithMatchObjectPath("/org/freedesktop/portal/desktop"),
|
|
dbus.WithMatchInterface("org.freedesktop.portal.Settings"),
|
|
dbus.WithMatchMember("SettingChanged"),
|
|
); err != nil {
|
|
fyne.LogError("D-Bus signal match failed", err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
dbusChan := make(chan *dbus.Signal)
|
|
conn.Signal(dbusChan)
|
|
|
|
for sig := range dbusChan {
|
|
for _, v := range sig.Body {
|
|
if v == "color-scheme" {
|
|
themeChanged()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|