diff --git a/src/main-windows.zig b/src/main-windows.zig index 6a51c49..e30d77d 100644 --- a/src/main-windows.zig +++ b/src/main-windows.zig @@ -341,6 +341,7 @@ const WM_INITMENUPOPUP = @as(u32, 279); const WM_DRAWCLIPBOARD = @as(u32, 776); const WM_SIZECLIPBOARD = @as(u32, 779); const WM_CHANGECBCHAIN = @as(u32, 781); +const WM_CLIPBOARDUPDATE = @as(u32, 797); const WM_USER = @as(u32, 1024); // USER constants here - not part of Windows headers @@ -492,6 +493,60 @@ extern "USER32" fn UpdateWindow( hWnd: ?w.HWND, ) callconv(w.WINAPI) BOOL; +const REDRAW_WINDOW_FLAGS = enum(u32) { + INVALIDATE = 1, + INTERNALPAINT = 2, + ERASE = 4, + VALIDATE = 8, + NOINTERNALPAINT = 16, + NOERASE = 32, + NOCHILDREN = 64, + ALLCHILDREN = 128, + UPDATENOW = 256, + ERASENOW = 512, + FRAME = 1024, + NOFRAME = 2048, + _, + pub fn initFlags(o: struct { + INVALIDATE: u1 = 0, + INTERNALPAINT: u1 = 0, + ERASE: u1 = 0, + VALIDATE: u1 = 0, + NOINTERNALPAINT: u1 = 0, + NOERASE: u1 = 0, + NOCHILDREN: u1 = 0, + ALLCHILDREN: u1 = 0, + UPDATENOW: u1 = 0, + ERASENOW: u1 = 0, + FRAME: u1 = 0, + NOFRAME: u1 = 0, + }) REDRAW_WINDOW_FLAGS { + return @intToEnum(REDRAW_WINDOW_FLAGS, (if (o.INVALIDATE == 1) @enumToInt(REDRAW_WINDOW_FLAGS.INVALIDATE) else 0) | (if (o.INTERNALPAINT == 1) @enumToInt(REDRAW_WINDOW_FLAGS.INTERNALPAINT) else 0) | (if (o.ERASE == 1) @enumToInt(REDRAW_WINDOW_FLAGS.ERASE) else 0) | (if (o.VALIDATE == 1) @enumToInt(REDRAW_WINDOW_FLAGS.VALIDATE) else 0) | (if (o.NOINTERNALPAINT == 1) @enumToInt(REDRAW_WINDOW_FLAGS.NOINTERNALPAINT) else 0) | (if (o.NOERASE == 1) @enumToInt(REDRAW_WINDOW_FLAGS.NOERASE) else 0) | (if (o.NOCHILDREN == 1) @enumToInt(REDRAW_WINDOW_FLAGS.NOCHILDREN) else 0) | (if (o.ALLCHILDREN == 1) @enumToInt(REDRAW_WINDOW_FLAGS.ALLCHILDREN) else 0) | (if (o.UPDATENOW == 1) @enumToInt(REDRAW_WINDOW_FLAGS.UPDATENOW) else 0) | (if (o.ERASENOW == 1) @enumToInt(REDRAW_WINDOW_FLAGS.ERASENOW) else 0) | (if (o.FRAME == 1) @enumToInt(REDRAW_WINDOW_FLAGS.FRAME) else 0) | (if (o.NOFRAME == 1) @enumToInt(REDRAW_WINDOW_FLAGS.NOFRAME) else 0)); + } +}; +const RDW_INVALIDATE = REDRAW_WINDOW_FLAGS.INVALIDATE; +const RDW_INTERNALPAINT = REDRAW_WINDOW_FLAGS.INTERNALPAINT; +const RDW_ERASE = REDRAW_WINDOW_FLAGS.ERASE; +const RDW_VALIDATE = REDRAW_WINDOW_FLAGS.VALIDATE; +const RDW_NOINTERNALPAINT = REDRAW_WINDOW_FLAGS.NOINTERNALPAINT; +const RDW_NOERASE = REDRAW_WINDOW_FLAGS.NOERASE; +const RDW_NOCHILDREN = REDRAW_WINDOW_FLAGS.NOCHILDREN; +const RDW_ALLCHILDREN = REDRAW_WINDOW_FLAGS.ALLCHILDREN; +const RDW_UPDATENOW = REDRAW_WINDOW_FLAGS.UPDATENOW; +const RDW_ERASENOW = REDRAW_WINDOW_FLAGS.ERASENOW; +const RDW_FRAME = REDRAW_WINDOW_FLAGS.FRAME; +const RDW_NOFRAME = REDRAW_WINDOW_FLAGS.NOFRAME; + +const HGDIOBJ = *opaque {}; +const HRGN = HGDIOBJ; + +extern "USER32" fn RedrawWindow( + hWnd: ?w.HWND, + lprcUpdate: ?*const RECT, + hrgnUpdate: ?HRGN, + flags: REDRAW_WINDOW_FLAGS, +) callconv(w.WINAPI) BOOL; + // Data exchange extern "USER32" fn GetClipboardData( uFormat: u32, @@ -499,19 +554,19 @@ extern "USER32" fn GetClipboardData( extern "USER32" fn GetClipboardOwner() callconv(w.WINAPI) ?w.HWND; -extern "USER32" fn SetClipboardViewer( - hWndNewViewer: ?w.HWND, -) callconv(w.WINAPI) ?w.HWND; - extern "USER32" fn OpenClipboard( hWndNewOwner: ?w.HWND, ) callconv(w.WINAPI) BOOL; extern "USER32" fn CloseClipboard() callconv(w.WINAPI) BOOL; -extern "USER32" fn ChangeClipboardChain( - hWndRemove: ?w.HWND, - hWndNewNext: ?w.HWND, +extern "USER32" fn AddClipboardFormatListener( + hwnd: ?w.HWND, +) callconv(w.WINAPI) BOOL; + +// TODO: this type is limited to platform 'windows6.0.6000' +extern "USER32" fn RemoveClipboardFormatListener( + hwnd: ?w.HWND, ) callconv(w.WINAPI) BOOL; extern "USER32" fn GetPriorityClipboardFormat( @@ -833,14 +888,29 @@ fn getWinStyleString(comptime buflen: u32, str: []const u8) [buflen]u8 { return buf; } +// Globals +var uFormat: w.UINT = @enumToInt(CF_TEXT); +var fAuto: w.BOOL = w.TRUE; +var icon_data: NOTIFYICONDATAA = undefined; +var window_state: union(enum(u8)) { + INITIAL = 0, + NORMAL = 1, + MINIMIZED = 2, +} = .INITIAL; +var gpa: std.heap.GeneralPurposeAllocator(.{}) = undefined; +var contents: [:0]const u8 = ""; +var free_contents = false; + pub export fn wWinMain(hInstance: w.HINSTANCE, hPrevInstance: ?w.HINSTANCE, lpCmdLine: w.PWSTR, nCmdShow: w.INT) w.INT { _ = hPrevInstance; _ = lpCmdLine; _ = nCmdShow; + gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + h_instance = hInstance; - dbg_msg = getWinStyleString(17, "TODO: Send now: x"); // Register the window class. var wc: WNDCLASSA = .{ .lpszClassName = "Clipboard watcher", @@ -903,33 +973,8 @@ pub export fn wWinMain(hInstance: w.HINSTANCE, hPrevInstance: ?w.HINSTANCE, lpCm return 0; } -// Globals -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; - // - // HDC hdc; - // HDC hdcMem; - // PAINTSTRUCT ps; - // LPPAINTSTRUCT lpps; - // RECT rc; - // LPRECT lprc; - // HGLOBAL hglb; - // LPSTR lpstr; - // HBITMAP hbm; - // HENHMETAFILE hemf; - // HWND hwndOwner; + const allocator = &gpa.allocator; switch (uMsg) { WM_PAINT => { @@ -989,21 +1034,9 @@ fn MainWndProc(hwnd: w.HWND, uMsg: u32, wParam: w.WPARAM, lParam: w.LPARAM) call // } }, @enumToInt(CF_TEXT) => { - if (OpenClipboard(hwnd) > 0) { - var hglb = GetClipboardData(uFormat).?; - const hnd = @intCast(isize, @ptrToInt(hglb)); - 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); - _ = CloseClipboard(); - } + _ = GetClientRect(hwnd, &rc); + // TODO: copy our lpstr and ship it to our common handler + _ = DrawTextA(hdc, contents, -1, &rc, DT_LEFT); }, @enumToInt(CF_ENHMETAFILE) => { @@ -1043,6 +1076,7 @@ fn MainWndProc(hwnd: w.HWND, uMsg: u32, wParam: w.WPARAM, lParam: w.LPARAM) call }, WM_SIZE => { if (uFormat == @enumToInt(CF_OWNERDISPLAY)) { + // I don't believe this will ever execute and can be removed var hwndOwner = GetClipboardOwner().?; var hglb = GlobalAlloc(GMEM_MOVEABLE, @sizeOf(RECT)); defer _ = GlobalFree(hglb); @@ -1054,11 +1088,35 @@ fn MainWndProc(hwnd: w.HWND, uMsg: u32, wParam: w.WPARAM, lParam: w.LPARAM) call _ = SendMessageA(hwndOwner, WM_SIZECLIPBOARD, @ptrToInt(hwnd), hglb); } }, + WM_CLIPBOARDUPDATE => { // Message arrives only after AddClipboardFormatListener + + // Get a lock on the clipboard + if (OpenClipboard(hwnd) > 0) { + defer _ = CloseClipboard(); + var hglb = GetClipboardData(uFormat).?; + const hnd = @intCast(isize, @ptrToInt(hglb)); + var lpstr = GlobalLock(hnd).?; + defer _ = GlobalUnlock(hnd); + + if (free_contents) { + allocator.free(contents); + } + free_contents = true; + contents = std.fmt.allocPrintZ(allocator, "{s}", .{@ptrCast([*:0]const u8, lpstr)}) catch blk: { + _ = MessageBoxA(hwnd, "Allocation failed copying clipboard contents", "Error", 0); + free_contents = false; + break :blk ""; + }; + } + if (RedrawWindow(hwnd, null, null, RDW_INVALIDATE) != w.TRUE) { + _ = MessageBoxA(hwnd, "Redraw failed", "Error", 0); + } + }, WM_CREATE => { - - // Add the window to the clipboard viewer chain. - - hwndNextViewer = SetClipboardViewer(hwnd); + if (AddClipboardFormatListener(hwnd) != w.TRUE) { + _ = MessageBoxA(hwnd, "Could not add clipboard listener", "Error", 0); + return -1; // oops + } var tip = getWinStyleString(128, "Clipboard processor"); icon_data = .{ .cbSize = @sizeOf(@TypeOf(icon_data)), @@ -1081,8 +1139,7 @@ fn MainWndProc(hwnd: w.HWND, uMsg: u32, wParam: w.WPARAM, lParam: w.LPARAM) call .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 @@ -1110,50 +1167,15 @@ fn MainWndProc(hwnd: w.HWND, uMsg: u32, wParam: w.WPARAM, lParam: w.LPARAM) call else => {}, } }, - WM_CHANGECBCHAIN => { - - // If the next window is closing, repair the chain. - - // if ((HWND) wParam == hwndNextViewer) - if (wParam == @ptrToInt(hwndNextViewer.?)) { - hwndNextViewer = @intToPtr(w.HWND, @intCast(usize, lParam)); // just a cast - - // Otherwise, pass the message to the next link. - - } else if (hwndNextViewer) |hnv| { - _ = SendMessageA(hnv, uMsg, wParam, lParam); - } - }, - WM_DESTROY => { - _ = ChangeClipboardChain(hwnd, hwndNextViewer); + _ = RemoveClipboardFormatListener(hwnd); _ = Shell_NotifyIconA(NIM_DELETE, &icon_data); PostQuitMessage(0); }, - WM_DRAWCLIPBOARD => { // clipboard contents changed. - - // Update the window by using Auto clipboard format. - - SetAutoView(hwnd); - - // Pass the message to the next window in clipboard - // viewer chain. - - _ = SendMessageA(hwndNextViewer, uMsg, wParam, lParam); - }, - - WM_INITMENUPOPUP => { - // if (!HIWORD(lParam)) - // if (lParam > 0xffffffff) - // InitMenu(hwnd, wParam); // (HMENU) wParam); - }, - WM_COMMAND => { - // switch (LOWORD(wParam)) { switch (wParam & 0xffffffff) { IDM_EXIT => _ = DestroyWindow(hwnd), - // IDM_AUTO => SetAutoView(hwnd), else => { fAuto = w.FALSE; uFormat = @intCast(w.UINT, wParam & 0xffffffff); // LOWORD(wParam); @@ -1165,103 +1187,3 @@ fn MainWndProc(hwnd: w.HWND, uMsg: u32, wParam: w.WPARAM, lParam: w.LPARAM) call } return 0; } -var auPriorityList: [4]u32 = .{ - @enumToInt(CF_OWNERDISPLAY), - @enumToInt(CF_TEXT), - @enumToInt(CF_ENHMETAFILE), - @enumToInt(CF_BITMAP), -}; - -fn SetAutoView(hwnd: w.HWND) callconv(w.WINAPI) void { - var runtime_zero: usize = 0; - var auPriorityListSlice = auPriorityList[runtime_zero..auPriorityList.len]; - uFormat = @intCast(c_uint, GetPriorityClipboardFormat(auPriorityListSlice.ptr, 4)); - fAuto = w.TRUE; - - _ = InvalidateRect(hwnd, null, w.TRUE); - _ = UpdateWindow(hwnd); -} - -// fn InitMenu(hwnd: w.HWND, hmenu: w.HMENU) callconv(w.WINAPI) void { -// // UINT uFormat; -// // char szFormatName[80]; -// // LPCSTR lpFormatName; -// // UINT fuFlags; -// // UINT idMenuItem; -// -// // If a menu is not the display menu, no initialization is necessary. -// -// if (GetMenuItemID(hmenu, 0) != IDM_AUTO) -// return; -// -// // Delete all menu items except the first. -// -// while (GetMenuItemCount(hmenu) > 1) -// DeleteMenu(hmenu, 1, MF_BYPOSITION); -// -// // Check or uncheck the Auto menu item. -// -// if (fAuto) { -// fuFlags = MF_BYCOMMAND | MF_CHECKED; -// } else fuFlags = MF_BYCOMMAND | MF_UNCHECKED; -// -// CheckMenuItem(hmenu, IDM_AUTO, fuFlags); -// -// // If there are no clipboard formats, return. -// -// if (CountClipboardFormats() == 0) -// return; -// -// // Open the clipboard. -// -// if (!OpenClipboard(hwnd)) -// return; -// -// // Add a separator and then a menu item for each format. -// -// AppendMenu(hmenu, MF_SEPARATOR, 0, null); -// var uFormat = EnumClipboardFormats(0); -// -// while (uFormat) { -// // Call an application-defined function to get the name -// // of the clipboard format. -// -// var lpFormatName = GetPredefinedClipboardFormatName(uFormat); -// -// // For registered formats, get the registered name. -// -// if (lpFormatName == null) { -// -// // Note that, if the format name is larger than the -// // buffer, it is truncated. -// if (GetClipboardFormatName(uFormat, szFormatName, @sizeOf(szFormatName))) { -// lpFormatName = szFormatName; -// } else lpFormatName = "(unknown)"; -// } -// -// // Add a menu item for the format. For displayable -// // formats, use the format ID for the menu ID. -// -// if (IsDisplayableFormat(uFormat)) { -// fuFlags = MF_STRING; -// idMenuItem = uFormat; -// } else { -// fuFlags = MF_STRING | MF_GRAYED; -// idMenuItem = 0; -// } -// AppendMenu(hmenu, fuFlags, idMenuItem, lpFormatName); -// -// uFormat = EnumClipboardFormats(uFormat); -// } -// CloseClipboard(); -// } -// -// fn IsDisplayableFormat(ufmt: w.UINT) callconv(w.WINAPI) w.BOOL { -// switch (ufmt) { -// CF_OWNERDISPLAY => return true, -// CF_TEXT => return true, -// CF_ENHMETAFILE => return true, -// CF_BITMAP => return true, -// else => return false, -// } -// }