diff --git a/README.md b/README.md
index f83d70b..7fae678 100644
--- a/README.md
+++ b/README.md
@@ -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:
+
+```
+
+
+
+
+
+
+
+
+
+
+ PerMonitorV2, PerMonitor
+ True
+
+
+
+```
+
+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:
diff --git a/example/example.manifest b/example/example.manifest
new file mode 100644
index 0000000..bf75b80
--- /dev/null
+++ b/example/example.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+ PerMonitorV2, PerMonitor
+ True
+
+
+
\ No newline at end of file
diff --git a/example/main.go b/example/main.go
index 5542b89..5efccbd 100644
--- a/example/main.go
+++ b/example/main.go
@@ -7,7 +7,6 @@ import (
"github.com/getlantern/systray"
"github.com/getlantern/systray/example/icon"
- "github.com/skratchdot/open-golang/open"
)
func main() {
@@ -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() {
@@ -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.
@@ -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()
diff --git a/example/rsrc.syso b/example/rsrc.syso
new file mode 100644
index 0000000..dbad196
Binary files /dev/null and b/example/rsrc.syso differ
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..505047b
--- /dev/null
+++ b/go.mod
@@ -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
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..9e9e928
--- /dev/null
+++ b/go.sum
@@ -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=
diff --git a/systray.go b/systray.go
index d433173..750e5f3 100644
--- a/systray.go
+++ b/systray.go
@@ -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)
@@ -78,7 +82,7 @@ func Run(onReady func(), onExit func()) {
}
systrayExit = onExit
- nativeLoop()
+ nativeLoop(title, width, height)
}
// Quit the systray
diff --git a/systray.h b/systray.h
index 36bcf98..128315f 100644
--- a/systray.h
+++ b/systray.h
@@ -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);
diff --git a/systray_browser_darwin.m b/systray_browser_darwin.m
new file mode 100644
index 0000000..ca7fa18
--- /dev/null
+++ b/systray_browser_darwin.m
@@ -0,0 +1,72 @@
+#import
+#import
+#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);
+ });
+}
diff --git a/systray_browser_linux.c b/systray_browser_linux.c
new file mode 100644
index 0000000..493d980
--- /dev/null
+++ b/systray_browser_linux.c
@@ -0,0 +1,57 @@
+#include
+#include
+
+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);
+}
\ No newline at end of file
diff --git a/systray_darwin.m b/systray_darwin.m
index b0f2172..fff2465 100644
--- a/systray_darwin.m
+++ b/systray_darwin.m
@@ -70,6 +70,11 @@
systray_ready();
}
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
+{
+ return FALSE;
+}
+
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
systray_on_exit();
@@ -182,9 +187,14 @@
@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;
}
diff --git a/systray_linux.c b/systray_linux.c
index 72cd614..7b045f2 100644
--- a/systray_linux.c
+++ b/systray_linux.c
@@ -23,7 +23,9 @@ 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);
@@ -31,6 +33,9 @@ int nativeLoop(void) {
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;
diff --git a/systray_nonwindows.go b/systray_nonwindows.go
index 4868b55..598df9e 100644
--- a/systray_nonwindows.go
+++ b/systray_nonwindows.go
@@ -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"
*/
@@ -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.
diff --git a/systray_windows.go b/systray_windows.go
index 1cb453c..4f8e731 100644
--- a/systray_windows.go
+++ b/systray_windows.go
@@ -3,719 +3,178 @@
package systray
import (
- "crypto/md5"
- "encoding/hex"
+ "fmt"
"io/ioutil"
"os"
- "path/filepath"
- "sort"
- "syscall"
- "unsafe"
+ "sync/atomic"
- "golang.org/x/sys/windows"
+ "github.com/lxn/walk"
)
-// 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")
+ tmpDir string
+ mainWindow *walk.MainWindow
+ webView *walk.WebView
+ notifyIcon *walk.NotifyIcon
+
+ actions = make(map[int32]*walk.Action)
+ nextActionId int32
+
+ okayToClose int32
)
-// 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
+func nativeLoop(title string, width int, height int) {
+ var err error
+ mainWindow, err = walk.NewMainWindow()
+ if err != nil {
+ fail("Unable to create main window", 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
+ mainWindow.Closing().Attach(func(canceled *bool, reason walk.CloseReason) {
+ fmt.Println(reason)
+ // don't close app unless we're actually finished
+ actuallyClose := atomic.LoadInt32(&okayToClose) == 1
+ *canceled = !actuallyClose
+ if !actuallyClose {
+ mainWindow.SetVisible(false)
+ }
+ })
+ layout := walk.NewVBoxLayout()
+ if err := mainWindow.SetLayout(layout); err != nil {
+ fail("Unable to set main layout", 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
+ notifyIcon, err = walk.NewNotifyIcon(mainWindow)
+ if err != nil {
+ fail("Unable to create notify icon", 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 LR_DEFAULTSIZE = 0x00000040 // Loads default-size icon for windows(SM_CXICON x SM_CYICON) if cx, cy are set to zero
- const NIF_ICON = 0x00000002
-
- // Save and reuse handles of loaded images
- h, ok := t.loadedImages[src]
- if !ok {
- srcPtr, err := windows.UTF16PtrFromString(src)
+ if title != "" {
+ webView, err = walk.NewWebView(mainWindow)
if err != nil {
- return err
+ fail("Unable to create web view", err)
}
- res, _, err := pLoadImage.Call(
- 0,
- uintptr(unsafe.Pointer(srcPtr)),
- IMAGE_ICON,
- 0,
- 0,
- LR_LOADFROMFILE|LR_DEFAULTSIZE,
- )
- if res == 0 {
- return err
+ if err := mainWindow.SetTitle(title); err != nil {
+ fail("Unable to set main title", 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)
+ if err := mainWindow.SetWidth(width); err != nil {
+ fail("Unable to set width", err)
}
- 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 = ¬ifyIconData{
- 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))
-
- t.addToVisibleItems(menuId)
- position := t.getVisibleItemIndex(menuId)
- res, _, err := pInsertMenuItem.Call(
- uintptr(t.menu),
- uintptr(position),
- 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)))
+ if err := mainWindow.SetHeight(height); err != nil {
+ fail("Unable to set height", err)
}
}
+ systrayReady()
+ mainWindow.Run()
}
func quit() {
- const WM_CLOSE = 0x0010
-
- pPostMessage.Call(
- uintptr(wt.window),
- WM_CLOSE,
- 0,
- 0,
- )
+ atomic.StoreInt32(&okayToClose, 1)
+ mainWindow.Close()
+ notifyIcon.Dispose()
+ systrayExit()
}
// 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
- }
+ filename := "systray.ico"
+ err := ioutil.WriteFile(filename, iconBytes, 0644)
+ if err != nil {
+ fail("Unable to save icon to disk", err)
}
-
- if err := wt.setIcon(iconFilePath); err != nil {
- log.Errorf("Unable to set icon: %v", err)
- return
+ defer os.Remove(filename)
+ icon, err := walk.Resources.Icon(filename)
+ if err != nil {
+ fail("Unable to load icon", err)
+ }
+ err = notifyIcon.SetIcon(icon)
+ if err != nil {
+ fail("Unable to set systray icon", err)
+ }
+ err = notifyIcon.SetVisible(true)
+ if err != nil {
+ fail("Unable to make systray icon visible", err)
}
}
// 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
+ // not supported on Windows
}
// 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)
+ if err := notifyIcon.SetToolTip(tooltip); err != nil {
+ fail("Unable to set tooltip", err)
+ }
+}
+
+// ShowAppWindow shows the given URL in the application window. Only works if
+// configureAppWindow has been called first.
+func ShowAppWindow(url string) {
+ if webView == nil {
return
}
+ webView.SetURL(url)
+ mainWindow.SetVisible(true)
}
func addOrUpdateMenuItem(item *MenuItem) {
- err := wt.addOrUpdateMenuItem(item.id, item.title, item.disabled, item.checked)
+ action := actions[item.id]
+ if action == nil {
+ item.id = atomic.AddInt32(&nextActionId, 1)
+ action = walk.NewAction()
+ action.Triggered().Attach(func() {
+ item.ClickedCh <- struct{}{}
+ })
+ if err := notifyIcon.ContextMenu().Actions().Add(action); err != nil {
+ fail("Unable to add menu item to systray", err)
+ }
+ actions[item.id] = action
+ }
+ err := action.SetText(item.title)
if err != nil {
- log.Errorf("Unable to addOrUpdateMenuItem: %v", err)
- return
+ fail("Unable to set menu item text", err)
+ }
+ err = action.SetChecked(item.checked)
+ if err != nil {
+ fail("Unable to set menu item checked", err)
+ }
+ err = action.SetEnabled(!item.Disabled())
+ if err != nil {
+ fail("Unable to set menu item enabled", err)
}
}
-func addSeparator(id int32) {
- err := wt.addSeparatorMenuItem(id)
+func (item *MenuItem) SetIcon(iconBytes []byte) {
+ filename := fmt.Sprintf("systray.%d.ico", item.id)
+ err := ioutil.WriteFile(filename, iconBytes, 0644)
if err != nil {
- log.Errorf("Unable to addSeparator: %v", err)
- return
+ fail("Unable to save icon to disk", err)
+ }
+ defer os.Remove(filename)
+ icon, err := walk.Resources.Image(filename)
+ if err != nil {
+ fail("Unable to load icon", err)
+ }
+ actions[item.id].SetImage(icon)
+}
+
+func addSeparator(id int32) {
+ action := walk.NewSeparatorAction()
+ if err := notifyIcon.ContextMenu().Actions().Add(action); err != nil {
+ fail("Unable to add separator", err)
}
}
func hideMenuItem(item *MenuItem) {
- err := wt.hideMenuItem(item.id)
- if err != nil {
- log.Errorf("Unable to hideMenuItem: %v", err)
- return
- }
+ actions[item.id].SetVisible(false)
}
func showMenuItem(item *MenuItem) {
- addOrUpdateMenuItem(item)
+ actions[item.id].SetVisible(true)
+}
+
+func fail(msg string, err error) {
+ panic(fmt.Errorf("%v: %v", msg, err))
}
diff --git a/systray_windows_test.go b/systray_windows_test.go
deleted file mode 100644
index 7cb6c75..0000000
--- a/systray_windows_test.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// +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)
-}