app_xdg.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. //go:build !ci && !js && !wasm && !test_web_driver && (linux || openbsd || freebsd || netbsd) && !android
  2. // +build !ci
  3. // +build !js
  4. // +build !wasm
  5. // +build !test_web_driver
  6. // +build linux openbsd freebsd netbsd
  7. // +build !android
  8. package app
  9. import (
  10. "net/url"
  11. "os"
  12. "path/filepath"
  13. "sync"
  14. "github.com/godbus/dbus/v5"
  15. "golang.org/x/sys/execabs"
  16. "fyne.io/fyne/v2"
  17. "fyne.io/fyne/v2/theme"
  18. )
  19. var once sync.Once
  20. func defaultVariant() fyne.ThemeVariant {
  21. return findFreedestktopColorScheme()
  22. }
  23. func (a *fyneApp) OpenURL(url *url.URL) error {
  24. cmd := execabs.Command("xdg-open", url.String())
  25. cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
  26. return cmd.Start()
  27. }
  28. // fetch color variant from dbus portal desktop settings.
  29. func findFreedestktopColorScheme() fyne.ThemeVariant {
  30. dbusConn, err := dbus.SessionBus()
  31. if err != nil {
  32. fyne.LogError("Unable to connect to session D-Bus", err)
  33. return theme.VariantDark
  34. }
  35. dbusObj := dbusConn.Object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop")
  36. call := dbusObj.Call(
  37. "org.freedesktop.portal.Settings.Read",
  38. dbus.FlagNoAutoStart,
  39. "org.freedesktop.appearance",
  40. "color-scheme",
  41. )
  42. if call.Err != nil {
  43. // many desktops don't have this exported yet
  44. return theme.VariantDark
  45. }
  46. var value uint8
  47. if err = call.Store(&value); err != nil {
  48. fyne.LogError("failed to read theme variant from D-Bus", err)
  49. return theme.VariantDark
  50. }
  51. // See: https://github.com/flatpak/xdg-desktop-portal/blob/1.16.0/data/org.freedesktop.impl.portal.Settings.xml#L32-L46
  52. // 0: No preference
  53. // 1: Prefer dark appearance
  54. // 2: Prefer light appearance
  55. switch value {
  56. case 2:
  57. return theme.VariantLight
  58. case 1:
  59. return theme.VariantDark
  60. default:
  61. // Default to light theme to support Gnome's default see https://github.com/fyne-io/fyne/pull/3561
  62. return theme.VariantLight
  63. }
  64. }
  65. func (a *fyneApp) SendNotification(n *fyne.Notification) {
  66. conn, err := dbus.SessionBus() // shared connection, don't close
  67. if err != nil {
  68. fyne.LogError("Unable to connect to session D-Bus", err)
  69. return
  70. }
  71. appName := fyne.CurrentApp().UniqueID()
  72. appIcon := a.cachedIconPath()
  73. timeout := int32(0) // we don't support this yet
  74. obj := conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
  75. call := obj.Call("org.freedesktop.Notifications.Notify", 0, appName, uint32(0),
  76. appIcon, n.Title, n.Content, []string{}, map[string]dbus.Variant{}, timeout)
  77. if call.Err != nil {
  78. fyne.LogError("Failed to send message to bus", call.Err)
  79. }
  80. }
  81. func (a *fyneApp) saveIconToCache(dirPath, filePath string) error {
  82. err := os.MkdirAll(dirPath, 0700)
  83. if err != nil {
  84. fyne.LogError("Unable to create application cache directory", err)
  85. return err
  86. }
  87. file, err := os.Create(filePath)
  88. if err != nil {
  89. fyne.LogError("Unable to create icon file", err)
  90. return err
  91. }
  92. defer file.Close()
  93. if icon := a.Icon(); icon != nil {
  94. _, err = file.Write(icon.Content())
  95. if err != nil {
  96. fyne.LogError("Unable to write icon contents", err)
  97. return err
  98. }
  99. }
  100. return nil
  101. }
  102. func (a *fyneApp) cachedIconPath() string {
  103. if a.Icon() == nil {
  104. return ""
  105. }
  106. dirPath := filepath.Join(rootCacheDir(), a.UniqueID())
  107. filePath := filepath.Join(dirPath, "icon.png")
  108. once.Do(func() {
  109. err := a.saveIconToCache(dirPath, filePath)
  110. if err != nil {
  111. filePath = ""
  112. }
  113. })
  114. return filePath
  115. }
  116. // SetSystemTrayMenu creates a system tray item and attaches the specified menu.
  117. // By default this will use the application icon.
  118. func (a *fyneApp) SetSystemTrayMenu(menu *fyne.Menu) {
  119. if desk, ok := a.Driver().(systrayDriver); ok { // don't use this on mobile tag
  120. desk.SetSystemTrayMenu(menu)
  121. }
  122. }
  123. // SetSystemTrayIcon sets a custom image for the system tray icon.
  124. // You should have previously called `SetSystemTrayMenu` to initialise the menu icon.
  125. func (a *fyneApp) SetSystemTrayIcon(icon fyne.Resource) {
  126. if desk, ok := a.Driver().(systrayDriver); ok { // don't use this on mobile tag
  127. desk.SetSystemTrayIcon(icon)
  128. }
  129. }
  130. func rootConfigDir() string {
  131. desktopConfig, _ := os.UserConfigDir()
  132. return filepath.Join(desktopConfig, "fyne")
  133. }
  134. func rootCacheDir() string {
  135. desktopCache, _ := os.UserCacheDir()
  136. return filepath.Join(desktopCache, "fyne")
  137. }
  138. func watchTheme() {
  139. go watchFreedekstopThemeChange()
  140. }
  141. func themeChanged() {
  142. fyne.CurrentApp().Settings().(*settings).setupTheme()
  143. }
  144. // connect to dbus to detect color-schem theme changes in portal settings.
  145. func watchFreedekstopThemeChange() {
  146. conn, err := dbus.SessionBus()
  147. if err != nil {
  148. fyne.LogError("Unable to connect to session D-Bus", err)
  149. return
  150. }
  151. if err := conn.AddMatchSignal(
  152. dbus.WithMatchObjectPath("/org/freedesktop/portal/desktop"),
  153. dbus.WithMatchInterface("org.freedesktop.portal.Settings"),
  154. dbus.WithMatchMember("SettingChanged"),
  155. ); err != nil {
  156. fyne.LogError("D-Bus signal match failed", err)
  157. return
  158. }
  159. defer conn.Close()
  160. dbusChan := make(chan *dbus.Signal)
  161. conn.Signal(dbusChan)
  162. for sig := range dbusChan {
  163. for _, v := range sig.Body {
  164. if v == "color-scheme" {
  165. themeChanged()
  166. break
  167. }
  168. }
  169. }
  170. }