aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Kulikov2018-04-08 15:41:06 +0300
committerAlexander Kulikov2018-04-08 15:41:06 +0300
commit9db10538d29eaf16e2f2c3033e73b007865e11a6 (patch)
tree1878a21fd618fdfe15918ccf68a9beaca665d71c
parentbccf28dbc642d05ed9100bb25e0c0d38ea71993e (diff)
downloadsystray-9db10538d29eaf16e2f2c3033e73b007865e11a6.tar.bz2
windows: made windows constants private, fixed little bugs, added automatic icon redrawing on explorer.exe restarts
-rw-r--r--systray_windows.go262
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 = &notifyIconData{
- Wnd: windows.Handle(wt.window),
+ t.nid = &notifyIconData{
+ 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)))
}
}