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.
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
Related
I am trying to learn how to work with raw Win32 API's and am following the tutorial here, but cannot, for the life of me, figure out how to pass int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) function signature to work. I do understand the int WINAPI isn't needed...but how do I get all of those parameters to pass to WinAPI calls? Especially hInstance and nCmdShow?
My Goal
Get hInstance and nShowCmd from
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {}
into a Rust program, perhaps something like:
fn main(/* args, such as hInstance, nShowCmd here /*) {
}
or, the more likely way:
fn main() {
std::env::/* hopefully something like args() /*;
}
What I've tried
I've tried getting the args, but this just passes the command-line arguments that I used to spawn the program, just as args[0] being the name of the program, which is the expected behavior. Also, calling args_os() gives the same result.
I've also tried to set the windows subsystem, but the previous behavior is the same, not the desired behavior...
#![windows_subsystem = "windows"]
I am able to get the hInstance handle by manually calling GetModuleHandle() and passing in a null pointer, but have no idea how to get the nShowCmd manually.
Important Note
I am using the windows crate, which is what I would like to use.
Any help to this eluding mystery would be much appreciated!
P.S. My window does open, and everything works fine, as expected, including working with FFI, and all the crazies involved there, lol. But I just would like to understand how this is done. One can get by without the nShowCmd, but I would really like to be able to understand how this is done in rust. I also cannot overwrite the fn main() function signature, so not sure how to go about it.
WinMain is the user-provided entry point of a Windows application. The raw application entry point as seen by the OS is far simpler:
DWORD CALLBACK RawEntryPoint(void);
It is now up to language support libraries to recover the startup information and call into the user-provided entry point (see WinMain is just the conventional name for the Win32 process entry point for details):
GetModuleHandle(NULL) for hInstance
hPrevInstance is always NULL in 32-bit and 64-bit Windows
GetCommandLine for the unparsed command line passed to the program
GetStartupInfo for lots of state information, including the wShowWindow that corresponds to nCmdShow
If you have Visual Studio installed you can have a look inside exe_common.inl to see how the C and C++ support libraries go about this.
With Rust things are unfortunately more complex. Even though the compiler and linker repurpose MSVC's CRT implementation responsible for extracting the information that would be passed into WinMain, I'm not aware of a way to get a hold of this from Rust.
You're going to have to recover that information manually. Getting to the nCmdShow parameter is a bit more involved, so let's illustrate that here:
// build.rs
// Using windows-rs 0.17.2; version 0.10.0 and later should be just fine
fn main() {
windows::build!(Windows::Win32::System::Threading::GetStartupInfoW,)
}
// src/main.rs
mod bindings {
windows::include_bindings!();
}
use bindings::Windows::Win32::System::Threading::{GetStartupInfoW, STARTUPINFOW};
fn main() {
let mut si = STARTUPINFOW {
cb: std::mem::size_of::<STARTUPINFOW>() as u32,
..Default::default()
};
unsafe { GetStartupInfoW(&mut si) };
let cmd_show = si.wShowWindow as i32;
println!("nCmdShow: {:?}", cmd_show);
}
With that you now have access to a value that corresponds to the nCmdShow parameter passed into WinMain when compiling for C or C++ (roughly, anyway). Ideally you would need to see whether dwFlags contains the STARTF_USESHOWWINDOW bit, and fabricate a reasonable default when it doesn't.
That said, I'm not even sure what purpose the nCmdShow argument passed into WinMain serves. As explained under ShowWindow, using that value doesn't have any effect when it is populated from caller-provided information.
Update 2021-10-28
Starting with version 0.22.1 the windows crate comes with pre-built bindings, making it a lot easier to consume the Windows API. The following implements the same program using the pre-built bindings in place of compile-time code generation.
Cargo.toml
[package]
name = "startup_info"
version = "0.0.0"
edition = "2021"
[dependencies.windows]
version = "0.22.1"
features = ["Win32_Foundation", "Win32_System_Threading"]
main.rs
use windows::Win32::System::Threading::{GetStartupInfoW, STARTUPINFOW};
fn main() {
let mut si = STARTUPINFOW {
cb: std::mem::size_of::<STARTUPINFOW>() as u32,
..Default::default()
};
unsafe { GetStartupInfoW(&mut si) };
let cmd_show = si.wShowWindow as i32;
println!("nCmdShow: {:?}", cmd_show);
}
There is this function in Kernel32: GetStartupInfo() that in windows-rs seems to be mapped to bindings::Windows::Win32::System::Threading::GetStartupInfoW.
This function fills a STARTUPINFOW structure that has, between a lot of useful fields, WORD wShowWindow that has the same value as the last argument in WinMain().
The funny thing about WinMain is that there is nothing magic in it, it is just the function that the real entry point function, WinMainCRTStartup calls from the CRT initialization code. You can get an idea of how it does its thing by looking at the equivalent Wine source code. There you can see that your idea of calling GetModuleHandle(NULL) to get the hInstance is the right one.
In my app, I catch all event via SetWindowsHookEx and when a user clicks on a button I retrieve an hwnd that I guess is the handle of the Tbutton.
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
DWORD lPrivate;
} MSG, *P
Now how can I from this hwnd retrieve the button name (or better the Delphi object representing the button?).
Maybe I can also retrieve the component via the POINT pt; ?
You can use FindControl, which will retrieve the object instance if the window is created by a control that belongs to the same instance of the VCL that calls the function. Since Name is published in TComponent, you can access the property regardless of the actual class type.
Every windowed VCL control has its object instance address stored in the API window's property list, along with properties containing information of module address, process id, thread id. This makes it possible for the VCL to backtrack a control from the window it created.
I am interested in sending key input through Ruby using the win32API.
I have found the function that I would like to call: SendInput
MSDN describes the signature as follows:
UINT WINAPI SendInput(
_In_ UINT nInputs,
_In_ LPINPUT pInputs,
_In_ int cbSize
);
And INPUT looks like
typedef struct tagINPUT {
DWORD type;
union {
MOUSEINPUT mi;
KEYBDINPUT ki;
HARDWAREINPUT hi;
};
} INPUT, *PINPUT;
So there's one integer that I need showing the input type.
Since I'm interested in keyboard events, I looked at the KEYBDINPUT struct
typedef struct tagKEYBDINPUT {
WORD wVk;
WORD wScan;
DWORD dwFlags;
DWORD time;
ULONG_PTR dwExtraInfo;
} KEYBDINPUT, *PKEYBDINPUT;
And there's another 5 arguments: two 16-bit integers and three 32-bit integers.
Now, from what I have gathered, SendInput takes an integer, a pointer to an INPUT struct, and an integer representing the size of the INPUT struct. Creating the Win32API object looks like
SendInput = Win32API.new("User32", "SendInput", "LPL", "L")
INPUT_KEYBOARD = 1
Now I build my INPUT struct:
input = [INPUT_KEYBOARD, 0x5A, 0, 0, 0, 0].pack('ISSIII')
First argument is the input type, followed by the five arguments for the KEYBDINPUT struct: the virtual keycode for the key I want to press, and some other flags that I don't need for testing purposes.
So I run it:
SendInput.call(1, input, input.size)
Nothing happens.
When I check GetLastError, it returns an error code of 87, which means there were invalid arguments.
I then decided to search around and found that someone else is building their input struct like this:
input = [INPUT_KEYBOARD, 0x5A, 0, 0, 0, 0, 0, 0].pack('ISSIIIII')
I tried running it, and it executes successfully!
Difference? There's two extra arguments for the input.
Now I am confused: what are these two extra arguments for the input struct?
Is there something that I'm missing when I was reading the docs?
I'm thinking it might have something to do with the ULONG_POINTER type, but when I look up the data types, it's a 32-bit integer.
Reference: http://www.dsprobotics.com/support/viewtopic.php?p=7110&sid=cd256b848d00e64a2e74e093863f837f#p7110
The function call takes a union of those structs, I would wager it has something to do with that.
Looks like MOUSEINPUT is larger than KEYBOARDINPUT:
typedef struct tagMOUSEINPUT {
LONG dx;
LONG dy;
DWORD mouseData;
DWORD dwFlags;
DWORD time;
ULONG_PTR dwExtraInfo;
} MOUSEINPUT, *PMOUSEINPUT;
Dunno for sure though, that seems like a pretty terrifying way to bridge those calls!
edit: Yeah, looks like using that MOUSEINPUT size, you end up w/28 bytes of data sent to the function, which is what pack('ISSIIIII') would net you.
I wrote some code to watch for window title changes. It works fine with different windows in my Windows 7. I use SetWinEventHook like that:
SetWinEventHook(EVENT_OBJECT_NAMECHANGE,
EVENT_OBJECT_NAMECHANGE,
0,
WinEventCallback,
processId,
threadId,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS | WINEVENT_SKIPOWNTHREAD);
Callback:
void CALLBACK WinEventCallback(HWINEVENTHOOK hWinEventHook,
DWORD dwEvent,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD dwEventThread,
DWORD dwmsEventTime)
{
qDebug("Window %p", hwnd);
...
GetWindowText(hwnd, ...);
}
For one specific window I see the debug message "Window 0x0" all the time, e.g. I get the window handle set to zero in the callback. In this case GetWindowText fails. All other windows work fine. The question is why? I don't see anything extraordinary in Spy++:
Not all events generated may be associated with a window, especially for something as generic as a name change. The hook documentation specifically states that NULL windows are possible, so simply ignore them if your hook logic is window-oriented. If you are seeing a window change its title but you are getting a NULL window in your callback, then either it is not a real window, or there was an issue marshaling the window to your callback, or something like that.
The problem comes for the WinEventCallback's signature you are using.
Fix it by using this one: WinEventCallback(IntPtr hWinEventHook, uint iEvent, IntPtr hWnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
Is it possible to find out to which process a MessageBox belongs? If yes, how?
You want to use GetWindowThreadProcessId. Here is an example.
#include <windows.h>
static const TCHAR g_cszClass = TEXT("#32770"); // dialog box class
// returned handle must be closed with CloseHandle() when no longer used
HANDLE GetMessageBoxProcess(__in_z LPCTSTR lpcszTitle, __in DWORD dwAccess)
{
HWND hWnd;
DWORD dwProcessId = 0;
HANDLE hRET;
hWnd = FindWindow(g_cszClass, lpcszTitle);
if (hWnd != NULL)
{
GetWindowThreadProcessId(hWnd, &dwProcessId);
if (dwProcessId != 0)
hRET = OpenProcess(dwAccess, FALSE, dwProcessId);
}
return hRET;
}
Not sure why you'd want the process though. Reasons I can think of:
terminating the message box completely
detecting a process
detecting a certain message box
all of which have superior & more optimal alternative solutions.
Another answer gives the programmatic solution. If this is a one-off thing for debugging, you can choose the window with Spy++, get the process ID from the properties window, and then look up the process in Task Manager.