driver_desktop.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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. app := fyne.CurrentApp()
  42. title := app.Metadata().Name
  43. if title == "" {
  44. title = app.UniqueID()
  45. }
  46. systray.SetTitle(title)
  47. // it must be refreshed after init, so an earlier call would have been ineffective
  48. d.refreshSystray(m)
  49. }, func() {
  50. // anything required for tear-down
  51. })
  52. // the only way we know the app was asked to quit is if this window is asked to close...
  53. w := d.CreateWindow("SystrayMonitor")
  54. w.(*window).create()
  55. w.SetCloseIntercept(d.Quit)
  56. w.SetOnClosed(systray.Quit)
  57. })
  58. d.refreshSystray(m)
  59. }
  60. func itemForMenuItem(i *fyne.MenuItem, parent *systray.MenuItem) *systray.MenuItem {
  61. if i.IsSeparator {
  62. if parent != nil {
  63. parent.AddSeparator()
  64. } else {
  65. systray.AddSeparator()
  66. }
  67. return nil
  68. }
  69. var item *systray.MenuItem
  70. if i.Checked {
  71. if parent != nil {
  72. item = parent.AddSubMenuItemCheckbox(i.Label, i.Label, true)
  73. } else {
  74. item = systray.AddMenuItemCheckbox(i.Label, i.Label, true)
  75. }
  76. } else {
  77. if parent != nil {
  78. item = parent.AddSubMenuItem(i.Label, i.Label)
  79. } else {
  80. item = systray.AddMenuItem(i.Label, i.Label)
  81. }
  82. }
  83. if i.Disabled {
  84. item.Disable()
  85. }
  86. if i.Icon != nil {
  87. data := i.Icon.Content()
  88. if svg.IsResourceSVG(i.Icon) {
  89. b := &bytes.Buffer{}
  90. res := i.Icon
  91. if runtime.GOOS == "windows" && isDark() { // windows menus don't match dark mode so invert icons
  92. res = theme.NewInvertedThemedResource(i.Icon)
  93. }
  94. img := painter.PaintImage(canvas.NewImageFromResource(res), nil, 64, 64)
  95. err := png.Encode(b, img)
  96. if err != nil {
  97. fyne.LogError("Failed to encode SVG icon for menu", err)
  98. } else {
  99. data = b.Bytes()
  100. }
  101. }
  102. img, err := toOSIcon(data)
  103. if err != nil {
  104. fyne.LogError("Failed to convert systray icon", err)
  105. } else {
  106. item.SetIcon(img)
  107. }
  108. }
  109. return item
  110. }
  111. func (d *gLDriver) refreshSystray(m *fyne.Menu) {
  112. d.systrayMenu = m
  113. systray.ResetMenu()
  114. d.refreshSystrayMenu(m, nil)
  115. addMissingQuitForMenu(m, d)
  116. }
  117. func (d *gLDriver) refreshSystrayMenu(m *fyne.Menu, parent *systray.MenuItem) {
  118. for _, i := range m.Items {
  119. item := itemForMenuItem(i, parent)
  120. if item == nil {
  121. continue // separator
  122. }
  123. if i.ChildMenu != nil {
  124. d.refreshSystrayMenu(i.ChildMenu, item)
  125. }
  126. fn := i.Action
  127. go func() {
  128. for range item.ClickedCh {
  129. if fn != nil {
  130. fn()
  131. }
  132. }
  133. }()
  134. }
  135. }
  136. func (d *gLDriver) SetSystemTrayIcon(resource fyne.Resource) {
  137. systrayIcon = resource // in case we need it later
  138. img, err := toOSIcon(resource.Content())
  139. if err != nil {
  140. fyne.LogError("Failed to convert systray icon", err)
  141. return
  142. }
  143. systray.SetIcon(img)
  144. }
  145. func (d *gLDriver) SystemTrayMenu() *fyne.Menu {
  146. return d.systrayMenu
  147. }
  148. func (d *gLDriver) CurrentKeyModifiers() fyne.KeyModifier {
  149. return d.currentKeyModifiers
  150. }
  151. func (d *gLDriver) catchTerm() {
  152. terminateSignal := make(chan os.Signal, 1)
  153. signal.Notify(terminateSignal, syscall.SIGINT, syscall.SIGTERM)
  154. <-terminateSignal
  155. d.Quit()
  156. }
  157. func addMissingQuitForMenu(menu *fyne.Menu, d *gLDriver) {
  158. var lastItem *fyne.MenuItem
  159. if len(menu.Items) > 0 {
  160. lastItem = menu.Items[len(menu.Items)-1]
  161. if lastItem.Label == "Quit" {
  162. lastItem.IsQuit = true
  163. }
  164. }
  165. if lastItem == nil || !lastItem.IsQuit { // make sure the menu always has a quit option
  166. quitItem := fyne.NewMenuItem("Quit", nil)
  167. quitItem.IsQuit = true
  168. menu.Items = append(menu.Items, fyne.NewMenuItemSeparator(), quitItem)
  169. }
  170. for _, item := range menu.Items {
  171. if item.IsQuit && item.Action == nil {
  172. item.Action = d.Quit
  173. }
  174. }
  175. }