Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,35 @@ sudo apt-get install libgtk-3-dev libappindicator3-dev
go build -ldflags -H=windowsgui
```

You'll also need to include a Manifest for your windows app. Assuming your executable is named `app.exe`, create a file `app.manifest` like this:

```
<?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="App Name Here" 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>
```

Then either compile the manifest using the [rsrc](https://github.com/akavel/rsrc) tool, like this:

```
go get github.com/akavel/rsrc
rsrc -manifest app.manifest -o rsrc.syso
```

or rename the app.manifest file to app.exe.manifest and distribute it with the application instead.

### macOS

On macOS, you will need to create an application bundle to wrap the binary; simply folders with the following minimal structure and assets:
Expand Down
15 changes: 15 additions & 0 deletions example/example.manifest
Original file line number Diff line number Diff line change
@@ -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>
7 changes: 3 additions & 4 deletions example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/getlantern/systray"
"github.com/getlantern/systray/example/icon"
"github.com/skratchdot/open-golang/open"
)

func main() {
Expand All @@ -18,7 +17,7 @@ func main() {
fmt.Println("Finished onExit")
}
// Should be called at the very beginning of main().
systray.Run(onReady, onExit)
systray.RunWithAppWindow("Lantern", 1024, 768, onReady, onExit)
}

func onReady() {
Expand All @@ -42,7 +41,7 @@ func onReady() {
mChecked := systray.AddMenuItem("Unchecked", "Check Me")
mEnabled := systray.AddMenuItem("Enabled", "Enabled")
systray.AddMenuItem("Ignored", "Ignored")
mUrl := systray.AddMenuItem("Open Lantern.org", "my home")
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.
Expand All @@ -67,7 +66,7 @@ func onReady() {
mEnabled.SetTitle("Disabled")
mEnabled.Disable()
case <-mUrl.ClickedCh:
open.Run("https://www.getlantern.org")
systray.ShowAppWindow("https://www.getlantern.org")
case <-mToggle.ClickedCh:
if shown {
mQuitOrig.Hide()
Expand Down
Binary file added example/rsrc.syso
Binary file not shown.
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/getlantern/systray

go 1.12

require (
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7
github.com/getlantern/uuid v1.2.0
github.com/lxn/walk v0.0.0-20190919092010-4386a6d42094
github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8 // indirect
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e
golang.org/x/sys v0.0.0-20190919044723-0c1ff786ef13 // indirect
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
)
33 changes: 33 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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/getlantern/uuid v1.2.0 h1:pGrGaCV7XEaG6lvjWkwf8Y92BjB/9yFmkKsNFpRQ7rc=
github.com/getlantern/uuid v1.2.0/go.mod h1:uX10hOzZUUDR+oYNSIks+RcozOEiwTNC/K2rw9SUi1k=
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-20190919092010-4386a6d42094 h1:axV9dR1/LfxQTFTpzjiBzQV9tFarx+Evs2ONi/ceq2U=
github.com/lxn/walk v0.0.0-20190919092010-4386a6d42094/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8 h1:RVMGIuuNgrpGB7I79f6xfhGCkpN47IaEGh8VTM0p7Xc=
github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e h1:VAzdS5Nw68fbf5RZ8RDVlUvPXNU6Z3jtPCK/qvm4FoQ=
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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-20190919044723-0c1ff786ef13 h1:/zi0zzlPHWXYXrO1LjNRByFu8sdGgCkj2JLDdBIB84k=
golang.org/x/sys v0.0.0-20190919044723-0c1ff786ef13/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=
10 changes: 7 additions & 3 deletions systray.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ var (
)

// Run initializes GUI and starts the event loop, then invokes the onReady
// callback.
// It blocks until systray.Quit() is called.
// 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(), onExit func()) {
RunWithAppWindow("", 0, 0, onReady, onExit)
}

// RunWithAppWindow is like Run but also enables an application window with the given title.
func RunWithAppWindow(title string, width int, height int, onReady func(), onExit func()) {
runtime.LockOSThread()
atomic.StoreInt64(&hasStarted, 1)

Expand All @@ -78,7 +82,7 @@ func Run(onReady func(), onExit func()) {
}
systrayExit = onExit

nativeLoop()
nativeLoop(title, width, height)
}

// Quit the systray
Expand Down
4 changes: 3 additions & 1 deletion systray.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
extern void systray_ready();
extern void systray_on_exit();
extern void systray_menu_item_selected(int menu_id);
int nativeLoop(void);
int nativeLoop(char* title, int width, int height);

void setIcon(const char* iconBytes, int length);
void setMenuItemIcon(const char* iconBytes, int length, int menuId);
void setTitle(char* title);
void setTooltip(char* tooltip);
void configureAppWindow(char* title, int width, int height);
void showAppWindow(char* url);
void add_or_update_menu_item(int menuId, char* title, char* tooltip, short disabled, short checked);
void add_separator(int menuId);
void hide_menu_item(int menuId);
Expand Down
72 changes: 72 additions & 0 deletions systray_browser_darwin.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#include "systray.h"

NSWindowController *windowController = nil;
WKWebView *webView = nil;

void configureAppWindow(char* title, int width, int height)
{
if (windowController != nil) {
// already configured, ignore
return;
}

NSRect frame = NSMakeRect(0, 0, width, height);
int mask = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable;
NSWindow *window =
[[NSWindow alloc] initWithContentRect:frame
styleMask:mask
backing:NSBackingStoreBuffered
defer:NO];
[window setTitle:[[NSString alloc] initWithUTF8String:title]];
[window makeKeyAndOrderFront:nil];
[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);
}

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.window orderFrontRegardless];
free(url);
}

void showAppWindow(char* url)
{
dispatch_async(dispatch_get_main_queue(), ^{
doShowAppWindow(url);
});
}
57 changes: 57 additions & 0 deletions systray_browser_linux.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <gtk/gtk.h>
#include <webkit2/webkit2.h>

static GtkWindow *web_window = NULL;
static WebKitWebView *web_view = NULL;

static gint x, y;
static bool needsMove = false;

gboolean on_window_deleted(GtkWidget *window, GdkEvent *event, gpointer data)
{
gtk_window_get_position(GTK_WINDOW(window), &x, &y);
needsMove = true;
gtk_widget_hide(window);
return TRUE;
}

void configureAppWindow(char* title, int width, int height)
{
// Create an 800x600 window that will contain the browser instance
web_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
gtk_window_set_title(web_window, title);
gtk_window_set_default_size(web_window, width, height);
gtk_window_set_skip_taskbar_hint (web_window, TRUE);
g_signal_connect(G_OBJECT(web_window), "delete-event", G_CALLBACK(on_window_deleted), NULL);

// Create a browser instance
web_view = WEBKIT_WEB_VIEW(webkit_web_view_new());

// Put the browser area into the web window
gtk_container_add(GTK_CONTAINER(web_window), GTK_WIDGET(web_view));

// Make sure that when the browser area becomes visible, it will get mouse
// and keyboard events
gtk_widget_grab_focus(GTK_WIDGET(web_view));
free(title);
}

gboolean do_show_app_window(gpointer data)
{
gtk_widget_show_all(GTK_WIDGET(web_window));
if (needsMove) {
gtk_window_move(web_window, x, y);
needsMove = false;
}
gtk_window_present(web_window);
return FALSE;
}

void showAppWindow(char* url)
{
// Load a web page into the browser instance
webkit_web_view_load_uri(web_view, url);

g_idle_add(do_show_app_window, NULL);
free(url);
}
12 changes: 11 additions & 1 deletion systray_darwin.m
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
systray_ready();
}

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
return FALSE;
}

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
systray_on_exit();
Expand Down Expand Up @@ -182,9 +187,14 @@ - (void) quit

@end

int nativeLoop(void) {
void configureAppWindow(char* title, int width, int height);

int nativeLoop(char* title, int width, int height) {
AppDelegate *delegate = [[AppDelegate alloc] init];
[[NSApplication sharedApplication] setDelegate:delegate];
if (strcmp(title, "") != 0) {
configureAppWindow(title, width, height);
}
[NSApp run];
return EXIT_SUCCESS;
}
Expand Down
7 changes: 6 additions & 1 deletion systray_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@ typedef struct {
short checked;
} MenuItemInfo;

int nativeLoop(void) {
void prepareBrowser();

int nativeLoop(char* title, int width, int height) {
gtk_init(0, NULL);
global_app_indicator = app_indicator_new("systray", "",
APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
app_indicator_set_status(global_app_indicator, APP_INDICATOR_STATUS_ACTIVE);
global_tray_menu = gtk_menu_new();
app_indicator_set_menu(global_app_indicator, GTK_MENU(global_tray_menu));
systray_ready();
if (strcmp(title, "") != 0) {
configureAppWindow(title, width, height);
}
gtk_main();
systray_on_exit();
return 0;
Expand Down
15 changes: 11 additions & 4 deletions systray_nonwindows.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
package systray

/*
#cgo linux pkg-config: gtk+-3.0 appindicator3-0.1
#cgo linux CFLAGS: -DWEBVIEW_GTK=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 appindicator3-0.1
#cgo darwin CFLAGS: -DDARWIN -x objective-c -fobjc-arc
#cgo darwin LDFLAGS: -framework Cocoa
#cgo darwin LDFLAGS: -framework Cocoa -framework WebKit

#include "systray.h"
*/
Expand All @@ -15,14 +16,20 @@ import (
"unsafe"
)

func nativeLoop() {
C.nativeLoop()
func nativeLoop(title string, width int, height int) {
C.nativeLoop(C.CString(title), C.int(width), C.int(height))
}

func quit() {
C.quit()
}

// ShowAppWindow shows the given URL in the application window. Only works if
// configureAppWindow has been called first.
func ShowAppWindow(url string) {
C.showAppWindow(C.CString(url))
}

// SetIcon sets the systray icon.
// iconBytes should be the content of .ico for windows and .ico/.jpg/.png
// for other platforms.
Expand Down
Loading