Search code examples
c++winapiunreal-engine4

Creating system tray right click menu C++


I am trying to create Systemtray icon in Unreal Engine 4. I am not a c++ expert. This is what i have scalped code from internet so far

#define NOTIFICATION_TRAY_ICON_MSG (WM_USER + 0x100)
AddTrayIcon(hwnd, 1, NOTIFICATION_TRAY_ICON_MSG, 0);
void AddTrayIcon(HWND hWnd, UINT uID, UINT uCallbackMsg, UINT uIcon) {

    //CREATE SYSTEN TRAY ICON.---------------------------------------------------------------------

    NOTIFYICONDATA  nid;

    nid.hWnd = hWnd;

    nid.uID = uID;

    nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;

    nid.uCallbackMessage = uCallbackMsg;
    FString ThePath = FPaths::ConvertRelativePathToFull(FPaths::RootDir()) ;
    FString GameName = FApp::GetName();
    
    ThePath.Append(GameName);
    ThePath.Append(".exe");
    GEngine->AddOnScreenDebugMessage(-1, 12.f, FColor::White, ThePath);
    WORD id = 0;
    nid.hIcon = ExtractAssociatedIcon(nullptr, ThePath.GetCharArray().GetData(),&id);
    //LPCWSTR iconfile = "C:/Temp/icon.ico";
    //ExtractIconEx(iconfile, 0, NULL, &(nid.hIcon), 1);

    //strcpy(nid.szTip, "Tool Tip");

    //SEND MESSAGE TO SYSTEM TRAY TO ADD ICON.--------------------------------------------

     Shell_NotifyIcon(NIM_ADD, &nid);
    

}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {

    case NOTIFICATION_TRAY_ICON_MSG:
    {
        // This is a message that originated with the
        // Notification Tray Icon. The lParam tells use exactly which event
        // it is.
        switch (lParam)
        {
        case WM_LBUTTONDBLCLK:
        {
            const int IDM_EXIT = 100;
            POINT pt;
            GetCursorPos(&pt);
            HMENU hmenu = CreatePopupMenu();
            InsertMenu(hmenu, 0, MF_BYPOSITION | MF_STRING, IDM_EXIT, L"Exit");
            TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_BOTTOMALIGN, pt.x, pt.y, 0, GetActiveWindow(), NULL);
            break;
        }
        }
    }
    }
    return 0;
}

Icon gets created but i cant rightclick and getpopup menu from it Also i cant figure out how LRESULT CALLBACK WndProc() function gets triggered.

Can any1 help Thanks


Solution

  • You are not initializing the NOTIFYICONDATA correctly. Specifically, you are not setting its cbSize or uVersion members, which directly influence how Shell_NotifyIcon() interacts with your app through the message you assign to the uCallbackMessage member. This is discussed in detail in the following MSDN documentation:

    Notifications and the Notification Area: Define the NOTIFYICONDATA Version

    Shell_NotifyIcon: Remarks

    NOTIFYICONDATA structure

    Also, you say you want to display the popup menu on a right-click, but your WndProc() is looking for a left double-click instead.

    But more importantly, you need to associate your WndProc() with the HWND that you give to Shell_NotifyIcon(), so that you can actually receive your NOTIFICATION_TRAY_ICON_MSG message at all. Since you are trying to use an HWND that you are not the creator of, UE4 is the creator, you will have to subclass the window, eg:

    #define NOTIFICATION_TRAY_ICON_MSG (WM_USER + 0x100)
    
    void AddTrayIcon(HWND hWnd, UINT uID, UINT uCallbackMsg, UINT uIcon);
    void RemoveTrayIcon(HWND hWnd, UINT uID);
    LRESULT CALLBACK SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
    
    ...
    
    HWND hwnd = ...; // GetActiveWindow(), etc
    if (SetWindowSubclass(hwnd, &SubclassProc, 1, 0))
        AddTrayIcon(hwnd, 1, NOTIFICATION_TRAY_ICON_MSG, 0);
    
    ...
    
    void AddTrayIcon(HWND hWnd, UINT uID, UINT uCallbackMsg, UINT uIcon) {
    
        //CREATE SYSTEM TRAY ICON.---------------------------------------------------------------------
    
        NOTIFYICONDATA nid = {};
        nid.cbSize = sizeof(nid);
        nid.hWnd = hWnd;
        nid.uID = uID;
        nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
        nid.uCallbackMessage = uCallbackMsg;
        nid.uVersion = NOTIFYICON_VERSION_4;
    
        FString ThePath = FPaths::ConvertRelativePathToFull(FPaths::RootDir()) ;
        FString GameName = FApp::GetName();
        
        ThePath.Append(GameName);
        ThePath.Append(".exe");
        GEngine->AddOnScreenDebugMessage(-1, 12.f, FColor::White, ThePath);
    
        WORD id = 0;
        nid.hIcon = ExtractAssociatedIcon(nullptr, ThePath.GetCharArray().GetData(), &id);
        //LPCWSTR iconfile = L"C:/Temp/icon.ico";
        //ExtractIconEx(iconfile, 0, NULL, &(nid.hIcon), 1);
    
        //strcpy(nid.szTip, "Tool Tip");
    
        //SEND MESSAGE TO SYSTEM TRAY TO ADD ICON.--------------------------------------------
    
        if (Shell_NotifyIcon(NIM_ADD, &nid))
            Shell_NotifyIcon(NIM_SETVERSION, &nid);
    }
    
    void RemoveTrayIcon(HWND hWnd, UINT uID) {
    
        //REMOVE SYSTEM TRAY ICON.---------------------------------------------------------------------
    
        NOTIFYICONDATA nid = {};
        nid.cbSize = sizeof(nid);
        nid.hWnd = hWnd;
        nid.uID = uID;
    
        //SEND MESSAGE TO SYSTEM TRAY TO REMOVE ICON.--------------------------------------------
    
        Shell_NotifyIcon(NIM_DELETE, &nid);
    }
    
    LRESULT CALLBACK SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
    {
        switch (uMsg)
        {
            case WM_NCDESTROY:
                RemoveTrayIcon(hWnd, 1);
                RemoveWindowSubclass(hWnd, &SubclassProc, uIdSubclass);
                break;
    
            case NOTIFICATION_TRAY_ICON_MSG:
            {
                // This is a message that originated with the
                // Notification Tray Icon. The lParam tells use exactly which event
                // it is.
                switch (LOWORD(lParam))
                {
                    case NIN_SELECT:
                    case NIN_KEYSELECT:
                    case WM_CONTEXTMENU:
                    {
                        const int IDM_EXIT = 100;
    
                        POINT pt;
                        GetCursorPos(&pt);
    
                        HMENU hmenu = CreatePopupMenu();
                        InsertMenu(hmenu, 0, MF_BYPOSITION | MF_STRING, IDM_EXIT, L"Exit");
    
                        SetForegroundWindow(hWnd);
    
                        int cmd = TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_NONOTIFY | TPM_RETURNCMD, pt.x, pt.y, 0, hWnd, NULL);
    
                        PostMessage(hWnd, WM_NULL, 0, 0);
    
                        if (cmd == IDM_EXIT)
                        {
                            ...
                        }
    
                        break;
                    }
                }
    
                return 0;
            }
        }
    
        return DefSubclassProc(hWnd, uMsg, wParam, lParam);
    }
    

    That being said, you really should create your own window to handle the interaction with the System Tray. You can call CreateWindowEx() directly to create a hidden message-only window and then associate your icon's WndProc() with that window. You DO NOT need to use the UE4 window:

    LRESULT CALLBACK SysTrayWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
    
    ...
    
    LPCTSTR SysTrayWndClass = TEXT("MySysTrayWnd");
    
    HINSTANCE hInst = ...; // GetModuleHandle(NULL), etc
    
    WNDCLASS wc = {};
    wc.lpfnWndProc = &SysTrayWndProc;
    wc.hInstance = hInst;
    wc.lpszClassName = SysTrayWndClass;
    RegisterClass(&wc);
    
    HWND hSysTrayIconWnd = CreateWindowEx(0, SysTrayWndClass, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, hInst, NULL);
    if (hSysTrayIconWnd) {
        AddTrayIcon(hSysTrayIconWnd, 1, NOTIFICATION_TRAY_ICON_MSG, 0);
    }
    ...
    if (hSysTrayIconWnd) {
        RemoveTrayIcon(hSysTrayIconWnd, 1);
        DestroyWindow(hSysTrayIconWnd);
    }
    
    ...
    
    LRESULT CALLBACK SysTrayWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
            case NOTIFICATION_TRAY_ICON_MSG:
            {
                // This is a message that originated with the
                // Notification Tray Icon. The lParam tells use exactly which event
                // it is.
                switch (LOWORD(lParam))
                {
                    case NIN_SELECT:
                    case NIN_KEYSELECT:
                    case WM_CONTEXTMENU:
                    {
                        const int IDM_EXIT = 100;
    
                        POINT pt;
                        GetCursorPos(&pt);
    
                        HMENU hmenu = CreatePopupMenu();
                        InsertMenu(hmenu, 0, MF_BYPOSITION | MF_STRING, IDM_EXIT, L"Exit");
    
                        SetForegroundWindow(hWnd);
    
                        int cmd = TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_BOTTOMALIGN, pt.x, pt.y, 0, hWnd, NULL);
    
                        PostMessage(hWnd, WM_NULL, 0, 0);
    
                        break;
                    }
                }
    
                return 0;
            }
    
           case WM_COMMAND:
               if (lParam == 0 && LOWORD(wParam) == IDM_EXIT)
               {
                   ...
               }
               break;
        }
    
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }