From e090ea1a2da8c4ff5e98fb09e37d51042e380b0a Mon Sep 17 00:00:00 2001
From: "Kali Kaneko (leap communications)" <kali@leap.se>
Date: Wed, 14 Nov 2018 20:45:46 +0100
Subject: [PATCH] [pkg] add temporary vendoring of getlantern/systray to fix
 win bug

this adds upstream PR #74
---
 vendor.json                                   |   9 +
 vendor/README                                 |   3 +
 .../github.com/getlantern/systray/.gitignore  |  11 +
 vendor/github.com/getlantern/systray/LICENSE  | 202 +++++
 .../github.com/getlantern/systray/README.md   |  50 ++
 .../github.com/getlantern/systray/systray.go  | 181 +++++
 .../github.com/getlantern/systray/systray.h   |  14 +
 .../getlantern/systray/systray_darwin.m       | 249 ++++++
 .../getlantern/systray/systray_linux.c        | 217 ++++++
 .../getlantern/systray/systray_nonwindows.go  |  99 +++
 .../getlantern/systray/systray_windows.go     | 718 ++++++++++++++++++
 .../systray/systray_windows_test.go           | 132 ++++
 12 files changed, 1885 insertions(+)
 create mode 100644 vendor.json
 create mode 100644 vendor/README
 create mode 100644 vendor/github.com/getlantern/systray/.gitignore
 create mode 100644 vendor/github.com/getlantern/systray/LICENSE
 create mode 100644 vendor/github.com/getlantern/systray/README.md
 create mode 100644 vendor/github.com/getlantern/systray/systray.go
 create mode 100644 vendor/github.com/getlantern/systray/systray.h
 create mode 100644 vendor/github.com/getlantern/systray/systray_darwin.m
 create mode 100644 vendor/github.com/getlantern/systray/systray_linux.c
 create mode 100644 vendor/github.com/getlantern/systray/systray_nonwindows.go
 create mode 100644 vendor/github.com/getlantern/systray/systray_windows.go
 create mode 100644 vendor/github.com/getlantern/systray/systray_windows_test.go

diff --git a/vendor.json b/vendor.json
new file mode 100644
index 00000000..14c76105
--- /dev/null
+++ b/vendor.json
@@ -0,0 +1,9 @@
+{
+	"comment": "",
+	"package": [
+		{
+			"path": "github.com/getlantern/systray"
+		}
+	]
+}
+		
diff --git a/vendor/README b/vendor/README
new file mode 100644
index 00000000..de0db4e9
--- /dev/null
+++ b/vendor/README
@@ -0,0 +1,3 @@
+2018-11-14
+-----------------
+* vendor getlantern/systray with PR #74, to work around menu positioning bug in windows.
diff --git a/vendor/github.com/getlantern/systray/.gitignore b/vendor/github.com/getlantern/systray/.gitignore
new file mode 100644
index 00000000..ae7e06bd
--- /dev/null
+++ b/vendor/github.com/getlantern/systray/.gitignore
@@ -0,0 +1,11 @@
+example/example
+*~
+*.swp
+*.exe
+Release
+Debug
+*.sdf
+dll/systray_unsigned.dll
+out.txt
+.vs
+on_exit*.txt
diff --git a/vendor/github.com/getlantern/systray/LICENSE b/vendor/github.com/getlantern/systray/LICENSE
new file mode 100644
index 00000000..3ee01626
--- /dev/null
+++ b/vendor/github.com/getlantern/systray/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2014 Brave New Software Project, Inc.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/vendor/github.com/getlantern/systray/README.md b/vendor/github.com/getlantern/systray/README.md
new file mode 100644
index 00000000..626c132b
--- /dev/null
+++ b/vendor/github.com/getlantern/systray/README.md
@@ -0,0 +1,50 @@
+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.
+
+## Usage
+```go
+func main() {
+	// Should be called at the very beginning of main().
+	systray.Run(onReady, onExit)
+}
+
+func onReady() {
+	systray.SetIcon(icon.Data)
+	systray.SetTitle("Awesome App")
+	systray.SetTooltip("Pretty awesome超级棒")
+	mQuit := systray.AddMenuItem("Quit", "Quit the whole app")
+
+	// Sets the icon of a menu item. Only available on Mac.
+	mQuit.SetIcon(icon.Data)
+}
+
+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
+
+### 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.
+
+```sh
+go get
+go run main.go
+```
+
+## Credits
+
+- https://github.com/xilp/systray
+- https://github.com/cratonica/trayhost
diff --git a/vendor/github.com/getlantern/systray/systray.go b/vendor/github.com/getlantern/systray/systray.go
new file mode 100644
index 00000000..d4331732
--- /dev/null
+++ b/vendor/github.com/getlantern/systray/systray.go
@@ -0,0 +1,181 @@
+/*
+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"
+)
+
+var (
+	hasStarted = int64(0)
+	hasQuit    = int64(0)
+)
+
+// 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 struct{}
+
+	// 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")
+
+	systrayReady  func()
+	systrayExit   func()
+	menuItems     = make(map[int32]*MenuItem)
+	menuItemsLock sync.RWMutex
+
+	currentID = int32(-1)
+)
+
+// 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(), onExit func()) {
+	runtime.LockOSThread()
+	atomic.StoreInt64(&hasStarted, 1)
+
+	if onReady == nil {
+		systrayReady = func() {}
+	} else {
+		// Run onReady on separate goroutine to avoid blocking event loop
+		readyCh := make(chan interface{})
+		go func() {
+			<-readyCh
+			onReady()
+		}()
+		systrayReady = 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()
+}
+
+// Quit the systray
+func Quit() {
+	if atomic.LoadInt64(&hasStarted) == 1 && atomic.CompareAndSwapInt64(&hasQuit, 0, 1) {
+		quit()
+	}
+}
+
+// AddMenuItem adds menu item with designated title and tooltip, returning a channel
+// that notifies whenever that menu item is clicked.
+//
+// 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.update()
+	return item
+}
+
+// AddSeparator adds a separator bar to the menu
+func AddSeparator() {
+	addSeparator(atomic.AddInt32(&currentID, 1))
+}
+
+// SetTitle set the text to display on a menu item
+func (item *MenuItem) SetTitle(title string) {
+	item.title = title
+	item.update()
+}
+
+// SetTooltip set the tooltip to show when mouse hover
+func (item *MenuItem) SetTooltip(tooltip string) {
+	item.tooltip = tooltip
+	item.update()
+}
+
+// Disabled checkes if the menu item is disabled
+func (item *MenuItem) Disabled() bool {
+	return item.disabled
+}
+
+// Enable a menu item regardless if it's previously enabled or not
+func (item *MenuItem) Enable() {
+	item.disabled = false
+	item.update()
+}
+
+// Disable a menu item regardless if it's previously disabled or not
+func (item *MenuItem) Disable() {
+	item.disabled = true
+	item.update()
+}
+
+// Hide hides a menu item
+func (item *MenuItem) Hide() {
+	hideMenuItem(item)
+}
+
+// Show shows a previously hidden menu item
+func (item *MenuItem) Show() {
+	showMenuItem(item)
+}
+
+// Checked returns if the menu item has a check mark
+func (item *MenuItem) Checked() bool {
+	return item.checked
+}
+
+// Check a menu item regardless if it's previously checked or not
+func (item *MenuItem) Check() {
+	item.checked = true
+	item.update()
+}
+
+// Uncheck a menu item regardless if it's previously unchecked or not
+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 systrayMenuItemSelected(id int32) {
+	menuItemsLock.RLock()
+	item := menuItems[id]
+	menuItemsLock.RUnlock()
+	select {
+	case item.ClickedCh <- struct{}{}:
+	// in case no one waiting for the channel
+	default:
+	}
+}
diff --git a/vendor/github.com/getlantern/systray/systray.h b/vendor/github.com/getlantern/systray/systray.h
new file mode 100644
index 00000000..36bcf989
--- /dev/null
+++ b/vendor/github.com/getlantern/systray/systray.h
@@ -0,0 +1,14 @@
+extern void systray_ready();
+extern void systray_on_exit();
+extern void systray_menu_item_selected(int menu_id);
+int nativeLoop(void);
+
+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 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);
+void show_menu_item(int menuId);
+void quit();
diff --git a/vendor/github.com/getlantern/systray/systray_darwin.m b/vendor/github.com/getlantern/systray/systray_darwin.m
new file mode 100644
index 00000000..3d928685
--- /dev/null
+++ b/vendor/github.com/getlantern/systray/systray_darwin.m
@@ -0,0 +1,249 @@
+#import <Cocoa/Cocoa.h>
+#include "systray.h"
+
+#ifndef NSControlStateValueOff
+  #define NSControlStateValueOff NSOffState
+#endif
+
+#ifndef NSControlStateValueOn
+  #define NSControlStateValueOn NSOnState
+#endif
+
+@interface MenuItem : NSObject
+{
+  @public
+    NSNumber* menuId;
+    NSString* title;
+    NSString* tooltip;
+    short disabled;
+    short checked;
+}
+-(id) initWithId: (int)theMenuId
+       withTitle: (const char*)theTitle
+     withTooltip: (const char*)theTooltip
+    withDisabled: (short)theDisabled
+     withChecked: (short)theChecked;
+     @end
+     @implementation MenuItem
+     -(id) initWithId: (int)theMenuId
+            withTitle: (const char*)theTitle
+          withTooltip: (const char*)theTooltip
+         withDisabled: (short)theDisabled
+          withChecked: (short)theChecked
+{
+  menuId = [NSNumber numberWithInt:theMenuId];
+  title = [[NSString alloc] initWithCString:theTitle
+                                   encoding:NSUTF8StringEncoding];
+  tooltip = [[NSString alloc] initWithCString:theTooltip
+                                     encoding:NSUTF8StringEncoding];
+  disabled = theDisabled;
+  checked = theChecked;
+  return self;
+}
+@end
+
+@interface AppDelegate: NSObject <NSApplicationDelegate>
+  - (void) add_or_update_menu_item:(MenuItem*) item;
+  - (IBAction)menuHandler:(id)sender;
+  @property (assign) IBOutlet NSWindow *window;
+  @end
+
+  @implementation AppDelegate
+{
+  NSStatusItem *statusItem;
+  NSMenu *menu;
+  NSCondition* cond;
+}
+
+@synthesize window = _window;
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
+{
+  self->statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
+  self->menu = [[NSMenu alloc] init];
+  [self->menu setAutoenablesItems: FALSE];
+  [self->statusItem setMenu:self->menu];
+  systray_ready();
+}
+
+- (void)applicationWillTerminate:(NSNotification *)aNotification
+{
+  systray_on_exit();
+}
+
+- (void)setIcon:(NSImage *)image {
+  statusItem.button.image = image;
+  [self updateTitleButtonStyle];
+}
+
+- (void)setTitle:(NSString *)title {
+  statusItem.button.title = title;
+  [self updateTitleButtonStyle];
+}
+
+-(void)updateTitleButtonStyle {
+  if (statusItem.button.image != nil) {
+    if ([statusItem.button.title length] == 0) {
+      statusItem.button.imagePosition = NSImageOnly;
+    } else {
+      statusItem.button.imagePosition = NSImageLeft;
+    }
+  } else {
+    statusItem.button.imagePosition = NSNoImage;
+  }
+}
+
+
+- (void)setTooltip:(NSString *)tooltip {
+  statusItem.button.toolTip = tooltip;
+}
+
+- (IBAction)menuHandler:(id)sender
+{
+  NSNumber* menuId = [sender representedObject];
+  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];
+
+  }
+  else {
+    menuItem = [menu itemAtIndex: existedMenuIndex];
+    [menuItem setTitle:item->title];
+  }
+  [menuItem setToolTip:item->tooltip];
+  if (item->disabled == 1) {
+    menuItem.enabled = FALSE;
+  } else {
+    menuItem.enabled = TRUE;
+  }
+  if (item->checked == 1) {
+    menuItem.state = NSControlStateValueOn;
+  } else {
+    menuItem.state = NSControlStateValueOff;
+  }
+}
+
+- (void) add_separator:(NSNumber*) menuId
+{
+  [menu addItem: [NSMenuItem separatorItem]];
+}
+
+- (void) hide_menu_item:(NSNumber*) menuId
+{
+  NSMenuItem* menuItem;
+  int existedMenuIndex = [menu indexOfItemWithRepresentedObject: menuId];
+  if (existedMenuIndex == -1) {
+    return;
+  }
+  menuItem = [menu itemAtIndex: existedMenuIndex];
+  [menuItem setHidden:TRUE];
+}
+
+- (void)setMenuItemIcon:(NSArray*)imageAndMenuId {
+  NSImage* image = [imageAndMenuId objectAtIndex:0];
+  NSNumber* menuId = [imageAndMenuId objectAtIndex:1];
+
+  NSMenuItem* menuItem;
+  int existedMenuIndex = [menu indexOfItemWithRepresentedObject: menuId];
+  if (existedMenuIndex == -1) {
+    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;
+  }
+  menuItem = [menu itemAtIndex: existedMenuIndex];
+  [menuItem setHidden:FALSE];
+}
+
+- (void) quit
+{
+  [NSApp terminate:self];
+}
+
+@end
+
+int nativeLoop(void) {
+  AppDelegate *delegate = [[AppDelegate alloc] init];
+  [[NSApplication sharedApplication] setDelegate:delegate];
+  [NSApp run];
+  return EXIT_SUCCESS;
+}
+
+void runInMainThread(SEL method, id object) {
+  [(AppDelegate*)[NSApp delegate]
+    performSelectorOnMainThread:method
+                     withObject:object
+                  waitUntilDone: YES];
+}
+
+void setIcon(const char* iconBytes, int length) {
+  NSData* buffer = [NSData dataWithBytes: iconBytes length:length];
+  NSImage *image = [[NSImage alloc] initWithData:buffer];
+  [image setSize:NSMakeSize(16, 16)];
+  runInMainThread(@selector(setIcon:), (id)image);
+}
+
+void setMenuItemIcon(const char* iconBytes, int length, int menuId) {
+  NSData* buffer = [NSData dataWithBytes: iconBytes length:length];
+  NSImage *image = [[NSImage alloc] initWithData:buffer];
+  [image setSize:NSMakeSize(16, 16)];
+
+  NSNumber *mId = [NSNumber numberWithInt:menuId];
+  runInMainThread(@selector(setMenuItemIcon:), @[image, (id)mId]);
+}
+
+void setTitle(char* ctitle) {
+  NSString* title = [[NSString alloc] initWithCString:ctitle
+                                             encoding:NSUTF8StringEncoding];
+  free(ctitle);
+  runInMainThread(@selector(setTitle:), (id)title);
+}
+
+void setTooltip(char* ctooltip) {
+  NSString* tooltip = [[NSString alloc] initWithCString:ctooltip
+                                               encoding:NSUTF8StringEncoding];
+  free(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];
+  free(title);
+  free(tooltip);
+  runInMainThread(@selector(add_or_update_menu_item:), (id)item);
+}
+
+void add_separator(int menuId) {
+  NSNumber *mId = [NSNumber numberWithInt:menuId];
+  runInMainThread(@selector(add_separator:), (id)mId);
+}
+
+void hide_menu_item(int menuId) {
+  NSNumber *mId = [NSNumber numberWithInt:menuId];
+  runInMainThread(@selector(hide_menu_item:), (id)mId);
+}
+
+void show_menu_item(int menuId) {
+  NSNumber *mId = [NSNumber numberWithInt:menuId];
+  runInMainThread(@selector(show_menu_item:), (id)mId);
+}
+
+void quit() {
+  runInMainThread(@selector(quit), nil);
+}
diff --git a/vendor/github.com/getlantern/systray/systray_linux.c b/vendor/github.com/getlantern/systray/systray_linux.c
new file mode 100644
index 00000000..72cd6140
--- /dev/null
+++ b/vendor/github.com/getlantern/systray/systray_linux.c
@@ -0,0 +1,217 @@
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <libappindicator/app-indicator.h>
+#include "systray.h"
+
+static AppIndicator *global_app_indicator;
+static GtkWidget *global_tray_menu = NULL;
+static GList *global_menu_items = NULL;
+static char temp_file_name[PATH_MAX] = "";
+
+typedef struct {
+	GtkWidget *menu_item;
+	int menu_id;
+} MenuItemNode;
+
+typedef struct {
+	int menu_id;
+	char* title;
+	char* tooltip;
+	short disabled;
+	short checked;
+} MenuItemInfo;
+
+int nativeLoop(void) {
+	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();
+	gtk_main();
+	systray_on_exit();
+	return 0;
+}
+
+void _unlink_temp_file() {
+	if (strlen(temp_file_name) != 0) {
+		int ret = unlink(temp_file_name);
+		if (ret == -1) {
+			printf("failed to remove temp icon file %s: %s\n", temp_file_name, strerror(errno));
+		}
+		temp_file_name[0] = '\0';
+	}
+}
+
+// runs in main thread, should always return FALSE to prevent gtk to execute it again
+gboolean do_set_icon(gpointer data) {
+	_unlink_temp_file();
+	char *tmpdir = getenv("TMPDIR");
+	if (NULL == tmpdir) {
+		tmpdir = "/tmp";
+	}
+	strncpy(temp_file_name, tmpdir, PATH_MAX-1);
+	strncat(temp_file_name, "/systray_XXXXXX", PATH_MAX-1);
+	temp_file_name[PATH_MAX-1] = '\0';
+
+	GBytes* bytes = (GBytes*)data;
+	int fd = mkstemp(temp_file_name);
+	if (fd == -1) {
+		printf("failed to create temp icon file %s: %s\n", temp_file_name, strerror(errno));
+		return FALSE;
+	}
+	gsize size = 0;
+	gconstpointer icon_data = g_bytes_get_data(bytes, &size);
+	ssize_t written = write(fd, icon_data, size);
+	close(fd);
+	if(written != size) {
+		printf("failed to write temp icon file %s: %s\n", temp_file_name, strerror(errno));
+		return FALSE;
+	}
+	app_indicator_set_icon_full(global_app_indicator, temp_file_name, "");
+	app_indicator_set_attention_icon_full(global_app_indicator, temp_file_name, "");
+	g_bytes_unref(bytes);
+	return FALSE;
+}
+
+void _systray_menu_item_selected(int *id) {
+	systray_menu_item_selected(*id);
+}
+
+// runs in main thread, should always return FALSE to prevent gtk to execute it again
+gboolean do_add_or_update_menu_item(gpointer data) {
+	MenuItemInfo *mii = (MenuItemInfo*)data;
+	GList* it;
+	for(it = global_menu_items; it != NULL; it = it->next) {
+		MenuItemNode* item = (MenuItemNode*)(it->data);
+		if(item->menu_id == mii->menu_id){
+			gtk_menu_item_set_label(GTK_MENU_ITEM(item->menu_item), mii->title);
+			break;
+		}
+	}
+
+	// menu id doesn't exist, add new item
+	if(it == NULL) {
+		GtkWidget *menu_item = gtk_menu_item_new_with_label(mii->title);
+		int *id = malloc(sizeof(int));
+		*id = mii->menu_id;
+		g_signal_connect_swapped(G_OBJECT(menu_item), "activate", G_CALLBACK(_systray_menu_item_selected), id);
+		gtk_menu_shell_append(GTK_MENU_SHELL(global_tray_menu), menu_item);
+
+		MenuItemNode* new_item = malloc(sizeof(MenuItemNode));
+		new_item->menu_id = mii->menu_id;
+		new_item->menu_item = menu_item;
+		GList* new_node = malloc(sizeof(GList));
+		new_node->data = new_item;
+		new_node->next = global_menu_items;
+		if(global_menu_items != NULL) {
+			global_menu_items->prev = new_node;
+		}
+		global_menu_items = new_node;
+		it = new_node;
+	}
+	GtkWidget * menu_item = GTK_WIDGET(((MenuItemNode*)(it->data))->menu_item);
+	gtk_widget_set_sensitive(menu_item, mii->disabled == 1 ? FALSE : TRUE);
+	gtk_widget_show(menu_item);
+
+	free(mii->title);
+	free(mii->tooltip);
+	free(mii);
+	return FALSE;
+}
+
+gboolean do_add_separator(gpointer data) {
+	GtkWidget *separator = gtk_separator_menu_item_new();
+	gtk_menu_shell_append(GTK_MENU_SHELL(global_tray_menu), separator);
+	gtk_widget_show(separator);
+	return FALSE;
+}
+
+// runs in main thread, should always return FALSE to prevent gtk to execute it again
+gboolean do_hide_menu_item(gpointer data) {
+	MenuItemInfo *mii = (MenuItemInfo*)data;
+	GList* it;
+	for(it = global_menu_items; it != NULL; it = it->next) {
+		MenuItemNode* item = (MenuItemNode*)(it->data);
+		if(item->menu_id == mii->menu_id){
+			gtk_widget_hide(GTK_WIDGET(item->menu_item));
+			break;
+		}
+	}
+	return FALSE;
+}
+
+// runs in main thread, should always return FALSE to prevent gtk to execute it again
+gboolean do_show_menu_item(gpointer data) {
+	MenuItemInfo *mii = (MenuItemInfo*)data;
+	GList* it;
+	for(it = global_menu_items; it != NULL; it = it->next) {
+		MenuItemNode* item = (MenuItemNode*)(it->data);
+		if(item->menu_id == mii->menu_id){
+			gtk_widget_show(GTK_WIDGET(item->menu_item));
+			break;
+		}
+	}
+	return FALSE;
+}
+
+// runs in main thread, should always return FALSE to prevent gtk to execute it again
+gboolean do_quit(gpointer data) {
+	_unlink_temp_file();
+	// app indicator doesn't provide a way to remove it, hide it as a workaround
+	app_indicator_set_status(global_app_indicator, APP_INDICATOR_STATUS_PASSIVE);
+	gtk_main_quit();
+	return FALSE;
+}
+
+void setIcon(const char* iconBytes, int length) {
+	GBytes* bytes = g_bytes_new_static(iconBytes, length);
+	g_idle_add(do_set_icon, bytes);
+}
+
+void setTitle(char* ctitle) {
+	app_indicator_set_label(global_app_indicator, ctitle, "");
+	free(ctitle);
+}
+
+void setTooltip(char* ctooltip) {
+	free(ctooltip);
+}
+
+void setMenuItemIcon(const char* iconBytes, int length, int menuId) {
+}
+
+void add_or_update_menu_item(int menu_id, char* title, char* tooltip, short disabled, short checked) {
+	MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
+	mii->menu_id = menu_id;
+	mii->title = title;
+	mii->tooltip = tooltip;
+	mii->disabled = disabled;
+	mii->checked = checked;
+	g_idle_add(do_add_or_update_menu_item, mii);
+}
+
+void add_separator(int menu_id) {
+	MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
+	mii->menu_id = menu_id;
+	g_idle_add(do_add_separator, mii);
+}
+
+void hide_menu_item(int menu_id) {
+	MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
+	mii->menu_id = menu_id;
+	g_idle_add(do_hide_menu_item, mii);
+}
+
+void show_menu_item(int menu_id) {
+	MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
+	mii->menu_id = menu_id;
+	g_idle_add(do_show_menu_item, mii);
+}
+
+void quit() {
+	g_idle_add(do_quit, NULL);
+}
diff --git a/vendor/github.com/getlantern/systray/systray_nonwindows.go b/vendor/github.com/getlantern/systray/systray_nonwindows.go
new file mode 100644
index 00000000..4868b554
--- /dev/null
+++ b/vendor/github.com/getlantern/systray/systray_nonwindows.go
@@ -0,0 +1,99 @@
+// +build !windows
+
+package systray
+
+/*
+#cgo linux pkg-config: gtk+-3.0 appindicator3-0.1
+#cgo darwin CFLAGS: -DDARWIN -x objective-c -fobjc-arc
+#cgo darwin LDFLAGS: -framework Cocoa
+
+#include "systray.h"
+*/
+import "C"
+
+import (
+	"unsafe"
+)
+
+func nativeLoop() {
+	C.nativeLoop()
+}
+
+func quit() {
+	C.quit()
+}
+
+// SetIcon sets the systray icon.
+// iconBytes should be the content of .ico for windows and .ico/.jpg/.png
+// for other platforms.
+func SetIcon(iconBytes []byte) {
+	cstr := (*C.char)(unsafe.Pointer(&iconBytes[0]))
+	C.setIcon(cstr, (C.int)(len(iconBytes)))
+}
+
+// SetTitle sets the systray title, only available on Mac.
+func SetTitle(title string) {
+	C.setTitle(C.CString(title))
+}
+
+// SetTooltip sets the systray tooltip to display on mouse hover of the tray icon,
+// only available on Mac and Windows.
+func SetTooltip(tooltip string) {
+	C.setTooltip(C.CString(tooltip))
+}
+
+func addOrUpdateMenuItem(item *MenuItem) {
+	var disabled C.short
+	if item.disabled {
+		disabled = 1
+	}
+	var checked C.short
+	if item.checked {
+		checked = 1
+	}
+	C.add_or_update_menu_item(
+		C.int(item.id),
+		C.CString(item.title),
+		C.CString(item.tooltip),
+		disabled,
+		checked,
+	)
+}
+
+// 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))
+}
+
+func hideMenuItem(item *MenuItem) {
+	C.hide_menu_item(
+		C.int(item.id),
+	)
+}
+
+func showMenuItem(item *MenuItem) {
+	C.show_menu_item(
+		C.int(item.id),
+	)
+}
+
+//export systray_ready
+func systray_ready() {
+	systrayReady()
+}
+
+//export systray_on_exit
+func systray_on_exit() {
+	systrayExit()
+}
+
+//export systray_menu_item_selected
+func systray_menu_item_selected(cID C.int) {
+	systrayMenuItemSelected(int32(cID))
+}
diff --git a/vendor/github.com/getlantern/systray/systray_windows.go b/vendor/github.com/getlantern/systray/systray_windows.go
new file mode 100644
index 00000000..9d560b72
--- /dev/null
+++ b/vendor/github.com/getlantern/systray/systray_windows.go
@@ -0,0 +1,718 @@
+// +build windows
+
+package systray
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sort"
+	"syscall"
+	"unsafe"
+
+	"golang.org/x/sys/windows"
+)
+
+// Helpful sources: https://github.com/golang/exp/blob/master/shiny/driver/internal/win32
+
+var (
+	k32                    = windows.NewLazySystemDLL("Kernel32.dll")
+	s32                    = windows.NewLazySystemDLL("Shell32.dll")
+	u32                    = windows.NewLazySystemDLL("User32.dll")
+	pGetModuleHandle       = k32.NewProc("GetModuleHandleW")
+	pShellNotifyIcon       = s32.NewProc("Shell_NotifyIconW")
+	pCreatePopupMenu       = u32.NewProc("CreatePopupMenu")
+	pCreateWindowEx        = u32.NewProc("CreateWindowExW")
+	pDefWindowProc         = u32.NewProc("DefWindowProcW")
+	pDeleteMenu            = u32.NewProc("DeleteMenu")
+	pDestroyWindow         = u32.NewProc("DestroyWindow")
+	pDispatchMessage       = u32.NewProc("DispatchMessageW")
+	pGetCursorPos          = u32.NewProc("GetCursorPos")
+	pGetMenuItemID         = u32.NewProc("GetMenuItemID")
+	pGetMessage            = u32.NewProc("GetMessageW")
+	pInsertMenuItem        = u32.NewProc("InsertMenuItemW")
+	pLoadIcon              = u32.NewProc("LoadIconW")
+	pLoadImage             = u32.NewProc("LoadImageW")
+	pLoadCursor            = u32.NewProc("LoadCursorW")
+	pPostMessage           = u32.NewProc("PostMessageW")
+	pPostQuitMessage       = u32.NewProc("PostQuitMessage")
+	pRegisterClass         = u32.NewProc("RegisterClassExW")
+	pRegisterWindowMessage = u32.NewProc("RegisterWindowMessageW")
+	pSetForegroundWindow   = u32.NewProc("SetForegroundWindow")
+	pSetMenuInfo           = u32.NewProc("SetMenuInfo")
+	pSetMenuItemInfo       = u32.NewProc("SetMenuItemInfoW")
+	pShowWindow            = u32.NewProc("ShowWindow")
+	pTrackPopupMenu        = u32.NewProc("TrackPopupMenu")
+	pTranslateMessage      = u32.NewProc("TranslateMessage")
+	pUnregisterClass       = u32.NewProc("UnregisterClassW")
+	pUpdateWindow          = u32.NewProc("UpdateWindow")
+)
+
+// Contains window class information.
+// It is used with the RegisterClassEx and GetClassInfoEx functions.
+// https://msdn.microsoft.com/en-us/library/ms633577.aspx
+type wndClassEx struct {
+	Size, Style                        uint32
+	WndProc                            uintptr
+	ClsExtra, WndExtra                 int32
+	Instance, Icon, Cursor, Background windows.Handle
+	MenuName, ClassName                *uint16
+	IconSm                             windows.Handle
+}
+
+// Registers a window class for subsequent use in calls to the CreateWindow or CreateWindowEx function.
+// https://msdn.microsoft.com/en-us/library/ms633587.aspx
+func (w *wndClassEx) register() error {
+	w.Size = uint32(unsafe.Sizeof(*w))
+	res, _, err := pRegisterClass.Call(uintptr(unsafe.Pointer(w)))
+	if res == 0 {
+		return err
+	}
+	return nil
+}
+
+// Unregisters a window class, freeing the memory required for the class.
+// https://msdn.microsoft.com/en-us/library/ms644899.aspx
+func (w *wndClassEx) unregister() error {
+	res, _, err := pUnregisterClass.Call(
+		uintptr(unsafe.Pointer(w.ClassName)),
+		uintptr(w.Instance),
+	)
+	if res == 0 {
+		return err
+	}
+	return nil
+}
+
+// Contains information that the system needs to display notifications in the notification area.
+// Used by Shell_NotifyIcon.
+// https://msdn.microsoft.com/en-us/library/windows/desktop/bb773352(v=vs.85).aspx
+// https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159
+type notifyIconData struct {
+	Size                       uint32
+	Wnd                        windows.Handle
+	ID, Flags, CallbackMessage uint32
+	Icon                       windows.Handle
+	Tip                        [128]uint16
+	State, StateMask           uint32
+	Info                       [256]uint16
+	Timeout, Version           uint32
+	InfoTitle                  [64]uint16
+	InfoFlags                  uint32
+	GuidItem                   windows.GUID
+	BalloonIcon                windows.Handle
+}
+
+func (nid *notifyIconData) add() error {
+	const NIM_ADD = 0x00000000
+	res, _, err := pShellNotifyIcon.Call(
+		uintptr(NIM_ADD),
+		uintptr(unsafe.Pointer(nid)),
+	)
+	if res == 0 {
+		return err
+	}
+	return nil
+}
+
+func (nid *notifyIconData) modify() error {
+	const NIM_MODIFY = 0x00000001
+	res, _, err := pShellNotifyIcon.Call(
+		uintptr(NIM_MODIFY),
+		uintptr(unsafe.Pointer(nid)),
+	)
+	if res == 0 {
+		return err
+	}
+	return nil
+}
+
+func (nid *notifyIconData) delete() error {
+	const NIM_DELETE = 0x00000002
+	res, _, err := pShellNotifyIcon.Call(
+		uintptr(NIM_DELETE),
+		uintptr(unsafe.Pointer(nid)),
+	)
+	if res == 0 {
+		return err
+	}
+	return nil
+}
+
+// Contains information about a menu item.
+// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
+type menuItemInfo struct {
+	Size, Mask, Type, State     uint32
+	ID                          uint32
+	SubMenu, Checked, Unchecked windows.Handle
+	ItemData                    uintptr
+	TypeData                    *uint16
+	Cch                         uint32
+	Item                        windows.Handle
+}
+
+// The POINT structure defines the x- and y- coordinates of a point.
+// https://msdn.microsoft.com/en-us/library/windows/desktop/dd162805(v=vs.85).aspx
+type point struct {
+	X, Y int32
+}
+
+// Contains information about loaded resources
+type winTray struct {
+	instance,
+	icon,
+	cursor,
+	window,
+	menu windows.Handle
+
+	loadedImages map[string]windows.Handle
+	nid          *notifyIconData
+	wcex         *wndClassEx
+
+	wmSystrayMessage,
+	wmTaskbarCreated uint32
+
+	visibleItems []uint32
+}
+
+// Loads an image from file and shows it in tray.
+// LoadImage: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx
+// Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx
+func (t *winTray) setIcon(src string) error {
+	const IMAGE_ICON = 1               // Loads an icon
+	const LR_LOADFROMFILE = 0x00000010 // Loads the stand-alone image from the file
+	const NIF_ICON = 0x00000002
+
+	// Save and reuse handles of loaded images
+	h, ok := t.loadedImages[src]
+	if !ok {
+		srcPtr, err := windows.UTF16PtrFromString(src)
+		if err != nil {
+			return err
+		}
+		res, _, err := pLoadImage.Call(
+			0,
+			uintptr(unsafe.Pointer(srcPtr)),
+			IMAGE_ICON,
+			64,
+			64,
+			LR_LOADFROMFILE,
+		)
+		if res == 0 {
+			return err
+		}
+		h = windows.Handle(res)
+		t.loadedImages[src] = h
+	}
+
+	t.nid.Icon = h
+	t.nid.Flags |= NIF_ICON
+	t.nid.Size = uint32(unsafe.Sizeof(*t.nid))
+
+	return t.nid.modify()
+}
+
+// Sets tooltip on icon.
+// Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx
+func (t *winTray) setTooltip(src string) error {
+	const NIF_TIP = 0x00000004
+	b, err := windows.UTF16FromString(src)
+	if err != nil {
+		return err
+	}
+	copy(t.nid.Tip[:], b[:])
+	t.nid.Flags |= NIF_TIP
+	t.nid.Size = uint32(unsafe.Sizeof(*t.nid))
+
+	return t.nid.modify()
+}
+
+var wt winTray
+
+// WindowProc callback function that processes messages sent to a window.
+// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx
+func (t *winTray) wndProc(hWnd windows.Handle, message uint32, wParam, lParam uintptr) (lResult uintptr) {
+	const (
+		WM_COMMAND    = 0x0111
+		WM_DESTROY    = 0x0002
+		WM_ENDSESSION = 0x16
+		WM_RBUTTONUP  = 0x0205
+		WM_LBUTTONUP  = 0x0202
+	)
+	switch message {
+	case WM_COMMAND:
+		menuId := int32(wParam)
+		if menuId != -1 {
+			systrayMenuItemSelected(menuId)
+		}
+	case WM_DESTROY:
+		// same as WM_ENDSESSION, but throws 0 exit code after all
+		defer pPostQuitMessage.Call(uintptr(int32(0)))
+		fallthrough
+	case WM_ENDSESSION:
+		if t.nid != nil {
+			t.nid.delete()
+		}
+		systrayExit()
+	case t.wmSystrayMessage:
+		switch lParam {
+		case WM_RBUTTONUP, WM_LBUTTONUP:
+			t.showMenu()
+		}
+	case t.wmTaskbarCreated: // on explorer.exe restarts
+		t.nid.add()
+	default:
+		// Calls the default window procedure to provide default processing for any window messages that an application does not process.
+		// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633572(v=vs.85).aspx
+		lResult, _, _ = pDefWindowProc.Call(
+			uintptr(hWnd),
+			uintptr(message),
+			uintptr(wParam),
+			uintptr(lParam),
+		)
+	}
+	return
+}
+
+func (t *winTray) initInstance() error {
+	const IDI_APPLICATION = 32512
+	const IDC_ARROW = 32512 // Standard arrow
+	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
+	const SW_HIDE = 0
+	const CW_USEDEFAULT = 0x80000000
+	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx
+	const (
+		WS_CAPTION     = 0x00C00000
+		WS_MAXIMIZEBOX = 0x00010000
+		WS_MINIMIZEBOX = 0x00020000
+		WS_OVERLAPPED  = 0x00000000
+		WS_SYSMENU     = 0x00080000
+		WS_THICKFRAME  = 0x00040000
+
+		WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
+	)
+	// https://msdn.microsoft.com/en-us/library/windows/desktop/ff729176
+	const (
+		CS_HREDRAW = 0x0002
+		CS_VREDRAW = 0x0001
+	)
+	const NIF_MESSAGE = 0x00000001
+
+	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644931(v=vs.85).aspx
+	const WM_USER = 0x0400
+
+	const (
+		className  = "SystrayClass"
+		windowName = ""
+	)
+
+	t.wmSystrayMessage = WM_USER + 1
+
+	taskbarEventNamePtr, _ := windows.UTF16PtrFromString("TaskbarCreated")
+	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644947
+	res, _, err := pRegisterWindowMessage.Call(
+		uintptr(unsafe.Pointer(taskbarEventNamePtr)),
+	)
+	t.wmTaskbarCreated = uint32(res)
+
+	t.loadedImages = make(map[string]windows.Handle)
+
+	instanceHandle, _, err := pGetModuleHandle.Call(0)
+	if instanceHandle == 0 {
+		return err
+	}
+	t.instance = windows.Handle(instanceHandle)
+
+	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648072(v=vs.85).aspx
+	iconHandle, _, err := pLoadIcon.Call(0, uintptr(IDI_APPLICATION))
+	if iconHandle == 0 {
+		return err
+	}
+	t.icon = windows.Handle(iconHandle)
+
+	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
+	cursorHandle, _, err := pLoadCursor.Call(0, uintptr(IDC_ARROW))
+	if cursorHandle == 0 {
+		return err
+	}
+	t.cursor = windows.Handle(cursorHandle)
+
+	classNamePtr, err := windows.UTF16PtrFromString(className)
+	if err != nil {
+		return err
+	}
+
+	windowNamePtr, err := windows.UTF16PtrFromString(windowName)
+	if err != nil {
+		return err
+	}
+
+	t.wcex = &wndClassEx{
+		Style:      CS_HREDRAW | CS_VREDRAW,
+		WndProc:    windows.NewCallback(t.wndProc),
+		Instance:   t.instance,
+		Icon:       t.icon,
+		Cursor:     t.cursor,
+		Background: windows.Handle(6), // (COLOR_WINDOW + 1)
+		ClassName:  classNamePtr,
+		IconSm:     t.icon,
+	}
+	if err := t.wcex.register(); err != nil {
+		return err
+	}
+
+	windowHandle, _, err := pCreateWindowEx.Call(
+		uintptr(0),
+		uintptr(unsafe.Pointer(classNamePtr)),
+		uintptr(unsafe.Pointer(windowNamePtr)),
+		uintptr(WS_OVERLAPPEDWINDOW),
+		uintptr(CW_USEDEFAULT),
+		uintptr(CW_USEDEFAULT),
+		uintptr(CW_USEDEFAULT),
+		uintptr(CW_USEDEFAULT),
+		uintptr(0),
+		uintptr(0),
+		uintptr(t.instance),
+		uintptr(0),
+	)
+	if windowHandle == 0 {
+		return err
+	}
+	t.window = windows.Handle(windowHandle)
+
+	pShowWindow.Call(
+		uintptr(t.window),
+		uintptr(SW_HIDE),
+	)
+
+	pUpdateWindow.Call(
+		uintptr(t.window),
+	)
+
+	t.nid = &notifyIconData{
+		Wnd:             windows.Handle(t.window),
+		ID:              100,
+		Flags:           NIF_MESSAGE,
+		CallbackMessage: t.wmSystrayMessage,
+	}
+	t.nid.Size = uint32(unsafe.Sizeof(*t.nid))
+
+	return t.nid.add()
+}
+
+func (t *winTray) createMenu() error {
+	const MIM_APPLYTOSUBMENUS = 0x80000000 // Settings apply to the menu and all of its submenus
+
+	menuHandle, _, err := pCreatePopupMenu.Call()
+	if menuHandle == 0 {
+		return err
+	}
+	t.menu = windows.Handle(menuHandle)
+
+	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647575(v=vs.85).aspx
+	mi := struct {
+		Size, Mask, Style, Max uint32
+		Background             windows.Handle
+		ContextHelpID          uint32
+		MenuData               uintptr
+	}{
+		Mask: MIM_APPLYTOSUBMENUS,
+	}
+	mi.Size = uint32(unsafe.Sizeof(mi))
+
+	res, _, err := pSetMenuInfo.Call(
+		uintptr(t.menu),
+		uintptr(unsafe.Pointer(&mi)),
+	)
+	if res == 0 {
+		return err
+	}
+	return nil
+}
+
+func (t *winTray) addOrUpdateMenuItem(menuId int32, title string, disabled, checked bool) error {
+	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
+	const (
+		MIIM_FTYPE  = 0x00000100
+		MIIM_STRING = 0x00000040
+		MIIM_ID     = 0x00000002
+		MIIM_STATE  = 0x00000001
+	)
+	const MFT_STRING = 0x00000000
+	const (
+		MFS_CHECKED  = 0x00000008
+		MFS_DISABLED = 0x00000003
+	)
+	titlePtr, err := windows.UTF16PtrFromString(title)
+	if err != nil {
+		return err
+	}
+
+	mi := menuItemInfo{
+		Mask:     MIIM_FTYPE | MIIM_STRING | MIIM_ID | MIIM_STATE,
+		Type:     MFT_STRING,
+		ID:       uint32(menuId),
+		TypeData: titlePtr,
+		Cch:      uint32(len(title)),
+	}
+	if disabled {
+		mi.State |= MFS_DISABLED
+	}
+	if checked {
+		mi.State |= MFS_CHECKED
+	}
+	mi.Size = uint32(unsafe.Sizeof(mi))
+
+	// We set the menu item info based on the menuID
+	res, _, err := pSetMenuItemInfo.Call(
+		uintptr(t.menu),
+		uintptr(menuId),
+		0,
+		uintptr(unsafe.Pointer(&mi)),
+	)
+
+	if res == 0 {
+		t.addToVisibleItems(menuId)
+		position := t.getVisibleItemIndex(menuId)
+		res, _, err = pInsertMenuItem.Call(
+			uintptr(t.menu),
+			uintptr(position),
+			1,
+			uintptr(unsafe.Pointer(&mi)),
+		)
+		if res == 0 {
+			t.delFromVisibleItems(menuId)
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (t *winTray) addSeparatorMenuItem(menuId int32) error {
+	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
+	const (
+		MIIM_FTYPE = 0x00000100
+		MIIM_ID    = 0x00000002
+		MIIM_STATE = 0x00000001
+	)
+	const MFT_SEPARATOR = 0x00000800
+
+	mi := menuItemInfo{
+		Mask: MIIM_FTYPE | MIIM_ID | MIIM_STATE,
+		Type: MFT_SEPARATOR,
+		ID:   uint32(menuId),
+	}
+
+	mi.Size = uint32(unsafe.Sizeof(mi))
+
+	res, _, err := pInsertMenuItem.Call(
+		uintptr(t.menu),
+		uintptr(menuId),
+		1,
+		uintptr(unsafe.Pointer(&mi)),
+	)
+	if res == 0 {
+		return err
+	}
+
+	return nil
+}
+
+func (t *winTray) hideMenuItem(menuId int32) error {
+	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647629(v=vs.85).aspx
+	const MF_BYCOMMAND = 0x00000000
+	const ERROR_SUCCESS syscall.Errno = 0
+
+	res, _, err := pDeleteMenu.Call(
+		uintptr(t.menu),
+		uintptr(uint32(menuId)),
+		MF_BYCOMMAND,
+	)
+	if res == 0 && err.(syscall.Errno) != ERROR_SUCCESS {
+		return err
+	}
+	t.delFromVisibleItems(menuId)
+
+	return nil
+}
+
+func (t *winTray) showMenu() error {
+	const (
+		TPM_BOTTOMALIGN = 0x0020
+		TPM_LEFTALIGN   = 0x0000
+	)
+	p := point{}
+	res, _, err := pGetCursorPos.Call(uintptr(unsafe.Pointer(&p)))
+	if res == 0 {
+		return err
+	}
+	pSetForegroundWindow.Call(uintptr(t.window))
+
+	res, _, err = pTrackPopupMenu.Call(
+		uintptr(t.menu),
+		TPM_BOTTOMALIGN|TPM_LEFTALIGN,
+		uintptr(p.X),
+		uintptr(p.Y),
+		0,
+		uintptr(t.window),
+		0,
+	)
+	if res == 0 {
+		return err
+	}
+
+	return nil
+}
+
+func (t *winTray) delFromVisibleItems(val int32) {
+	for i, itemval := range t.visibleItems {
+		if uint32(val) == itemval {
+			t.visibleItems = append(t.visibleItems[:i], t.visibleItems[i+1:]...)
+			break
+		}
+	}
+}
+
+func (t *winTray) addToVisibleItems(val int32) {
+	newvisible := append(t.visibleItems, uint32(val))
+	sort.Slice(newvisible, func(i, j int) bool { return newvisible[i] < newvisible[j] })
+	t.visibleItems = newvisible
+}
+
+func (t *winTray) getVisibleItemIndex(val int32) int {
+	for i, itemval := range t.visibleItems {
+		if uint32(val) == itemval {
+			return i
+		}
+	}
+	return -1
+}
+
+func nativeLoop() {
+	if err := wt.initInstance(); err != nil {
+		log.Errorf("Unable to init instance: %v", err)
+		return
+	}
+
+	if err := wt.createMenu(); err != nil {
+		log.Errorf("Unable to create menu: %v", err)
+		return
+	}
+
+	defer func() {
+		pDestroyWindow.Call(uintptr(wt.window))
+		wt.wcex.unregister()
+	}()
+
+	go systrayReady()
+
+	// Main message pump.
+	m := &struct {
+		WindowHandle windows.Handle
+		Message      uint32
+		Wparam       uintptr
+		Lparam       uintptr
+		Time         uint32
+		Pt           point
+	}{}
+	for {
+		ret, _, err := pGetMessage.Call(uintptr(unsafe.Pointer(m)), 0, 0, 0)
+
+		// If the function retrieves a message other than WM_QUIT, the return value is nonzero.
+		// If the function retrieves the WM_QUIT message, the return value is zero.
+		// If there is an error, the return value is -1
+		// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936(v=vs.85).aspx
+		switch int32(ret) {
+		case -1:
+			log.Errorf("Error at message loop: %v", err)
+			return
+		case 0:
+			return
+		default:
+			pTranslateMessage.Call(uintptr(unsafe.Pointer(m)))
+			pDispatchMessage.Call(uintptr(unsafe.Pointer(m)))
+		}
+	}
+}
+
+func quit() {
+	const WM_CLOSE = 0x0010
+
+	pPostMessage.Call(
+		uintptr(wt.window),
+		WM_CLOSE,
+		0,
+		0,
+	)
+}
+
+// SetIcon sets the systray icon.
+// iconBytes should be the content of .ico for windows and .ico/.jpg/.png
+// for other platforms.
+func SetIcon(iconBytes []byte) {
+	bh := md5.Sum(iconBytes)
+	dataHash := hex.EncodeToString(bh[:])
+	iconFilePath := filepath.Join(os.TempDir(), "systray_temp_icon_"+dataHash)
+
+	if _, err := os.Stat(iconFilePath); os.IsNotExist(err) {
+		if err := ioutil.WriteFile(iconFilePath, iconBytes, 0644); err != nil {
+			log.Errorf("Unable to write icon data to temp file: %v", err)
+			return
+		}
+	}
+
+	if err := wt.setIcon(iconFilePath); err != nil {
+		log.Errorf("Unable to set icon: %v", err)
+		return
+	}
+}
+
+// SetTitle sets the systray title, only available on Mac.
+func SetTitle(title string) {
+	// do nothing
+}
+
+// SetIcon sets the icon of a menu item. Only available on Mac.
+func (item *MenuItem) SetIcon(iconBytes []byte) {
+	// do nothing
+}
+
+// SetTooltip sets the systray tooltip to display on mouse hover of the tray icon,
+// only available on Mac and Windows.
+func SetTooltip(tooltip string) {
+	if err := wt.setTooltip(tooltip); err != nil {
+		log.Errorf("Unable to set tooltip: %v", err)
+		return
+	}
+}
+
+func addOrUpdateMenuItem(item *MenuItem) {
+	err := wt.addOrUpdateMenuItem(item.id, item.title, item.disabled, item.checked)
+	if err != nil {
+		log.Errorf("Unable to addOrUpdateMenuItem: %v", err)
+		return
+	}
+}
+
+func addSeparator(id int32) {
+	err := wt.addSeparatorMenuItem(id)
+	if err != nil {
+		log.Errorf("Unable to addSeparator: %v", err)
+		return
+	}
+}
+
+func hideMenuItem(item *MenuItem) {
+	err := wt.hideMenuItem(item.id)
+	if err != nil {
+		log.Errorf("Unable to hideMenuItem: %v", err)
+		return
+	}
+}
+
+func showMenuItem(item *MenuItem) {
+	addOrUpdateMenuItem(item)
+}
diff --git a/vendor/github.com/getlantern/systray/systray_windows_test.go b/vendor/github.com/getlantern/systray/systray_windows_test.go
new file mode 100644
index 00000000..7cb6c757
--- /dev/null
+++ b/vendor/github.com/getlantern/systray/systray_windows_test.go
@@ -0,0 +1,132 @@
+// +build windows
+
+package systray
+
+import (
+	"io/ioutil"
+	"runtime"
+	"sync/atomic"
+	"testing"
+	"time"
+	"unsafe"
+
+	"golang.org/x/sys/windows"
+)
+
+const iconFilePath = "example/icon/iconwin.ico"
+
+func TestBaseWindowsTray(t *testing.T) {
+	systrayReady = func() {}
+	systrayExit = func() {}
+
+	runtime.LockOSThread()
+
+	if err := wt.initInstance(); err != nil {
+		t.Fatalf("initInstance failed: %s", err)
+	}
+
+	if err := wt.createMenu(); err != nil {
+		t.Fatalf("createMenu failed: %s", err)
+	}
+
+	defer func() {
+		pDestroyWindow.Call(uintptr(wt.window))
+		wt.wcex.unregister()
+	}()
+
+	if err := wt.setIcon(iconFilePath); err != nil {
+		t.Errorf("SetIcon failed: %s", err)
+	}
+
+	if err := wt.setTooltip("Cyrillic tooltip тест:)"); err != nil {
+		t.Errorf("SetIcon failed: %s", err)
+	}
+
+	var id int32 = 0
+	err := wt.addOrUpdateMenuItem(atomic.AddInt32(&id, 1), "Simple enabled", false, false)
+	if err != nil {
+		t.Errorf("mergeMenuItem failed: %s", err)
+	}
+	err = wt.addOrUpdateMenuItem(atomic.AddInt32(&id, 1), "Simple disabled", true, false)
+	if err != nil {
+		t.Errorf("mergeMenuItem failed: %s", err)
+	}
+	err = wt.addSeparatorMenuItem(atomic.AddInt32(&id, 1))
+	if err != nil {
+		t.Errorf("addSeparatorMenuItem failed: %s", err)
+	}
+	err = wt.addOrUpdateMenuItem(atomic.AddInt32(&id, 1), "Simple checked enabled", false, true)
+	if err != nil {
+		t.Errorf("mergeMenuItem failed: %s", err)
+	}
+	err = wt.addOrUpdateMenuItem(atomic.AddInt32(&id, 1), "Simple checked disabled", true, true)
+	if err != nil {
+		t.Errorf("mergeMenuItem failed: %s", err)
+	}
+
+	err = wt.hideMenuItem(1)
+	if err != nil {
+		t.Errorf("hideMenuItem failed: %s", err)
+	}
+
+	err = wt.hideMenuItem(100)
+	if err == nil {
+		t.Error("hideMenuItem failed: must return error on invalid item id")
+	}
+
+	err = wt.addOrUpdateMenuItem(2, "Simple disabled update", true, false)
+	if err != nil {
+		t.Errorf("mergeMenuItem failed: %s", err)
+	}
+
+	time.AfterFunc(1*time.Second, quit)
+
+	m := struct {
+		WindowHandle windows.Handle
+		Message      uint32
+		Wparam       uintptr
+		Lparam       uintptr
+		Time         uint32
+		Pt           point
+	}{}
+	for {
+		ret, _, err := pGetMessage.Call(uintptr(unsafe.Pointer(&m)), 0, 0, 0)
+		res := int32(ret)
+		if res == -1 {
+			t.Errorf("win32 GetMessage failed: %v", err)
+			return
+		} else if res == 0 {
+			break
+		}
+		pTranslateMessage.Call(uintptr(unsafe.Pointer(&m)))
+		pDispatchMessage.Call(uintptr(unsafe.Pointer(&m)))
+	}
+}
+
+func TestWindowsRun(t *testing.T) {
+	onReady := func() {
+		b, err := ioutil.ReadFile(iconFilePath)
+		if err != nil {
+			t.Fatalf("Can't load icon file: %v", err)
+		}
+		SetIcon(b)
+		SetTitle("Test title с кириллицей")
+
+		bSomeBtn := AddMenuItem("Йа кнопко", "")
+		bSomeBtn.Check()
+		AddSeparator()
+		bQuit := AddMenuItem("Quit", "Quit the whole app")
+		go func() {
+			<-bQuit.ClickedCh
+			t.Log("Quit reqested")
+			Quit()
+		}()
+		time.AfterFunc(1*time.Second, Quit)
+	}
+
+	onExit := func() {
+		t.Log("Exit success")
+	}
+
+	Run(onReady, onExit)
+}
-- 
GitLab