diff options
| author | Alexander Kulikov | 2018-04-08 15:41:06 +0300 | 
|---|---|---|
| committer | Alexander Kulikov | 2018-04-08 15:41:06 +0300 | 
| commit | 9db10538d29eaf16e2f2c3033e73b007865e11a6 (patch) | |
| tree | 1878a21fd618fdfe15918ccf68a9beaca665d71c | |
| parent | bccf28dbc642d05ed9100bb25e0c0d38ea71993e (diff) | |
| download | systray-9db10538d29eaf16e2f2c3033e73b007865e11a6.tar.bz2 | |
windows: made windows constants private, fixed little bugs, added automatic icon redrawing on explorer.exe restarts
| -rw-r--r-- | systray_windows.go | 262 | 
1 files changed, 137 insertions, 125 deletions
| diff --git a/systray_windows.go b/systray_windows.go index 59e5885..68e20cf 100644 --- a/systray_windows.go +++ b/systray_windows.go @@ -3,75 +3,49 @@  package systray  import ( +	"crypto/md5" +	"encoding/hex"  	"io/ioutil"  	"os" +	"path/filepath"  	"unsafe"  	"golang.org/x/sys/windows" -	"fmt" -	"crypto/md5" -	"encoding/hex" -	"path/filepath"  )  // Helpful sources: https://github.com/golang/exp/blob/master/shiny/driver/internal/win32 -// https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159 -const ( -	NIM_ADD    = 0x00000000 -	NIM_MODIFY = 0x00000001 -	NIM_DELETE = 0x00000002 -) - -const ( -	NIF_MESSAGE = 0x00000001 -	NIF_ICON    = 0x00000002 -	NIF_TIP     = 0x00000004 -) - -// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644931(v=vs.85).aspx -const ( -	WM_USER       = 0x0400 -	WM_COMMAND    = 0x0111 -	WM_DESTROY    = 0x0002 -	WM_ENDSESSION = 0x16 -	WM_RBUTTONUP  = 0x0205 -	WM_LBUTTONUP  = 0x0202 - -	// custom messages -	WM_SYSTRAY_MESSAGE = WM_USER + 1 -) -  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") -	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") +	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. @@ -88,7 +62,7 @@ type wndClassEx struct {  // 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 { +func (w *wndClassEx) register() error {  	w.Size = uint32(unsafe.Sizeof(*w))  	res, _, err := pRegisterClass.Call(uintptr(unsafe.Pointer(w)))  	if res == 0 { @@ -99,7 +73,7 @@ func (w *wndClassEx) Register() error {  // 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 { +func (w *wndClassEx) unregister() error {  	res, _, err := pUnregisterClass.Call(  		uintptr(unsafe.Pointer(w.ClassName)),  		uintptr(w.Instance), @@ -113,6 +87,7 @@ func (w *wndClassEx) Unregister() error {  // 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 @@ -128,7 +103,43 @@ type notifyIconData struct {  	BalloonIcon                windows.Handle  } -// Contains information about a menu item +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 @@ -157,18 +168,18 @@ type winTray struct {  	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 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] @@ -193,39 +204,25 @@ func (t *winTray) setIcon(src string) error {  	}  	t.nid.Icon = h -	t.nid.Flags = NIF_ICON +	t.nid.Flags |= NIF_ICON  	t.nid.Size = uint32(unsafe.Sizeof(*t.nid)) -	showIconRes, _, err := pShellNotifyIcon.Call( -		uintptr(NIM_MODIFY), -		uintptr(unsafe.Pointer(t.nid)), -	) -	if showIconRes == 0 { -		return err -	} - -	return nil +	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.Flags |= NIF_TIP  	t.nid.Size = uint32(unsafe.Sizeof(*t.nid)) -	showIconRes, _, err := pShellNotifyIcon.Call( -		uintptr(NIM_MODIFY), -		uintptr(unsafe.Pointer(t.nid)), -	) -	if showIconRes == 0 { -		return err -	} -	return nil +	return t.nid.modify()  }  var wt winTray @@ -233,6 +230,13 @@ 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) @@ -245,19 +249,16 @@ func (t *winTray) wndProc(hWnd windows.Handle, message uint32, wParam, lParam ui  		fallthrough  	case WM_ENDSESSION:  		if t.nid != nil { -			pShellNotifyIcon.Call( -				NIM_DELETE, -				uintptr(unsafe.Pointer(t.nid)), -			) -		} -		if systrayExit != nil { -			systrayExit() +			t.nid.delete()  		} -	case WM_SYSTRAY_MESSAGE: +		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 @@ -293,12 +294,26 @@ func (t *winTray) initInstance() error {  		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 = ""  	) -	wt.loadedImages = make(map[string]windows.Handle) + +	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 { @@ -336,11 +351,11 @@ func (t *winTray) initInstance() error {  		Instance:   t.instance,  		Icon:       t.icon,  		Cursor:     t.cursor, -		Background: windows.Handle(6), //(COLOR_WINDOW + 1) +		Background: windows.Handle(6), // (COLOR_WINDOW + 1)  		ClassName:  classNamePtr,  		IconSm:     t.icon,  	} -	if err := t.wcex.Register(); err != nil { +	if err := t.wcex.register(); err != nil {  		return err  	} @@ -372,23 +387,15 @@ func (t *winTray) initInstance() error {  		uintptr(t.window),  	) -	wt.nid = ¬ifyIconData{ -		Wnd:             windows.Handle(wt.window), +	t.nid = ¬ifyIconData{ +		Wnd:             windows.Handle(t.window),  		ID:              100,  		Flags:           NIF_MESSAGE, -		CallbackMessage: WM_SYSTRAY_MESSAGE, +		CallbackMessage: t.wmSystrayMessage,  	}  	t.nid.Size = uint32(unsafe.Sizeof(*t.nid)) -	showIconRes, _, err := pShellNotifyIcon.Call( -		uintptr(NIM_ADD), -		uintptr(unsafe.Pointer(t.nid)), -	) -	if showIconRes == 0 { -		return err -	} - -	return nil +	return t.nid.add()  }  func (t *winTray) createMenu() error { @@ -558,25 +565,24 @@ func (t *winTray) showMenu() error {  func nativeLoop() {  	if err := wt.initInstance(); err != nil { -		fmt.Printf("initInstance failed: %s", err) +		log.Errorf("Unable to init instance: %v", err) +		return  	}  	if err := wt.createMenu(); err != nil { -		fmt.Printf("createMenu failed: %s", err) +		log.Errorf("Unable to create menu: %v", err)  		return  	}  	defer func() {  		pDestroyWindow.Call(uintptr(wt.window)) -		wt.wcex.Unregister() +		wt.wcex.unregister()  	}() -	if systrayReady != nil { -		systrayReady() -	} +	go systrayReady()  	// Main message pump. -	m := struct { +	m := &struct {  		WindowHandle windows.Handle  		Message      uint32  		Wparam       uintptr @@ -585,16 +591,22 @@ func nativeLoop() {  		Pt           point  	}{}  	for { -		ret, _, err := pGetMessage.Call(uintptr(unsafe.Pointer(&m)), 0, 0, 0) -		res := int32(ret) -		if res == -1 { -			log.Errorf("win32 GetMessage failed: %v", err) +		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 -		} else if res == 0 { // WM_QUIT -			break +		default: +			pTranslateMessage.Call(uintptr(unsafe.Pointer(m))) +			pDispatchMessage.Call(uintptr(unsafe.Pointer(m)))  		} -		pTranslateMessage.Call(uintptr(unsafe.Pointer(&m))) -		pDispatchMessage.Call(uintptr(unsafe.Pointer(&m)))  	}  } | 
