diff --git a/build.zig b/build.zig index f74cc615..efaca2e5 100644 --- a/build.zig +++ b/build.zig @@ -402,6 +402,7 @@ fn buildExamples( .root_source_file = b.path(b.fmt("examples/{s}/main.zig", .{example.name})), .target = target, .optimize = optimize, + .win32_manifest = b.path("src/core/windows/win32.manifest"), }); exe.root_module.addImport("mach", mach_mod); diff --git a/src/core/Windows.zig b/src/core/Windows.zig index 76cbf191..29ee6eae 100644 --- a/src/core/Windows.zig +++ b/src/core/Windows.zig @@ -3,6 +3,8 @@ const w = @import("../win32.zig"); const mach = @import("../main.zig"); const Core = @import("../Core.zig"); +const windowmsg = @import("windowmsg.zig"); + const gpu = mach.gpu; const Event = Core.Event; const KeyEvent = Core.KeyEvent; @@ -17,20 +19,19 @@ const Position = Core.Position; const Key = Core.Key; const KeyMods = Core.KeyMods; +const log = std.log.scoped(.mach); const EventQueue = std.fifo.LinearFifo(Event, .Dynamic); const Win32 = @This(); -pub const Native = struct { - window: w.HWND = undefined, - surrogate: u16 = 0, - dinput: *w.IDirectInput8W = undefined, - saved_window_rect: w.RECT = undefined, - surface_descriptor_from_hwnd: gpu.Surface.DescriptorFromWindowsHWND = undefined, +const window_ex_style: w.WINDOW_EX_STYLE = .{ + .APPWINDOW = 1, + .NOREDIRECTIONBITMAP = 1, }; -pub const Context = struct { - core: *Core, - window_id: mach.ObjectID, +pub const Native = struct { + hwnd: w.HWND, + surrogate: u16 = 0, + // dinput: *w.IDirectInput8W = undefined, }; pub fn run(comptime on_each_update_fn: anytype, args_tuple: std.meta.ArgsTuple(@TypeOf(on_each_update_fn))) void { @@ -38,140 +39,250 @@ pub fn run(comptime on_each_update_fn: anytype, args_tuple: std.meta.ArgsTuple(@ } pub fn tick(core: *Core) !void { - var windows = core.windows.slice(); - while (windows.next()) |window_id| { - const native_opt: ?Native = core.windows.get(window_id, .native); - - if (native_opt) |native| { - _ = native; // autofix - var msg: w.MSG = undefined; - while (w.PeekMessageW(&msg, null, 0, 0, w.PM_REMOVE) != 0) { - _ = w.TranslateMessage(&msg); - _ = w.DispatchMessageW(&msg); + { + var windows = core.windows.slice(); + while (windows.next()) |window_id| { + if (core.windows.get(window_id, .native) != null) { + // TODO: propagate window.decorated and all others + // Handle resizing the window when the user changes width or height + if (core.windows.updated(window_id, .width) or core.windows.updated(window_id, .height)) { + setWindowSize( + core.windows.get(window_id, .native).?.hwnd, + .{ + .width = core.windows.get(window_id, .width), + .height = core.windows.get(window_id, .height), + }, + ); + } + } else { + try initWindow(core, window_id); + std.debug.assert(core.windows.getValue(window_id).native != null); } - - // Handle resizing the window when the user changes width or height - if (core.windows.updated(window_id, .width) or core.windows.updated(window_id, .height)) {} - } else { - try initWindow(core, window_id); } } + + var msg: w.MSG = undefined; + while (true) { + const result = w.PeekMessageW(&msg, null, 0, 0, w.PM_REMOVE); + if (result < 0) fatalWin32("PeekMessage", w.GetLastError()); + if (result == 0) break; + if (msg.message == w.WM_QUIT) { + std.log.info("quit (exit code {})", .{msg.wParam}); + w.ExitProcess(std.math.cast(u32, msg.wParam) orelse 0xffffffff); + } + _ = w.TranslateMessage(&msg); + _ = w.DispatchMessageW(&msg); + } } +fn setWindowSize(hwnd: w.HWND, size_pt: Size) void { + const dpi = w.dpiFromHwnd(hwnd); + const style = styleFromHwnd(hwnd); + var rect: w.RECT = .{ + .left = 0, + .top = 0, + .right = w.pxFromPt(i32, @intCast(size_pt.width), dpi), + .bottom = w.pxFromPt(i32, @intCast(size_pt.height), dpi), + }; + if (0 == w.AdjustWindowRectExForDpi(&rect, style, w.FALSE, window_ex_style, dpi)) fatalWin32( + "AdjustWindowRectExForDpi", + w.GetLastError(), + ); + if (0 == w.SetWindowPos( + hwnd, + null, + undefined, + undefined, + rect.right - rect.left, + rect.bottom - rect.top, + .{ .NOZORDER = 1, .NOMOVE = 1 }, + )) fatalWin32("SetWindowPos", w.GetLastError()); +} + +fn updateWindowSize( + dpi: u32, + window_style: w.WINDOW_STYLE, + hwnd: w.HWND, + requested_client_size: w.SIZE, +) void { + const monitor = blk: { + var rect: w.RECT = undefined; + if (0 == w.GetWindowRect(hwnd, &rect)) fatalWin32("GetWindowRect", w.GetLastError()); + + break :blk w.MonitorFromPoint( + .{ .x = rect.left, .y = rect.top }, + w.MONITOR_DEFAULTTONULL, + ) orelse { + log.warn("MonitorFromPoint {},{} failed with {}", .{ rect.left, rect.top, w.GetLastError() }); + return; + }; + }; + + const work_rect: w.RECT = blk: { + var info: w.MONITORINFO = undefined; + info.cbSize = @sizeOf(w.MONITORINFO); + if (0 == w.GetMonitorInfoW(monitor, &info)) { + log.warn("GetMonitorInfo failed with {}", .{w.GetLastError()}); + return; + } + break :blk info.rcWork; + }; + + const work_size: w.SIZE = .{ + .cx = work_rect.right - work_rect.left, + .cy = work_rect.bottom - work_rect.top, + }; + log.debug( + "primary monitor work topleft={},{} size={}x{}", + .{ work_rect.left, work_rect.top, work_size.cx, work_size.cy }, + ); + + const wanted_size: w.SIZE = blk: { + var rect: w.RECT = .{ + .left = 0, + .top = 0, + .right = requested_client_size.cx, + .bottom = requested_client_size.cy, + }; + if (0 == w.AdjustWindowRectExForDpi(&rect, window_style, w.FALSE, window_ex_style, dpi)) fatalWin32( + "AdjustWindowRectExForDpi", + w.GetLastError(), + ); + break :blk .{ + .cx = rect.right - rect.left, + .cy = rect.bottom - rect.top, + }; + }; + + const window_size: w.SIZE = .{ + .cx = @min(wanted_size.cx, work_size.cx), + .cy = @min(wanted_size.cy, work_size.cy), + }; + if (0 == w.SetWindowPos( + hwnd, + null, + work_rect.left + @divTrunc(work_size.cx - window_size.cx, 2), + work_rect.top + @divTrunc(work_size.cy - window_size.cy, 2), + window_size.cx, + window_size.cy, + .{ .NOZORDER = 1 }, + )) fatalWin32("SetWindowPos", w.GetLastError()); +} + +const CreateWindowArgs = struct { + window_id: mach.ObjectID, +}; + +var wndproc_core: *Core = undefined; + fn initWindow( core: *Core, window_id: mach.ObjectID, ) !void { + wndproc_core = core; + var core_window = core.windows.getValue(window_id); const hInstance = w.GetModuleHandleW(null); const class_name = w.L("mach"); - const class = std.mem.zeroInit(w.WNDCLASSW, .{ - .style = w.CS_OWNDC, - .lpfnWndProc = wndProc, - .hInstance = hInstance, - .hIcon = w.LoadIconW(null, @as([*:0]align(1) const u16, @ptrFromInt(@as(u32, w.IDI_APPLICATION)))), - .hCursor = w.LoadCursorW(null, @as([*:0]align(1) const u16, @ptrFromInt(@as(u32, w.IDC_ARROW)))), - .lpszClassName = class_name, - }); - if (w.RegisterClassW(&class) == 0) return error.Unexpected; + { + const class: w.WNDCLASSW = .{ + .style = .{}, + .lpfnWndProc = wndProc, + .cbClsExtra = 0, + .cbWndExtra = @sizeOf(mach.ObjectID), + .hInstance = hInstance, + .hIcon = w.LoadIconW(null, w.IDI_APPLICATION), + .hCursor = w.LoadCursorW(null, w.IDC_ARROW), + .hbrBackground = null, + .lpszMenuName = null, + .lpszClassName = class_name, + }; + if (w.RegisterClassW(&class) == 0) fatalWin32("RegisterClass", w.GetLastError()); + } const title = try std.unicode.utf8ToUtf16LeAllocZ(core.allocator, core_window.title); defer core.allocator.free(title); - var request_window_width: i32 = @bitCast(core_window.width); - var request_window_height: i32 = @bitCast(core_window.height); - - const window_ex_style: w.WINDOW_EX_STYLE = .{ .APPWINDOW = 1 }; const window_style: w.WINDOW_STYLE = if (core_window.decorated) w.WS_OVERLAPPEDWINDOW else w.WS_POPUPWINDOW; // w.WINDOW_STYLE{.POPUP = 1}; - var rect: w.RECT = .{ .left = 0, .top = 0, .right = request_window_width, .bottom = request_window_height }; - - if (w.TRUE == w.AdjustWindowRectEx(&rect, window_style, w.FALSE, window_ex_style)) { - request_window_width = rect.right - rect.left; - request_window_height = rect.bottom - rect.top; - } - - const native_window = w.CreateWindowExW( + const create_args: CreateWindowArgs = .{ + .window_id = window_id, + }; + const hwnd = w.CreateWindowExW( window_ex_style, class_name, title, window_style, w.CW_USEDEFAULT, w.CW_USEDEFAULT, - request_window_width, - request_window_height, + w.CW_USEDEFAULT, + w.CW_USEDEFAULT, null, null, hInstance, - null, + @constCast(@ptrCast(&create_args)), ) orelse return error.Unexpected; - var native: Native = .{}; + const dpi = w.dpiFromHwnd(hwnd); - var dinput: ?*w.IDirectInput8W = undefined; - const ptr: ?*?*anyopaque = @ptrCast(&dinput); - if (w.DirectInput8Create(hInstance, w.DIRECTINPUT_VERSION, w.IID_IDirectInput8W, ptr, null) != w.DI_OK) { - return error.Unexpected; - } - native.dinput = dinput.?; + updateWindowSize(dpi, window_style, hwnd, .{ + .cx = @bitCast(core_window.width), + .cy = @bitCast(core_window.height), + }); - native.surface_descriptor_from_hwnd = .{ - .hinstance = std.os.windows.kernel32.GetModuleHandleW(null).?, - .hwnd = native_window, - }; + // const dinput = blk: { + // var dinput: ?*w.IDirectInput8W = undefined; + // const ptr: ?*?*anyopaque = @ptrCast(&dinput); + // if (w.DirectInput8Create(hInstance, w.DIRECTINPUT_VERSION, w.IID_IDirectInput8W, ptr, null) != w.DI_OK) { + // return error.Unexpected; + // } + // break :blk dinput; + // }; - core_window.surface_descriptor = .{ .next_in_chain = .{ - .from_windows_hwnd = &native.surface_descriptor_from_hwnd, - } }; - - const context = try core.allocator.create(Context); - context.* = .{ .core = core, .window_id = window_id }; - - _ = w.SetWindowLongPtrW(native_window, w.GWLP_USERDATA, @bitCast(@intFromPtr(context))); - - restoreWindowPosition(core, window_id); - - const size = getClientRect(core, window_id); + const size = getClientSize(hwnd); core_window.width = size.width; core_window.height = size.height; - _ = w.GetWindowRect(native.window, &native.saved_window_rect); + _ = w.ShowWindow(hwnd, w.SW_SHOW); - core_window.native = native; - core.windows.setValueRaw(window_id, core_window); - try core.initWindow(window_id); - _ = w.ShowWindow(native_window, w.SW_SHOW); + // try some things to bring our window to the top + const HWND_TOP: ?w.HWND = null; + _ = w.SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, .{ .NOMOVE = 1, .NOSIZE = 1 }); + _ = w.SetForegroundWindow(hwnd); + _ = w.BringWindowToTop(hwnd); + + { + // TODO: make this lifetime better + var surface_descriptor_from_hwnd: gpu.Surface.DescriptorFromWindowsHWND = .{ + .hinstance = std.os.windows.kernel32.GetModuleHandleW(null).?, + .hwnd = hwnd, + }; + core.windows.setRaw(window_id, .surface_descriptor, .{ .next_in_chain = .{ + .from_windows_hwnd = &surface_descriptor_from_hwnd, + } }); + try core.initWindow(window_id); + } } +fn windowIdFromHwnd(hwnd: w.HWND) mach.ObjectID { + const userdata: usize = @bitCast(w.GetWindowLongPtrW(hwnd, @enumFromInt(0))); + if (userdata == 0) unreachable; + return @bitCast(userdata - 1); +} +fn styleFromHwnd(hwnd: w.HWND) w.WINDOW_STYLE { + return @bitCast(@as(u32, @truncate(@as(usize, @bitCast(w.GetWindowLongPtrW(hwnd, w.GWL_EXSTYLE)))))); +} // ----------------------------- // Internal functions // ----------------------------- -fn getClientRect(core: *Core, window_id: mach.ObjectID) Size { - const window = core.windows.getValue(window_id); - - if (window.native) |native| { - var rect: w.RECT = undefined; - _ = w.GetClientRect(native.window, &rect); - - const width: u32 = @intCast(rect.right - rect.left); - const height: u32 = @intCast(rect.bottom - rect.top); - - return .{ .width = width, .height = height }; - } - - return .{ .width = 0, .height = 0 }; -} - -fn restoreWindowPosition(core: *Core, window_id: mach.ObjectID) void { - const window = core.windows.getValue(window_id); - if (window.native) |native| { - if (native.saved_window_rect.right - native.saved_window_rect.left == 0) { - _ = w.ShowWindow(native.window, w.SW_RESTORE); - } else { - _ = w.SetWindowPos(native.window, null, native.saved_window_rect.left, native.saved_window_rect.top, native.saved_window_rect.right - native.saved_window_rect.left, native.saved_window_rect.bottom - native.saved_window_rect.top, w.SWP_SHOWWINDOW); - } - } +fn getClientSize(hwnd: w.HWND) Size { + var rect: w.RECT = undefined; + if (0 == w.GetClientRect(hwnd, &rect)) + fatalWin32("GetClientRect", w.GetLastError()); + std.debug.assert(rect.left == 0); + std.debug.assert(rect.top == 0); + return .{ .width = @intCast(rect.right), .height = @intCast(rect.bottom) }; } fn getKeyboardModifiers() mach.Core.KeyMods { @@ -186,52 +297,79 @@ fn getKeyboardModifiers() mach.Core.KeyMods { }; } -fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w.WINAPI) w.LRESULT { - const context = blk: { - const userdata: usize = @bitCast(w.GetWindowLongPtrW(wnd, w.GWLP_USERDATA)); - const ptr: ?*Context = @ptrFromInt(userdata); - break :blk ptr orelse return w.DefWindowProcW(wnd, msg, wParam, lParam); +const debug_wndproc_log = false; +var global_msg_tail: ?*windowmsg.MessageNode = null; + +fn wndProc(hwnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w.WINAPI) w.LRESULT { + var msg_node: windowmsg.MessageNode = undefined; + if (debug_wndproc_log) msg_node.init(&global_msg_tail, hwnd, msg, wParam, lParam); + defer if (debug_wndproc_log) msg_node.deinit(); + if (debug_wndproc_log) switch (msg) { + w.WM_MOUSEMOVE => {}, + else => std.log.info("{}", .{msg_node.fmtPath()}), }; - const core = context.core; - const window_id = context.window_id; - - var core_window = core.windows.getValue(window_id); - + const core = wndproc_core; switch (msg) { - w.WM_CLOSE => { - core.pushEvent(.{ .close = .{ .window_id = window_id } }); + w.WM_CREATE => { + const create_struct: *w.CREATESTRUCTW = @ptrFromInt(@as(usize, @bitCast(lParam))); + const create_args: *CreateWindowArgs = @alignCast(@ptrCast(create_struct.lpCreateParams)); + const window_id = create_args.window_id; + + core.windows.setRaw(window_id, .native, .{ .hwnd = hwnd }); + // we add 1 to distinguish between a valid window id and an uninitialized slot + std.debug.assert(0 == w.SetWindowLongPtrW(hwnd, @enumFromInt(0), @bitCast(create_args.window_id + 1))); + std.debug.assert(create_args.window_id == windowIdFromHwnd(hwnd)); return 0; }, - w.WM_SIZE => { - const width: u32 = @as(u32, @intCast(lParam & 0xFFFF)); - const height: u32 = @as(u32, @intCast((lParam >> 16) & 0xFFFF)); + w.WM_DESTROY => @panic("Mach doesn't support destroying windows yet"), + w.WM_CLOSE => { + core.pushEvent(.{ .close = .{ .window_id = windowIdFromHwnd(hwnd) } }); + return 0; + }, + w.WM_DPICHANGED, w.WM_WINDOWPOSCHANGED => { + const client_size_px = getClientSize(hwnd); - if (core_window.width != width or core_window.height != height) { + const window_id = windowIdFromHwnd(hwnd); + var core_window = core.windows.getValue(window_id); + + var change = false; + if (core_window.framebuffer_width != client_size_px.width or core_window.framebuffer_height != client_size_px.height) { + change = true; // Recreate the swap_chain core_window.swap_chain.release(); - core_window.swap_chain_descriptor.width = width; - core_window.swap_chain_descriptor.height = height; + core_window.swap_chain_descriptor.width = client_size_px.width; + core_window.swap_chain_descriptor.height = client_size_px.height; core_window.swap_chain = core_window.device.createSwapChain(core_window.surface, &core_window.swap_chain_descriptor); - - core_window.width = width; - core_window.height = height; - core_window.framebuffer_width = width; - core_window.framebuffer_height = height; - - core.pushEvent(.{ .window_resize = .{ .window_id = window_id, .size = .{ .width = width, .height = height } } }); - - core.windows.setValueRaw(window_id, core_window); + core_window.framebuffer_width = client_size_px.width; + core_window.framebuffer_height = client_size_px.height; } - // TODO (win32): only send resize event when sizing is done. - // the main mach loops does not run while resizing. - // Which means if events are pushed here they will - // queue up until resize is done. + const dpi = w.dpiFromHwnd(hwnd); + const client_size_pt: Size = .{ + .width = w.ptFromPx(u32, client_size_px.width, dpi), + .height = w.ptFromPx(u32, client_size_px.height, dpi), + }; + if (core_window.width != client_size_pt.width or core_window.height != client_size_pt.height) { + change = true; + core_window.width = client_size_pt.width; + core_window.height = client_size_pt.height; + } + if (change) { + core.pushEvent(.{ .window_resize = .{ + .window_id = window_id, + .size = .{ .width = client_size_pt.width, .height = client_size_pt.height }, + } }); + core.windows.setValueRaw(window_id, core_window); + } return 0; }, w.WM_KEYDOWN, w.WM_KEYUP, w.WM_SYSKEYDOWN, w.WM_SYSKEYUP => { + // ScanCode: Unique Identifier for a physical button. + // Virtulkey: A key with a name, multiple physical buttons can produce the same virtual key. + const window_id = windowIdFromHwnd(hwnd); + const vkey: w.VIRTUAL_KEY = @enumFromInt(wParam); if (vkey == w.VK_PROCESSKEY) return 0; @@ -241,43 +379,47 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w return 0; } - const flags = lParam >> 16; - const scancode: u9 = @intCast(flags & 0x1FF); + const WinKeyFlags = packed struct(u32) { + repeat_count: u16, + scancode: u8, + extended: bool, + reserved: u4, + context: bool, + previous: bool, + transition: bool, + }; + const flags: WinKeyFlags = @bitCast(@as(u32, @truncate(@as(usize, @bitCast(lParam))))); + const scancode: u9 = flags.scancode | (@as(u9, if (flags.extended) 1 else 0) << 8); if (scancode == 0x1D) { // right alt sends left control first var next: w.MSG = undefined; const time = w.GetMessageTime(); - if (core_window.native) |native| { - if (w.PeekMessageW(&next, native.window, 0, 0, w.PM_NOREMOVE) != 0 and - next.time == time and - (next.message == msg or (msg == w.WM_SYSKEYDOWN and next.message == w.WM_KEYUP)) and - ((next.lParam >> 16) & 0x1FF) == 0x138) - { - return 0; - } + if (w.PeekMessageW(&next, hwnd, 0, 0, w.PM_NOREMOVE) != 0 and + next.time == time and + (next.message == msg or (msg == w.WM_SYSKEYDOWN and next.message == w.WM_KEYUP)) and + ((next.lParam >> 16) & 0x1FF) == 0x138) + { + return 0; } } const mods = getKeyboardModifiers(); const key = keyFromScancode(scancode); if (msg == w.WM_KEYDOWN or msg == w.WM_SYSKEYDOWN) { - if (flags & w.KF_REPEAT == 0) - core.pushEvent(.{ - .key_press = .{ - .window_id = window_id, - .key = key, - .mods = mods, - }, - }) - else - core.pushEvent(.{ - .key_repeat = .{ - .window_id = window_id, - .key = key, - .mods = mods, - }, - }); + if (flags.previous) core.pushEvent(.{ + .key_repeat = .{ + .window_id = window_id, + .key = key, + .mods = mods, + }, + }) else core.pushEvent(.{ + .key_press = .{ + .window_id = window_id, + .key = key, + .mods = mods, + }, + }); } else core.pushEvent(.{ .key_release = .{ .window_id = window_id, @@ -289,29 +431,32 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w return 0; }, w.WM_CHAR => { - if (core_window.native) |*native| { - const char: u16 = @truncate(wParam); - var chars: []const u16 = undefined; - if (native.surrogate != 0) { - chars = &.{ native.surrogate, char }; - native.surrogate = 0; - } else if (std.unicode.utf16IsHighSurrogate(char)) { - native.surrogate = char; + const window_id = windowIdFromHwnd(hwnd); + + const chars: [2]u16 = blk: { + var native = core.windows.get(window_id, .native).?; + defer core.windows.setRaw(window_id, .native, native); + const chars = [2]u16{ native.surrogate, @truncate(wParam) }; + if (std.unicode.utf16IsHighSurrogate(chars[1])) { + native.surrogate = chars[1]; return 0; - } else { - chars = &.{char}; } - var iter = std.unicode.Utf16LeIterator.init(chars); - if (iter.nextCodepoint()) |codepoint| { - core.pushEvent(.{ .char_input = .{ - .window_id = window_id, - .codepoint = codepoint.?, - } }); - } else |err| { - err catch {}; + native.surrogate = 0; + break :blk chars; + }; + const codepoint: u21 = blk: { + if (std.unicode.utf16IsHighSurrogate(chars[0])) { + if (std.unicode.utf16DecodeSurrogatePair(&chars)) |c| break :blk c else |e| switch (e) { + error.ExpectedSecondSurrogateHalf => {}, + } } - return 0; - } + break :blk chars[1]; + }; + core.pushEvent(.{ .char_input = .{ + .window_id = window_id, + .codepoint = codepoint, + } }); + return 0; }, w.WM_LBUTTONDOWN, w.WM_LBUTTONUP, @@ -322,15 +467,26 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w w.WM_XBUTTONDOWN, w.WM_XBUTTONUP, => { + const window_id = windowIdFromHwnd(hwnd); const mods = getKeyboardModifiers(); - const x: f64 = @floatFromInt(@as(i16, @truncate(lParam & 0xFFFF))); - const y: f64 = @floatFromInt(@as(i16, @truncate((lParam >> 16) & 0xFFFF))); - const xbutton: u32 = @truncate(wParam >> 16); + const point = w.pointFromLparam(lParam); + + const MouseFlags = packed struct(u8) { + left_down: bool, + right_down: bool, + shift_down: bool, + control_down: bool, + middle_down: bool, + xbutton1_down: bool, + xbutton2_down: bool, + _: bool, + }; + const flags: MouseFlags = @bitCast(@as(u8, @truncate(wParam))); const button: MouseButton = switch (msg) { w.WM_LBUTTONDOWN, w.WM_LBUTTONUP => .left, w.WM_RBUTTONDOWN, w.WM_RBUTTONUP => .right, w.WM_MBUTTONDOWN, w.WM_MBUTTONUP => .middle, - else => if (xbutton == @as(u32, @bitCast(w.XBUTTON1))) .four else .five, + else => if (flags.xbutton1_down) .four else .five, }; switch (msg) { @@ -343,7 +499,7 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w .window_id = window_id, .button = button, .mods = mods, - .pos = .{ .x = x, .y = y }, + .pos = .{ .x = @floatFromInt(point.x), .y = @floatFromInt(point.y) }, }, }), else => core.pushEvent(.{ @@ -351,7 +507,7 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w .window_id = window_id, .button = button, .mods = mods, - .pos = .{ .x = x, .y = y }, + .pos = .{ .x = @floatFromInt(point.x), .y = @floatFromInt(point.y) }, }, }), } @@ -359,20 +515,21 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w return if (msg == w.WM_XBUTTONDOWN or msg == w.WM_XBUTTONUP) w.TRUE else 0; }, w.WM_MOUSEMOVE => { - const x: f64 = @floatFromInt(@as(i16, @truncate(lParam & 0xFFFF))); - const y: f64 = @floatFromInt(@as(i16, @truncate((lParam >> 16) & 0xFFFF))); + const window_id = windowIdFromHwnd(hwnd); + const point = w.pointFromLparam(lParam); core.pushEvent(.{ .mouse_motion = .{ .window_id = window_id, .pos = .{ - .x = x, - .y = y, + .x = @floatFromInt(point.x), + .y = @floatFromInt(point.y), }, }, }); return 0; }, w.WM_MOUSEWHEEL => { + const window_id = windowIdFromHwnd(hwnd); const WHEEL_DELTA = 120.0; const wheel_high_word: u16 = @truncate((wParam >> 16) & 0xffff); const delta_y: f32 = @as(f32, @floatFromInt(@as(i16, @bitCast(wheel_high_word)))) / WHEEL_DELTA; @@ -387,17 +544,17 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w return 0; }, w.WM_SETFOCUS => { + const window_id = windowIdFromHwnd(hwnd); core.pushEvent(.{ .focus_gained = .{ .window_id = window_id } }); return 0; }, w.WM_KILLFOCUS => { + const window_id = windowIdFromHwnd(hwnd); core.pushEvent(.{ .focus_lost = .{ .window_id = window_id } }); return 0; }, - else => return w.DefWindowProcW(wnd, msg, wParam, lParam), + else => return w.DefWindowProcW(hwnd, msg, wParam, lParam), } - - return w.DefWindowProcW(wnd, msg, wParam, lParam); } fn keyFromScancode(scancode: u9) Key { @@ -542,7 +699,10 @@ fn keyFromScancode(scancode: u9) Key { return if (scancode > 0 and scancode <= table.len) table[scancode - 1] else .unknown; } +fn fatalWin32(what: []const u8, last_error: u32) noreturn { + std.debug.panic("{s} failed, error={}", .{ what, last_error }); +} + // TODO (win32) Implement consistent error handling when interfacing with the Windows API. -// TODO (win32) Support High DPI awareness // TODO (win32) Consider to add support for mouse capture // TODO (win32) Change to using WM_INPUT for mouse movement. diff --git a/src/core/windowmsg.zig b/src/core/windowmsg.zig new file mode 100644 index 00000000..b1230aba --- /dev/null +++ b/src/core/windowmsg.zig @@ -0,0 +1,349 @@ +const std = @import("std"); +const win32 = @import("../win32.zig"); + +pub fn pointFromLparam(lparam: win32.LPARAM) win32.POINT { + return .{ + .x = @as(i16, @bitCast(win32.loword(lparam))), + .y = @as(i16, @bitCast(win32.hiword(lparam))), + }; +} + +pub const MessageNode = struct { + tail_ref: *?*MessageNode, + hwnd: win32.HWND, + msg: u32, + wparam: win32.WPARAM, + lparam: win32.LPARAM, + old_tail: ?*MessageNode, + pub fn init( + self: *MessageNode, + tail_ref: *?*MessageNode, + hwnd: win32.HWND, + msg: u32, + wparam: win32.WPARAM, + lparam: win32.LPARAM, + ) void { + if (tail_ref.*) |old_tail| { + std.debug.assert(old_tail.hwnd == hwnd); + } + self.* = .{ + .tail_ref = tail_ref, + .hwnd = hwnd, + .msg = msg, + .wparam = wparam, + .lparam = lparam, + .old_tail = tail_ref.*, + }; + tail_ref.* = self; + } + pub fn deinit(self: *MessageNode) void { + std.debug.assert(self.tail_ref.* == self); + self.tail_ref.* = self.old_tail; + } + pub fn fmtPath(self: *MessageNode) FmtPath { + return .{ .node = self }; + } +}; + +fn writeMessageNodePath( + writer: anytype, + node: *MessageNode, +) !void { + if (node.old_tail) |old_tail| { + try writeMessageNodePath(writer, old_tail); + try writer.writeAll(" > "); + } + try writer.print("{s}:{}", .{ msg_name(node.msg) orelse "?", node.msg }); + switch (node.msg) { + win32.WM_CAPTURECHANGED => { + try writer.print("({})", .{node.lparam}); + }, + win32.WM_SYSCOMMAND => { + try writer.print("(type=0x{x})", .{0xfff0 & node.wparam}); + }, + else => {}, + } +} + +const FmtPath = struct { + node: *MessageNode, + const Self = @This(); + pub fn format( + self: Self, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) @TypeOf(writer).Error!void { + _ = fmt; + _ = options; + try writeMessageNodePath(writer, self.node); + } +}; + +pub fn msg_name(msg: u32) ?[]const u8 { + return switch (msg) { + 0 => "WM_NULL", + 1 => "WM_CREATE", + 2 => "WM_DESTROY", + 3 => "WM_MOVE", + 5 => "WM_SIZE", + 6 => "WM_ACTIVATE", + 7 => "WM_SETFOCUS", + 8 => "WM_KILLFOCUS", + 10 => "WM_ENABLE", + 11 => "WM_SETREDRAW", + 12 => "WM_SETTEXT", + 13 => "WM_GETTEXT", + 14 => "WM_GETTEXTLENGTH", + 15 => "WM_PAINT", + 16 => "WM_CLOSE", + 17 => "WM_QUERYENDSESSION", + 18 => "WM_QUIT", + 19 => "WM_QUERYOPEN", + 20 => "WM_ERASEBKGND", + 21 => "WM_SYSCOLORCHANGE", + 22 => "WM_ENDSESSION", + 24 => "WM_SHOWWINDOW", + 25 => "WM_CTLCOLOR", + 26 => "WM_WININICHANGE", + 27 => "WM_DEVMODECHANGE", + 28 => "WM_ACTIVATEAPP", + 29 => "WM_FONTCHANGE", + 30 => "WM_TIMECHANGE", + 31 => "WM_CANCELMODE", + 32 => "WM_SETCURSOR", + 33 => "WM_MOUSEACTIVATE", + 34 => "WM_CHILDACTIVATE", + 35 => "WM_QUEUESYNC", + 36 => "WM_GETMINMAXINFO", + 38 => "WM_PAINTICON", + 39 => "WM_ICONERASEBKGND", + 40 => "WM_NEXTDLGCTL", + 42 => "WM_SPOOLERSTATUS", + 43 => "WM_DRAWITEM", + 44 => "WM_MEASUREITEM", + 45 => "WM_DELETEITEM", + 46 => "WM_VKEYTOITEM", + 47 => "WM_CHARTOITEM", + 48 => "WM_SETFONT", + 49 => "WM_GETFONT", + 50 => "WM_SETHOTKEY", + 51 => "WM_GETHOTKEY", + 55 => "WM_QUERYDRAGICON", + 57 => "WM_COMPAREITEM", + 61 => "WM_GETOBJECT", + 65 => "WM_COMPACTING", + 68 => "WM_COMMNOTIFY", + 70 => "WM_WINDOWPOSCHANGING", + 71 => "WM_WINDOWPOSCHANGED", + 72 => "WM_POWER", + 73 => "WM_COPYGLOBALDATA", + 74 => "WM_COPYDATA", + 75 => "WM_CANCELJOURNAL", + 78 => "WM_NOTIFY", + 80 => "WM_INPUTLANGCHANGEREQUEST", + 81 => "WM_INPUTLANGCHANGE", + 82 => "WM_TCARD", + 83 => "WM_HELP", + 84 => "WM_USERCHANGED", + 85 => "WM_NOTIFYFORMAT", + 123 => "WM_CONTEXTMENU", + 124 => "WM_STYLECHANGING", + 125 => "WM_STYLECHANGED", + 126 => "WM_DISPLAYCHANGE", + 127 => "WM_GETICON", + 128 => "WM_SETICON", + 129 => "WM_NCCREATE", + 130 => "WM_NCDESTROY", + 131 => "WM_NCCALCSIZE", + 132 => "WM_NCHITTEST", + 133 => "WM_NCPAINT", + 134 => "WM_NCACTIVATE", + 135 => "WM_GETDLGCODE", + 136 => "WM_SYNCPAINT", + 160 => "WM_NCMOUSEMOVE", + 161 => "WM_NCLBUTTONDOWN", + 162 => "WM_NCLBUTTONUP", + 163 => "WM_NCLBUTTONDBLCLK", + 164 => "WM_NCRBUTTONDOWN", + 165 => "WM_NCRBUTTONUP", + 166 => "WM_NCRBUTTONDBLCLK", + 167 => "WM_NCMBUTTONDOWN", + 168 => "WM_NCMBUTTONUP", + 169 => "WM_NCMBUTTONDBLCLK", + 171 => "WM_NCXBUTTONDOWN", + 172 => "WM_NCXBUTTONUP", + 173 => "WM_NCXBUTTONDBLCLK", + 255 => "WM_INPUT", + 256 => "WM_KEYDOWN", + 257 => "WM_KEYUP", + 258 => "WM_CHAR", + 259 => "WM_DEADCHAR", + 260 => "WM_SYSKEYDOWN", + 261 => "WM_SYSKEYUP", + 262 => "WM_SYSCHAR", + 263 => "WM_SYSDEADCHAR", + 265 => "WM_UNICHAR", + 266 => "WM_CONVERTREQUEST", + 267 => "WM_CONVERTRESULT", + 268 => "WM_INTERIM", + 269 => "WM_IME_STARTCOMPOSITION", + 270 => "WM_IME_ENDCOMPOSITION", + 271 => "WM_IME_COMPOSITION", + 272 => "WM_INITDIALOG", + 273 => "WM_COMMAND", + 274 => "WM_SYSCOMMAND", + 275 => "WM_TIMER", + 276 => "WM_HSCROLL", + 277 => "WM_VSCROLL", + 278 => "WM_INITMENU", + 279 => "WM_INITMENUPOPUP", + 280 => "WM_SYSTIMER", + 287 => "WM_MENUSELECT", + 288 => "WM_MENUCHAR", + 289 => "WM_ENTERIDLE", + 290 => "WM_MENURBUTTONUP", + 291 => "WM_MENUDRAG", + 292 => "WM_MENUGETOBJECT", + 293 => "WM_UNINITMENUPOPUP", + 294 => "WM_MENUCOMMAND", + 295 => "WM_CHANGEUISTATE", + 296 => "WM_UPDATEUISTATE", + 297 => "WM_QUERYUISTATE", + 305 => "WM_LBTRACKPOINT", + 306 => "WM_CTLCOLORMSGBOX", + 307 => "WM_CTLCOLOREDIT", + 308 => "WM_CTLCOLORLISTBOX", + 309 => "WM_CTLCOLORBTN", + 310 => "WM_CTLCOLORDLG", + 311 => "WM_CTLCOLORSCROLLBAR", + 312 => "WM_CTLCOLORSTATIC", + 512 => "WM_MOUSEMOVE", + 513 => "WM_LBUTTONDOWN", + 514 => "WM_LBUTTONUP", + 515 => "WM_LBUTTONDBLCLK", + 516 => "WM_RBUTTONDOWN", + 517 => "WM_RBUTTONUP", + 518 => "WM_RBUTTONDBLCLK", + 519 => "WM_MBUTTONDOWN", + 520 => "WM_MBUTTONUP", + 521 => "WM_MBUTTONDBLCLK", + 522 => "WM_MOUSEWHEEL", + 523 => "WM_XBUTTONDOWN", + 524 => "WM_XBUTTONUP", + 525 => "WM_XBUTTONDBLCLK", + 526 => "WM_MOUSEHWHEEL", + 528 => "WM_PARENTNOTIFY", + 529 => "WM_ENTERMENULOOP", + 530 => "WM_EXITMENULOOP", + 531 => "WM_NEXTMENU", + 532 => "WM_SIZING", + 533 => "WM_CAPTURECHANGED", + 534 => "WM_MOVING", + 536 => "WM_POWERBROADCAST", + 537 => "WM_DEVICECHANGE", + 544 => "WM_MDICREATE", + 545 => "WM_MDIDESTROY", + 546 => "WM_MDIACTIVATE", + 547 => "WM_MDIRESTORE", + 548 => "WM_MDINEXT", + 549 => "WM_MDIMAXIMIZE", + 550 => "WM_MDITILE", + 551 => "WM_MDICASCADE", + 552 => "WM_MDIICONARRANGE", + 553 => "WM_MDIGETACTIVE", + 560 => "WM_MDISETMENU", + 561 => "WM_ENTERSIZEMOVE", + 562 => "WM_EXITSIZEMOVE", + 563 => "WM_DROPFILES", + 564 => "WM_MDIREFRESHMENU", + 640 => "WM_IME_REPORT", + 641 => "WM_IME_SETCONTEXT", + 642 => "WM_IME_NOTIFY", + 643 => "WM_IME_CONTROL", + 644 => "WM_IME_COMPOSITIONFULL", + 645 => "WM_IME_SELECT", + 646 => "WM_IME_CHAR", + 648 => "WM_IME_REQUEST", + 656 => "WM_IME_KEYDOWN", + 657 => "WM_IME_KEYUP", + 672 => "WM_NCMOUSEHOVER", + 673 => "WM_MOUSEHOVER", + 674 => "WM_NCMOUSELEAVE", + 675 => "WM_MOUSELEAVE", + 768 => "WM_CUT", + 769 => "WM_COPY", + 770 => "WM_PASTE", + 771 => "WM_CLEAR", + 772 => "WM_UNDO", + 773 => "WM_RENDERFORMAT", + 774 => "WM_RENDERALLFORMATS", + 775 => "WM_DESTROYCLIPBOARD", + 776 => "WM_DRAWCLIPBOARD", + 777 => "WM_PAINTCLIPBOARD", + 778 => "WM_VSCROLLCLIPBOARD", + 779 => "WM_SIZECLIPBOARD", + 780 => "WM_ASKCBFORMATNAME", + 781 => "WM_CHANGECBCHAIN", + 782 => "WM_HSCROLLCLIPBOARD", + 783 => "WM_QUERYNEWPALETTE", + 784 => "WM_PALETTEISCHANGING", + 785 => "WM_PALETTECHANGED", + 786 => "WM_HOTKEY", + 791 => "WM_PRINT", + 792 => "WM_PRINTCLIENT", + 793 => "WM_APPCOMMAND", + 799 => "WM_DWMNCRENDERINGCHANGED", + 856 => "WM_HANDHELDFIRST", + 863 => "WM_HANDHELDLAST", + 864 => "WM_AFXFIRST", + 895 => "WM_AFXLAST", + 896 => "WM_PENWINFIRST", + 897 => "WM_RCRESULT", + 898 => "WM_HOOKRCRESULT", + 899 => "WM_GLOBALRCCHANGE", + 900 => "WM_SKB", + 901 => "WM_PENCTL", + 902 => "WM_PENMISC", + 903 => "WM_CTLINIT", + 904 => "WM_PENEVENT", + 911 => "WM_PENWINLAST", + 1024 => "WM_USER+0", + 1025 => "WM_USER+1", + 1026 => "WM_USER+2", + 1027 => "WM_USER+3", + 1028 => "WM_USER+4", + 1029 => "WM_USER+5", + 1030 => "WM_USER+6", + else => null, + }; +} + +pub fn getHitName(hit: win32.LRESULT) ?[]const u8 { + return switch (hit) { + win32.HTERROR => "err", + win32.HTTRANSPARENT => "transprnt", + win32.HTNOWHERE => "nowhere", + win32.HTCLIENT => "client", + win32.HTCAPTION => "caption", + win32.HTSYSMENU => "sysmnu", + win32.HTSIZE => "size", + win32.HTMENU => "menu", + win32.HTHSCROLL => "hscroll", + win32.HTVSCROLL => "vscroll", + win32.HTMINBUTTON => "minbtn", + win32.HTMAXBUTTON => "max", + win32.HTLEFT => "left", + win32.HTRIGHT => "right", + win32.HTTOP => "top", + win32.HTTOPLEFT => "topleft", + win32.HTTOPRIGHT => "topright", + win32.HTBOTTOM => "bottom", + win32.HTBOTTOMLEFT => "botmleft", + win32.HTBOTTOMRIGHT => "botmright", + win32.HTBORDER => "border", + win32.HTCLOSE => "close", + win32.HTHELP => "help", + else => null, + }; +} diff --git a/src/core/windows/win32.manifest b/src/core/windows/win32.manifest new file mode 100644 index 00000000..89e28573 --- /dev/null +++ b/src/core/windows/win32.manifest @@ -0,0 +1,14 @@ + + + + + true/pm + PerMonitorV2 + + + + + + + + diff --git a/src/win32.zig b/src/win32.zig index 4819532d..b8a323fa 100644 --- a/src/win32.zig +++ b/src/win32.zig @@ -27,6 +27,13 @@ pub const WINAPI = w.WINAPI; pub const TRUE = w.TRUE; pub const FALSE = w.FALSE; +pub const SIZE = extern struct { + cx: i32, + cy: i32, +}; + +pub extern "kernel32" fn GetLastError() callconv(w.WINAPI) u32; + //pub const GetModuleHandleW = w.kernel32.GetModuleHandleW; // TODO: this type is limited to platform 'windows5.1.2600' pub extern "kernel32" fn GetModuleHandleW( @@ -99,19 +106,12 @@ pub extern "user32" fn GetWindowRect( lpRect: ?*RECT, ) callconv(@import("std").os.windows.WINAPI) BOOL; -// TODO: this type is limited to platform 'windows5.0' -pub extern "user32" fn AdjustWindowRect( - lpRect: ?*RECT, - dwStyle: WINDOW_STYLE, - bMenu: BOOL, -) callconv(@import("std").os.windows.WINAPI) BOOL; - -// TODO: this type is limited to platform 'windows5.0' -pub extern "user32" fn AdjustWindowRectEx( +pub extern "user32" fn AdjustWindowRectExForDpi( lpRect: ?*RECT, dwStyle: WINDOW_STYLE, bMenu: BOOL, dwExStyle: WINDOW_EX_STYLE, + dpi: u32, ) callconv(@import("std").os.windows.WINAPI) BOOL; // TODO: this type is limited to platform 'windows5.0' @@ -271,6 +271,14 @@ pub extern "user32" fn SetWindowLongW( dwNewLong: i32, ) callconv(@import("std").os.windows.WINAPI) i32; +pub extern "user32" fn BringWindowToTop( + hWnd: ?HWND, +) callconv(@import("std").os.windows.WINAPI) BOOL; + +pub extern "user32" fn SetForegroundWindow( + hWnd: ?HWND, +) callconv(@import("std").os.windows.WINAPI) BOOL; + //pub extern "user32" fn SetWindowPos(hWnd: HWND, hWndInsertAfter: HWND, X: i32, Y: i32, cx: i32, cy: i32, uFlags: u32) callconv(WINAPI) BOOL; pub extern "user32" fn SetWindowPos( hWnd: ?HWND, @@ -1206,13 +1214,13 @@ pub const LPDIENUMDEVICEOBJECTSCALLBACKW = switch (@import("builtin").zig_backen ) callconv(@import("std").os.windows.WINAPI) BOOL, }; -pub const IDI_APPLICATION = 32512; +pub const IDI_APPLICATION: [*:0]align(1) const u16 = @ptrFromInt(32512); pub extern "user32" fn LoadIconW( hInstance: ?HINSTANCE, lpIconName: ?[*:0]align(1) const u16, ) callconv(@import("std").os.windows.WINAPI) ?HICON; -pub const IDC_ARROW = 32512; +pub const IDC_ARROW: [*:0]align(1) const u16 = @ptrFromInt(32512); pub const IDC_HAND = 32649; pub const IDC_HELP = 32651; pub const IDC_IBEAM = 32513; @@ -1636,6 +1644,7 @@ pub const WM_QUERYOPEN = @as(u32, 19); pub const WM_ENDSESSION = @as(u32, 22); pub const WM_QUIT = @as(u32, 18); pub const WM_GETMINMAXINFO = @as(u32, 36); +pub const WM_WINDOWPOSCHANGED = @as(u32, 71); pub const WM_KEYFIRST = @as(u32, 256); pub const WM_KEYDOWN = @as(u32, 256); pub const WM_KEYUP = @as(u32, 257); @@ -1712,6 +1721,7 @@ pub const WM_SIZING = @as(u32, 532); pub const WM_CAPTURECHANGED = @as(u32, 533); pub const WM_MOVING = @as(u32, 534); pub const WM_POWERBROADCAST = @as(u32, 536); +pub const WM_DPICHANGED = @as(u32, 736); pub const KEYBD_EVENT_FLAGS = packed struct(u32) { EXTENDEDKEY: u1 = 0, @@ -2604,6 +2614,10 @@ pub extern "kernel32" fn GetProcAddress( hModule: ?*anyopaque, lpProcName: ?[*:0]const u8, ) callconv(WINAPI) ?*const fn () callconv(WINAPI) isize; +pub extern "kernel32" fn ExitProcess( + uExitCode: u32, +) callconv(@import("std").os.windows.WINAPI) noreturn; + pub const INFINITE = 4294967295; pub const SECURITY_ATTRIBUTES = extern struct { nLength: u32, @@ -4157,3 +4171,83 @@ pub const AUDCLNT_E_HEADTRACKING_ENABLED = -2004287440; pub const AUDCLNT_E_HEADTRACKING_UNSUPPORTED = -2004287424; pub const AUDCLNT_E_EFFECT_NOT_AVAILABLE = -2004287423; pub const AUDCLNT_E_EFFECT_STATE_READ_ONLY = -2004287422; + +pub extern "user32" fn GetDpiForWindow( + hwnd: ?HWND, +) callconv(@import("std").os.windows.WINAPI) u32; + +pub fn dpiFromHwnd(hwnd: HWND) u32 { + const value = GetDpiForWindow(hwnd); + if (value == 0) std.debug.panic( + "GetDpiForWindow failed, error={}", + .{GetLastError()}, + ); + return value; +} + +fn getDpiFactor(dpi: u32) f32 { + std.debug.assert(dpi >= 96); + return @as(f32, @floatFromInt(dpi)) / 96.0; +} + +pub fn pxFromPt(comptime T: type, value: T, dpi: u32) T { + switch (@typeInfo(T)) { + .float => return value * getDpiFactor(dpi), + .int => return @intFromFloat(@round(@as(f32, @floatFromInt(value)) * getDpiFactor(dpi))), + else => @compileError("scaleDpi does not support type " ++ @typeName(@TypeOf(value))), + } +} + +pub fn ptFromPx(comptime T: type, value: T, dpi: u32) T { + switch (@typeInfo(T)) { + .float => return value / getDpiFactor(dpi), + .int => return @intFromFloat(@round(@as(f32, @floatFromInt(value)) / getDpiFactor(dpi))), + else => @compileError("scaleDpi does not support type " ++ @typeName(@TypeOf(value))), + } +} + +pub const CREATESTRUCTW = extern struct { + lpCreateParams: ?*anyopaque, + hInstance: ?HINSTANCE, + hMenu: ?HMENU, + hwndParent: ?HWND, + cy: i32, + cx: i32, + y: i32, + x: i32, + style: i32, + lpszName: ?[*:0]const u16, + lpszClass: ?[*:0]const u16, + dwExStyle: u32, +}; + +pub fn loword(value: anytype) u16 { + switch (@typeInfo(@TypeOf(value))) { + .int => |int| switch (int.signedness) { + .signed => return loword(@as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = int.bits } }), @bitCast(value))), + .unsigned => return if (int.bits <= 16) value else @intCast(0xffff & value), + }, + else => {}, + } + @compileError("unsupported type " ++ @typeName(@TypeOf(value))); +} +pub fn hiword(value: anytype) u16 { + switch (@typeInfo(@TypeOf(value))) { + .int => |int| switch (int.signedness) { + .signed => return hiword(@as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = int.bits } }), @bitCast(value))), + .unsigned => return @intCast(0xffff & (value >> 16)), + }, + else => {}, + } + @compileError("unsupported type " ++ @typeName(@TypeOf(value))); +} + +fn xFromLparam(lparam: LPARAM) i16 { + return @bitCast(loword(lparam)); +} +fn yFromLparam(lparam: LPARAM) i16 { + return @bitCast(hiword(lparam)); +} +pub fn pointFromLparam(lparam: LPARAM) POINT { + return POINT{ .x = xFromLparam(lparam), .y = yFromLparam(lparam) }; +}