Reputation: 11
I have a notification icon app in zig using Win32 API. It has this code as its WndProc:
const std = @import("std");
const windows = std.os.windows;
const zeroes = std.mem.zeroes;
pub const WNDPROC = *const fn (
hwnd: windows.HWND,
uMsg: windows.UINT,
wParam: windows.WPARAM,
lParam: windows.LPARAM,
) callconv(windows.WINAPI) windows.LRESULT;
pub const WNDCLASSEXA = extern struct {
cbSize: windows.UINT = @sizeOf(WNDCLASSEXA),
style: windows.UINT,
lpfnWndProc: WNDPROC,
cbClsExtra: i32 = 0,
cbWndExtra: i32 = 0,
hInstance: windows.HINSTANCE,
hIcon: ?windows.HICON,
hCursor: ?windows.HCURSOR,
hbrBackground: ?windows.HBRUSH,
lpszMenuName: ?[*:0]const u8,
lpszClassName: [*:0]const u8,
hIconSm: ?windows.HICON,
};
pub const NOTIFYICONDATAW = extern struct {
cbSize: windows.DWORD = @sizeOf(NOTIFYICONDATAW),
hWnd: windows.HWND,
uID: windows.UINT,
uFlags: windows.UINT,
uCallbackMessage: windows.UINT,
hIcon: windows.HICON,
szTip: [128]u8,
dwState: windows.DWORD,
dwStateMask: windows.DWORD,
szInfo: [256]u16,
DUMMYUNIONNAME: extern union {
uTimeout: windows.UINT,
uVersion: windows.UINT,
},
szInfoTitle: [64]u16,
dwInfoFlags: windows.DWORD,
guidItem: windows.GUID,
hBalloonIcon: windows.HICON,
};
pub const MSG = extern struct {
hWnd: ?windows.HWND,
message: windows.UINT,
wParam: windows.WPARAM,
lParam: windows.LPARAM,
time: windows.DWORD,
pt: windows.POINT,
lPrivate: windows.DWORD,
};
pub const HBITMAP = *opaque {};
pub const MENUITEMINFOW = extern struct {
cbSize: windows.UINT = @sizeOf(MENUITEMINFOW),
fMask: windows.UINT,
fType: windows.UINT,
fState: windows.UINT,
wID: windows.UINT,
hSubMenu: windows.HMENU,
hbmpChecked: HBITMAP,
hbmpUnchecked: HBITMAP,
dwItemData: windows.ULONG_PTR,
dwTypeData: windows.LPCWSTR,
cch: windows.UINT,
hbmpItem: HBITMAP,
};
pub const NIF_ICON = 0x00000002;
pub const NIF_INFO = 0x00000010;
pub const NIF_MESSAGE = 0x00000001;
pub const NIM_ADD = 0x00000000;
pub const NIM_DELETE = 0x00000002;
pub const SW_HIDE = 0;
pub const TPM_LEFTALIGN = 0x0000;
pub const TPM_NONOTIFY = 0x0080;
pub const TPM_RETURNCMD = 0x100;
pub const TPM_RIGHTBUTTON = 0x0002;
pub const WM_CLOSE = 0x0010;
pub const WM_COMMAND = 0x0111;
pub const WM_DESTROY = 0x0002;
pub const WM_LBUTTONUP = 0x0202;
pub const WM_RBUTTONUP = 0x0205;
pub const WM_USER = 0x0400;
pub extern "shell32" fn Shell_NotifyIconW(
dwMessage: windows.DWORD,
lpData: [*c]NOTIFYICONDATAW,
) callconv(windows.WINAPI) windows.BOOL;
pub extern "shell32" fn ExtractIconExA(
lpszFile: windows.LPCSTR,
nIconIndex: i32,
phiconLarge: [*c]windows.HICON,
phiconSmall: [*c]windows.HICON,
nIcons: windows.UINT,
) callconv(windows.WINAPI) windows.UINT;
pub extern "user32" fn CreateWindowExA(
dwExStyle: windows.DWORD,
lpClassName: [*:0]const u8,
lpWindowName: [*:0]const u8,
dwStyle: windows.DWORD,
X: i32,
Y: i32,
nWidth: i32,
nHeight: i32,
hWindParent: ?windows.HWND,
hMenu: ?windows.HMENU,
hInstance: windows.HINSTANCE,
lpParam: ?windows.LPVOID,
) callconv(windows.WINAPI) ?windows.HWND;
pub extern "user32" fn PostQuitMessage(
nExitCode: i32,
) callconv(windows.WINAPI) void;
pub extern "user32" fn DestroyWindow(
hWnd: windows.HWND,
) callconv(windows.WINAPI) windows.BOOL;
pub extern "user32" fn GetWindowLongPtrA(
hWnd: windows.HWND,
nIndex: i32,
) callconv(windows.WINAPI) windows.LONG_PTR;
pub extern "user32" fn SetWindowLongPtrA(
hWnd: windows.HWND,
nIndex: i32,
dwNewLong: windows.LONG_PTR,
) callconv(windows.WINAPI) windows.LONG_PTR;
pub extern "user32" fn DefWindowProcA(
hWnd: windows.HWND,
Msg: windows.UINT,
wParam: windows.WPARAM,
lParam: windows.LPARAM,
) callconv(windows.WINAPI) windows.LRESULT;
pub extern "user32" fn GetMessageA(
lpMsg: *MSG,
hWnd: ?windows.HWND,
wMsgFilterMin: windows.UINT,
wMsgFilterMax: windows.UINT,
) callconv(windows.WINAPI) windows.BOOL;
pub extern "user32" fn RegisterClassExA(
*const WNDCLASSEXA,
) callconv(windows.WINAPI) windows.ATOM;
pub extern "user32" fn TranslateMessage(
lpMsg: *const MSG,
) callconv(windows.WINAPI) windows.BOOL;
pub extern "user32" fn DispatchMessageA(
lpMsg: *const MSG,
) callconv(windows.WINAPI) windows.LRESULT;
pub extern "user32" fn CreatePopupMenu() callconv(windows.WINAPI) windows.HMENU;
pub extern "user32" fn GetCursorPos(
lpPoint: [*c]windows.POINT,
) callconv(windows.WINAPI) windows.BOOL;
pub extern "user32" fn SetForegroundWindow(
hWnd: windows.HWND,
) callconv(windows.WINAPI) windows.BOOL;
pub extern "user32" fn TrackPopupMenu(
hMenu: windows.HMENU,
uFlags: windows.UINT,
x: i32,
y: i32,
nReserved: i32,
hWnd: windows.HWND,
prcRect: [*c]const windows.RECT,
) callconv(windows.WINAPI) windows.BOOL;
pub extern "user32" fn InsertMenuItemW(
hMenu: std.os.windows.HMENU,
item: std.os.windows.UINT,
fByPosition: std.os.windows.BOOL,
lpmi: [*c]MENUITEMINFOW,
) callconv(std.os.windows.WINAPI) std.os.windows.BOOL;
fn registerClassExA(window_class: *const WNDCLASSEXA) !windows.ATOM {
const atom = RegisterClassExA(window_class);
if (atom != 0) return atom;
switch (windows.kernel32.GetLastError()) {
.CLASS_ALREADY_EXISTS => return error.AlreadyExists,
.INVALID_PARAMETER => unreachable,
else => |err| return windows.unexpectedError(err),
}
}
fn createWindowExA(
dwExStyle: u32,
lpClassName: [*:0]const u8,
lpWindowName: [*:0]const u8,
dwStyle: u32,
X: i32,
Y: i32,
nWidth: i32,
nHeight: i32,
hWindParent: ?windows.HWND,
hMenu: ?windows.HMENU,
hInstance: windows.HINSTANCE,
lpParam: ?*anyopaque,
) !windows.HWND {
const window = CreateWindowExA(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWindParent, hMenu, hInstance, lpParam);
if (window) |win| return win;
switch (windows.kernel32.GetLastError()) {
.CLASS_DOES_NOT_EXIST => return error.ClassDoesNotExist,
.INVALID_PARAMETER => unreachable,
else => |err| return windows.unexpectedError(err),
}
}
pub fn createIconFromFile(path: [:0]const u8) !windows.HICON {
var icon: windows.HICON = undefined;
const ret = ExtractIconExA(
path,
0,
null,
&icon,
1,
);
if (ret != 1) {
return error.NotIcon;
}
return icon;
}
var isRunning = true;
fn ProcessIconMessage(hwnd: windows.HWND, _: windows.UINT, _: windows.WPARAM, lParam: windows.LPARAM, tray: *Tray) void {
if (lParam == WM_LBUTTONUP) {
_ = DestroyWindow(hwnd);
isRunning = false;
} else if (lParam == WM_RBUTTONUP) {
var point: windows.POINT = undefined;
_ = GetCursorPos(&point);
_ = SetForegroundWindow(hwnd);
_ = TrackPopupMenu(tray.hmenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, point.x, point.y, 0, hwnd, null);
}
}
fn WndProc(hwnd: windows.HWND, uMsg: windows.UINT, wParam: windows.WPARAM, lParam: windows.LPARAM) callconv(windows.WINAPI) windows.LRESULT {
const tray_pointer = GetWindowLongPtrA(hwnd, 0);
if (tray_pointer == 0) {
return DefWindowProcA(hwnd, uMsg, wParam, lParam);
}
const tray = @as(*Tray, @ptrFromInt(@as(usize, @intCast(tray_pointer))));
switch (uMsg) {
WM_CLOSE => {
_ = DestroyWindow(hwnd);
},
WM_DESTROY => {
_ = PostQuitMessage(0);
},
WM_TRAY_CALLBACK_MESSAGE => ProcessIconMessage(hwnd, uMsg, wParam, lParam, tray),
WM_COMMAND => ProcessIconMessage(hwnd, uMsg, wParam, lParam, tray),
else => return DefWindowProcA(hwnd, uMsg, wParam, lParam),
}
return 0;
}
pub const ID_TRAY_FIRST = 1000;
pub const MFT_STRING = 0x00000000;
pub const WM_TRAY_CALLBACK_MESSAGE = WM_USER + 1;
pub const WC_TRAY_CLASS_NAME = "TRAY";
fn createMenu(id: *windows.UINT) windows.HMENU {
const hmenu = CreatePopupMenu();
{
defer id.* += 1;
var mitem = zeroes(MENUITEMINFOW);
mitem.cbSize = @sizeOf(MENUITEMINFOW);
mitem.fType = MFT_STRING;
mitem.fState = 0;
_ = InsertMenuItemW(hmenu, id.*, windows.TRUE, &mitem);
}
return hmenu;
}
pub const Tray = struct {
wc: WNDCLASSEXA = zeroes(WNDCLASSEXA),
hmenu: windows.HMENU = undefined,
pub fn init(self: *Tray) !void {
self.wc.cbSize = @sizeOf(WNDCLASSEXA);
self.wc.lpfnWndProc = WndProc;
self.wc.cbWndExtra = @sizeOf(*Tray);
self.wc.hInstance = @as(windows.HINSTANCE, @ptrCast(windows.kernel32.GetModuleHandleW(null)));
self.wc.lpszClassName = WC_TRAY_CLASS_NAME;
_ = try registerClassExA(&self.wc);
}
pub fn setupMenu(self: *Tray, _: windows.HWND, _: NOTIFYICONDATAW) !void {
var id: windows.UINT = ID_TRAY_FIRST;
self.hmenu = createMenu(&id);
}
};
pub fn main() !void {
var tray = Tray{};
const icon = try createIconFromFile("icon.ico");
try tray.init();
// try create window
const hwnd = try createWindowExA(0, WC_TRAY_CLASS_NAME, WC_TRAY_CLASS_NAME, 0, 0, 0, 0, 0, null, null, tray.wc.hInstance, null);
_ = SetWindowLongPtrA(hwnd, 0, @as(windows.LONG_PTR, @intCast(@intFromPtr(&tray))));
const text = "Tray Icon";
var szTip: [128]u8 = zeroes([128]u8); // Initialize the array to zeros
// Copy the string literal into the fixed-size array
std.mem.copyForwards(u8, &szTip, text);
var nid: NOTIFYICONDATAW = zeroes(NOTIFYICONDATAW);
nid.cbSize = @sizeOf(NOTIFYICONDATAW);
nid.hWnd = hwnd;
nid.uID = 1;
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_INFO;
nid.uCallbackMessage = WM_TRAY_CALLBACK_MESSAGE;
nid.hIcon = icon;
nid.szTip = szTip;
_ = Shell_NotifyIconW(NIM_ADD, &nid);
try tray.setupMenu(hwnd, nid);
while (isRunning) {
var msg: MSG = undefined;
_ = GetMessageA(&msg, hwnd, 0, 0);
_ = TranslateMessage(&msg);
_ = DispatchMessageA(&msg);
}
}
The program will exit correctly if I just left-click on my app notification icon.
If I right-click first so it calls SetForegroundWindow()
, then left-click, the icon is deleted from the notification area but the program doesn't exit, it just hangs there. If I Ctrl+C then the app exits properly.
I was expecting it to also just exit the program if I right-click before left-click.
This is the last few console output when just left clicking the icon where it quit properly
info: WndProc os.windows.HWND__opaque_2021@1b0d24 1025 1 512
info: Tray: 48926552960
info: WndProc os.windows.HWND__opaque_2021@1b0d24 1025 1 513
info: Tray: 48926552960
info: WndProc os.windows.HWND__opaque_2021@1b0d24 1025 1 514
info: Tray: 48926552960
info: WndProc os.windows.HWND__opaque_2021@1b0d24 144 0 0
info: Tray: 48926552960
info: WndProc os.windows.HWND__opaque_2021@1b0d24 2 0 0
info: Tray: 48926552960
info: WndProc os.windows.HWND__opaque_2021@1b0d24 130 0 0
info: Tray: 48926552960
main
~\....\zig-notification-area
➜
And here is if I right click first
info: WndProc os.windows.HWND__opaque_2021@3a190e 1025 1 512
info: Tray: 149451435920
info: WndProc os.windows.HWND__opaque_2021@3a190e 1025 1 513
info: Tray: 149451435920
info: WndProc os.windows.HWND__opaque_2021@3a190e 1025 1 514
info: Tray: 149451435920
info: WndProc os.windows.HWND__opaque_2021@3a190e 144 0 0
info: Tray: 149451435920
info: WndProc os.windows.HWND__opaque_2021@3a190e 2 0 0
info: Tray: 149451435920
info: WndProc os.windows.HWND__opaque_2021@3a190e 130 0 0
info: Tray: 149451435920
It's almost identical except the app just stuck in there hanging not quitting the program, I'm expecting both of them result the program exit correctly
Upvotes: 1
Views: 102