aboutsummaryrefslogtreecommitdiffstats
path: root/systray_windows.go
diff options
context:
space:
mode:
Diffstat (limited to 'systray_windows.go')
-rw-r--r--systray_windows.go731
1 files changed, 620 insertions, 111 deletions
diff --git a/systray_windows.go b/systray_windows.go
index 9ee0297..68e20cf 100644
--- a/systray_windows.go
+++ b/systray_windows.go
@@ -1,92 +1,645 @@
+// +build windows
+
package systray
import (
- "fmt"
+ "crypto/md5"
+ "encoding/hex"
"io/ioutil"
"os"
"path/filepath"
- "runtime"
- "syscall"
"unsafe"
- "github.com/getlantern/filepersist"
+ "golang.org/x/sys/windows"
)
+// Helpful sources: https://github.com/golang/exp/blob/master/shiny/driver/internal/win32
+
var (
- iconFiles = make([]*os.File, 0)
- dllDir = filepath.Join(os.Getenv("APPDATA"), "systray")
- dllFileName = "systray" + runtime.GOARCH + ".dll"
- dllFile = filepath.Join(dllDir, dllFileName)
-
- mod = syscall.NewLazyDLL(dllFile)
- _nativeLoop = mod.NewProc("nativeLoop")
- _quit = mod.NewProc("quit")
- _setIcon = mod.NewProc("setIcon")
- _setTitle = mod.NewProc("setTitle")
- _setTooltip = mod.NewProc("setTooltip")
- _add_or_update_menu_item = mod.NewProc("add_or_update_menu_item")
- _add_separator = mod.NewProc("add_separator")
- _hide_menu_item = mod.NewProc("hide_menu_item")
+ 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")
)
-func init() {
- // Write DLL to file
- b, err := Asset(dllFileName)
+// 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
+}
+
+// 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 {
- panic(fmt.Errorf("Unable to read "+dllFileName+": %v", err))
+ 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
- err = os.MkdirAll(dllDir, 0755)
+ 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 {
- panic(fmt.Errorf("Unable to create directory %v to hold "+dllFileName+": %v", dllDir, err))
+ return err
}
- err = filepersist.Save(dllFile, b, 0644)
+ windowNamePtr, err := windows.UTF16PtrFromString(windowName)
if err != nil {
- panic(fmt.Errorf("Unable to save "+dllFileName+" to %v: %v", dllFile, err))
+ 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 nativeLoop() {
- _nativeLoop.Call(
- syscall.NewCallbackCDecl(systray_ready),
- syscall.NewCallbackCDecl(systray_on_exit),
- syscall.NewCallbackCDecl(systray_menu_item_selected))
+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 quit() {
- _quit.Call()
- for _, f := range iconFiles {
- err := os.Remove(f.Name())
- if err != nil {
- log.Debugf("Unable to delete temporary icon file %v: %v", f.Name(), err)
+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))
+
+ // The return value is the identifier of the specified menu item.
+ // If the menu item identifier is NULL or if the specified item opens a submenu, the return value is -1.
+ res, _, err := pGetMenuItemID.Call(uintptr(t.menu), uintptr(menuId))
+ if int32(res) == -1 {
+ res, _, err = pInsertMenuItem.Call(
+ uintptr(t.menu),
+ uintptr(menuId),
+ 1,
+ uintptr(unsafe.Pointer(&mi)),
+ )
+ if res == 0 {
+ return err
+ }
+ } else {
+ res, _, err = pSetMenuItemInfo.Call(
+ uintptr(t.menu),
+ uintptr(menuId),
+ 0,
+ uintptr(unsafe.Pointer(&mi)),
+ )
+ if res == 0 {
+ 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
+
+ res, _, err := pDeleteMenu.Call(
+ uintptr(t.menu),
+ uintptr(uint32(menuId)),
+ MF_BYCOMMAND,
+ )
+ if res == 0 {
+ return err
+ }
+
+ 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 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) {
- f, err := ioutil.TempFile("", "systray_temp_icon")
- if err != nil {
- log.Errorf("Unable to create temp icon: %v", err)
- return
- }
- defer f.Close()
- _, err = f.Write(iconBytes)
- if err != nil {
- log.Errorf("Unable to write icon to temp file %v: %v", f.Name(), f)
- return
+ 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
+ }
}
- // Need to close file before we load it to make sure contents is flushed.
- f.Close()
- name, err := strUTF16(f.Name())
- if err != nil {
- log.Errorf("Unable to convert name to string pointer: %v", err)
+
+ if err := wt.setIcon(iconFilePath); err != nil {
+ log.Errorf("Unable to set icon: %v", err)
return
}
- _setIcon.Call(name.Raw())
}
// SetTitle sets the systray title, only available on Mac.
@@ -97,80 +650,36 @@ func SetTitle(title string) {
// SetTooltip sets the systray tooltip to display on mouse hover of the tray icon,
// only available on Mac and Windows.
func SetTooltip(tooltip string) {
- t, err := strUTF16(tooltip)
- if err != nil {
- log.Errorf("Unable to convert tooltip to string pointer: %v", err)
+ if err := wt.setTooltip(tooltip); err != nil {
+ log.Errorf("Unable to set tooltip: %v", err)
return
}
- _setTooltip.Call(t.Raw())
}
func addOrUpdateMenuItem(item *MenuItem) {
- var disabled = 0
- if item.disabled {
- disabled = 1
- }
- var checked = 0
- if item.checked {
- checked = 1
- }
- title, err := strUTF16(item.title)
- if err != nil {
- log.Errorf("Unable to convert title to string pointer: %v", err)
- return
- }
- tooltip, err := strUTF16(item.tooltip)
+ err := wt.addOrUpdateMenuItem(item.id, item.title, item.disabled, item.checked)
if err != nil {
- log.Errorf("Unable to convert tooltip to string pointer: %v", err)
+ log.Errorf("Unable to addOrUpdateMenuItem: %v", err)
return
}
- _add_or_update_menu_item.Call(
- uintptr(item.id),
- title.Raw(),
- tooltip.Raw(),
- uintptr(disabled),
- uintptr(checked),
- )
}
func addSeparator(id int32) {
- _add_separator.Call(uintptr(id))
+ err := wt.addSeparatorMenuItem(id)
+ if err != nil {
+ log.Errorf("Unable to addSeparator: %v", err)
+ return
+ }
}
func hideMenuItem(item *MenuItem) {
- _hide_menu_item.Call(uintptr(item.id))
+ err := wt.hideMenuItem(item.id)
+ if err != nil {
+ log.Errorf("Unable to hideMenuItem: %v", err)
+ return
+ }
}
func showMenuItem(item *MenuItem) {
addOrUpdateMenuItem(item)
}
-
-type utf16 []uint16
-
-// Raw returns the underlying *wchar_t of an utf16 so we can pass to DLL
-func (u utf16) Raw() uintptr {
- return uintptr(unsafe.Pointer(&u[0]))
-}
-
-// strUTF16 converts a Go string into a utf16 byte sequence
-func strUTF16(s string) (utf16, error) {
- return syscall.UTF16FromString(s)
-}
-
-// systray_ready takes an ignored parameter just so we can compile a callback
-// (for some reason in Go 1.4.x, syscall.NewCallback panics if there's no
-// parameter to the function).
-func systray_ready(ignore uintptr) uintptr {
- systrayReady()
- return 0
-}
-
-func systray_on_exit(ignore uintptr) uintptr {
- systrayExit()
- return 0
-}
-
-func systray_menu_item_selected(id uintptr) uintptr {
- systrayMenuItemSelected(int32(id))
- return 0
-}