driver_desktop.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. //go:build !js && !wasm && !test_web_driver
  2. // +build !js,!wasm,!test_web_driver
  3. package glfw
  4. import (
  5. "bytes"
  6. "image/png"
  7. "os"
  8. "os/signal"
  9. "runtime"
  10. "sync"
  11. "syscall"
  12. "fyne.io/fyne/v2/canvas"
  13. "fyne.io/fyne/v2/internal/painter"
  14. "fyne.io/fyne/v2/internal/svg"
  15. "fyne.io/systray"
  16. "fyne.io/fyne/v2"
  17. "fyne.io/fyne/v2/theme"
  18. )
  19. var (
  20. systrayIcon fyne.Resource
  21. setup sync.Once
  22. )
  23. func goroutineID() (id uint64) {
  24. var buf [30]byte
  25. runtime.Stack(buf[:], false)
  26. for i := 10; buf[i] != ' '; i++ {
  27. id = id*10 + uint64(buf[i]&15)
  28. }
  29. return id
  30. }
  31. func (d *gLDriver) SetSystemTrayMenu(m *fyne.Menu) {
  32. setup.Do(func() {
  33. d.trayStart, d.trayStop = systray.RunWithExternalLoop(func() {
  34. if systrayIcon != nil {
  35. d.SetSystemTrayIcon(systrayIcon)
  36. } else if fyne.CurrentApp().Icon() != nil {
  37. d.SetSystemTrayIcon(fyne.CurrentApp().Icon())
  38. } else {
  39. d.SetSystemTrayIcon(theme.BrokenImageIcon())
  40. }
  41. // Some XDG systray crash without a title (See #3678)
  42. if runtime.GOOS == "linux" || runtime.GOOS == "openbsd" || runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" {
  43. app := fyne.CurrentApp()
  44. title := app.Metadata().Name
  45. if title == "" {
  46. title = app.UniqueID()
  47. }
  48. systray.SetTitle(title)
  49. }
  50. // it must be refreshed after init, so an earlier call would have been ineffective
  51. d.refreshSystray(m)
  52. }, func() {
  53. // anything required for tear-down
  54. })
  55. // the only way we know the app was asked to quit is if this window is asked to close...
  56. w := d.CreateWindow("SystrayMonitor")
  57. w.(*window).create()
  58. w.SetCloseIntercept(d.Quit)
  59. w.SetOnClosed(systray.Quit)
  60. })
  61. d.refreshSystray(m)
  62. }
  63. func itemForMenuItem(i *fyne.MenuItem, parent *systray.MenuItem) *systray.MenuItem {
  64. if i.IsSeparator {
  65. if parent != nil {
  66. parent.AddSeparator()
  67. } else {
  68. systray.AddSeparator()
  69. }
  70. return nil
  71. }
  72. var item *systray.MenuItem
  73. if i.Checked {
  74. if parent != nil {
  75. item = parent.AddSubMenuItemCheckbox(i.Label, i.Label, true)
  76. } else {
  77. item = systray.AddMenuItemCheckbox(i.Label, i.Label, true)
  78. }
  79. } else {
  80. if parent != nil {
  81. item = parent.AddSubMenuItem(i.Label, i.Label)
  82. } else {
  83. item = systray.AddMenuItem(i.Label, i.Label)
  84. }
  85. }
  86. if i.Disabled {
  87. item.Disable()
  88. }
  89. if i.Icon != nil {
  90. data := i.Icon.Content()
  91. if svg.IsResourceSVG(i.Icon) {
  92. b := &bytes.Buffer{}
  93. res := i.Icon
  94. if runtime.GOOS == "windows" && isDark() { // windows menus don't match dark mode so invert icons
  95. res = theme.NewInvertedThemedResource(i.Icon)
  96. }
  97. img := painter.PaintImage(canvas.NewImageFromResource(res), nil, 64, 64)
  98. err := png.Encode(b, img)
  99. if err != nil {
  100. fyne.LogError("Failed to encode SVG icon for menu", err)
  101. } else {
  102. data = b.Bytes()
  103. }
  104. }
  105. img, err := toOSIcon(data)
  106. if err != nil {
  107. fyne.LogError("Failed to convert systray icon", err)
  108. } else {
  109. item.SetIcon(img)
  110. }
  111. }
  112. return item
  113. }
  114. func (d *gLDriver) refreshSystray(m *fyne.Menu) {
  115. d.systrayMenu = m
  116. systray.ResetMenu()
  117. d.refreshSystrayMenu(m, nil)
  118. addMissingQuitForMenu(m, d)
  119. }
  120. func (d *gLDriver) refreshSystrayMenu(m *fyne.Menu, parent *systray.MenuItem) {
  121. for _, i := range m.Items {
  122. item := itemForMenuItem(i, parent)
  123. if item == nil {
  124. continue // separator
  125. }
  126. if i.ChildMenu != nil {
  127. d.refreshSystrayMenu(i.ChildMenu, item)
  128. }
  129. fn := i.Action
  130. go func() {
  131. for range item.ClickedCh {
  132. if fn != nil {
  133. fn()
  134. }
  135. }
  136. }()
  137. }
  138. }
  139. func (d *gLDriver) SetSystemTrayIcon(resource fyne.Resource) {
  140. systrayIcon = resource // in case we need it later
  141. img, err := toOSIcon(resource.Content())
  142. if err != nil {
  143. fyne.LogError("Failed to convert systray icon", err)
  144. return
  145. }
  146. systray.SetIcon(img)
  147. }
  148. func (d *gLDriver) SystemTrayMenu() *fyne.Menu {
  149. return d.systrayMenu
  150. }
  151. func (d *gLDriver) CurrentKeyModifiers() fyne.KeyModifier {
  152. return d.currentKeyModifiers
  153. }
  154. func (d *gLDriver) catchTerm() {
  155. terminateSignal := make(chan os.Signal, 1)
  156. signal.Notify(terminateSignal, syscall.SIGINT, syscall.SIGTERM)
  157. <-terminateSignal
  158. d.Quit()
  159. }
  160. func addMissingQuitForMenu(menu *fyne.Menu, d *gLDriver) {
  161. var lastItem *fyne.MenuItem
  162. if len(menu.Items) > 0 {
  163. lastItem = menu.Items[len(menu.Items)-1]
  164. if lastItem.Label == "Quit" {
  165. lastItem.IsQuit = true
  166. }
  167. }
  168. if lastItem == nil || !lastItem.IsQuit { // make sure the menu always has a quit option
  169. quitItem := fyne.NewMenuItem("Quit", nil)
  170. quitItem.IsQuit = true
  171. menu.Items = append(menu.Items, fyne.NewMenuItemSeparator(), quitItem)
  172. }
  173. for _, item := range menu.Items {
  174. if item.IsQuit && item.Action == nil {
  175. item.Action = d.Quit
  176. }
  177. }
  178. }