1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
/*
Package systray is a cross platfrom Go library to place an icon and menu in the notification area.
Supports Windows, Mac OSX and Linux currently.
Methods can be called from any goroutine except Run(), which should be called at the very beginning of main() to lock at main thread.
*/
package systray
import (
"runtime"
"sync"
"sync/atomic"
"github.com/getlantern/golog"
)
// MenuItem is used to keep track each menu item of systray
// Don't create it directly, use the one systray.AddMenuItem() returned
type MenuItem struct {
// ClickedCh is the channel which will be notified when the menu item is clicked
ClickedCh chan interface{}
// id uniquely identify a menu item, not supposed to be modified
id int32
// title is the text shown on menu item
title string
// tooltip is the text shown when pointing to menu item
tooltip string
// disabled menu item is grayed out and has no effect when clicked
disabled bool
// checked menu item has a tick before the title
checked bool
}
var (
log = golog.LoggerFor("systray")
readyCh = make(chan interface{})
clickedCh = make(chan interface{})
menuItems = make(map[int32]*MenuItem)
menuItemsLock sync.RWMutex
currentId int32
)
// Run initializes GUI and starts the event loop, then invokes the onReady
// callback.
// It blocks until systray.Quit() is called.
// Should be called at the very beginning of main() to lock at main thread.
func Run(onReady func()) {
runtime.LockOSThread()
go func() {
<-readyCh
onReady()
}()
nativeLoop()
}
// Quit the systray and whole app
func Quit() {
quit()
}
// Add menu item with designated title and tooltip, returning a channel that
// notifies whenever that menu item has been clicked.
//
// Menu items are keyed to an id. If the same id is added twice, the 2nd one
// overwrites the first.
//
// AddMenuItem can be safely invoked from different goroutines.
func AddMenuItem(title string, tooltip string) *MenuItem {
id := atomic.AddInt32(¤tId, 1)
item := &MenuItem{nil, id, title, tooltip, false, false}
item.ClickedCh = make(chan interface{})
item.update()
return item
}
func (item *MenuItem) SetTitle(title string) {
item.title = title
item.update()
}
func (item *MenuItem) SetTooltip(tooltip string) {
item.tooltip = tooltip
item.update()
}
func (item *MenuItem) Disabled() bool {
return item.disabled
}
func (item *MenuItem) Enable() {
item.disabled = false
item.update()
}
func (item *MenuItem) Disable() {
item.disabled = true
item.update()
}
func (item *MenuItem) Checked() bool {
return item.checked
}
func (item *MenuItem) Check() {
item.checked = true
item.update()
}
func (item *MenuItem) Uncheck() {
item.checked = false
item.update()
}
// update propogates changes on a menu item to systray
func (item *MenuItem) update() {
menuItemsLock.Lock()
defer menuItemsLock.Unlock()
menuItems[item.id] = item
addOrUpdateMenuItem(item)
}
func systrayReady() {
readyCh <- nil
}
func systrayMenuItemSelected(id int32) {
menuItemsLock.RLock()
item := menuItems[id]
menuItemsLock.RUnlock()
select {
case item.ClickedCh <- nil:
// in case no one waiting for the channel
default:
}
}
|