Merge pull request #140 from getlantern/3662-no-walk

Backport all features and fixes from master
This commit is contained in:
joesis
2020-05-17 17:07:00 -07:00
committed by GitHub
23 changed files with 874 additions and 223 deletions
+2 -1
View File
@@ -1,7 +1,8 @@
example/example
webview_example/webview_example
*~
*.swp
*.exe
**/*.exe
Release
Debug
*.sdf
+65 -22
View File
@@ -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!
![Awesome App screenshot](example/screenshot.png)
## 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
View File
@@ -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

+12
View File
@@ -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
)
+34
View File
@@ -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
View File
@@ -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(&currentID, 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(&currentID, 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(&currentID, 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()
+6 -3
View File
@@ -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);
+37
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+29
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -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>
+32
View File
@@ -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.
+3
View File
@@ -0,0 +1,3 @@
void configureAppWindow(char* title, int width, int height);
void showAppWindow(char* url);
+78
View File
@@ -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