diff options
| author | Percy Wegmann | 2018-08-23 11:56:16 -0500 | 
|---|---|---|
| committer | GitHub | 2018-08-23 11:56:16 -0500 | 
| commit | a5355ff39972108c64b8e3566107a72add0f5bbc (patch) | |
| tree | 59cd6325d1b3b1fe9865854cfa936a54801ef072 /systray_windows.go | |
| parent | 62af9eb8030c168caa78856c967e6d2c4a7d813e (diff) | |
| parent | 4cffa496a5deba17d84af93509d1b37eb4ccd243 (diff) | |
| download | systray-a5355ff39972108c64b8e3566107a72add0f5bbc.tar.bz2 | |
Merge pull request #44 from amkulikov/dev_windows_syscall_pr
Syscalls instead of custom DLLs
Diffstat (limited to 'systray_windows.go')
| -rw-r--r-- | systray_windows.go | 731 | 
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 = ¬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 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 -} | 
