| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106 |
- //go:build windows
- // +build windows
- package systray
- import (
- "crypto/md5"
- "encoding/hex"
- "errors"
- "io/ioutil"
- "log"
- "os"
- "path/filepath"
- "sort"
- "sync"
- "syscall"
- "unsafe"
- "github.com/tevino/abool"
- "golang.org/x/sys/windows"
- )
- // Helpful sources: https://github.com/golang/exp/blob/master/shiny/driver/internal/win32
- var (
- g32 = windows.NewLazySystemDLL("Gdi32.dll")
- pCreateCompatibleBitmap = g32.NewProc("CreateCompatibleBitmap")
- pCreateCompatibleDC = g32.NewProc("CreateCompatibleDC")
- pCreateDIBSection = g32.NewProc("CreateDIBSection")
- pDeleteDC = g32.NewProc("DeleteDC")
- pSelectObject = g32.NewProc("SelectObject")
- k32 = windows.NewLazySystemDLL("Kernel32.dll")
- pGetModuleHandle = k32.NewProc("GetModuleHandleW")
- s32 = windows.NewLazySystemDLL("Shell32.dll")
- pShellNotifyIcon = s32.NewProc("Shell_NotifyIconW")
- u32 = windows.NewLazySystemDLL("User32.dll")
- pCreateMenu = u32.NewProc("CreateMenu")
- pCreatePopupMenu = u32.NewProc("CreatePopupMenu")
- pCreateWindowEx = u32.NewProc("CreateWindowExW")
- pDefWindowProc = u32.NewProc("DefWindowProcW")
- pDeleteMenu = u32.NewProc("DeleteMenu")
- pDestroyMenu = u32.NewProc("DestroyMenu")
- pRemoveMenu = u32.NewProc("RemoveMenu")
- pDestroyWindow = u32.NewProc("DestroyWindow")
- pDispatchMessage = u32.NewProc("DispatchMessageW")
- pDrawIconEx = u32.NewProc("DrawIconEx")
- pGetCursorPos = u32.NewProc("GetCursorPos")
- pGetDC = u32.NewProc("GetDC")
- pGetMessage = u32.NewProc("GetMessageW")
- pGetSystemMetrics = u32.NewProc("GetSystemMetrics")
- pInsertMenuItem = u32.NewProc("InsertMenuItemW")
- pLoadCursor = u32.NewProc("LoadCursorW")
- pLoadIcon = u32.NewProc("LoadIconW")
- pLoadImage = u32.NewProc("LoadImageW")
- pPostMessage = u32.NewProc("PostMessageW")
- pPostQuitMessage = u32.NewProc("PostQuitMessage")
- pRegisterClass = u32.NewProc("RegisterClassExW")
- pRegisterWindowMessage = u32.NewProc("RegisterWindowMessageW")
- pReleaseDC = u32.NewProc("ReleaseDC")
- 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")
- // ErrTrayNotReadyYet is returned by functions when they are called before the tray has been initialized.
- ErrTrayNotReadyYet = errors.New("tray not ready yet")
- )
- // 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
- BMPItem 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
- }
- // The BITMAPINFO structure defines the dimensions and color information for a DIB.
- // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfo
- type bitmapInfo struct {
- BmiHeader bitmapInfoHeader
- BmiColors windows.Handle
- }
- // The BITMAPINFOHEADER structure contains information about the dimensions and color format of a device-independent bitmap (DIB).
- // https://learn.microsoft.com/en-us/previous-versions/dd183376(v=vs.85)
- // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
- type bitmapInfoHeader struct {
- BiSize uint32
- BiWidth int32
- BiHeight int32
- BiPlanes uint16
- BiBitCount uint16
- BiCompression uint32
- BiSizeImage uint32
- BiXPelsPerMeter int32
- BiYPelsPerMeter int32
- BiClrUsed uint32
- BiClrImportant uint32
- }
- // Contains information about loaded resources
- type winTray struct {
- instance,
- icon,
- cursor,
- window windows.Handle
- loadedImages map[string]windows.Handle
- muLoadedImages sync.RWMutex
- // menus keeps track of the submenus keyed by the menu item ID, plus 0
- // which corresponds to the main popup menu.
- menus map[uint32]windows.Handle
- muMenus sync.RWMutex
- // menuOf keeps track of the menu each menu item belongs to.
- menuOf map[uint32]windows.Handle
- muMenuOf sync.RWMutex
- // menuItemIcons maintains the bitmap of each menu item (if applies). It's
- // needed to show the icon correctly when showing a previously hidden menu
- // item again.
- menuItemIcons map[uint32]windows.Handle
- muMenuItemIcons sync.RWMutex
- visibleItems map[uint32][]uint32
- muVisibleItems sync.RWMutex
- nid *notifyIconData
- muNID sync.RWMutex
- wcex *wndClassEx
- wmSystrayMessage,
- wmTaskbarCreated uint32
- initialized *abool.AtomicBool
- }
- // isReady checks if the tray as already been initialized. It is not goroutine safe with in regard to the initialization function, but prevents a panic when functions are called too early.
- func (t *winTray) isReady() bool {
- return t.initialized.IsSet()
- }
- // Loads an image from file and shows it in tray.
- // Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx
- func (t *winTray) setIcon(src string) error {
- if !wt.isReady() {
- return ErrTrayNotReadyYet
- }
- const NIF_ICON = 0x00000002
- h, err := t.loadIconFrom(src)
- if err != nil {
- return err
- }
- t.muNID.Lock()
- defer t.muNID.Unlock()
- 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 {
- if !wt.isReady() {
- return ErrTrayNotReadyYet
- }
- const NIF_TIP = 0x00000004
- b, err := windows.UTF16FromString(src)
- if err != nil {
- return err
- }
- t.muNID.Lock()
- defer t.muNID.Unlock()
- 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{
- initialized: abool.New(),
- }
- // 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_RBUTTONUP = 0x0205
- WM_LBUTTONUP = 0x0202
- WM_COMMAND = 0x0111
- WM_ENDSESSION = 0x0016
- WM_CLOSE = 0x0010
- WM_DESTROY = 0x0002
- )
- switch message {
- case WM_COMMAND:
- menuItemId := int32(wParam)
- // https://docs.microsoft.com/en-us/windows/win32/menurc/wm-command#menus
- if menuItemId != -1 {
- systrayMenuItemSelected(uint32(wParam))
- }
- case WM_CLOSE:
- pDestroyWindow.Call(uintptr(t.window))
- t.wcex.unregister()
- case WM_DESTROY:
- // same as WM_ENDSESSION, but throws 0 exit code after all
- defer pPostQuitMessage.Call(uintptr(int32(0)))
- fallthrough
- case WM_ENDSESSION:
- t.muNID.Lock()
- if t.nid != nil {
- t.nid.delete()
- }
- t.muNID.Unlock()
- runSystrayExit()
- case t.wmSystrayMessage:
- switch lParam {
- case WM_RBUTTONUP, WM_LBUTTONUP:
- t.showMenu()
- }
- case t.wmTaskbarCreated: // on explorer.exe restarts
- t.muNID.Lock()
- t.nid.add()
- t.muNID.Unlock()
- 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
- const (
- className = "SystrayClass"
- windowName = ""
- )
- t.wmSystrayMessage = WM_USER + 1
- t.visibleItems = make(map[uint32][]uint32)
- t.menus = make(map[uint32]windows.Handle)
- t.menuOf = make(map[uint32]windows.Handle)
- t.menuItemIcons = make(map[uint32]windows.Handle)
- 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 {
- return err
- }
- windowNamePtr, err := windows.UTF16PtrFromString(windowName)
- if err != nil {
- 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.muNID.Lock()
- defer t.muNID.Unlock()
- 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 (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.menus[0] = 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.menus[0]),
- uintptr(unsafe.Pointer(&mi)),
- )
- if res == 0 {
- return err
- }
- return nil
- }
- func (t *winTray) convertToSubMenu(menuItemId uint32) (windows.Handle, error) {
- const MIIM_SUBMENU = 0x00000004
- res, _, err := pCreateMenu.Call()
- if res == 0 {
- return 0, err
- }
- menu := windows.Handle(res)
- mi := menuItemInfo{Mask: MIIM_SUBMENU, SubMenu: menu}
- mi.Size = uint32(unsafe.Sizeof(mi))
- t.muMenuOf.RLock()
- hMenu := t.menuOf[menuItemId]
- t.muMenuOf.RUnlock()
- res, _, err = pSetMenuItemInfo.Call(
- uintptr(hMenu),
- uintptr(menuItemId),
- 0,
- uintptr(unsafe.Pointer(&mi)),
- )
- if res == 0 {
- return 0, err
- }
- t.muMenus.Lock()
- t.menus[menuItemId] = menu
- t.muMenus.Unlock()
- return menu, nil
- }
- func (t *winTray) addOrUpdateMenuItem(menuItemId uint32, parentId uint32, title string, disabled, checked bool) error {
- if !wt.isReady() {
- return ErrTrayNotReadyYet
- }
- // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
- const (
- MIIM_FTYPE = 0x00000100
- MIIM_BITMAP = 0x00000080
- MIIM_STRING = 0x00000040
- MIIM_SUBMENU = 0x00000004
- 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(menuItemId),
- TypeData: titlePtr,
- Cch: uint32(len(title)),
- }
- mi.Size = uint32(unsafe.Sizeof(mi))
- if disabled {
- mi.State |= MFS_DISABLED
- }
- if checked {
- mi.State |= MFS_CHECKED
- }
- t.muMenuItemIcons.RLock()
- hIcon := t.menuItemIcons[menuItemId]
- t.muMenuItemIcons.RUnlock()
- if hIcon > 0 {
- mi.Mask |= MIIM_BITMAP
- mi.BMPItem = hIcon
- }
- var res uintptr
- t.muMenus.RLock()
- menu, exists := t.menus[parentId]
- t.muMenus.RUnlock()
- if !exists {
- menu, err = t.convertToSubMenu(parentId)
- if err != nil {
- return err
- }
- t.muMenus.Lock()
- t.menus[parentId] = menu
- t.muMenus.Unlock()
- } else if t.getVisibleItemIndex(parentId, menuItemId) != -1 {
- // We set the menu item info based on the menuID
- res, _, err = pSetMenuItemInfo.Call(
- uintptr(menu),
- uintptr(menuItemId),
- 0,
- uintptr(unsafe.Pointer(&mi)),
- )
- }
- if res == 0 {
- // Menu item does not already exist, create it
- t.muMenus.RLock()
- submenu, exists := t.menus[menuItemId]
- t.muMenus.RUnlock()
- if exists {
- mi.Mask |= MIIM_SUBMENU
- mi.SubMenu = submenu
- }
- t.addToVisibleItems(parentId, menuItemId)
- position := t.getVisibleItemIndex(parentId, menuItemId)
- res, _, err = pInsertMenuItem.Call(
- uintptr(menu),
- uintptr(position),
- 1,
- uintptr(unsafe.Pointer(&mi)),
- )
- if res == 0 {
- t.delFromVisibleItems(parentId, menuItemId)
- return err
- }
- t.muMenuOf.Lock()
- t.menuOf[menuItemId] = menu
- t.muMenuOf.Unlock()
- }
- return nil
- }
- func (t *winTray) addSeparatorMenuItem(menuItemId, parentId uint32) error {
- if !wt.isReady() {
- return ErrTrayNotReadyYet
- }
- // 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(menuItemId),
- }
- mi.Size = uint32(unsafe.Sizeof(mi))
- t.addToVisibleItems(parentId, menuItemId)
- position := t.getVisibleItemIndex(parentId, menuItemId)
- t.muMenus.RLock()
- menu := uintptr(t.menus[parentId])
- t.muMenus.RUnlock()
- res, _, err := pInsertMenuItem.Call(
- menu,
- uintptr(position),
- 1,
- uintptr(unsafe.Pointer(&mi)),
- )
- if res == 0 {
- return err
- }
- return nil
- }
- func (t *winTray) removeMenuItem(menuItemId, parentId uint32) error {
- if !wt.isReady() {
- return ErrTrayNotReadyYet
- }
- const MF_BYCOMMAND = 0x00000000
- const ERROR_SUCCESS syscall.Errno = 0
- t.muMenus.RLock()
- menu := uintptr(t.menus[parentId])
- t.muMenus.RUnlock()
- res, _, err := pDeleteMenu.Call(
- menu,
- uintptr(menuItemId),
- MF_BYCOMMAND,
- )
- if res == 0 && err.(syscall.Errno) != ERROR_SUCCESS {
- return err
- }
- t.delFromVisibleItems(parentId, menuItemId)
- return nil
- }
- func (t *winTray) hideMenuItem(menuItemId, parentId uint32) error {
- if !wt.isReady() {
- return ErrTrayNotReadyYet
- }
- const MF_BYCOMMAND = 0x00000000
- const ERROR_SUCCESS syscall.Errno = 0
- t.muMenus.RLock()
- menu := uintptr(t.menus[parentId])
- t.muMenus.RUnlock()
- res, _, err := pRemoveMenu.Call(
- menu,
- uintptr(menuItemId),
- MF_BYCOMMAND,
- )
- if res == 0 && err.(syscall.Errno) != ERROR_SUCCESS {
- return err
- }
- t.delFromVisibleItems(parentId, menuItemId)
- return nil
- }
- func (t *winTray) showMenu() error {
- if !wt.isReady() {
- return ErrTrayNotReadyYet
- }
- 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.menus[0]),
- TPM_BOTTOMALIGN|TPM_LEFTALIGN,
- uintptr(p.X),
- uintptr(p.Y),
- 0,
- uintptr(t.window),
- 0,
- )
- if res == 0 {
- return err
- }
- return nil
- }
- func (t *winTray) delFromVisibleItems(parent, val uint32) {
- t.muVisibleItems.Lock()
- defer t.muVisibleItems.Unlock()
- visibleItems := t.visibleItems[parent]
- for i, itemval := range visibleItems {
- if val == itemval {
- t.visibleItems[parent] = append(visibleItems[:i], visibleItems[i+1:]...)
- break
- }
- }
- }
- func (t *winTray) addToVisibleItems(parent, val uint32) {
- t.muVisibleItems.Lock()
- defer t.muVisibleItems.Unlock()
- if visibleItems, exists := t.visibleItems[parent]; !exists {
- t.visibleItems[parent] = []uint32{val}
- } else {
- newvisible := append(visibleItems, val)
- sort.Slice(newvisible, func(i, j int) bool { return newvisible[i] < newvisible[j] })
- t.visibleItems[parent] = newvisible
- }
- }
- func (t *winTray) getVisibleItemIndex(parent, val uint32) int {
- t.muVisibleItems.RLock()
- defer t.muVisibleItems.RUnlock()
- for i, itemval := range t.visibleItems[parent] {
- if val == itemval {
- return i
- }
- }
- return -1
- }
- // Loads an image from file to be shown in tray or menu item.
- // LoadImage: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx
- func (t *winTray) loadIconFrom(src string) (windows.Handle, error) {
- if !wt.isReady() {
- return 0, ErrTrayNotReadyYet
- }
- const IMAGE_ICON = 1 // Loads an icon
- const LR_LOADFROMFILE = 0x00000010 // Loads the stand-alone image from the file
- const LR_DEFAULTSIZE = 0x00000040 // Loads default-size icon for windows(SM_CXICON x SM_CYICON) if cx, cy are set to zero
- // Save and reuse handles of loaded images
- t.muLoadedImages.RLock()
- h, ok := t.loadedImages[src]
- t.muLoadedImages.RUnlock()
- if !ok {
- srcPtr, err := windows.UTF16PtrFromString(src)
- if err != nil {
- return 0, err
- }
- res, _, err := pLoadImage.Call(
- 0,
- uintptr(unsafe.Pointer(srcPtr)),
- IMAGE_ICON,
- 0,
- 0,
- LR_LOADFROMFILE|LR_DEFAULTSIZE,
- )
- if res == 0 {
- return 0, err
- }
- h = windows.Handle(res)
- t.muLoadedImages.Lock()
- t.loadedImages[src] = h
- t.muLoadedImages.Unlock()
- }
- return h, nil
- }
- func iconToBitmap(hIcon windows.Handle) (windows.Handle, error) {
- const SM_CXSMICON = 49
- const SM_CYSMICON = 50
- const DI_NORMAL = 0x3
- hDC, _, err := pGetDC.Call(uintptr(0))
- if hDC == 0 {
- return 0, err
- }
- defer pReleaseDC.Call(uintptr(0), hDC)
- hMemDC, _, err := pCreateCompatibleDC.Call(hDC)
- if hMemDC == 0 {
- return 0, err
- }
- defer pDeleteDC.Call(hMemDC)
- cx, _, _ := pGetSystemMetrics.Call(SM_CXSMICON)
- cy, _, _ := pGetSystemMetrics.Call(SM_CYSMICON)
- hMemBmp, err := create32BitHBitmap(hMemDC, int32(cx), int32(cy))
- hOriginalBmp, _, _ := pSelectObject.Call(hMemDC, hMemBmp)
- defer pSelectObject.Call(hMemDC, hOriginalBmp)
- res, _, err := pDrawIconEx.Call(hMemDC, 0, 0, uintptr(hIcon), cx, cy, 0, uintptr(0), DI_NORMAL)
- if res == 0 {
- return 0, err
- }
- return windows.Handle(hMemBmp), nil
- }
- // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createdibsection
- func create32BitHBitmap(hDC uintptr, cx, cy int32) (uintptr, error) {
- const BI_RGB uint32 = 0
- const DIB_RGB_COLORS = 0
- bmi := bitmapInfo{
- BmiHeader: bitmapInfoHeader{
- BiPlanes: 1,
- BiCompression: BI_RGB,
- BiWidth: cx,
- BiHeight: cy,
- BiBitCount: 32,
- },
- }
- bmi.BmiHeader.BiSize = uint32(unsafe.Sizeof(bmi.BmiHeader))
- var bits uintptr
- hBitmap, _, err := pCreateDIBSection.Call(
- hDC,
- uintptr(unsafe.Pointer(&bmi)),
- DIB_RGB_COLORS,
- uintptr(unsafe.Pointer(&bits)),
- uintptr(0),
- 0,
- )
- if hBitmap == 0 {
- return 0, err
- }
- return hBitmap, nil
- }
- func registerSystray() {
- if err := wt.initInstance(); err != nil {
- log.Printf("systray error: unable to init instance: %s\n", err)
- return
- }
- if err := wt.createMenu(); err != nil {
- log.Printf("systray error: unable to create menu: %s\n", err)
- return
- }
- wt.initialized.Set()
- systrayReady()
- }
- var m = &struct {
- WindowHandle windows.Handle
- Message uint32
- Wparam uintptr
- Lparam uintptr
- Time uint32
- Pt point
- }{}
- func nativeLoop() {
- for doNativeTick() {
- }
- }
- func nativeEnd() {
- }
- func nativeStart() {
- go func() {
- for doNativeTick() {
- }
- }()
- }
- func doNativeTick() bool {
- 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.Printf("systray error: message loop failure: %s\n", err)
- return false
- case 0:
- return false
- default:
- pTranslateMessage.Call(uintptr(unsafe.Pointer(m)))
- pDispatchMessage.Call(uintptr(unsafe.Pointer(m)))
- }
- return true
- }
- func quit() {
- const WM_CLOSE = 0x0010
- pPostMessage.Call(
- uintptr(wt.window),
- WM_CLOSE,
- 0,
- 0,
- )
- wt.muNID.Lock()
- if wt.nid != nil {
- wt.nid.delete()
- }
- wt.muNID.Unlock()
- runSystrayExit()
- }
- func setInternalLoop(bool) {
- }
- func iconBytesToFilePath(iconBytes []byte) (string, error) {
- 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 {
- return "", err
- }
- }
- return iconFilePath, nil
- }
- // 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) {
- iconFilePath, err := iconBytesToFilePath(iconBytes)
- if err != nil {
- log.Printf("systray error: unable to write icon data to temp file: %s\n", err)
- return
- }
- if err := wt.setIcon(iconFilePath); err != nil {
- log.Printf("systray error: unable to set icon: %s\n", err)
- return
- }
- }
- // SetTemplateIcon sets the systray icon as a template icon (on macOS), falling back
- // to a regular icon on other platforms.
- // templateIconBytes and iconBytes should be the content of .ico for windows and
- // .ico/.jpg/.png for other platforms.
- func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
- SetIcon(regularIconBytes)
- }
- // SetTitle sets the systray title, only available on Mac and Linux.
- func SetTitle(title string) {
- // do nothing
- }
- func (item *MenuItem) parentId() uint32 {
- if item.parent != nil {
- return uint32(item.parent.id)
- }
- return 0
- }
- // SetIcon sets the icon of a menu item. Only works on macOS and Windows.
- // iconBytes should be the content of .ico/.jpg/.png
- func (item *MenuItem) SetIcon(iconBytes []byte) {
- iconFilePath, err := iconBytesToFilePath(iconBytes)
- if err != nil {
- log.Printf("systray error: unable to write icon data to temp file: %s\n", err)
- return
- }
- h, err := wt.loadIconFrom(iconFilePath)
- if err != nil {
- log.Printf("systray error: unable to load icon from temp file: %s\n", err)
- return
- }
- h, err = iconToBitmap(h)
- if err != nil {
- log.Printf("systray error: unable to convert icon to bitmap: %s\n", err)
- return
- }
- wt.muMenuItemIcons.Lock()
- wt.menuItemIcons[uint32(item.id)] = h
- wt.muMenuItemIcons.Unlock()
- err = wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
- if err != nil {
- log.Printf("systray error: unable to addOrUpdateMenuItem: %s\n", err)
- return
- }
- }
- // SetTooltip sets the systray tooltip to display on mouse hover of the tray icon,
- // only available on Mac and Windows.
- func SetTooltip(tooltip string) {
- if err := wt.setTooltip(tooltip); err != nil {
- log.Printf("systray error: unable to set tooltip: %s\n", err)
- return
- }
- }
- func addOrUpdateMenuItem(item *MenuItem) {
- err := wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
- if err != nil {
- log.Printf("systray error: unable to addOrUpdateMenuItem: %s\n", err)
- return
- }
- }
- // SetTemplateIcon sets the icon of a menu item as a template icon (on macOS). On Windows, it
- // falls back to the regular icon bytes and on Linux it does nothing.
- // templateIconBytes and regularIconBytes should be the content of .ico for windows and
- // .ico/.jpg/.png for other platforms.
- func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
- item.SetIcon(regularIconBytes)
- }
- func addSeparator(id uint32, parent uint32) {
- err := wt.addSeparatorMenuItem(id, parent)
- if err != nil {
- log.Printf("systray error: unable to addSeparator: %s\n", err)
- return
- }
- }
- func hideMenuItem(item *MenuItem) {
- err := wt.hideMenuItem(uint32(item.id), item.parentId())
- if err != nil {
- log.Printf("systray error: unable to hideMenuItem: %s\n", err)
- return
- }
- }
- func removeMenuItem(item *MenuItem) {
- err := wt.removeMenuItem(uint32(item.id), item.parentId())
- if err != nil {
- log.Printf("systray error: unable to removeMenuItem: %s\n", err)
- return
- }
- }
- func showMenuItem(item *MenuItem) {
- addOrUpdateMenuItem(item)
- }
- func resetMenu() {
- _, _, _ = pDestroyMenu.Call(uintptr(wt.menus[0]))
- wt.visibleItems = make(map[uint32][]uint32)
- wt.menus = make(map[uint32]windows.Handle)
- wt.menuOf = make(map[uint32]windows.Handle)
- wt.menuItemIcons = make(map[uint32]windows.Handle)
- wt.createMenu()
- }
|