driver_desktop.go 3.9 KB

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