diff --git a/example/main.go b/example/main.go index f426fca..a7308bf 100644 --- a/example/main.go +++ b/example/main.go @@ -6,8 +6,6 @@ import ( "io/ioutil" "time" - "github.com/lxn/walk" - "github.com/getlantern/systray" "github.com/getlantern/systray/example/icon" "github.com/skratchdot/open-golang/open" @@ -25,26 +23,8 @@ func main() { } if *webview { - systray.Register(onExit) - onReady() - mainWindow, err := walk.NewMainWindow() - if err != nil { - panic(fmt.Sprintf("Failed to create main window: %v\n", err)) - } - mainWindow.SetTitle("Webview") - mainWindow.SetWidth(800) - mainWindow.SetHeight(600) - layout := walk.NewVBoxLayout() - if err := mainWindow.SetLayout(layout); err != nil { - panic(fmt.Sprintf("Failed to set layout: %v\n", err)) - } - webView, err := walk.NewWebView(mainWindow) - if err != nil { - panic(fmt.Sprintf("Failed to create webview window: %v\n", err)) - } - webView.SetURL("https://www.getlantern.org") - mainWindow.SetVisible(true) - mainWindow.Run() + systray.Register(onReady, onExit) + showWebviewOnWindows() } else { systray.Run(onReady, onExit) } diff --git a/example/webview_nonwindows.go b/example/webview_nonwindows.go new file mode 100644 index 0000000..9496193 --- /dev/null +++ b/example/webview_nonwindows.go @@ -0,0 +1,7 @@ +//+build !windows + +package main + +func showWebviewOnWindows() { + panic("not implemented") +} diff --git a/example/webview_windows.go b/example/webview_windows.go new file mode 100644 index 0000000..22d4572 --- /dev/null +++ b/example/webview_windows.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "github.com/lxn/walk" +) + +func showWebviewOnWindows() { + mainWindow, err := walk.NewMainWindow() + if err != nil { + panic(fmt.Sprintf("Failed to create main window: %v\n", err)) + } + mainWindow.SetTitle("Webview") + mainWindow.SetWidth(800) + mainWindow.SetHeight(600) + layout := walk.NewVBoxLayout() + if err := mainWindow.SetLayout(layout); err != nil { + panic(fmt.Sprintf("Failed to set layout: %v\n", err)) + } + webView, err := walk.NewWebView(mainWindow) + if err != nil { + panic(fmt.Sprintf("Failed to create webview window: %v\n", err)) + } + webView.SetURL("https://www.getlantern.org") + mainWindow.SetVisible(true) + mainWindow.Run() +} diff --git a/systray.go b/systray.go index 2109657..987e9d2 100644 --- a/systray.go +++ b/systray.go @@ -1,8 +1,5 @@ /* Package systray is a cross-platform Go library to place an icon and menu in the notification area. - -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 @@ -16,11 +13,22 @@ import ( ) var ( - hasStarted = int64(0) - hasQuit = int64(0) + log = golog.LoggerFor("systray") + + systrayReady func() + systrayExit func() + menuItems = make(map[int32]*MenuItem) + menuItemsLock sync.RWMutex + + currentID = int32(-1) + quitOnce sync.Once ) -// MenuItem is used to keep track each menu item of systray +func init() { + runtime.LockOSThread() +} + +// MenuItem is used to keep track each menu item of systray. // Don't create it directly, use the one systray.AddMenuItem() returned type MenuItem struct { // ClickedCh is the channel which will be notified when the menu item is clicked @@ -60,25 +68,17 @@ func newMenuItem(title string, tooltip string, parent *MenuItem) *MenuItem { } } -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. +// callback. It blocks until systray.Quit() is called. func Run(onReady func(), onExit func()) { - runtime.LockOSThread() - atomic.StoreInt64(&hasStarted, 1) + Register(onReady, onExit) + nativeLoop() +} +// Register initializes GUI and registers the callbacks but relies on the +// caller to run the event loop somewhere else. It's useful if the program +// needs to show other UI elements, for example, webview. +func Register(onReady func(), onExit func()) { if onReady == nil { systrayReady = func() {} } else { @@ -92,38 +92,18 @@ func Run(onReady func(), onExit func()) { close(readyCh) } } - // unlike onReady, onExit runs in the event loop to make sure it has time to // finish before the process terminates if onExit == nil { onExit = func() {} } systrayExit = onExit - - nativeLoop() -} - -func Register(onExit func()) { - runtime.LockOSThread() - atomic.StoreInt64(&hasStarted, 1) - // 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 - register() -} - -func Run2() { - run() + registerSystray() } // Quit the systray func Quit() { - if atomic.LoadInt64(&hasStarted) == 1 && atomic.CompareAndSwapInt64(&hasQuit, 0, 1) { - quit() - } + quitOnce.Do(quit) } // AddMenuItem adds a menu item with the designated title and tooltip. diff --git a/systray.h b/systray.h index 1a9ad96..e1a1f98 100644 --- a/systray.h +++ b/systray.h @@ -3,6 +3,7 @@ extern void systray_ready(); extern void systray_on_exit(); extern void systray_menu_item_selected(int menu_id); +void registerSystray(void); int nativeLoop(void); void setIcon(const char* iconBytes, int length, bool template); diff --git a/systray_darwin.m b/systray_darwin.m index 67e696c..b4f38bc 100644 --- a/systray_darwin.m +++ b/systray_darwin.m @@ -209,9 +209,12 @@ NSMenuItem *find_menu_item(NSMenu *ourMenu, NSNumber *menuId) { @end -int nativeLoop(void) { +void registerSystray(void) { AppDelegate *delegate = [[AppDelegate alloc] init]; [[NSApplication sharedApplication] setDelegate:delegate]; +} + +int nativeLoop(void) { [NSApp run]; return EXIT_SUCCESS; } diff --git a/systray_linux.c b/systray_linux.c index 9b4342e..7586f1b 100644 --- a/systray_linux.c +++ b/systray_linux.c @@ -23,7 +23,7 @@ typedef struct { short checked; } MenuItemInfo; -int nativeLoop(void) { +void registerSystray(void) { gtk_init(0, NULL); global_app_indicator = app_indicator_new("systray", "", APP_INDICATOR_CATEGORY_APPLICATION_STATUS); @@ -31,6 +31,9 @@ int nativeLoop(void) { global_tray_menu = gtk_menu_new(); app_indicator_set_menu(global_app_indicator, GTK_MENU(global_tray_menu)); systray_ready(); +} + +int nativeLoop(void) { gtk_main(); systray_on_exit(); return 0; diff --git a/systray_nonwindows.go b/systray_nonwindows.go index b92f7dc..6e06c3d 100644 --- a/systray_nonwindows.go +++ b/systray_nonwindows.go @@ -15,6 +15,10 @@ import ( "unsafe" ) +func registerSystray() { + C.registerSystray() +} + func nativeLoop() { C.nativeLoop() } diff --git a/systray_windows.go b/systray_windows.go index 4736b02..454ab2e 100644 --- a/systray_windows.go +++ b/systray_windows.go @@ -236,14 +236,17 @@ var wt winTray // 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_CLOSE = 0x0010 - WM_ENDSESSION = 0x16 WM_RBUTTONUP = 0x0205 WM_LBUTTONUP = 0x0202 + WM_COMMAND = 0x0111 + WM_ENDSESSION = 0x0016 + WM_CLOSE = 0x0010 + WM_DESTROY = 0x0002 + WM_CREATE = 0x0001 ) switch message { + case WM_CREATE: + systrayReady() case WM_COMMAND: menuItemId := int32(wParam) // https://docs.microsoft.com/en-us/windows/win32/menurc/wm-command#menus @@ -712,13 +715,7 @@ func (t *winTray) iconToBitmap(hIcon windows.Handle) (windows.Handle, error) { return windows.Handle(hMemBmp), nil } -func nativeLoop() { - register() - go systrayReady() - run() -} - -func register() { +func registerSystray() { if err := wt.initInstance(); err != nil { log.Errorf("Unable to init instance: %v", err) return @@ -731,7 +728,7 @@ func register() { } -func run() { +func nativeLoop() { // Main message pump. m := &struct { WindowHandle windows.Handle