Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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;
}
like image 909
user2403531 Avatar asked Jan 04 '19 23:01

user2403531


People also ask

How do I create an icon tray in python?

To create a System Tray icon of a tkinter application, we can use pystray module in Python. It has many inbuilt functions and methods that can be used to configure the system tray icon of the application. To install pystray in your machine you can type "pip install pystray" command in your shell or command prompt.


1 Answers

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"] }
like image 189
user2403531 Avatar answered Oct 23 '22 09:10

user2403531