Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add icon tray on Windows using native Golang API

I'm developing a daemon with no UI apart from a simple icon in the Windows systray.

I would like to have no dependencies on any other package(s), so I'm trying to use the syscall package and implement the necessary call(s) by myself.

Documentation

  • After some research, I think that I must use the Shell_NotifyIcon function in shell32.dll.
  • The Golang WIKI gives three ways to call a Windows DLL. The third solution is to be excluded. For many reasons, I want to build sources with the Golang compiler only.
  • I found an interesting article on "Developing for Multiple Platforms With Go" which explained how to use Shell_NotifyIconW (Unicode declination), but the implementation is partial.
  • I have found a few Golang libraries which implement Windows System Tray. They are useful to help understand the structure and calls involved in dealing with it.

Libraries

  • getlantern/systray
  • cratonica/trayhost
  • xilp/systray
  • lxn/walk

Implementation

Structures

Built with xilp/systray documentation.

type HANDLE uintptr

type HICON HANDLE
    type HWND HANDLE

type GUID struct {
    Data1 uint32
    Data2 uint16
    Data3 uint16
    Data4 [8]byte
}

type NOTIFYICONDATA struct {
    CbSize           uint32
    HWnd             HWND
    UID              uint32
    UFlags           uint32
    UCallbackMessage uint32
    HIcon            HICON
    SzTip            [128]uint16
    DwState          uint32
    DwStateMask      uint32
    SzInfo           [256]uint16
    UVersion         uint32
    SzInfoTitle      [64]uint16
    DwInfoFlags      uint32
    GuidItem         GUID
}

Variables

const (
    NIM_ADD = 0x00000000
    NIM_MODIFY = 0x00000001
    NIM_DELETE = 0x00000002
    NIM_SETVERSION = 0x00000004

    NIF_MESSAGE = 0x00000001
    NIF_ICON = 0x00000002
    NIF_TIP = 0x00000004
    NIF_STATE = 0x00000008

    NIF_HIDDEN = 0x00000001
)

Source

package main

import (
    "log"
    "syscall"
    "unsafe"
)

func main() {
    shell32 := syscall.MustLoadDLL("shell32.dll")
    Shell_NotifyIcon := shell32.MustFindProc("Shell_NotifyIconW")

    iconData := NOTIFYICONDATA{
        HWnd: 0,
        UFlags: NIF_MESSAGE | NIF_STATE,
        DwState: NIF_HIDDEN,
        DwStateMask: NIS_HIDDEN,
    }
    iconData.CbSize = uint32(unsafe.Sizeof(iconData))

    ret, _, _ := Shell_NotifyIcon.Call(
        NIM_ADD,
        uintptr(unsafe.Pointer(&iconData)),
    )

    if ret == 0 {
        log.Println("Failed")
        return
    }

    // Do anything, like open a HTTP server to keep the program running
    http.ListenAndServe(":8080", nil)
}

Details

  • I have no idea what information to give in HWnd, but without it, the executable crashes.
  • UFlags, DwState and DwStateMask have values that I have found in different projects.

I know that it is possible; the Golang WIKI gives an implementation to call a message box.

like image 860
Baptiste Donaux Avatar asked Mar 06 '18 08:03

Baptiste Donaux


People also ask

How do I add an app to the taskbar tray?

In Windows 10 On Windows 10, you can access more detailed setting by right-clicking the taskbar and selecting “Settings”. This takes you straight to the Settings > Personalization > Taskbar screen. Scroll down to the “Notification Area” section and click the “Select which icons appear on the taskbar” link.


1 Answers

Fields of NOTIFYICONDATA

hWnd

hWnd field of NOTIFYICONDATA holds a window handle that is associated with notifyicon itself, as mentioned in MSDN:

hWnd

A handle to the window that receives notifications associated with an icon in the notification area.

I found that it's necessary to associate a window handle, even if the window is not visible.

uFlags tells which fields of NOTIFYICONDATA are valid in single command. As you see there are lots of fields in NOTIFYICONDATA, and if you are going to change just the icon of the notifyicon, you can leave other fields unchanged and set only hIcon field then pass the whole NOTIFYICONDATA to Shell_NotifyIcon. If you want to change both icon and message, just set it to NIF_MESSAGE|NIF_ICON.

dwState

dwState can be used to control icon's visibility. If you specify NIF_STATE for uFlags, and NIS_HIDDEN for dwState and dwStateMask, it'll make notifyicon hidden.

dwStateMask

And in most case, just set dwStateMask as same as dwState. It just tells which bit of dwState is valid for the command:

The possible values are the same as those for dwState.

Example

You can find full example I've wrote at here: https://github.com/hallazzang/go-windows-programming/tree/master/example/gui/notifyicon

like image 89
hallazzang Avatar answered Oct 18 '22 02:10

hallazzang