address review: port win32 apprt to zigwin32

Per @marler8997 and @jcollie, use the marlersoft/zigwin32 bindings
instead of hand-rolled extern decls for the Win32 API. Drops the
inline type / constant / function declarations in App.zig and
Surface.zig in favor of `@import("win32").everything`. The binding
is declared lazy in build.zig.zon so it's only fetched when building
the win32 apprt.

Net effect: -240 / +101 lines. Type-safe style flags (packed structs
for `WNDCLASS_STYLES`, `WINDOW_STYLE`, pixel format flags) replace
raw OR'd u32 constants. Also clears a known Zig 0.16 risk since
std.os.windows is shedding symbols the earlier code was relying on
(pointed out in jcollie's review note).

Co-authored-by: Claude <noreply@anthropic.com>
pull/12403/head
Yasuhiro Matsumoto 2026-04-24 14:01:38 +09:00
parent 289c6a578f
commit 2ad00dc628
No known key found for this signature in database
GPG Key ID: F2EA90DF2C146D1E
4 changed files with 101 additions and 240 deletions

View File

@ -61,6 +61,12 @@
.hash = "gobject-0.3.0-Skun7ANLnwDvEfIpVmohcppXgOvg_I6YOJFmPIsKfXk-",
.lazy = true,
},
.win32 = .{
// marlersoft/zigwin32 -- Win32 API bindings for the win32 apprt.
.url = "git+https://github.com/marlersoft/zigwin32#ec98bb4d9eea532320a8551720a9e3ec6de64994",
.hash = "win32-25.0.28-preview-mX5pFWMt5QPTVIGh3r2-OpPunpcCCjApyRbA6Zn6WALH",
.lazy = true,
},
// C libs
.dcimgui = .{ .path = "./pkg/dcimgui", .lazy = true },

View File

@ -4,6 +4,7 @@ const App = @This();
const std = @import("std");
const builtin = @import("builtin");
const win32 = @import("win32").everything;
const Allocator = std.mem.Allocator;
const apprt = @import("../../apprt.zig");
const configpkg = @import("../../config.zig");
@ -15,109 +16,9 @@ const renderer = @import("../../renderer.zig");
const log = std.log.scoped(.win32);
// Win32 type definitions
const BOOL = i32;
const UINT = u32;
const DWORD = u32;
const WPARAM = usize;
const LPARAM = isize;
const LRESULT = isize;
const HWND = std.os.windows.HWND;
const HINSTANCE = std.os.windows.HINSTANCE;
const HICON = ?*anyopaque;
const HCURSOR = ?*anyopaque;
const HBRUSH = ?*anyopaque;
const HDC = ?*anyopaque;
const HMENU = ?*anyopaque;
const ATOM = u16;
const LONG_PTR = isize;
const POINT = extern struct {
x: i32,
y: i32,
};
const MSG = extern struct {
hwnd: ?HWND,
message: UINT,
wParam: WPARAM,
lParam: LPARAM,
time: DWORD,
pt: POINT,
};
const RECT = extern struct {
left: i32,
top: i32,
right: i32,
bottom: i32,
};
const PAINTSTRUCT = extern struct {
hdc: HDC,
fErase: BOOL,
rcPaint: RECT,
fRestore: BOOL,
fIncUpdate: BOOL,
rgbReserved: [32]u8,
};
const WNDPROC = *const fn (HWND, UINT, WPARAM, LPARAM) callconv(.winapi) LRESULT;
const WNDCLASSEXW = extern struct {
cbSize: UINT,
style: UINT,
lpfnWndProc: WNDPROC,
cbClsExtra: c_int,
cbWndExtra: c_int,
hInstance: ?HINSTANCE,
hIcon: HICON,
hCursor: HCURSOR,
hbrBackground: HBRUSH,
lpszMenuName: ?[*:0]const u16,
lpszClassName: [*:0]const u16,
hIconSm: HICON,
};
// Win32 constants
const WM_CLOSE = 0x0010;
const WM_DESTROY = 0x0002;
const WM_PAINT = 0x000F;
const WM_SIZE = 0x0005;
const WM_KEYDOWN = 0x0100;
const WM_CHAR = 0x0102;
const WM_USER = 0x0400;
const WM_WAKEUP = WM_USER + 1;
const CS_HREDRAW = 0x0002;
const CS_VREDRAW = 0x0001;
const CS_OWNDC = 0x0020;
const WS_OVERLAPPEDWINDOW = 0x00CF0000;
const CW_USEDEFAULT: i32 = @bitCast(@as(u32, 0x80000000));
const SW_SHOWNORMAL = 1;
const IDC_ARROW: ?[*:0]align(1) const u16 = @ptrFromInt(32512);
const GWLP_USERDATA: c_int = -21;
// Win32 API extern declarations
extern "user32" fn RegisterClassExW(lpWndClass: *const WNDCLASSEXW) callconv(.winapi) ATOM;
extern "user32" fn CreateWindowExW(dwExStyle: DWORD, lpClassName: ?[*:0]const u16, lpWindowName: ?[*:0]const u16, dwStyle: DWORD, x: i32, y: i32, nWidth: i32, nHeight: i32, hWndParent: ?HWND, hMenu: HMENU, hInstance: ?HINSTANCE, lpParam: ?*anyopaque) callconv(.winapi) ?HWND;
extern "user32" fn ShowWindow(hWnd: HWND, nCmdShow: c_int) callconv(.winapi) BOOL;
extern "user32" fn UpdateWindow(hWnd: HWND) callconv(.winapi) BOOL;
extern "user32" fn DestroyWindow(hWnd: HWND) callconv(.winapi) BOOL;
extern "user32" fn DefWindowProcW(hWnd: HWND, msg: UINT, wParam: WPARAM, lParam: LPARAM) callconv(.winapi) LRESULT;
extern "user32" fn GetMessageW(lpMsg: *MSG, hWnd: ?HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) callconv(.winapi) BOOL;
extern "user32" fn TranslateMessage(lpMsg: *const MSG) callconv(.winapi) BOOL;
extern "user32" fn DispatchMessageW(lpMsg: *const MSG) callconv(.winapi) LRESULT;
extern "user32" fn PostMessageW(hWnd: HWND, msg: UINT, wParam: WPARAM, lParam: LPARAM) callconv(.winapi) BOOL;
extern "user32" fn PostQuitMessage(nExitCode: c_int) callconv(.winapi) void;
extern "user32" fn BeginPaint(hWnd: HWND, lpPaint: *PAINTSTRUCT) callconv(.winapi) HDC;
extern "user32" fn EndPaint(hWnd: HWND, lpPaint: *const PAINTSTRUCT) callconv(.winapi) BOOL;
extern "user32" fn LoadCursorW(hInstance: ?HINSTANCE, lpCursorName: ?[*:0]align(1) const u16) callconv(.winapi) HCURSOR;
extern "user32" fn SetWindowLongPtrW(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) callconv(.winapi) LONG_PTR;
extern "user32" fn GetWindowLongPtrW(hWnd: HWND, nIndex: c_int) callconv(.winapi) LONG_PTR;
extern "user32" fn GetClientRect(hWnd: HWND, lpRect: *RECT) callconv(.winapi) BOOL;
extern "user32" fn InvalidateRect(hWnd: ?HWND, lpRect: ?*const RECT, bErase: BOOL) callconv(.winapi) BOOL;
extern "kernel32" fn GetModuleHandleW(lpModuleName: ?[*:0]const u16) callconv(.winapi) ?HINSTANCE;
extern "kernel32" fn GetLastError() callconv(.winapi) DWORD;
/// User-defined wakeup message sent via PostMessage to break out of
/// GetMessage and run the core app's tick.
const WM_WAKEUP = win32.WM_USER + 1;
/// The core app instance.
core_app: *CoreApp,
@ -132,7 +33,7 @@ alloc: Allocator,
running: bool = true,
/// The main window handle.
hwnd: ?HWND = null,
hwnd: ?win32.HWND = null,
/// The surface for the main window.
surface: Surface = undefined,
@ -165,8 +66,14 @@ pub fn init(
// Initialize the surface with OpenGL
try self.surface.init(self.hwnd.?);
// Store self pointer in window for use in wndProc
_ = SetWindowLongPtrW(self.hwnd.?, GWLP_USERDATA, @bitCast(@intFromPtr(self)));
// Store self pointer in window for use in wndProc. SetWindowLongPtrW
// returns the previous value, which for a freshly created window is 0;
// we don't care about it here.
_ = win32.SetWindowLongPtrW(
self.hwnd.?,
win32.GWLP_USERDATA,
@bitCast(@intFromPtr(self)),
);
// Initialize the core surface (terminal emulation + rendering)
try self.initCoreSurface();
@ -176,27 +83,27 @@ pub fn run(self: *App) !void {
log.info("starting Win32 event loop", .{});
while (self.running) {
var msg: MSG = std.mem.zeroes(MSG);
const ret = GetMessageW(&msg, null, 0, 0);
var msg: win32.MSG = std.mem.zeroes(win32.MSG);
const ret = win32.GetMessageW(&msg, null, 0, 0);
if (ret == 0) {
// WM_QUIT
self.running = false;
break;
}
if (ret == -1) {
log.err("GetMessage failed", .{});
log.err("GetMessage failed: err={d}", .{@intFromEnum(win32.GetLastError())});
return error.Win32Error;
}
_ = TranslateMessage(&msg);
_ = DispatchMessageW(&msg);
_ = win32.TranslateMessage(&msg);
_ = win32.DispatchMessageW(&msg);
}
}
pub fn terminate(self: *App) void {
self.surface.deinit();
if (self.hwnd) |hwnd| {
if (DestroyWindow(hwnd) == 0) {
log.warn("DestroyWindow failed: err={d}", .{GetLastError()});
if (win32.DestroyWindow(hwnd) == 0) {
log.warn("DestroyWindow failed: err={d}", .{@intFromEnum(win32.GetLastError())});
}
self.hwnd = null;
}
@ -206,8 +113,8 @@ pub fn terminate(self: *App) void {
pub fn wakeup(self: *App) void {
if (self.hwnd) |hwnd| {
if (PostMessageW(hwnd, WM_WAKEUP, 0, 0) == 0) {
log.warn("PostMessage(WM_WAKEUP) failed: err={d}", .{GetLastError()});
if (win32.PostMessageW(hwnd, WM_WAKEUP, 0, 0) == 0) {
log.warn("PostMessage(WM_WAKEUP) failed: err={d}", .{@intFromEnum(win32.GetLastError())});
}
}
}
@ -224,7 +131,7 @@ pub fn performAction(
switch (action) {
.quit => {
PostQuitMessage(0);
win32.PostQuitMessage(0);
return true;
},
.new_window => {
@ -287,68 +194,66 @@ fn initCoreSurface(self: *App) !void {
}
fn createWindow(self: *App) !void {
const class_name = std.unicode.utf8ToUtf16LeStringLiteral("GhosttyWindow");
const hinstance = GetModuleHandleW(null);
const class_name = win32.L("GhosttyWindow");
const hinstance = win32.GetModuleHandleW(null);
const wc: WNDCLASSEXW = .{
.cbSize = @sizeOf(WNDCLASSEXW),
.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
const wc: win32.WNDCLASSEXW = .{
.cbSize = @sizeOf(win32.WNDCLASSEXW),
.style = .{ .HREDRAW = 1, .VREDRAW = 1, .OWNDC = 1 },
.lpfnWndProc = wndProc,
.cbClsExtra = 0,
.cbWndExtra = 0,
.hInstance = hinstance,
.hIcon = null,
.hCursor = LoadCursorW(null, IDC_ARROW),
.hCursor = win32.LoadCursorW(null, win32.IDC_ARROW),
.hbrBackground = null,
.lpszMenuName = null,
.lpszClassName = class_name,
.hIconSm = null,
};
if (RegisterClassExW(&wc) == 0) {
log.err("RegisterClassEx failed", .{});
if (win32.RegisterClassExW(&wc) == 0) {
log.err("RegisterClassExW failed: err={d}", .{@intFromEnum(win32.GetLastError())});
return error.Win32Error;
}
const title = std.unicode.utf8ToUtf16LeStringLiteral("Ghostty");
const title = win32.L("Ghostty");
self.hwnd = CreateWindowExW(
0,
self.hwnd = win32.CreateWindowExW(
.{},
class_name,
title,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
win32.WS_OVERLAPPEDWINDOW,
win32.CW_USEDEFAULT,
win32.CW_USEDEFAULT,
800,
600,
null,
null,
hinstance,
null,
);
if (self.hwnd == null) {
log.err("CreateWindowEx failed", .{});
) orelse {
log.err("CreateWindowExW failed: err={d}", .{@intFromEnum(win32.GetLastError())});
return error.Win32Error;
}
};
_ = ShowWindow(self.hwnd.?, SW_SHOWNORMAL);
_ = UpdateWindow(self.hwnd.?);
_ = win32.ShowWindow(self.hwnd.?, win32.SW_SHOWNORMAL);
_ = win32.UpdateWindow(self.hwnd.?);
}
fn getApp(hwnd: HWND) ?*App {
const ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA);
fn getApp(hwnd: win32.HWND) ?*App {
const ptr = win32.GetWindowLongPtrW(hwnd, win32.GWLP_USERDATA);
if (ptr == 0) return null;
return @ptrFromInt(@as(usize, @bitCast(ptr)));
}
fn wndProc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) callconv(.winapi) LRESULT {
fn wndProc(hwnd: win32.HWND, msg: u32, wparam: win32.WPARAM, lparam: win32.LPARAM) callconv(.winapi) win32.LRESULT {
switch (msg) {
WM_CLOSE => {
PostQuitMessage(0);
win32.WM_CLOSE => {
win32.PostQuitMessage(0);
return 0;
},
WM_SIZE => {
win32.WM_SIZE => {
if (getApp(hwnd)) |app| {
const width: u32 = @intCast(lparam & 0xFFFF);
const height: u32 = @intCast((lparam >> 16) & 0xFFFF);
@ -367,13 +272,13 @@ fn wndProc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) callconv(.wina
}
return 0;
},
WM_PAINT => {
var ps: PAINTSTRUCT = std.mem.zeroes(PAINTSTRUCT);
_ = BeginPaint(hwnd, &ps);
win32.WM_PAINT => {
var ps: win32.PAINTSTRUCT = std.mem.zeroes(win32.PAINTSTRUCT);
_ = win32.BeginPaint(hwnd, &ps);
if (getApp(hwnd)) |app| {
app.surface.swapBuffers();
}
_ = EndPaint(hwnd, &ps);
_ = win32.EndPaint(hwnd, &ps);
return 0;
},
WM_WAKEUP => {
@ -384,6 +289,6 @@ fn wndProc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) callconv(.wina
}
return 0;
},
else => return DefWindowProcW(hwnd, msg, wparam, lparam),
else => return win32.DefWindowProcW(hwnd, msg, wparam, lparam),
}
}

View File

@ -4,6 +4,7 @@
const Self = @This();
const std = @import("std");
const win32 = @import("win32").everything;
const Allocator = std.mem.Allocator;
const apprt = @import("../../apprt.zig");
const configpkg = @import("../../config.zig");
@ -12,71 +13,17 @@ const CoreApp = @import("../../App.zig");
const log = std.log.scoped(.win32_surface);
// Win32 types
const HWND = std.os.windows.HWND;
const HINSTANCE = std.os.windows.HINSTANCE;
const BOOL = i32;
const HDC = ?*anyopaque;
const HGLRC = ?*anyopaque;
const PIXELFORMATDESCRIPTOR = extern struct {
nSize: u16,
nVersion: u16,
dwFlags: u32,
iPixelType: u8,
cColorBits: u8,
cRedBits: u8,
cRedShift: u8,
cGreenBits: u8,
cGreenShift: u8,
cBlueBits: u8,
cBlueShift: u8,
cAlphaBits: u8,
cAlphaShift: u8,
cAccumBits: u8,
cAccumRedBits: u8,
cAccumGreenBits: u8,
cAccumBlueBits: u8,
cAccumAlphaBits: u8,
cDepthBits: u8,
cStencilBits: u8,
cAuxBuffers: u8,
iLayerType: u8,
bReserved: u8,
dwLayerMask: u32,
dwVisibleMask: u32,
dwDamageMask: u32,
};
// WGL / GDI constants
const PFD_DRAW_TO_WINDOW = 0x00000004;
const PFD_SUPPORT_OPENGL = 0x00000020;
const PFD_DOUBLEBUFFER = 0x00000001;
const PFD_TYPE_RGBA = 0;
const PFD_MAIN_PLANE = 0;
// WGL / GDI extern declarations
extern "user32" fn GetDC(hWnd: ?HWND) callconv(.winapi) HDC;
extern "user32" fn ReleaseDC(hWnd: ?HWND, hDC: HDC) callconv(.winapi) c_int;
extern "gdi32" fn ChoosePixelFormat(hdc: HDC, ppfd: *const PIXELFORMATDESCRIPTOR) callconv(.winapi) c_int;
extern "gdi32" fn SetPixelFormat(hdc: HDC, format: c_int, ppfd: *const PIXELFORMATDESCRIPTOR) callconv(.winapi) BOOL;
extern "gdi32" fn SwapBuffers(hdc: HDC) callconv(.winapi) BOOL;
extern "opengl32" fn wglCreateContext(hdc: HDC) callconv(.winapi) HGLRC;
extern "opengl32" fn wglDeleteContext(hglrc: HGLRC) callconv(.winapi) BOOL;
extern "opengl32" fn wglMakeCurrent(hdc: HDC, hglrc: HGLRC) callconv(.winapi) BOOL;
extern "kernel32" fn GetLastError() callconv(.winapi) u32;
/// The window this surface belongs to.
hwnd: HWND,
hwnd: win32.HWND,
/// Pointer back to the App.
app: ?*App = null,
/// GDI device context.
hdc: HDC = null,
hdc: ?win32.HDC = null,
/// OpenGL rendering context.
hglrc: HGLRC = null,
hglrc: ?win32.HGLRC = null,
/// The core surface, if initialized.
core_surface: ?*CoreSurface = null,
@ -95,7 +42,7 @@ pub fn rtApp(self: *Self) *App {
return self.app.?;
}
pub fn init(self: *Self, hwnd: HWND) !void {
pub fn init(self: *Self, hwnd: win32.HWND) !void {
self.* = .{ .hwnd = hwnd };
try self.initOpenGL();
}
@ -105,51 +52,49 @@ pub fn deinit(self: *Self) void {
surface.deinit();
// core_surface is allocated by CoreApp, freed there
}
if (self.hglrc != null) {
_ = wglMakeCurrent(null, null);
_ = wglDeleteContext(self.hglrc);
if (self.hglrc) |hglrc| {
_ = win32.wglMakeCurrent(null, null);
_ = win32.wglDeleteContext(hglrc);
}
if (self.hdc != null) {
_ = ReleaseDC(self.hwnd, self.hdc);
if (self.hdc) |hdc| {
_ = win32.ReleaseDC(self.hwnd, hdc);
}
}
fn initOpenGL(self: *Self) !void {
self.hdc = GetDC(self.hwnd);
if (self.hdc == null) {
log.err("GetDC failed", .{});
self.hdc = win32.GetDC(self.hwnd) orelse {
log.err("GetDC failed: err={d}", .{@intFromEnum(win32.GetLastError())});
return error.Win32Error;
}
};
var pfd: PIXELFORMATDESCRIPTOR = std.mem.zeroes(PIXELFORMATDESCRIPTOR);
pfd.nSize = @sizeOf(PIXELFORMATDESCRIPTOR);
var pfd: win32.PIXELFORMATDESCRIPTOR = std.mem.zeroes(win32.PIXELFORMATDESCRIPTOR);
pfd.nSize = @sizeOf(win32.PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.dwFlags = .{ .DRAW_TO_WINDOW = 1, .SUPPORT_OPENGL = 1, .DOUBLEBUFFER = 1 };
pfd.iPixelType = .RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 24;
pfd.cStencilBits = 8;
pfd.iLayerType = PFD_MAIN_PLANE;
pfd.iLayerType = .MAIN_PLANE;
const pixel_format = ChoosePixelFormat(self.hdc, &pfd);
const pixel_format = win32.ChoosePixelFormat(self.hdc, &pfd);
if (pixel_format == 0) {
log.err("ChoosePixelFormat failed", .{});
log.err("ChoosePixelFormat failed: err={d}", .{@intFromEnum(win32.GetLastError())});
return error.Win32Error;
}
if (SetPixelFormat(self.hdc, pixel_format, &pfd) == 0) {
log.err("SetPixelFormat failed", .{});
if (win32.SetPixelFormat(self.hdc, pixel_format, &pfd) == 0) {
log.err("SetPixelFormat failed: err={d}", .{@intFromEnum(win32.GetLastError())});
return error.Win32Error;
}
self.hglrc = wglCreateContext(self.hdc);
if (self.hglrc == null) {
log.err("wglCreateContext failed", .{});
self.hglrc = win32.wglCreateContext(self.hdc) orelse {
log.err("wglCreateContext failed: err={d}", .{@intFromEnum(win32.GetLastError())});
return error.Win32Error;
}
};
if (wglMakeCurrent(self.hdc, self.hglrc) == 0) {
log.err("wglMakeCurrent failed", .{});
if (win32.wglMakeCurrent(self.hdc, self.hglrc) == 0) {
log.err("wglMakeCurrent failed: err={d}", .{@intFromEnum(win32.GetLastError())});
return error.Win32Error;
}
@ -157,34 +102,36 @@ fn initOpenGL(self: *Self) !void {
}
pub fn swapBuffers(self: *Self) void {
if (self.hdc != null) {
if (SwapBuffers(self.hdc) == 0) {
log.warn("SwapBuffers failed: err={d}", .{GetLastError()});
if (self.hdc) |hdc| {
if (win32.SwapBuffers(hdc) == 0) {
log.warn("SwapBuffers failed: err={d}", .{@intFromEnum(win32.GetLastError())});
}
}
}
/// Make the WGL context current on the calling thread.
pub fn makeContextCurrent(self: *Self) void {
if (self.hdc != null and self.hglrc != null) {
if (wglMakeCurrent(self.hdc, self.hglrc) == 0) {
log.warn("wglMakeCurrent failed: err={d}", .{GetLastError()});
if (self.hdc) |hdc| {
if (self.hglrc) |hglrc| {
if (win32.wglMakeCurrent(hdc, hglrc) == 0) {
log.warn("wglMakeCurrent failed: err={d}", .{@intFromEnum(win32.GetLastError())});
}
}
}
}
/// Release the WGL context from the calling thread.
pub fn releaseContext() void {
if (wglMakeCurrent(null, null) == 0) {
log.warn("wglMakeCurrent(null) failed: err={d}", .{GetLastError()});
if (win32.wglMakeCurrent(null, null) == 0) {
log.warn("wglMakeCurrent(null) failed: err={d}", .{@intFromEnum(win32.GetLastError())});
}
}
/// Release context from the main thread before handing off to renderer thread.
pub fn releaseMainThreadContext(self: *Self) void {
_ = self;
if (wglMakeCurrent(null, null) == 0) {
log.warn("wglMakeCurrent(null) failed: err={d}", .{GetLastError()});
if (win32.wglMakeCurrent(null, null) == 0) {
log.warn("wglMakeCurrent(null) failed: err={d}", .{@intFromEnum(win32.GetLastError())});
}
}

View File

@ -609,6 +609,9 @@ pub fn add(
step.linkSystemLibrary2("user32", .{});
step.linkSystemLibrary2("gdi32", .{});
step.linkSystemLibrary2("opengl32", .{});
if (b.lazyDependency("win32", .{})) |dep| {
step.root_module.addImport("win32", dep.module("win32"));
}
},
}
}