basic working as tray
This commit is contained in:
parent
b6ffab3ae3
commit
70163d4e12
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user