mirror of
https://github.com/netbirdio/systray.git
synced 2026-05-22 17:08:41 -07:00
Merge pull request #140 from getlantern/3662-no-walk
Backport all features and fixes from master
This commit is contained in:
+2
-1
@@ -1,7 +1,8 @@
|
||||
example/example
|
||||
webview_example/webview_example
|
||||
*~
|
||||
*.swp
|
||||
*.exe
|
||||
**/*.exe
|
||||
Release
|
||||
Debug
|
||||
*.sdf
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
Package systray is a cross platfrom Go library to place an icon and menu in the notification area.
|
||||
Tested on Windows 8, Mac OSX, Ubuntu 14.10 and Debian 7.6.
|
||||
systray is a cross-platform Go library to place an icon and menu in the notification area.
|
||||
|
||||
## Features
|
||||
|
||||
* Supported on Windows, macOS, and Linux
|
||||
* Menu items can be checked and/or disabled
|
||||
* Methods may be called from any Goroutine
|
||||
|
||||
## API
|
||||
|
||||
## Usage
|
||||
```go
|
||||
func main() {
|
||||
// Should be called at the very beginning of main().
|
||||
systray.Run(onReady, onExit)
|
||||
}
|
||||
|
||||
@@ -14,7 +19,7 @@ func onReady() {
|
||||
systray.SetTooltip("Pretty awesome超级棒")
|
||||
mQuit := systray.AddMenuItem("Quit", "Quit the whole app")
|
||||
|
||||
// Sets the icon of a menu item. Only available on Mac.
|
||||
// Sets the icon of a menu item. Only available on Mac and Windows.
|
||||
mQuit.SetIcon(icon.Data)
|
||||
}
|
||||
|
||||
@@ -22,35 +27,61 @@ func onExit() {
|
||||
// clean up here
|
||||
}
|
||||
```
|
||||
Menu item can be checked and / or disabled. Methods except `Run()` can be invoked from any goroutine. See demo code under `example` folder.
|
||||
|
||||
## Platform specific concerns
|
||||
## Try the example app!
|
||||
|
||||
Have go v1.12+ or higher installed? Here's an example to get started on macOS:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/getlantern/systray
|
||||
cd example
|
||||
env GO111MODULE=on go build
|
||||
./example
|
||||
```
|
||||
|
||||
On Windows, you should build like this:
|
||||
|
||||
```
|
||||
env GO111MODULE=on go build -ldflags "-H=windowsgui"
|
||||
```
|
||||
|
||||
The following text will then appear on the console:
|
||||
|
||||
|
||||
```sh
|
||||
go: finding github.com/skratchdot/open-golang latest
|
||||
go: finding github.com/getlantern/systray latest
|
||||
go: finding github.com/getlantern/golog latest
|
||||
```
|
||||
|
||||
Now look for *Awesome App* in your menu bar!
|
||||
|
||||

|
||||
|
||||
## The Webview example
|
||||
|
||||
The code under `webview_example` is to demostrate how it can co-exist with other UI elements.
|
||||
|
||||
## Platform notes
|
||||
|
||||
### Linux
|
||||
|
||||
```sh
|
||||
sudo apt-get install libgtk-3-dev libappindicator3-dev
|
||||
```
|
||||
Checked menu item not implemented on Linux yet.
|
||||
|
||||
## Try
|
||||
|
||||
Under `example` folder.
|
||||
Place tray icon under `icon`, and use `make_icon.bat` or `make_icon.sh`, whichever suit for your os, to convert the icon to byte array.
|
||||
Your icon should be .ico file under Windows, whereas .ico, .jpg and .png is supported on other platform.
|
||||
* Building apps requires gcc as well as the `gtk3` and `libappindicator3` development headers to be installed. For Debian or Ubuntu, you may install these using:
|
||||
|
||||
```sh
|
||||
go get
|
||||
go run main.go
|
||||
sudo apt-get install gcc libgtk-3-dev libappindicator3-dev
|
||||
```
|
||||
|
||||
## Building and the Console Window
|
||||
On Linux Mint, `libxapp-dev` is also required .
|
||||
|
||||
To build `webview_example`, you also need to install `libwebkit2gtk-4.0-dev` and remove `webview_example/rsrc.syso` which is required on Windows.
|
||||
|
||||
* Submenu and checked menu items are not yet implemented
|
||||
|
||||
By default, the binary created by `go build` will cause a console window to be opened on both Windows and macOS when run.
|
||||
|
||||
### Windows
|
||||
|
||||
To prevent launching a console window when running on Windows, add these command-line build flags:
|
||||
* To avoid opening a console at application startup, use these compile flags:
|
||||
|
||||
```sh
|
||||
go build -ldflags -H=windowsgui
|
||||
@@ -70,6 +101,18 @@ SystrayApp.app/
|
||||
SystrayApp.icns
|
||||
```
|
||||
|
||||
When running as an app bundle, you may want to add one or both of the following to your Info.plist:
|
||||
|
||||
```xml
|
||||
<!-- avoid having a blurry icon and text -->
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>True</string>
|
||||
|
||||
<!-- avoid showing the app on the Dock -->
|
||||
<key>LSUIElement</key>
|
||||
<string>1</string>
|
||||
```
|
||||
|
||||
Consult the [Official Apple Documentation here](https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1).
|
||||
|
||||
## Credits
|
||||
|
||||
Binary file not shown.
+34
-15
@@ -12,17 +12,15 @@ import (
|
||||
|
||||
func main() {
|
||||
onExit := func() {
|
||||
fmt.Println("Starting onExit")
|
||||
now := time.Now()
|
||||
ioutil.WriteFile(fmt.Sprintf(`on_exit_%d.txt`, now.UnixNano()), []byte(now.String()), 0644)
|
||||
fmt.Println("Finished onExit")
|
||||
}
|
||||
// Should be called at the very beginning of main().
|
||||
|
||||
systray.Run(onReady, onExit)
|
||||
}
|
||||
|
||||
func onReady() {
|
||||
systray.SetIcon(icon.Data)
|
||||
systray.SetTemplateIcon(icon.Data, icon.Data)
|
||||
systray.SetTitle("Awesome App")
|
||||
systray.SetTooltip("Lantern")
|
||||
mQuitOrig := systray.AddMenuItem("Quit", "Quit the whole app")
|
||||
@@ -35,14 +33,23 @@ func onReady() {
|
||||
|
||||
// We can manipulate the systray in other goroutines
|
||||
go func() {
|
||||
systray.SetIcon(icon.Data)
|
||||
systray.SetTemplateIcon(icon.Data, icon.Data)
|
||||
systray.SetTitle("Awesome App")
|
||||
systray.SetTooltip("Pretty awesome棒棒嗒")
|
||||
mChange := systray.AddMenuItem("Change Me", "Change Me")
|
||||
mChecked := systray.AddMenuItem("Unchecked", "Check Me")
|
||||
mEnabled := systray.AddMenuItem("Enabled", "Enabled")
|
||||
// Sets the icon of a menu item. Only available on Mac.
|
||||
mEnabled.SetTemplateIcon(icon.Data, icon.Data)
|
||||
|
||||
systray.AddMenuItem("Ignored", "Ignored")
|
||||
mUrl := systray.AddMenuItem("Open Lantern.org", "my home")
|
||||
|
||||
subMenuTop := systray.AddMenuItem("SubMenu", "SubMenu Test (top)")
|
||||
subMenuMiddle := subMenuTop.AddSubMenuItem("SubMenu - Level 2", "SubMenu Test (middle)")
|
||||
subMenuBottom := subMenuMiddle.AddSubMenuItem("SubMenu - Level 3", "SubMenu Test (bottom)")
|
||||
subMenuBottom2 := subMenuMiddle.AddSubMenuItem("Panic!", "SubMenu Test (bottom)")
|
||||
|
||||
mUrl := systray.AddMenuItem("Open UI", "my home")
|
||||
mQuit := systray.AddMenuItem("退出", "Quit the whole app")
|
||||
|
||||
// Sets the icon of a menu item. Only available on Mac.
|
||||
@@ -51,6 +58,22 @@ func onReady() {
|
||||
systray.AddSeparator()
|
||||
mToggle := systray.AddMenuItem("Toggle", "Toggle the Quit button")
|
||||
shown := true
|
||||
toggle := func() {
|
||||
if shown {
|
||||
subMenuBottom.Check()
|
||||
subMenuBottom2.Hide()
|
||||
mQuitOrig.Hide()
|
||||
mEnabled.Hide()
|
||||
shown = false
|
||||
} else {
|
||||
subMenuBottom.Uncheck()
|
||||
subMenuBottom2.Show()
|
||||
mQuitOrig.Show()
|
||||
mEnabled.Show()
|
||||
shown = true
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-mChange.ClickedCh:
|
||||
@@ -68,16 +91,12 @@ func onReady() {
|
||||
mEnabled.Disable()
|
||||
case <-mUrl.ClickedCh:
|
||||
open.Run("https://www.getlantern.org")
|
||||
case <-subMenuBottom2.ClickedCh:
|
||||
panic("panic button pressed")
|
||||
case <-subMenuBottom.ClickedCh:
|
||||
toggle()
|
||||
case <-mToggle.ClickedCh:
|
||||
if shown {
|
||||
mQuitOrig.Hide()
|
||||
mEnabled.Hide()
|
||||
shown = false
|
||||
} else {
|
||||
mQuitOrig.Show()
|
||||
mEnabled.Show()
|
||||
shown = true
|
||||
}
|
||||
toggle()
|
||||
case <-mQuit.ClickedCh:
|
||||
systray.Quit()
|
||||
fmt.Println("Quit2 now...")
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
@@ -0,0 +1,12 @@
|
||||
module github.com/getlantern/systray
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7
|
||||
github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1
|
||||
github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
golang.org/x/sys v0.0.0-20200513112337-417ce2331b5c
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1 h1:/QwQcwWVOQXcoNuV9tHx30gQ3q7jCE/rKcGjwzsa5tg=
|
||||
github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4 h1:5BmtGkQbch91lglMHQ9JIDGiYCL3kBRBA0ItZTvOcEI=
|
||||
github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200513112337-417ce2331b5c h1:kISX68E8gSkNYAFRFiDU8rl5RIn1sJYKYb/r2vMLDrU=
|
||||
golang.org/x/sys v0.0.0-20200513112337-417ce2331b5c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
|
||||
+58
-35
@@ -1,13 +1,10 @@
|
||||
/*
|
||||
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 is a cross-platform Go library to place an icon and menu in the notification area.
|
||||
*/
|
||||
package systray
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -16,11 +13,22 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
hasStarted = int64(0)
|
||||
hasQuit = int64(0)
|
||||
log = golog.LoggerFor("systray")
|
||||
|
||||
systrayReady func()
|
||||
systrayExit func()
|
||||
menuItems = make(map[int32]*MenuItem)
|
||||
menuItemsLock sync.RWMutex
|
||||
|
||||
currentID = int32(-1)
|
||||
quitOnce sync.Once
|
||||
)
|
||||
|
||||
// MenuItem is used to keep track each menu item of systray
|
||||
func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -36,27 +44,41 @@ type MenuItem struct {
|
||||
disabled bool
|
||||
// checked menu item has a tick before the title
|
||||
checked bool
|
||||
// parent item, for sub menus
|
||||
parent *MenuItem
|
||||
}
|
||||
|
||||
var (
|
||||
log = golog.LoggerFor("systray")
|
||||
func (item *MenuItem) String() string {
|
||||
if item.parent == nil {
|
||||
return fmt.Sprintf("MenuItem[%d, %q]", item.id, item.title)
|
||||
}
|
||||
return fmt.Sprintf("MenuItem[%d, parent %d, %q]", item.id, item.parent.id, item.title)
|
||||
}
|
||||
|
||||
systrayReady func()
|
||||
systrayExit func()
|
||||
menuItems = make(map[int32]*MenuItem)
|
||||
menuItemsLock sync.RWMutex
|
||||
|
||||
currentID = int32(-1)
|
||||
)
|
||||
// newMenuItem returns a populated MenuItem object
|
||||
func newMenuItem(title string, tooltip string, parent *MenuItem) *MenuItem {
|
||||
return &MenuItem{
|
||||
ClickedCh: make(chan struct{}),
|
||||
id: atomic.AddInt32(¤tID, 1),
|
||||
title: title,
|
||||
tooltip: tooltip,
|
||||
disabled: false,
|
||||
checked: false,
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// callback. It blocks until systray.Quit() is called.
|
||||
func Run(onReady func(), onExit func()) {
|
||||
runtime.LockOSThread()
|
||||
atomic.StoreInt64(&hasStarted, 1)
|
||||
Register(onReady, onExit)
|
||||
nativeLoop()
|
||||
}
|
||||
|
||||
// Register initializes GUI and registers the callbacks but relies on the
|
||||
// caller to run the event loop somewhere else. It's useful if the program
|
||||
// needs to show other UI elements, for example, webview.
|
||||
func Register(onReady func(), onExit func()) {
|
||||
if onReady == nil {
|
||||
systrayReady = func() {}
|
||||
} else {
|
||||
@@ -70,32 +92,25 @@ func Run(onReady func(), onExit func()) {
|
||||
close(readyCh)
|
||||
}
|
||||
}
|
||||
|
||||
// unlike onReady, onExit runs in the event loop to make sure it has time to
|
||||
// finish before the process terminates
|
||||
if onExit == nil {
|
||||
onExit = func() {}
|
||||
}
|
||||
systrayExit = onExit
|
||||
|
||||
nativeLoop()
|
||||
registerSystray()
|
||||
}
|
||||
|
||||
// Quit the systray
|
||||
func Quit() {
|
||||
if atomic.LoadInt64(&hasStarted) == 1 && atomic.CompareAndSwapInt64(&hasQuit, 0, 1) {
|
||||
quit()
|
||||
}
|
||||
quitOnce.Do(quit)
|
||||
}
|
||||
|
||||
// AddMenuItem adds menu item with designated title and tooltip, returning a channel
|
||||
// that notifies whenever that menu item is clicked.
|
||||
// AddMenuItem adds a menu item with the designated title and tooltip.
|
||||
//
|
||||
// It 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 struct{})
|
||||
item := newMenuItem(title, tooltip, nil)
|
||||
item.update()
|
||||
return item
|
||||
}
|
||||
@@ -105,6 +120,14 @@ func AddSeparator() {
|
||||
addSeparator(atomic.AddInt32(¤tID, 1))
|
||||
}
|
||||
|
||||
// AddSubMenuItem adds a nested sub-menu item with the designated title and tooltip.
|
||||
// It can be safely invoked from different goroutines.
|
||||
func (item *MenuItem) AddSubMenuItem(title string, tooltip string) *MenuItem {
|
||||
child := newMenuItem(title, tooltip, item)
|
||||
child.update()
|
||||
return child
|
||||
}
|
||||
|
||||
// SetTitle set the text to display on a menu item
|
||||
func (item *MenuItem) SetTitle(title string) {
|
||||
item.title = title
|
||||
@@ -117,7 +140,7 @@ func (item *MenuItem) SetTooltip(tooltip string) {
|
||||
item.update()
|
||||
}
|
||||
|
||||
// Disabled checkes if the menu item is disabled
|
||||
// Disabled checks if the menu item is disabled
|
||||
func (item *MenuItem) Disabled() bool {
|
||||
return item.disabled
|
||||
}
|
||||
@@ -161,7 +184,7 @@ func (item *MenuItem) Uncheck() {
|
||||
item.update()
|
||||
}
|
||||
|
||||
// update propogates changes on a menu item to systray
|
||||
// update propagates changes on a menu item to systray
|
||||
func (item *MenuItem) update() {
|
||||
menuItemsLock.Lock()
|
||||
defer menuItemsLock.Unlock()
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#include "stdbool.h"
|
||||
|
||||
extern void systray_ready();
|
||||
extern void systray_on_exit();
|
||||
extern void systray_menu_item_selected(int menu_id);
|
||||
void registerSystray(void);
|
||||
int nativeLoop(void);
|
||||
|
||||
void setIcon(const char* iconBytes, int length);
|
||||
void setMenuItemIcon(const char* iconBytes, int length, int menuId);
|
||||
void setIcon(const char* iconBytes, int length, bool template);
|
||||
void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template);
|
||||
void setTitle(char* title);
|
||||
void setTooltip(char* tooltip);
|
||||
void add_or_update_menu_item(int menuId, char* title, char* tooltip, short disabled, short checked);
|
||||
void add_or_update_menu_item(int menuId, int parentMenuId, char* title, char* tooltip, short disabled, short checked);
|
||||
void add_separator(int menuId);
|
||||
void hide_menu_item(int menuId);
|
||||
void show_menu_item(int menuId);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package systray
|
||||
|
||||
/*
|
||||
#cgo darwin CFLAGS: -DDARWIN -x objective-c -fobjc-arc
|
||||
#cgo darwin LDFLAGS: -framework Cocoa -framework WebKit
|
||||
|
||||
#include "systray.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// SetTemplateIcon sets the systray icon as a template icon (on Mac), falling back
|
||||
// to a regular icon on other platforms.
|
||||
// templateIconBytes and regularIconBytes should be the content of .ico for windows and
|
||||
// .ico/.jpg/.png for other platforms.
|
||||
func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
|
||||
cstr := (*C.char)(unsafe.Pointer(&templateIconBytes[0]))
|
||||
C.setIcon(cstr, (C.int)(len(templateIconBytes)), true)
|
||||
}
|
||||
|
||||
// SetIcon sets the icon of a menu item. Only works on macOS and Windows.
|
||||
// iconBytes should be the content of .ico/.jpg/.png
|
||||
func (item *MenuItem) SetIcon(iconBytes []byte) {
|
||||
SetTemplateIcon(iconBytes, iconBytes)
|
||||
}
|
||||
|
||||
// SetTemplateIcon sets the icon of a menu item as a template icon (on macOS). On Windows, it
|
||||
// falls back to the regular icon bytes and on Linux it does nothing.
|
||||
// templateIconBytes and regularIconBytes should be the content of .ico for windows and
|
||||
// .ico/.jpg/.png for other platforms.
|
||||
func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
|
||||
cstr := (*C.char)(unsafe.Pointer(&templateIconBytes[0]))
|
||||
C.setMenuItemIcon(cstr, (C.int)(len(templateIconBytes)), C.int(item.id), true)
|
||||
}
|
||||
+65
-34
@@ -17,12 +17,14 @@
|
||||
{
|
||||
@public
|
||||
NSNumber* menuId;
|
||||
NSNumber* parentMenuId;
|
||||
NSString* title;
|
||||
NSString* tooltip;
|
||||
short disabled;
|
||||
short checked;
|
||||
}
|
||||
-(id) initWithId: (int)theMenuId
|
||||
withParentMenuId: (int)theParentMenuId
|
||||
withTitle: (const char*)theTitle
|
||||
withTooltip: (const char*)theTooltip
|
||||
withDisabled: (short)theDisabled
|
||||
@@ -30,12 +32,14 @@
|
||||
@end
|
||||
@implementation MenuItem
|
||||
-(id) initWithId: (int)theMenuId
|
||||
withParentMenuId: (int)theParentMenuId
|
||||
withTitle: (const char*)theTitle
|
||||
withTooltip: (const char*)theTooltip
|
||||
withDisabled: (short)theDisabled
|
||||
withChecked: (short)theChecked
|
||||
{
|
||||
menuId = [NSNumber numberWithInt:theMenuId];
|
||||
parentMenuId = [NSNumber numberWithInt:theParentMenuId];
|
||||
title = [[NSString alloc] initWithCString:theTitle
|
||||
encoding:NSUTF8StringEncoding];
|
||||
tooltip = [[NSString alloc] initWithCString:theTooltip
|
||||
@@ -108,20 +112,30 @@
|
||||
systray_menu_item_selected(menuId.intValue);
|
||||
}
|
||||
|
||||
- (void) add_or_update_menu_item:(MenuItem*) item
|
||||
{
|
||||
NSMenuItem* menuItem;
|
||||
int existedMenuIndex = [menu indexOfItemWithRepresentedObject: item->menuId];
|
||||
if (existedMenuIndex == -1) {
|
||||
menuItem = [menu addItemWithTitle:item->title action:@selector(menuHandler:) keyEquivalent:@""];
|
||||
[menuItem setTarget:self];
|
||||
[menuItem setRepresentedObject: item->menuId];
|
||||
|
||||
- (void)add_or_update_menu_item:(MenuItem *)item {
|
||||
NSMenu *theMenu = self->menu;
|
||||
NSMenuItem *parentItem;
|
||||
if ([item->parentMenuId integerValue] > 0) {
|
||||
parentItem = find_menu_item(menu, item->parentMenuId);
|
||||
if (parentItem.hasSubmenu) {
|
||||
theMenu = parentItem.submenu;
|
||||
} else {
|
||||
theMenu = [[NSMenu alloc] init];
|
||||
[parentItem setSubmenu:theMenu];
|
||||
}
|
||||
}
|
||||
else {
|
||||
menuItem = [menu itemAtIndex: existedMenuIndex];
|
||||
[menuItem setTitle:item->title];
|
||||
|
||||
NSMenuItem *menuItem;
|
||||
menuItem = find_menu_item(theMenu, item->menuId);
|
||||
if (menuItem == NULL) {
|
||||
menuItem = [theMenu addItemWithTitle:item->title
|
||||
action:@selector(menuHandler:)
|
||||
keyEquivalent:@""];
|
||||
[menuItem setRepresentedObject:item->menuId];
|
||||
}
|
||||
[menuItem setTitle:item->title];
|
||||
[menuItem setTag:[item->menuId integerValue]];
|
||||
[menuItem setTarget:self];
|
||||
[menuItem setToolTip:item->tooltip];
|
||||
if (item->disabled == 1) {
|
||||
menuItem.enabled = FALSE;
|
||||
@@ -135,6 +149,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
NSMenuItem *find_menu_item(NSMenu *ourMenu, NSNumber *menuId) {
|
||||
NSMenuItem *foundItem = [ourMenu itemWithTag:[menuId integerValue]];
|
||||
if (foundItem != NULL) {
|
||||
return foundItem;
|
||||
}
|
||||
NSArray *menu_items = ourMenu.itemArray;
|
||||
int i;
|
||||
for (i = 0; i < [menu_items count]; i++) {
|
||||
NSMenuItem *i_item = [menu_items objectAtIndex:i];
|
||||
if (i_item.hasSubmenu) {
|
||||
foundItem = find_menu_item(i_item.submenu, menuId);
|
||||
if (foundItem != NULL) {
|
||||
return foundItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
};
|
||||
|
||||
- (void) add_separator:(NSNumber*) menuId
|
||||
{
|
||||
[menu addItem: [NSMenuItem separatorItem]];
|
||||
@@ -142,37 +176,30 @@
|
||||
|
||||
- (void) hide_menu_item:(NSNumber*) menuId
|
||||
{
|
||||
NSMenuItem* menuItem;
|
||||
int existedMenuIndex = [menu indexOfItemWithRepresentedObject: menuId];
|
||||
if (existedMenuIndex == -1) {
|
||||
return;
|
||||
NSMenuItem* menuItem = find_menu_item(menu, menuId);
|
||||
if (menuItem != NULL) {
|
||||
[menuItem setHidden:TRUE];
|
||||
}
|
||||
menuItem = [menu itemAtIndex: existedMenuIndex];
|
||||
[menuItem setHidden:TRUE];
|
||||
}
|
||||
|
||||
- (void)setMenuItemIcon:(NSArray*)imageAndMenuId {
|
||||
- (void) setMenuItemIcon:(NSArray*)imageAndMenuId {
|
||||
NSImage* image = [imageAndMenuId objectAtIndex:0];
|
||||
NSNumber* menuId = [imageAndMenuId objectAtIndex:1];
|
||||
|
||||
NSMenuItem* menuItem;
|
||||
int existedMenuIndex = [menu indexOfItemWithRepresentedObject: menuId];
|
||||
if (existedMenuIndex == -1) {
|
||||
menuItem = find_menu_item(menu, menuId);
|
||||
if (menuItem == NULL) {
|
||||
return;
|
||||
}
|
||||
menuItem = [menu itemAtIndex: existedMenuIndex];
|
||||
menuItem.image = image;
|
||||
}
|
||||
|
||||
- (void) show_menu_item:(NSNumber*) menuId
|
||||
{
|
||||
NSMenuItem* menuItem;
|
||||
int existedMenuIndex = [menu indexOfItemWithRepresentedObject: menuId];
|
||||
if (existedMenuIndex == -1) {
|
||||
return;
|
||||
NSMenuItem* menuItem = find_menu_item(menu, menuId);
|
||||
if (menuItem != NULL) {
|
||||
[menuItem setHidden:FALSE];
|
||||
}
|
||||
menuItem = [menu itemAtIndex: existedMenuIndex];
|
||||
[menuItem setHidden:FALSE];
|
||||
}
|
||||
|
||||
- (void) quit
|
||||
@@ -182,9 +209,12 @@
|
||||
|
||||
@end
|
||||
|
||||
int nativeLoop(void) {
|
||||
void registerSystray(void) {
|
||||
AppDelegate *delegate = [[AppDelegate alloc] init];
|
||||
[[NSApplication sharedApplication] setDelegate:delegate];
|
||||
}
|
||||
|
||||
int nativeLoop(void) {
|
||||
[NSApp run];
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -196,18 +226,19 @@ void runInMainThread(SEL method, id object) {
|
||||
waitUntilDone: YES];
|
||||
}
|
||||
|
||||
void setIcon(const char* iconBytes, int length) {
|
||||
void setIcon(const char* iconBytes, int length, bool template) {
|
||||
NSData* buffer = [NSData dataWithBytes: iconBytes length:length];
|
||||
NSImage *image = [[NSImage alloc] initWithData:buffer];
|
||||
[image setSize:NSMakeSize(16, 16)];
|
||||
image.template = template;
|
||||
runInMainThread(@selector(setIcon:), (id)image);
|
||||
}
|
||||
|
||||
void setMenuItemIcon(const char* iconBytes, int length, int menuId) {
|
||||
void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template) {
|
||||
NSData* buffer = [NSData dataWithBytes: iconBytes length:length];
|
||||
NSImage *image = [[NSImage alloc] initWithData:buffer];
|
||||
[image setSize:NSMakeSize(16, 16)];
|
||||
|
||||
image.template = template;
|
||||
NSNumber *mId = [NSNumber numberWithInt:menuId];
|
||||
runInMainThread(@selector(setMenuItemIcon:), @[image, (id)mId]);
|
||||
}
|
||||
@@ -226,8 +257,8 @@ void setTooltip(char* ctooltip) {
|
||||
runInMainThread(@selector(setTooltip:), (id)tooltip);
|
||||
}
|
||||
|
||||
void add_or_update_menu_item(int menuId, char* title, char* tooltip, short disabled, short checked) {
|
||||
MenuItem* item = [[MenuItem alloc] initWithId: menuId withTitle: title withTooltip: tooltip withDisabled: disabled withChecked: checked];
|
||||
void add_or_update_menu_item(int menuId, int parentMenuId, char* title, char* tooltip, short disabled, short checked) {
|
||||
MenuItem* item = [[MenuItem alloc] initWithId: menuId withParentMenuId: parentMenuId withTitle: title withTooltip: tooltip withDisabled: disabled withChecked: checked];
|
||||
free(title);
|
||||
free(tooltip);
|
||||
runInMainThread(@selector(add_or_update_menu_item:), (id)item);
|
||||
|
||||
+8
-4
@@ -23,7 +23,7 @@ typedef struct {
|
||||
short checked;
|
||||
} MenuItemInfo;
|
||||
|
||||
int nativeLoop(void) {
|
||||
void registerSystray(void) {
|
||||
gtk_init(0, NULL);
|
||||
global_app_indicator = app_indicator_new("systray", "",
|
||||
APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
|
||||
@@ -31,6 +31,9 @@ int nativeLoop(void) {
|
||||
global_tray_menu = gtk_menu_new();
|
||||
app_indicator_set_menu(global_app_indicator, GTK_MENU(global_tray_menu));
|
||||
systray_ready();
|
||||
}
|
||||
|
||||
int nativeLoop(void) {
|
||||
gtk_main();
|
||||
systray_on_exit();
|
||||
return 0;
|
||||
@@ -167,7 +170,7 @@ gboolean do_quit(gpointer data) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void setIcon(const char* iconBytes, int length) {
|
||||
void setIcon(const char* iconBytes, int length, bool template) {
|
||||
GBytes* bytes = g_bytes_new_static(iconBytes, length);
|
||||
g_idle_add(do_set_icon, bytes);
|
||||
}
|
||||
@@ -181,10 +184,11 @@ void setTooltip(char* ctooltip) {
|
||||
free(ctooltip);
|
||||
}
|
||||
|
||||
void setMenuItemIcon(const char* iconBytes, int length, int menuId) {
|
||||
void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template) {
|
||||
}
|
||||
|
||||
void add_or_update_menu_item(int menu_id, char* title, char* tooltip, short disabled, short checked) {
|
||||
void add_or_update_menu_item(int menu_id, int parent_menu_id, char* title, char* tooltip, short disabled, short checked) {
|
||||
// TODO: add support for sub-menus
|
||||
MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
|
||||
mii->menu_id = menu_id;
|
||||
mii->title = title;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package systray
|
||||
|
||||
/*
|
||||
#cgo darwin CFLAGS: -DDARWIN -x objective-c -fobjc-arc
|
||||
#cgo darwin LDFLAGS: -framework Cocoa -framework WebKit
|
||||
|
||||
#include "systray.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// SetTemplateIcon sets the systray icon as a template icon (on macOS), falling back
|
||||
// to a regular icon on other platforms.
|
||||
// templateIconBytes and iconBytes should be the content of .ico for windows and
|
||||
// .ico/.jpg/.png for other platforms.
|
||||
func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
|
||||
SetIcon(regularIconBytes)
|
||||
}
|
||||
|
||||
// SetIcon sets the icon of a menu item. Only works on macOS and Windows.
|
||||
// iconBytes should be the content of .ico/.jpg/.png
|
||||
func (item *MenuItem) SetIcon(iconBytes []byte) {
|
||||
}
|
||||
|
||||
// SetTemplateIcon sets the icon of a menu item as a template icon (on macOS). On Windows, it
|
||||
// falls back to the regular icon bytes and on Linux it does nothing.
|
||||
// templateIconBytes and regularIconBytes should be the content of .ico for windows and
|
||||
// .ico/.jpg/.png for other platforms.
|
||||
func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
|
||||
}
|
||||
+10
-8
@@ -15,6 +15,10 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func registerSystray() {
|
||||
C.registerSystray()
|
||||
}
|
||||
|
||||
func nativeLoop() {
|
||||
C.nativeLoop()
|
||||
}
|
||||
@@ -28,7 +32,7 @@ func quit() {
|
||||
// for other platforms.
|
||||
func SetIcon(iconBytes []byte) {
|
||||
cstr := (*C.char)(unsafe.Pointer(&iconBytes[0]))
|
||||
C.setIcon(cstr, (C.int)(len(iconBytes)))
|
||||
C.setIcon(cstr, (C.int)(len(iconBytes)), false)
|
||||
}
|
||||
|
||||
// SetTitle sets the systray title, only available on Mac.
|
||||
@@ -51,8 +55,13 @@ func addOrUpdateMenuItem(item *MenuItem) {
|
||||
if item.checked {
|
||||
checked = 1
|
||||
}
|
||||
var parentID int32 = 0
|
||||
if item.parent != nil {
|
||||
parentID = item.parent.id
|
||||
}
|
||||
C.add_or_update_menu_item(
|
||||
C.int(item.id),
|
||||
C.int(parentID),
|
||||
C.CString(item.title),
|
||||
C.CString(item.tooltip),
|
||||
disabled,
|
||||
@@ -60,13 +69,6 @@ func addOrUpdateMenuItem(item *MenuItem) {
|
||||
)
|
||||
}
|
||||
|
||||
// SetIcon sets the icon of a menu item. Only available on Mac.
|
||||
// iconBytes should be the content of .ico/.jpg/.png
|
||||
func (item *MenuItem) SetIcon(iconBytes []byte) {
|
||||
cstr := (*C.char)(unsafe.Pointer(&iconBytes[0]))
|
||||
C.setMenuItemIcon(cstr, (C.int)(len(iconBytes)), C.int(item.id))
|
||||
}
|
||||
|
||||
func addSeparator(id int32) {
|
||||
C.add_separator(C.int(id))
|
||||
}
|
||||
|
||||
+272
-101
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="Lantern" type="win32"/>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True</dpiAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/getlantern/systray"
|
||||
"github.com/getlantern/systray/example/icon"
|
||||
)
|
||||
|
||||
func main() {
|
||||
systray.Register(onReady, nil)
|
||||
configureWebview("Webview example", 1024, 768)
|
||||
}
|
||||
|
||||
func onReady() {
|
||||
systray.SetTemplateIcon(icon.Data, icon.Data)
|
||||
systray.SetTitle("Webview example")
|
||||
mShowLantern := systray.AddMenuItem("Show Lantern", "")
|
||||
mShowWikipedia := systray.AddMenuItem("Show Wikipedia", "")
|
||||
mQuit := systray.AddMenuItem("Quit", "Quit the whole app")
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-mShowLantern.ClickedCh:
|
||||
showWebview("https://www.getlantern.org")
|
||||
case <-mShowWikipedia.ClickedCh:
|
||||
showWebview("https://www.wikipedia.org")
|
||||
case <-mQuit.ClickedCh:
|
||||
systray.Quit()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
void configureAppWindow(char* title, int width, int height);
|
||||
void showAppWindow(char* url);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
#include "webview.h"
|
||||
|
||||
NSWindowController *windowController = nil;
|
||||
NSWindow *window = nil;
|
||||
WKWebView *webView = nil;
|
||||
|
||||
void configureAppWindow(char* title, int width, int height)
|
||||
{
|
||||
if (windowController != nil) {
|
||||
// already configured, ignore
|
||||
return;
|
||||
}
|
||||
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
[app setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
[app activateIgnoringOtherApps:YES];
|
||||
|
||||
NSRect frame = NSMakeRect(0, 0, width, height);
|
||||
int mask = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable;
|
||||
window = [[NSWindow alloc] initWithContentRect:frame
|
||||
styleMask:mask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
[window setTitle:[[NSString alloc] initWithUTF8String:title]];
|
||||
[window center];
|
||||
|
||||
NSView *contentView = [window contentView];
|
||||
webView = [[WKWebView alloc] initWithFrame:[contentView bounds]];
|
||||
[webView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[contentView addSubview:webView];
|
||||
[contentView addConstraint:
|
||||
[NSLayoutConstraint constraintWithItem:webView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:contentView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
[contentView addConstraint:
|
||||
[NSLayoutConstraint constraintWithItem:webView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:contentView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
// Window controller:
|
||||
windowController = [[NSWindowController alloc] initWithWindow:window];
|
||||
|
||||
free(title);
|
||||
[NSApp run];
|
||||
}
|
||||
|
||||
void doShowAppWindow(char* url)
|
||||
{
|
||||
if (windowController == nil) {
|
||||
// no app window to open
|
||||
return;
|
||||
}
|
||||
|
||||
id nsURL = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
|
||||
id req = [[NSURLRequest alloc] initWithURL: nsURL
|
||||
cachePolicy: NSURLRequestUseProtocolCachePolicy
|
||||
timeoutInterval: 5];
|
||||
[webView loadRequest:req];
|
||||
[windowController showWindow:window];
|
||||
free(url);
|
||||
}
|
||||
|
||||
void showAppWindow(char* url)
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
doShowAppWindow(url);
|
||||
});
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user