basic working as tray

This commit is contained in:
Emil Lerch 2021-11-22 17:49:47 -08:00
parent b6ffab3ae3
commit 70163d4e12
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
2 changed files with 274 additions and 3 deletions

View File

@ -46,6 +46,7 @@ pub fn build(b: *std.build.Builder) void {
// woah...we don't actually need libc!
exe.linkSystemLibrary("user32");
exe.linkSystemLibrary("kernel32");
exe.linkSystemLibrary("shell32");
}
exe.install();

View File

@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const w = std.os.windows;
extern "user32" fn MessageBoxA(hWnd: ?w.HANDLE, lpText: ?w.LPCSTR, lpCaption: ?w.LPCSTR, uType: w.UINT) callconv(w.WINAPI) c_int;
@ -341,6 +342,10 @@ const WM_DRAWCLIPBOARD = @as(u32, 776);
const WM_SIZECLIPBOARD = @as(u32, 779);
const WM_CHANGECBCHAIN = @as(u32, 781);
const WM_USER = @as(u32, 1024);
// USER constants here - not part of Windows headers
const WM_TRAY = @as(u32, WM_USER + 1);
const RECT = extern struct {
left: i32,
top: i32,
@ -616,6 +621,199 @@ extern "KERNEL32" fn GlobalFree(
hMem: isize,
) callconv(w.WINAPI) isize;
// Shell
const Arch = enum { X86, X64, Arm64 };
const arch: Arch = switch (builtin.target.cpu.arch) {
.i386 => .X86,
.x86_64 => .X64,
.arm, .armeb => .Arm64,
else => @compileError("unable to determine win32 arch"),
};
const NOTIFY_ICON_DATA_FLAGS = enum(u32) {
MESSAGE = 1,
ICON = 2,
TIP = 4,
STATE = 8,
INFO = 16,
GUID = 32,
REALTIME = 64,
SHOWTIP = 128,
_,
pub fn initFlags(o: struct {
MESSAGE: u1 = 0,
ICON: u1 = 0,
TIP: u1 = 0,
STATE: u1 = 0,
INFO: u1 = 0,
GUID: u1 = 0,
REALTIME: u1 = 0,
SHOWTIP: u1 = 0,
}) NOTIFY_ICON_DATA_FLAGS {
return @intToEnum(NOTIFY_ICON_DATA_FLAGS, (if (o.MESSAGE == 1) @enumToInt(NOTIFY_ICON_DATA_FLAGS.MESSAGE) else 0) | (if (o.ICON == 1) @enumToInt(NOTIFY_ICON_DATA_FLAGS.ICON) else 0) | (if (o.TIP == 1) @enumToInt(NOTIFY_ICON_DATA_FLAGS.TIP) else 0) | (if (o.STATE == 1) @enumToInt(NOTIFY_ICON_DATA_FLAGS.STATE) else 0) | (if (o.INFO == 1) @enumToInt(NOTIFY_ICON_DATA_FLAGS.INFO) else 0) | (if (o.GUID == 1) @enumToInt(NOTIFY_ICON_DATA_FLAGS.GUID) else 0) | (if (o.REALTIME == 1) @enumToInt(NOTIFY_ICON_DATA_FLAGS.REALTIME) else 0) | (if (o.SHOWTIP == 1) @enumToInt(NOTIFY_ICON_DATA_FLAGS.SHOWTIP) else 0));
}
};
const NIF_MESSAGE = NOTIFY_ICON_DATA_FLAGS.MESSAGE;
const NIF_ICON = NOTIFY_ICON_DATA_FLAGS.ICON;
const NIF_TIP = NOTIFY_ICON_DATA_FLAGS.TIP;
const NIF_STATE = NOTIFY_ICON_DATA_FLAGS.STATE;
const NIF_INFO = NOTIFY_ICON_DATA_FLAGS.INFO;
const NIF_GUID = NOTIFY_ICON_DATA_FLAGS.GUID;
const NIF_REALTIME = NOTIFY_ICON_DATA_FLAGS.REALTIME;
const NIF_SHOWTIP = NOTIFY_ICON_DATA_FLAGS.SHOWTIP;
const NOTIFYICONDATAA = switch (arch) {
.X64, .Arm64 => extern struct {
cbSize: u32,
hWnd: ?w.HWND,
uID: u32,
uFlags: NOTIFY_ICON_DATA_FLAGS,
uCallbackMessage: u32,
hIcon: ?w.HICON,
szTip: [128]w.CHAR,
dwState: u32,
dwStateMask: u32,
szInfo: [256]w.CHAR,
Anonymous: extern union {
uTimeout: u32,
uVersion: u32,
},
szInfoTitle: [64]w.CHAR,
dwInfoFlags: u32,
guidItem: Guid,
hBalloonIcon: ?w.HICON,
},
.X86 => packed struct {
cbSize: u32,
hWnd: ?w.HWND,
uID: u32,
uFlags: NOTIFY_ICON_DATA_FLAGS,
uCallbackMessage: u32,
hIcon: ?w.HICON,
szTip: [128]w.CHAR,
dwState: u32,
dwStateMask: u32,
szInfo: [256]w.CHAR,
Anonymous: packed union {
uTimeout: u32,
uVersion: u32,
},
szInfoTitle: [64]w.CHAR,
dwInfoFlags: u32,
guidItem: Guid,
hBalloonIcon: ?w.HICON,
},
};
const NOTIFY_ICON_MESSAGE = enum(u32) {
ADD = 0,
MODIFY = 1,
DELETE = 2,
SETFOCUS = 3,
SETVERSION = 4,
};
const NIM_ADD = NOTIFY_ICON_MESSAGE.ADD;
const NIM_MODIFY = NOTIFY_ICON_MESSAGE.MODIFY;
const NIM_DELETE = NOTIFY_ICON_MESSAGE.DELETE;
const NIM_SETFOCUS = NOTIFY_ICON_MESSAGE.SETFOCUS;
const NIM_SETVERSION = NOTIFY_ICON_MESSAGE.SETVERSION;
const WM_LBUTTONDOWN = @as(u32, 513);
const WM_LBUTTONUP = @as(u32, 514);
const WM_LBUTTONDBLCLK = @as(u32, 515);
extern "SHELL32" fn Shell_NotifyIconA(
dwMessage: NOTIFY_ICON_MESSAGE,
lpData: ?*NOTIFYICONDATAA,
) callconv(w.WINAPI) BOOL;
pub fn typedConst(comptime T: type, comptime value: anytype) T {
return typedConst2(T, T, value);
}
pub fn typedConst2(comptime ReturnType: type, comptime SwitchType: type, comptime value: anytype) ReturnType {
const target_type_error = @as([]const u8, "typedConst cannot convert to " ++ @typeName(ReturnType));
const value_type_error = @as([]const u8, "typedConst cannot convert " ++ @typeName(@TypeOf(value)) ++ " to " ++ @typeName(ReturnType));
switch (@typeInfo(SwitchType)) {
.Int => |target_type_info| {
if (value >= std.math.maxInt(SwitchType)) {
if (target_type_info.signedness == .signed) {
const UnsignedT = @Type(std.builtin.TypeInfo{ .Int = .{ .signedness = .unsigned, .bits = target_type_info.bits } });
return @bitCast(SwitchType, @as(UnsignedT, value));
}
}
return value;
},
.Pointer => |target_type_info| switch (target_type_info.size) {
.One, .Many, .C => {
switch (@typeInfo(@TypeOf(value))) {
.ComptimeInt, .Int => {
const usize_value = if (value >= 0) value else @bitCast(usize, @as(isize, value));
return @intToPtr(ReturnType, usize_value);
},
else => @compileError(value_type_error),
}
},
else => target_type_error,
},
.Optional => |target_type_info| switch (@typeInfo(target_type_info.child)) {
.Pointer => return typedConst2(ReturnType, target_type_info.child, value),
else => target_type_error,
},
.Enum => |_| switch (@typeInfo(@TypeOf(value))) {
.Int => return @intToEnum(ReturnType, value),
else => target_type_error,
},
else => @compileError(target_type_error),
}
}
const Guid = extern union {
Ints: extern struct {
a: u32,
b: u16,
c: u16,
d: [8]u8,
},
Bytes: [16]u8,
const big_endian_hex_offsets = [16]u6{ 0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34 };
const little_endian_hex_offsets = [16]u6{ 6, 4, 2, 0, 11, 9, 16, 14, 19, 21, 24, 26, 28, 30, 32, 34 };
const hex_offsets = switch (builtin.target.cpu.arch.endian()) {
.Big => big_endian_hex_offsets,
.Little => little_endian_hex_offsets,
};
pub fn initString(s: []const u8) Guid {
var guid = Guid{ .Bytes = undefined };
for (hex_offsets) |hex_offset, i| {
//guid.Bytes[i] = decodeHexByte(s[offset..offset+2]);
guid.Bytes[i] = decodeHexByte([2]u8{ s[hex_offset], s[hex_offset + 1] });
}
return guid;
}
};
comptime {
std.debug.assert(@sizeOf(Guid) == 16);
}
// TODO: is this in the standard lib somewhere?
fn hexVal(c: u8) u4 {
if (c <= '9') return @intCast(u4, c - '0');
if (c >= 'a') return @intCast(u4, c + 10 - 'a');
return @intCast(u4, c + 10 - 'A');
}
// TODO: is this in the standard lib somewhere?
fn decodeHexByte(hex: [2]u8) u8 {
return @intCast(u8, hexVal(hex[0])) << 4 | hexVal(hex[1]);
}
pub const IDI_APPLICATION = typedConst([*:0]const u16, @as(u32, 32512));
pub extern "USER32" fn LoadIconW(
hInstance: ?w.HINSTANCE,
lpIconName: ?[*:0]const u16,
) callconv(w.WINAPI) ?w.HICON;
// resource.h
// #define IDD_MFPLAYBACK_DIALOG 102
// #define IDM_EXIT 105
@ -627,10 +825,22 @@ extern "KERNEL32" fn GlobalFree(
// #define IDC_STATIC -1
const IDM_EXIT = @as(u32, 105);
var h_instance: w.HINSTANCE = undefined;
fn getWinStyleString(comptime buflen: u32, str: []const u8) [buflen]u8 {
var buf: [buflen]u8 = .{0} ** buflen;
for (str) |c, i| buf[i] = c;
return buf;
}
pub export fn wWinMain(hInstance: w.HINSTANCE, hPrevInstance: ?w.HINSTANCE, lpCmdLine: w.PWSTR, nCmdShow: w.INT) w.INT {
_ = hPrevInstance;
_ = lpCmdLine;
_ = nCmdShow;
h_instance = hInstance;
dbg_msg = getWinStyleString(17, "TODO: Send now: x");
// Register the window class.
var wc: WNDCLASSA = .{
.lpszClassName = "Clipboard watcher",
@ -662,7 +872,7 @@ pub export fn wWinMain(hInstance: w.HINSTANCE, hPrevInstance: ?w.HINSTANCE, lpCm
CW_USEDEFAULT,
CW_USEDEFAULT,
null, // Parent window
null, // Parent window: use HWND_MESSAGE to make this completely invisible
null, // Menu
hInstance, // Instance handle
null, // Additional application data
@ -671,8 +881,6 @@ pub export fn wWinMain(hInstance: w.HINSTANCE, hPrevInstance: ?w.HINSTANCE, lpCm
if (hwnd == null)
return 0;
_ = ShowWindow(hwnd, @intToEnum(SHOW_WINDOW_CMD, nCmdShow));
// Run the message loop.
var msg: MSG = .{
@ -699,6 +907,14 @@ pub export fn wWinMain(hInstance: w.HINSTANCE, hPrevInstance: ?w.HINSTANCE, lpCm
var uFormat: w.UINT = @bitReverse(c_uint, 0);
var fAuto: w.BOOL = w.TRUE;
var hwndNextViewer: ?w.HWND = null;
var icon_data: NOTIFYICONDATAA = undefined;
var dbg_msg: [17]u8 = undefined;
var cnt: u8 = 0;
var window_state: union(enum(u8)) {
INITIAL = 0,
NORMAL = 1,
MINIMIZED = 2,
} = .INITIAL;
fn MainWndProc(hwnd: w.HWND, uMsg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w.WINAPI) w.LRESULT { //APIENTRY {
// static HWND hwndNextViewer;
@ -779,6 +995,10 @@ fn MainWndProc(hwnd: w.HWND, uMsg: u32, wParam: w.WPARAM, lParam: w.LPARAM) call
var lpstr = GlobalLock(hnd).?;
_ = GetClientRect(hwnd, &rc);
// TODO: copy our lpstr and ship it to our common handler
cnt = (cnt + 1) % 10;
dbg_msg[dbg_msg.len - 1] = '0' + cnt;
_ = MessageBoxA(hwnd, @ptrCast([*:0]const u8, &dbg_msg), "Debug", 0);
_ = DrawTextA(hdc, @ptrCast([*:0]const u8, lpstr), -1, &rc, DT_LEFT);
_ = GlobalUnlock(hnd);
@ -839,8 +1059,57 @@ fn MainWndProc(hwnd: w.HWND, uMsg: u32, wParam: w.WPARAM, lParam: w.LPARAM) call
// Add the window to the clipboard viewer chain.
hwndNextViewer = SetClipboardViewer(hwnd);
var tip = getWinStyleString(128, "Clipboard processor");
icon_data = .{
.cbSize = @sizeOf(@TypeOf(icon_data)),
.hWnd = hwnd,
.uFlags = NOTIFY_ICON_DATA_FLAGS.initFlags(.{
.MESSAGE = 1,
.ICON = 1,
.TIP = 1,
}),
.uCallbackMessage = WM_TRAY,
.uID = 4242,
.hIcon = LoadIconW(null, IDI_APPLICATION), // TODO: Custom icon
.szTip = tip,
.dwState = 0,
.dwStateMask = 0,
.szInfo = getWinStyleString(256, "Info"),
.Anonymous = .{ .uTimeout = 0 },
.szInfoTitle = getWinStyleString(64, "Info Title"),
.dwInfoFlags = 0,
.guidItem = Guid.initString("3e781b84-3ffd-44a1-b3ab-11d0f90136f9"), // generated from duckduckgo
.hBalloonIcon = null,
};
// stData.hIcon = g_hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_TRAYICON));
// LoadStringSafe(IDS_TIP, stData.szTip, _countof(stData.szTip));
if (Shell_NotifyIconA(NIM_ADD, &icon_data) != w.TRUE) {
_ = MessageBoxA(hwnd, "Notification Icon failed to create", "Error", 0);
return -1; // oops
}
},
WM_TRAY => {
switch (lParam) {
WM_LBUTTONUP => {
switch (window_state) {
.INITIAL => {
_ = ShowWindow(hwnd, SHOW_WINDOW_CMD.SHOWNORMAL);
window_state = .NORMAL;
},
.NORMAL => {
_ = ShowWindow(hwnd, SHOW_WINDOW_CMD.HIDE);
window_state = .MINIMIZED;
},
.MINIMIZED => {
_ = ShowWindow(hwnd, SHOW_WINDOW_CMD.SHOW);
window_state = .NORMAL;
},
}
},
else => {},
}
},
WM_CHANGECBCHAIN => {
// If the next window is closing, repair the chain.
@ -858,6 +1127,7 @@ fn MainWndProc(hwnd: w.HWND, uMsg: u32, wParam: w.WPARAM, lParam: w.LPARAM) call
WM_DESTROY => {
_ = ChangeClipboardChain(hwnd, hwndNextViewer);
_ = Shell_NotifyIconA(NIM_DELETE, &icon_data);
PostQuitMessage(0);
},