app_xdg.go 4.6 KB

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