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
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;
}
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.
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"] }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With