const Window = @This(); handle: win32.foundation.HWND, const EnumContext = struct { allocator: std.mem.Allocator, list: *std.ArrayList(Window), err: ?std.mem.Allocator.Error = null, }; /// Appends each manageable open window to `list`. pub fn enumerate(allocator: std.mem.Allocator, list: *std.ArrayList(Window)) !void { var ctx: EnumContext = .{ .allocator = allocator, .list = list }; _ = win32.ui.windows_and_messaging.EnumWindows(&enumWindowsProc, @intCast(@intFromPtr(&ctx))); if (ctx.err) |e| { log.err("couldn't get the windows: {s}", .{@errorName(e)}); return e; } } fn enumWindowsProc( hwnd: win32.foundation.HWND, lparam: win32.foundation.LPARAM, ) callconv(.c) win32.foundation.BOOL { const ctx: *EnumContext = @ptrFromInt(@as(usize, @intCast(lparam))); const window: Window = .{ .handle = hwnd }; if (!window.shouldManage()) return 1; ctx.list.ensureUnusedCapacity(ctx.allocator, 1) catch |e| { ctx.err = e; return 0; }; ctx.list.appendAssumeCapacity(window); log.debug("spotted window (now {d})", .{ctx.list.items.len}); return 1; } pub fn shouldManage(window: Window) bool { if (!window.isTopLevel()) return false; // Ignore windows owned by our own process // if (window.processId() == win32.system.threading.GetCurrentProcessId()) return false; if (!window.isVisible()) return false; if (window.isCloaked()) return false; if (window.isSystemWindow()) return false; const styles: win32.ui.windows_and_messaging.WINDOW_STYLE = window.style(); const ex_styles: win32.ui.windows_and_messaging.WINDOW_EX_STYLE = window.exStyle(); if (styles.CHILD == 1) return false; if (ex_styles.TOOLWINDOW == 1 and ex_styles.NOACTIVATE == 1) return false; if (window.owner() != null) { if (!(styles.BORDER == 1 and styles.DLGFRAME == 1)) return false; } return true; } /// Returns the monitor the window is currently on (nearest if it's off-screen). pub fn monitor(window: Window) Monitor { return .{ .handle = win32.graphics.gdi.MonitorFromWindow( window.handle, win32.graphics.gdi.MONITOR_DEFAULTTONEAREST, ).?, }; } fn rootAncestor(window: Window) ?win32.foundation.HWND { return win32.ui.windows_and_messaging.GetAncestor( window.handle, win32.ui.windows_and_messaging.GA_ROOT, ); } fn isTopLevel(window: Window) bool { return window.rootAncestor() == window.handle; } fn processId(window: Window) u32 { var pid: u32 = 0; _ = win32.ui.windows_and_messaging.GetWindowThreadProcessId(window.handle, &pid); return pid; } fn isVisible(window: Window) bool { return win32.ui.windows_and_messaging.IsWindowVisible(window.handle) != 0; } fn isCloaked(window: Window) bool { var cloaked: u32 = 0; const result: win32.foundation.HRESULT = win32.graphics.dwm.DwmGetWindowAttribute( window.handle, .CLOAKED, &cloaked, @sizeOf(u32), ); return result == 0 and cloaked != 0; } fn style(window: Window) win32.ui.windows_and_messaging.WINDOW_STYLE { const raw: isize = win32.ui.windows_and_messaging.GetWindowLongPtrW( window.handle, win32.ui.windows_and_messaging.GWL_STYLE, ); return @bitCast(@as(u32, @truncate(@as(usize, @bitCast(raw))))); } fn exStyle(window: Window) win32.ui.windows_and_messaging.WINDOW_EX_STYLE { const raw: isize = win32.ui.windows_and_messaging.GetWindowLongPtrW( window.handle, win32.ui.windows_and_messaging.GWL_EXSTYLE, ); return @bitCast(@as(u32, @truncate(@as(usize, @bitCast(raw))))); } fn owner(window: Window) ?win32.foundation.HWND { return win32.ui.windows_and_messaging.GetWindow( window.handle, win32.ui.windows_and_messaging.GW_OWNER, ); } const class_name_max_len: usize = 256; fn className(window: Window, buf: *[class_name_max_len:0]u16) []const u16 { const len: i32 = win32.ui.windows_and_messaging.GetClassNameW(window.handle, buf, class_name_max_len); if (len <= 0) return &.{}; return buf[0..@intCast(len)]; } const system_classes: [4][]const u16 = .{ std.unicode.utf8ToUtf16LeStringLiteral("SysListView32"), std.unicode.utf8ToUtf16LeStringLiteral("WorkerW"), std.unicode.utf8ToUtf16LeStringLiteral("Shell_TrayWnd"), std.unicode.utf8ToUtf16LeStringLiteral("Shell_SecondaryTrayWnd"), }; fn isSystemWindow(window: Window) bool { if (window.handle == win32.ui.windows_and_messaging.GetDesktopWindow()) return true; if (window.handle == win32.ui.windows_and_messaging.GetShellWindow()) return true; var buf: [class_name_max_len:0]u16 = undefined; const name: []const u16 = window.className(&buf); for (system_classes) |sys_class| { if (std.mem.eql(u16, name, sys_class)) return true; } return false; } const log = std.log.scoped(.window); const Monitor = @import("Monitor.zig"); const win32 = @import("win32"); const std = @import("std");