1// +build windows
2
3package systray
4
5import (
6	"crypto/md5"
7	"encoding/hex"
8	"io/ioutil"
9	"log"
10	"os"
11	"path/filepath"
12	"sort"
13	"sync"
14	"syscall"
15	"unsafe"
16
17	"golang.org/x/sys/windows"
18)
19
20// Helpful sources: https://github.com/golang/exp/blob/master/shiny/driver/internal/win32
21
22var (
23	g32                     = windows.NewLazySystemDLL("Gdi32.dll")
24	pCreateCompatibleBitmap = g32.NewProc("CreateCompatibleBitmap")
25	pCreateCompatibleDC     = g32.NewProc("CreateCompatibleDC")
26	pDeleteDC               = g32.NewProc("DeleteDC")
27	pSelectObject           = g32.NewProc("SelectObject")
28
29	k32              = windows.NewLazySystemDLL("Kernel32.dll")
30	pGetModuleHandle = k32.NewProc("GetModuleHandleW")
31
32	s32              = windows.NewLazySystemDLL("Shell32.dll")
33	pShellNotifyIcon = s32.NewProc("Shell_NotifyIconW")
34
35	u32                    = windows.NewLazySystemDLL("User32.dll")
36	pCreateMenu            = u32.NewProc("CreateMenu")
37	pCreatePopupMenu       = u32.NewProc("CreatePopupMenu")
38	pCreateWindowEx        = u32.NewProc("CreateWindowExW")
39	pDefWindowProc         = u32.NewProc("DefWindowProcW")
40	pDeleteMenu            = u32.NewProc("DeleteMenu")
41	pDestroyWindow         = u32.NewProc("DestroyWindow")
42	pDispatchMessage       = u32.NewProc("DispatchMessageW")
43	pDrawIconEx            = u32.NewProc("DrawIconEx")
44	pGetCursorPos          = u32.NewProc("GetCursorPos")
45	pGetDC                 = u32.NewProc("GetDC")
46	pGetMenuItemID         = u32.NewProc("GetMenuItemID")
47	pGetMessage            = u32.NewProc("GetMessageW")
48	pGetSystemMetrics      = u32.NewProc("GetSystemMetrics")
49	pInsertMenuItem        = u32.NewProc("InsertMenuItemW")
50	pLoadCursor            = u32.NewProc("LoadCursorW")
51	pLoadIcon              = u32.NewProc("LoadIconW")
52	pLoadImage             = u32.NewProc("LoadImageW")
53	pPostMessage           = u32.NewProc("PostMessageW")
54	pPostQuitMessage       = u32.NewProc("PostQuitMessage")
55	pRegisterClass         = u32.NewProc("RegisterClassExW")
56	pRegisterWindowMessage = u32.NewProc("RegisterWindowMessageW")
57	pReleaseDC             = u32.NewProc("ReleaseDC")
58	pSetForegroundWindow   = u32.NewProc("SetForegroundWindow")
59	pSetMenuInfo           = u32.NewProc("SetMenuInfo")
60	pSetMenuItemInfo       = u32.NewProc("SetMenuItemInfoW")
61	pShowWindow            = u32.NewProc("ShowWindow")
62	pTrackPopupMenu        = u32.NewProc("TrackPopupMenu")
63	pTranslateMessage      = u32.NewProc("TranslateMessage")
64	pUnregisterClass       = u32.NewProc("UnregisterClassW")
65	pUpdateWindow          = u32.NewProc("UpdateWindow")
66)
67
68// Contains window class information.
69// It is used with the RegisterClassEx and GetClassInfoEx functions.
70// https://msdn.microsoft.com/en-us/library/ms633577.aspx
71type wndClassEx struct {
72	Size, Style                        uint32
73	WndProc                            uintptr
74	ClsExtra, WndExtra                 int32
75	Instance, Icon, Cursor, Background windows.Handle
76	MenuName, ClassName                *uint16
77	IconSm                             windows.Handle
78}
79
80// Registers a window class for subsequent use in calls to the CreateWindow or CreateWindowEx function.
81// https://msdn.microsoft.com/en-us/library/ms633587.aspx
82func (w *wndClassEx) register() error {
83	w.Size = uint32(unsafe.Sizeof(*w))
84	res, _, err := pRegisterClass.Call(uintptr(unsafe.Pointer(w)))
85	if res == 0 {
86		return err
87	}
88	return nil
89}
90
91// Unregisters a window class, freeing the memory required for the class.
92// https://msdn.microsoft.com/en-us/library/ms644899.aspx
93func (w *wndClassEx) unregister() error {
94	res, _, err := pUnregisterClass.Call(
95		uintptr(unsafe.Pointer(w.ClassName)),
96		uintptr(w.Instance),
97	)
98	if res == 0 {
99		return err
100	}
101	return nil
102}
103
104// Contains information that the system needs to display notifications in the notification area.
105// Used by Shell_NotifyIcon.
106// https://msdn.microsoft.com/en-us/library/windows/desktop/bb773352(v=vs.85).aspx
107// https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159
108type notifyIconData struct {
109	Size                       uint32
110	Wnd                        windows.Handle
111	ID, Flags, CallbackMessage uint32
112	Icon                       windows.Handle
113	Tip                        [128]uint16
114	State, StateMask           uint32
115	Info                       [256]uint16
116	Timeout, Version           uint32
117	InfoTitle                  [64]uint16
118	InfoFlags                  uint32
119	GuidItem                   windows.GUID
120	BalloonIcon                windows.Handle
121}
122
123func (nid *notifyIconData) add() error {
124	const NIM_ADD = 0x00000000
125	res, _, err := pShellNotifyIcon.Call(
126		uintptr(NIM_ADD),
127		uintptr(unsafe.Pointer(nid)),
128	)
129	if res == 0 {
130		return err
131	}
132	return nil
133}
134
135func (nid *notifyIconData) modify() error {
136	const NIM_MODIFY = 0x00000001
137	res, _, err := pShellNotifyIcon.Call(
138		uintptr(NIM_MODIFY),
139		uintptr(unsafe.Pointer(nid)),
140	)
141	if res == 0 {
142		return err
143	}
144	return nil
145}
146
147func (nid *notifyIconData) delete() error {
148	const NIM_DELETE = 0x00000002
149	res, _, err := pShellNotifyIcon.Call(
150		uintptr(NIM_DELETE),
151		uintptr(unsafe.Pointer(nid)),
152	)
153	if res == 0 {
154		return err
155	}
156	return nil
157}
158
159// Contains information about a menu item.
160// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
161type menuItemInfo struct {
162	Size, Mask, Type, State     uint32
163	ID                          uint32
164	SubMenu, Checked, Unchecked windows.Handle
165	ItemData                    uintptr
166	TypeData                    *uint16
167	Cch                         uint32
168	BMPItem                     windows.Handle
169}
170
171// The POINT structure defines the x- and y- coordinates of a point.
172// https://msdn.microsoft.com/en-us/library/windows/desktop/dd162805(v=vs.85).aspx
173type point struct {
174	X, Y int32
175}
176
177// Contains information about loaded resources
178type winTray struct {
179	instance,
180	icon,
181	cursor,
182	window windows.Handle
183
184	loadedImages   map[string]windows.Handle
185	muLoadedImages sync.RWMutex
186	// menus keeps track of the submenus keyed by the menu item ID, plus 0
187	// which corresponds to the main popup menu.
188	menus   map[uint32]windows.Handle
189	muMenus sync.RWMutex
190	// menuOf keeps track of the menu each menu item belongs to.
191	menuOf   map[uint32]windows.Handle
192	muMenuOf sync.RWMutex
193	// menuItemIcons maintains the bitmap of each menu item (if applies). It's
194	// needed to show the icon correctly when showing a previously hidden menu
195	// item again.
196	menuItemIcons   map[uint32]windows.Handle
197	muMenuItemIcons sync.RWMutex
198	visibleItems    map[uint32][]uint32
199	muVisibleItems  sync.RWMutex
200
201	nid   *notifyIconData
202	muNID sync.RWMutex
203	wcex  *wndClassEx
204
205	wmSystrayMessage,
206	wmTaskbarCreated uint32
207}
208
209// Loads an image from file and shows it in tray.
210// Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx
211func (t *winTray) setIcon(src string) error {
212	const NIF_ICON = 0x00000002
213
214	h, err := t.loadIconFrom(src)
215	if err != nil {
216		return err
217	}
218
219	t.muNID.Lock()
220	defer t.muNID.Unlock()
221	t.nid.Icon = h
222	t.nid.Flags |= NIF_ICON
223	t.nid.Size = uint32(unsafe.Sizeof(*t.nid))
224
225	return t.nid.modify()
226}
227
228// Sets tooltip on icon.
229// Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx
230func (t *winTray) setTooltip(src string) error {
231	const NIF_TIP = 0x00000004
232	b, err := windows.UTF16FromString(src)
233	if err != nil {
234		return err
235	}
236
237	t.muNID.Lock()
238	defer t.muNID.Unlock()
239	copy(t.nid.Tip[:], b[:])
240	t.nid.Flags |= NIF_TIP
241	t.nid.Size = uint32(unsafe.Sizeof(*t.nid))
242
243	return t.nid.modify()
244}
245
246var wt winTray
247
248// WindowProc callback function that processes messages sent to a window.
249// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx
250func (t *winTray) wndProc(hWnd windows.Handle, message uint32, wParam, lParam uintptr) (lResult uintptr) {
251	const (
252		WM_RBUTTONUP  = 0x0205
253		WM_LBUTTONUP  = 0x0202
254		WM_COMMAND    = 0x0111
255		WM_ENDSESSION = 0x0016
256		WM_CLOSE      = 0x0010
257		WM_DESTROY    = 0x0002
258		WM_CREATE     = 0x0001
259	)
260	switch message {
261	case WM_CREATE:
262		systrayReady()
263	case WM_COMMAND:
264		menuItemId := int32(wParam)
265		// https://docs.microsoft.com/en-us/windows/win32/menurc/wm-command#menus
266		if menuItemId != -1 {
267			systrayMenuItemSelected(uint32(wParam))
268		}
269	case WM_CLOSE:
270		pDestroyWindow.Call(uintptr(t.window))
271		t.wcex.unregister()
272	case WM_DESTROY:
273		// same as WM_ENDSESSION, but throws 0 exit code after all
274		defer pPostQuitMessage.Call(uintptr(int32(0)))
275		fallthrough
276	case WM_ENDSESSION:
277		t.muNID.Lock()
278		if t.nid != nil {
279			t.nid.delete()
280		}
281		t.muNID.Unlock()
282		systrayExit()
283	case t.wmSystrayMessage:
284		switch lParam {
285		case WM_RBUTTONUP, WM_LBUTTONUP:
286			t.showMenu()
287		}
288	case t.wmTaskbarCreated: // on explorer.exe restarts
289		t.muNID.Lock()
290		t.nid.add()
291		t.muNID.Unlock()
292	default:
293		// Calls the default window procedure to provide default processing for any window messages that an application does not process.
294		// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633572(v=vs.85).aspx
295		lResult, _, _ = pDefWindowProc.Call(
296			uintptr(hWnd),
297			uintptr(message),
298			uintptr(wParam),
299			uintptr(lParam),
300		)
301	}
302	return
303}
304
305func (t *winTray) initInstance() error {
306	const IDI_APPLICATION = 32512
307	const IDC_ARROW = 32512 // Standard arrow
308	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
309	const SW_HIDE = 0
310	const CW_USEDEFAULT = 0x80000000
311	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx
312	const (
313		WS_CAPTION     = 0x00C00000
314		WS_MAXIMIZEBOX = 0x00010000
315		WS_MINIMIZEBOX = 0x00020000
316		WS_OVERLAPPED  = 0x00000000
317		WS_SYSMENU     = 0x00080000
318		WS_THICKFRAME  = 0x00040000
319
320		WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
321	)
322	// https://msdn.microsoft.com/en-us/library/windows/desktop/ff729176
323	const (
324		CS_HREDRAW = 0x0002
325		CS_VREDRAW = 0x0001
326	)
327	const NIF_MESSAGE = 0x00000001
328
329	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644931(v=vs.85).aspx
330	const WM_USER = 0x0400
331
332	const (
333		className  = "SystrayClass"
334		windowName = ""
335	)
336
337	t.wmSystrayMessage = WM_USER + 1
338	t.visibleItems = make(map[uint32][]uint32)
339	t.menus = make(map[uint32]windows.Handle)
340	t.menuOf = make(map[uint32]windows.Handle)
341	t.menuItemIcons = make(map[uint32]windows.Handle)
342
343	taskbarEventNamePtr, _ := windows.UTF16PtrFromString("TaskbarCreated")
344	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644947
345	res, _, err := pRegisterWindowMessage.Call(
346		uintptr(unsafe.Pointer(taskbarEventNamePtr)),
347	)
348	t.wmTaskbarCreated = uint32(res)
349
350	t.loadedImages = make(map[string]windows.Handle)
351
352	instanceHandle, _, err := pGetModuleHandle.Call(0)
353	if instanceHandle == 0 {
354		return err
355	}
356	t.instance = windows.Handle(instanceHandle)
357
358	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648072(v=vs.85).aspx
359	iconHandle, _, err := pLoadIcon.Call(0, uintptr(IDI_APPLICATION))
360	if iconHandle == 0 {
361		return err
362	}
363	t.icon = windows.Handle(iconHandle)
364
365	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
366	cursorHandle, _, err := pLoadCursor.Call(0, uintptr(IDC_ARROW))
367	if cursorHandle == 0 {
368		return err
369	}
370	t.cursor = windows.Handle(cursorHandle)
371
372	classNamePtr, err := windows.UTF16PtrFromString(className)
373	if err != nil {
374		return err
375	}
376
377	windowNamePtr, err := windows.UTF16PtrFromString(windowName)
378	if err != nil {
379		return err
380	}
381
382	t.wcex = &wndClassEx{
383		Style:      CS_HREDRAW | CS_VREDRAW,
384		WndProc:    windows.NewCallback(t.wndProc),
385		Instance:   t.instance,
386		Icon:       t.icon,
387		Cursor:     t.cursor,
388		Background: windows.Handle(6), // (COLOR_WINDOW + 1)
389		ClassName:  classNamePtr,
390		IconSm:     t.icon,
391	}
392	if err := t.wcex.register(); err != nil {
393		return err
394	}
395
396	windowHandle, _, err := pCreateWindowEx.Call(
397		uintptr(0),
398		uintptr(unsafe.Pointer(classNamePtr)),
399		uintptr(unsafe.Pointer(windowNamePtr)),
400		uintptr(WS_OVERLAPPEDWINDOW),
401		uintptr(CW_USEDEFAULT),
402		uintptr(CW_USEDEFAULT),
403		uintptr(CW_USEDEFAULT),
404		uintptr(CW_USEDEFAULT),
405		uintptr(0),
406		uintptr(0),
407		uintptr(t.instance),
408		uintptr(0),
409	)
410	if windowHandle == 0 {
411		return err
412	}
413	t.window = windows.Handle(windowHandle)
414
415	pShowWindow.Call(
416		uintptr(t.window),
417		uintptr(SW_HIDE),
418	)
419
420	pUpdateWindow.Call(
421		uintptr(t.window),
422	)
423
424	t.muNID.Lock()
425	defer t.muNID.Unlock()
426	t.nid = &notifyIconData{
427		Wnd:             windows.Handle(t.window),
428		ID:              100,
429		Flags:           NIF_MESSAGE,
430		CallbackMessage: t.wmSystrayMessage,
431	}
432	t.nid.Size = uint32(unsafe.Sizeof(*t.nid))
433
434	return t.nid.add()
435}
436
437func (t *winTray) createMenu() error {
438	const MIM_APPLYTOSUBMENUS = 0x80000000 // Settings apply to the menu and all of its submenus
439
440	menuHandle, _, err := pCreatePopupMenu.Call()
441	if menuHandle == 0 {
442		return err
443	}
444	t.menus[0] = windows.Handle(menuHandle)
445
446	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647575(v=vs.85).aspx
447	mi := struct {
448		Size, Mask, Style, Max uint32
449		Background             windows.Handle
450		ContextHelpID          uint32
451		MenuData               uintptr
452	}{
453		Mask: MIM_APPLYTOSUBMENUS,
454	}
455	mi.Size = uint32(unsafe.Sizeof(mi))
456
457	res, _, err := pSetMenuInfo.Call(
458		uintptr(t.menus[0]),
459		uintptr(unsafe.Pointer(&mi)),
460	)
461	if res == 0 {
462		return err
463	}
464	return nil
465}
466
467func (t *winTray) convertToSubMenu(menuItemId uint32) (windows.Handle, error) {
468	const MIIM_SUBMENU = 0x00000004
469
470	res, _, err := pCreateMenu.Call()
471	if res == 0 {
472		return 0, err
473	}
474	menu := windows.Handle(res)
475
476	mi := menuItemInfo{Mask: MIIM_SUBMENU, SubMenu: menu}
477	mi.Size = uint32(unsafe.Sizeof(mi))
478	t.muMenuOf.RLock()
479	hMenu := t.menuOf[menuItemId]
480	t.muMenuOf.RUnlock()
481	res, _, err = pSetMenuItemInfo.Call(
482		uintptr(hMenu),
483		uintptr(menuItemId),
484		0,
485		uintptr(unsafe.Pointer(&mi)),
486	)
487	if res == 0 {
488		return 0, err
489	}
490	t.muMenus.Lock()
491	t.menus[menuItemId] = menu
492	t.muMenus.Unlock()
493	return menu, nil
494}
495
496func (t *winTray) addOrUpdateMenuItem(menuItemId uint32, parentId uint32, title string, disabled, checked bool) error {
497	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
498	const (
499		MIIM_FTYPE   = 0x00000100
500		MIIM_BITMAP  = 0x00000080
501		MIIM_STRING  = 0x00000040
502		MIIM_SUBMENU = 0x00000004
503		MIIM_ID      = 0x00000002
504		MIIM_STATE   = 0x00000001
505	)
506	const MFT_STRING = 0x00000000
507	const (
508		MFS_CHECKED  = 0x00000008
509		MFS_DISABLED = 0x00000003
510	)
511	titlePtr, err := windows.UTF16PtrFromString(title)
512	if err != nil {
513		return err
514	}
515
516	mi := menuItemInfo{
517		Mask:     MIIM_FTYPE | MIIM_STRING | MIIM_ID | MIIM_STATE,
518		Type:     MFT_STRING,
519		ID:       uint32(menuItemId),
520		TypeData: titlePtr,
521		Cch:      uint32(len(title)),
522	}
523	mi.Size = uint32(unsafe.Sizeof(mi))
524	if disabled {
525		mi.State |= MFS_DISABLED
526	}
527	if checked {
528		mi.State |= MFS_CHECKED
529	}
530	t.muMenuItemIcons.RLock()
531	hIcon := t.menuItemIcons[menuItemId]
532	t.muMenuItemIcons.RUnlock()
533	if hIcon > 0 {
534		mi.Mask |= MIIM_BITMAP
535		mi.BMPItem = hIcon
536	}
537
538	var res uintptr
539	t.muMenus.RLock()
540	menu, exists := t.menus[parentId]
541	t.muMenus.RUnlock()
542	if !exists {
543		menu, err = t.convertToSubMenu(parentId)
544		if err != nil {
545			return err
546		}
547		t.muMenus.Lock()
548		t.menus[parentId] = menu
549		t.muMenus.Unlock()
550	} else if t.getVisibleItemIndex(parentId, menuItemId) != -1 {
551		// We set the menu item info based on the menuID
552		res, _, err = pSetMenuItemInfo.Call(
553			uintptr(menu),
554			uintptr(menuItemId),
555			0,
556			uintptr(unsafe.Pointer(&mi)),
557		)
558	}
559
560	if res == 0 {
561		t.addToVisibleItems(parentId, menuItemId)
562		position := t.getVisibleItemIndex(parentId, menuItemId)
563		res, _, err = pInsertMenuItem.Call(
564			uintptr(menu),
565			uintptr(position),
566			1,
567			uintptr(unsafe.Pointer(&mi)),
568		)
569		if res == 0 {
570			t.delFromVisibleItems(parentId, menuItemId)
571			return err
572		}
573		t.muMenuOf.Lock()
574		t.menuOf[menuItemId] = menu
575		t.muMenuOf.Unlock()
576	}
577
578	return nil
579}
580
581func (t *winTray) addSeparatorMenuItem(menuItemId, parentId uint32) error {
582	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
583	const (
584		MIIM_FTYPE = 0x00000100
585		MIIM_ID    = 0x00000002
586		MIIM_STATE = 0x00000001
587	)
588	const MFT_SEPARATOR = 0x00000800
589
590	mi := menuItemInfo{
591		Mask: MIIM_FTYPE | MIIM_ID | MIIM_STATE,
592		Type: MFT_SEPARATOR,
593		ID:   uint32(menuItemId),
594	}
595
596	mi.Size = uint32(unsafe.Sizeof(mi))
597
598	t.addToVisibleItems(parentId, menuItemId)
599	position := t.getVisibleItemIndex(parentId, menuItemId)
600	t.muMenus.RLock()
601	menu := uintptr(t.menus[parentId])
602	t.muMenus.RUnlock()
603	res, _, err := pInsertMenuItem.Call(
604		menu,
605		uintptr(position),
606		1,
607		uintptr(unsafe.Pointer(&mi)),
608	)
609	if res == 0 {
610		return err
611	}
612
613	return nil
614}
615
616func (t *winTray) hideMenuItem(menuItemId, parentId uint32) error {
617	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647629(v=vs.85).aspx
618	const MF_BYCOMMAND = 0x00000000
619	const ERROR_SUCCESS syscall.Errno = 0
620
621	t.muMenus.RLock()
622	menu := uintptr(t.menus[parentId])
623	t.muMenus.RUnlock()
624	res, _, err := pDeleteMenu.Call(
625		menu,
626		uintptr(menuItemId),
627		MF_BYCOMMAND,
628	)
629	if res == 0 && err.(syscall.Errno) != ERROR_SUCCESS {
630		return err
631	}
632	t.delFromVisibleItems(parentId, menuItemId)
633
634	return nil
635}
636
637func (t *winTray) showMenu() error {
638	const (
639		TPM_BOTTOMALIGN = 0x0020
640		TPM_LEFTALIGN   = 0x0000
641	)
642	p := point{}
643	res, _, err := pGetCursorPos.Call(uintptr(unsafe.Pointer(&p)))
644	if res == 0 {
645		return err
646	}
647	pSetForegroundWindow.Call(uintptr(t.window))
648
649	res, _, err = pTrackPopupMenu.Call(
650		uintptr(t.menus[0]),
651		TPM_BOTTOMALIGN|TPM_LEFTALIGN,
652		uintptr(p.X),
653		uintptr(p.Y),
654		0,
655		uintptr(t.window),
656		0,
657	)
658	if res == 0 {
659		return err
660	}
661
662	return nil
663}
664
665func (t *winTray) delFromVisibleItems(parent, val uint32) {
666	t.muVisibleItems.Lock()
667	defer t.muVisibleItems.Unlock()
668	visibleItems := t.visibleItems[parent]
669	for i, itemval := range visibleItems {
670		if val == itemval {
671			visibleItems = append(visibleItems[:i], visibleItems[i+1:]...)
672			break
673		}
674	}
675}
676
677func (t *winTray) addToVisibleItems(parent, val uint32) {
678	t.muVisibleItems.Lock()
679	defer t.muVisibleItems.Unlock()
680	if visibleItems, exists := t.visibleItems[parent]; !exists {
681		t.visibleItems[parent] = []uint32{val}
682	} else {
683		newvisible := append(visibleItems, val)
684		sort.Slice(newvisible, func(i, j int) bool { return newvisible[i] < newvisible[j] })
685		t.visibleItems[parent] = newvisible
686	}
687}
688
689func (t *winTray) getVisibleItemIndex(parent, val uint32) int {
690	t.muVisibleItems.RLock()
691	defer t.muVisibleItems.RUnlock()
692	for i, itemval := range t.visibleItems[parent] {
693		if val == itemval {
694			return i
695		}
696	}
697	return -1
698}
699
700// Loads an image from file to be shown in tray or menu item.
701// LoadImage: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx
702func (t *winTray) loadIconFrom(src string) (windows.Handle, error) {
703	const IMAGE_ICON = 1               // Loads an icon
704	const LR_LOADFROMFILE = 0x00000010 // Loads the stand-alone image from the file
705	const LR_DEFAULTSIZE = 0x00000040  // Loads default-size icon for windows(SM_CXICON x SM_CYICON) if cx, cy are set to zero
706
707	// Save and reuse handles of loaded images
708	t.muLoadedImages.RLock()
709	h, ok := t.loadedImages[src]
710	t.muLoadedImages.RUnlock()
711	if !ok {
712		srcPtr, err := windows.UTF16PtrFromString(src)
713		if err != nil {
714			return 0, err
715		}
716		res, _, err := pLoadImage.Call(
717			0,
718			uintptr(unsafe.Pointer(srcPtr)),
719			IMAGE_ICON,
720			0,
721			0,
722			LR_LOADFROMFILE|LR_DEFAULTSIZE,
723		)
724		if res == 0 {
725			return 0, err
726		}
727		h = windows.Handle(res)
728		t.muLoadedImages.Lock()
729		t.loadedImages[src] = h
730		t.muLoadedImages.Unlock()
731	}
732	return h, nil
733}
734
735func (t *winTray) iconToBitmap(hIcon windows.Handle) (windows.Handle, error) {
736	const SM_CXSMICON = 49
737	const SM_CYSMICON = 50
738	const DI_NORMAL = 0x3
739	hDC, _, err := pGetDC.Call(uintptr(0))
740	if hDC == 0 {
741		return 0, err
742	}
743	defer pReleaseDC.Call(uintptr(0), hDC)
744	hMemDC, _, err := pCreateCompatibleDC.Call(hDC)
745	if hMemDC == 0 {
746		return 0, err
747	}
748	defer pDeleteDC.Call(hMemDC)
749	cx, _, _ := pGetSystemMetrics.Call(SM_CXSMICON)
750	cy, _, _ := pGetSystemMetrics.Call(SM_CYSMICON)
751	hMemBmp, _, err := pCreateCompatibleBitmap.Call(hDC, cx, cy)
752	if hMemBmp == 0 {
753		return 0, err
754	}
755	hOriginalBmp, _, _ := pSelectObject.Call(hMemDC, hMemBmp)
756	defer pSelectObject.Call(hMemDC, hOriginalBmp)
757	res, _, err := pDrawIconEx.Call(hMemDC, 0, 0, uintptr(hIcon), cx, cy, 0, uintptr(0), DI_NORMAL)
758	if res == 0 {
759		return 0, err
760	}
761	return windows.Handle(hMemBmp), nil
762}
763
764func registerSystray() {
765	if err := wt.initInstance(); err != nil {
766		log.Printf("Unable to init instance: %v", err)
767		return
768	}
769
770	if err := wt.createMenu(); err != nil {
771		log.Printf("Unable to create menu: %v", err)
772		return
773	}
774
775}
776
777func nativeLoop() {
778	// Main message pump.
779	m := &struct {
780		WindowHandle windows.Handle
781		Message      uint32
782		Wparam       uintptr
783		Lparam       uintptr
784		Time         uint32
785		Pt           point
786	}{}
787	for {
788		ret, _, err := pGetMessage.Call(uintptr(unsafe.Pointer(m)), 0, 0, 0)
789
790		// If the function retrieves a message other than WM_QUIT, the return value is nonzero.
791		// If the function retrieves the WM_QUIT message, the return value is zero.
792		// If there is an error, the return value is -1
793		// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936(v=vs.85).aspx
794		switch int32(ret) {
795		case -1:
796			log.Printf("Error at message loop: %v", err)
797			return
798		case 0:
799			return
800		default:
801			pTranslateMessage.Call(uintptr(unsafe.Pointer(m)))
802			pDispatchMessage.Call(uintptr(unsafe.Pointer(m)))
803		}
804	}
805}
806
807func quit() {
808	const WM_CLOSE = 0x0010
809
810	pPostMessage.Call(
811		uintptr(wt.window),
812		WM_CLOSE,
813		0,
814		0,
815	)
816}
817
818func iconBytesToFilePath(iconBytes []byte) (string, error) {
819	bh := md5.Sum(iconBytes)
820	dataHash := hex.EncodeToString(bh[:])
821	iconFilePath := filepath.Join(os.TempDir(), "systray_temp_icon_"+dataHash)
822
823	if _, err := os.Stat(iconFilePath); os.IsNotExist(err) {
824		if err := ioutil.WriteFile(iconFilePath, iconBytes, 0644); err != nil {
825			return "", err
826		}
827	}
828	return iconFilePath, nil
829}
830
831// SetIcon sets the systray icon.
832// iconBytes should be the content of .ico for windows and .ico/.jpg/.png
833// for other platforms.
834func SetIcon(iconBytes []byte) {
835	iconFilePath, err := iconBytesToFilePath(iconBytes)
836	if err != nil {
837		log.Printf("Unable to write icon data to temp file: %v", err)
838		return
839	}
840	if err := wt.setIcon(iconFilePath); err != nil {
841		log.Printf("Unable to set icon: %v", err)
842		return
843	}
844}
845
846// SetTemplateIcon sets the systray icon as a template icon (on macOS), falling back
847// to a regular icon on other platforms.
848// templateIconBytes and iconBytes should be the content of .ico for windows and
849// .ico/.jpg/.png for other platforms.
850func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
851	SetIcon(regularIconBytes)
852}
853
854// SetTitle sets the systray title, only available on Mac and Linux.
855func SetTitle(title string) {
856	// do nothing
857}
858
859func (item *MenuItem) parentId() uint32 {
860	if item.parent != nil {
861		return uint32(item.parent.id)
862	}
863	return 0
864}
865
866// SetIcon sets the icon of a menu item. Only works on macOS and Windows.
867// iconBytes should be the content of .ico/.jpg/.png
868func (item *MenuItem) SetIcon(iconBytes []byte) {
869	iconFilePath, err := iconBytesToFilePath(iconBytes)
870	if err != nil {
871		log.Printf("Unable to write icon data to temp file: %v", err)
872		return
873	}
874
875	h, err := wt.loadIconFrom(iconFilePath)
876	if err != nil {
877		log.Printf("Unable to load icon from temp file: %v", err)
878		return
879	}
880
881	h, err = wt.iconToBitmap(h)
882	if err != nil {
883		log.Printf("Unable to convert icon to bitmap: %v", err)
884		return
885	}
886	wt.muMenuItemIcons.Lock()
887	wt.menuItemIcons[uint32(item.id)] = h
888	wt.muMenuItemIcons.Unlock()
889
890	err = wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
891	if err != nil {
892		log.Printf("Unable to addOrUpdateMenuItem: %v", err)
893		return
894	}
895}
896
897// SetTooltip sets the systray tooltip to display on mouse hover of the tray icon,
898// only available on Mac and Windows.
899func SetTooltip(tooltip string) {
900	if err := wt.setTooltip(tooltip); err != nil {
901		log.Printf("Unable to set tooltip: %v", err)
902		return
903	}
904}
905
906func addOrUpdateMenuItem(item *MenuItem) {
907	err := wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
908	if err != nil {
909		log.Printf("Unable to addOrUpdateMenuItem: %v", err)
910		return
911	}
912}
913
914// SetTemplateIcon sets the icon of a menu item as a template icon (on macOS). On Windows, it
915// falls back to the regular icon bytes and on Linux it does nothing.
916// templateIconBytes and regularIconBytes should be the content of .ico for windows and
917// .ico/.jpg/.png for other platforms.
918func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
919	item.SetIcon(regularIconBytes)
920}
921
922func addSeparator(id uint32) {
923	err := wt.addSeparatorMenuItem(id, 0)
924	if err != nil {
925		log.Printf("Unable to addSeparator: %v", err)
926		return
927	}
928}
929
930func hideMenuItem(item *MenuItem) {
931	err := wt.hideMenuItem(uint32(item.id), item.parentId())
932	if err != nil {
933		log.Printf("Unable to hideMenuItem: %v", err)
934		return
935	}
936}
937
938func showMenuItem(item *MenuItem) {
939	addOrUpdateMenuItem(item)
940}
941