Search code examples
winapirusttrayicon

How to make a tray icon for Windows using the winapi crate?


I am trying to use Rust's winapi crate to make a simple tray icon. I managed to do it before in C, but I can't make Rust happy. Later on I'll include the C code to show what bits of the NOTIFYICONDATA part I want to use.

Super basic goals:

  • Make it say words

  • Make it a default icon like this

    default icon

    This is the simplest; I can figure out other built-in icons later.

  • Update the words

  • Delete it when the program is finished

Link to Rust's winapi library (with search function!)

https://docs.rs/winapi/*/x86_64-pc-windows-msvc/winapi/um/wincon/fn.GetConsoleWindow.html

I really don't know Windows API at all, so it's all Greek to me and I just match syntax I've found in other examples, etc. So please don't skip anything, cause I prob won't know what was implicitly there (e.g. a use std:: or something)!

  • Rust version 1.3.1

  • winapi crate version 0.3.6

  • Windows 10

Here's the Rust code I've managed so far (but doesn't work!):

//-----Import Libraries (called crates)-----
extern crate winapi;
//-----Import Built-in Libraries (not called crates)-----
use std::process::Command; //use cmd.exe
use std::mem::size_of; //get size of stuff

fn main()
{
// to navigate calling with the winapi "crate" use the search function at link
// https://docs.rs/winapi/*/x86_64-pc-windows-msvc/winapi/um/wincon/fn.GetConsoleWindow.html
let hWnd = unsafe { winapi::um::wincon::GetConsoleWindow }; //gets the current console window handle

//System Tray Icon support - here it is
let WM_MYMESSAGE = winapi::um::winuser::WM_APP + 100; //prep WM_MYMESSAGE
let mut trayToolTip = "Tool tip words here"; //record tooltip words for the icon
let nid = winapi::um::shellapi::NOTIFYICONDATAA //thing that has info on window and system tray stuff in it
{   
    cbSize: size_of::<winapi::um::shellapi::NOTIFYICONDATAA>() as u32, //prep
    hWnd: hWnd(), //links the console window
    uID: 1001, //it's a number
    uCallbackMessage: WM_MYMESSAGE, //whoknows should be related to click capture but doesn't so
    //Couldn't find anything for WM_MYMESSAGE at all
    hIcon: winapi::um::winuser::LoadIconA(winapi::shared::ntdef::NULL, winapi::um::winuser::IDI_APPLICATION), //icon idk
    szTip: trayToolTip, //tooltip for the icon
    uFlags: winapi::um::shellapi::NIF_MESSAGE | winapi::um::shellapi::NIF_ICON | winapi::um::shellapi::NIF_TIP, //who knows
};
let nidszTipLength: u64 = szTip.chars().count(); //gets the size of nid.szTip (tooltip length)

winapi::um::shellapi::Shell_NotifyIconA(winapi::um::shellapi::NIM_ADD, &nid); //shows the icon
let _ = Command::new("cmd.exe").arg("/c").arg("pause").status();

nid.szTip: "An updated tooltip is now here!"; //tooltip for the icon
//abs total guess hoping some Python . stuff that I see sometimes in Rust works here and maybe it gets a : instead of a = too
winapi::um::shellapi::Shell_NotifyIconA(winapi::um::shellapi::NIM_MODIFY, &nid); //updates system tray icon

let _ = Command::new("cmd.exe").arg("/c").arg("pause").status();

winapi::um::shellapi::Shell_NotifyIconA(winapi::um::shellapi::NIM_DELETE, &nid); //deletes system tray icon when done

let _ = Command::new("cmd.exe").arg("/c").arg("pause").status();

}

The Cargo.toml needs this:

[target.'cfg(windows)'.dependencies]
winapi = { version = "*", features = ["wincon","shellapi","ntdef"] }

And here is the C code functionality I'm trying to mimic (not sure what libraries are needed where so I tossed most of them in):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#define _WIN32_WINNT 0x0500 //must be before windows.h for mystical reasons such as widnows.h overwrites it with not right thing
#include <windows.h>
#include <shellapi.h> // make some system tray stuff go on
#define WM_MYMESSAGE (WM_USER + 1) //for that tray icon

int main()
{
    HWND hWnd = GetConsoleWindow(); // from https://stackoverflow.com/questions/11812095/hide-the-console-window-of-a-c-program via Anthropos

    NOTIFYICONDATA nid; //thing that has info on window and system tray stuff in it
        nid.cbSize = sizeof(NOTIFYICONDATA); //prep
        nid.hWnd = hWnd; //links the console window
        nid.uID = 1001; //it's a number
        nid.uCallbackMessage = WM_MYMESSAGE; //whoknows should be related to click capture but doesn't so
        nid.hIcon = LoadIcon(NULL, IDI_APPLICATION); //icon idk
        strcpy(nid.szTip, "Tool tip words here"); //tooltip for the icon
        nid.szTip[19] = '\0'; //null at the end of it
        nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; //who knows
        size_t nidszTipLength = sizeof(nid.szTip) / sizeof(nid.szTip[0]); //gets the size of nid.szTip (tooltip length)

    Shell_NotifyIcon(NIM_ADD, &nid); //shows the icon

    system("pause");

    strcpy(nid.szTip, "An updated tooltip is now here!"); //tooltip for the icon
    Shell_NotifyIcon(NIM_MODIFY, &nid); //updates system tray icon
    nid.szTip[31] = '\0'; //null at the end of it

    system("pause");

    Shell_NotifyIcon(NIM_DELETE, &nid); //deletes system tray icon when done

    system("pause");

    return 0;
}

Solution

  • I struck out on my own and headed over to the source of the winapi in Rust https://github.com/retep998/winapi-rs/issues/725 and got enough help to solve this issue successfully. The code is now syntactically valid as a wondrous bonus!

    A couple of upgrades were needed, largely:

    • Work a string into UTF-16 format for the OS to read

    • Write that UTF-16 into a 128-long uint16 vector array

    • Create nid outside of unsafe{ } so it can be used elsewhere

    • Switch to the W-series of winapi calls instead of the A-series (not sure of the difference other than the A-series wanted odd things, like int8 instead of uint16 in LoadIcon[letter])

    The working code follows:

    //-----Import Libraries (called crates)-----
    extern crate winapi;
    //-----Import Built-in Libraries (not called crates)-----
    use std::process::Command; //use cmd.exe
    use std::mem::{size_of, zeroed}; //get size of stuff and init with zeros
    use std::ptr::null_mut; //use a null pointer (I think)
    use std::ffi::OsStr;
    use std::os::windows::ffi::OsStrExt;
    
    fn main()
    {
    // to navigate calling with the winapi "crate" use the search function at link
    // https://docs.rs/winapi/*/x86_64-pc-windows-msvc/winapi/um/wincon/fn.GetConsoleWindow.html
    let hWnd = unsafe { winapi::um::wincon::GetConsoleWindow }; //gets the current console window handle
    
    //System Tray Icon support - here it is
    let WM_MYMESSAGE = winapi::um::winuser::WM_APP + 100; //prep WM_MYMESSAGE
    let mut trayToolTip = "Tool tip words here".to_string(); //record tooltip words for the icon
    let mut trayToolTipInt: [u16; 128] = [0; 128]; //fill with 0's
    let trayToolTipStrStep: &str = &*trayToolTip; //these two types of strings
    let mut trayToolTipStepOS = OsStr::new(trayToolTipStrStep); //convert to OS string format or something
    let mut trayToolTipStepUTF16 = trayToolTipStepOS.encode_wide().collect::<Vec<u16>>(); //now actually convert to UTF16 format for the OS
    trayToolTipInt[..trayToolTipStepUTF16.len()].copy_from_slice(&trayToolTipStepUTF16); //record it in that nice integer holder
    
    let mut nid: winapi::um::shellapi::NOTIFYICONDATAW = unsafe{ zeroed() }; //thing that has info on window and system tray stuff in it 
    unsafe
    {
        nid.cbSize = size_of::<winapi::um::shellapi::NOTIFYICONDATAW>() as u32; //prep
        nid.hWnd = hWnd(); //links the console window
        nid.uID = 1001; //it's a number
        nid.uCallbackMessage = WM_MYMESSAGE; //whoknows should be related to click capture but doesn't so
        nid.hIcon = winapi::um::winuser::LoadIconW(null_mut(), winapi::um::winuser::IDI_APPLICATION); //icon idk
        nid.szTip = trayToolTipInt; //tooltip for the icon
        nid.uFlags = winapi::um::shellapi::NIF_MESSAGE | winapi::um::shellapi::NIF_ICON | winapi::um::shellapi::NIF_TIP; //who knows
    };
    
    //let mut nidszTipLength = trayToolTip.chars().count() as u64; //gets the size of nid.szTip (tooltip length) indirectly (not the right size!)
    let mut nidszTipLength = trayToolTipStepUTF16.len() as u64; //gets the size of nid.szTip (tooltip length) for the UTF-16 format, which is what Windows cares about
    
    unsafe{ winapi::um::shellapi::Shell_NotifyIconW(winapi::um::shellapi::NIM_ADD, &mut nid) }; //shows the icon
    let _ = Command::new("cmd.exe").arg("/c").arg("pause").status();
    
    trayToolTip = "An updated tooltip is now here!".to_string(); //update the tooltip string
    trayToolTipInt = [0; 128]; //fill with 0's (clear it out I hope)
    let trayToolTipStrStep: &str = &*trayToolTip; //these two types of strings are hella annoying
    trayToolTipStepOS = OsStr::new(trayToolTipStrStep); //convert to OS string format or something
    trayToolTipStepUTF16 = trayToolTipStepOS.encode_wide().collect::<Vec<u16>>(); //now actually convert to UTF16 format for the OS
    trayToolTipInt[..trayToolTipStepUTF16.len()].copy_from_slice(&trayToolTipStepUTF16); //record it in that nice integer holder
    nid.szTip = trayToolTipInt; //tooltip for the icon
    //nidszTipLength = trayToolTip.chars().count() as u64; //gets the size of nid.szTip (tooltip length) indirectly (not the right size!)
    nidszTipLength = trayToolTipStepUTF16.len() as u64; //gets the size of nid.szTip (tooltip length) for the UTF-16 format, which is what Windows cares about
    unsafe{ winapi::um::shellapi::Shell_NotifyIconW(winapi::um::shellapi::NIM_MODIFY, &mut nid) }; //updates system tray icon
    
    let _ = Command::new("cmd.exe").arg("/c").arg("pause").status();
    
    unsafe{ winapi::um::shellapi::Shell_NotifyIconW(winapi::um::shellapi::NIM_DELETE, &mut nid) }; //deletes system tray icon when done
    
    let _ = Command::new("cmd.exe").arg("/c").arg("pause").status();
    
    }
    

    And don't forget about including the following in your Cargo.toml!

    [target.'cfg(windows)'.dependencies]
    winapi = { version = "*", features = ["winuser","wincon","shellapi"] }