├── .gitattributes ├── .gitignore ├── 0.13.0 ├── build.zig ├── build.zig.zon └── common.zig ├── ComOverloads.txt ├── README.md ├── build.zig ├── build.zig.zon ├── examples ├── basewin.zig ├── d2dcircle.zig ├── helloworld-window.zig ├── helloworld.zig ├── net.zig ├── opendialog.zig ├── tests.zig ├── testwindow.zig ├── unionpointers.zig ├── wasapi.zig └── win32.manifest ├── extra.txt ├── src ├── StringPool.zig ├── cameltosnake.zig ├── common.zig ├── diff.zig ├── extra.zig ├── genzig.zig ├── genzigexports.zig ├── handletypes.zig ├── jsonextra.zig ├── metadata.zig ├── pass1.zig ├── pass1data.zig └── static │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── build.zig │ ├── win32.zig │ ├── win32 │ ├── windowlongptr.zig │ └── zig.zig │ └── zig.mod ├── test ├── badcomoverload.zig └── comoverload.zig └── update-merged-branch /.gitattributes: -------------------------------------------------------------------------------- 1 | *.zig text eol=lf 2 | *.zon text eol=lf 3 | *.txt text eol=lf 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .zig-cache/ 2 | zig-out/ 3 | /diffrepo/ 4 | -------------------------------------------------------------------------------- /0.13.0/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const common = @import("common.zig"); 3 | 4 | pub fn build(b: *std.Build) !void { 5 | const optimize = b.standardOptimizeOption(.{}); 6 | // NOTE: currently depends on ensuring the bindings 7 | // are already generated/installed from build.zig 8 | const win32 = b.dependency("win32", .{}).module("win32"); 9 | common.addExamples(b, optimize, win32, b.dependency("examples", .{}).path(".")); 10 | } 11 | -------------------------------------------------------------------------------- /0.13.0/build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = "zigwin32", 3 | .version = "0.0.0", 4 | .minimum_zig_version = "0.13.0", 5 | .dependencies = .{ 6 | .win32 = .{ 7 | .path = "../zig-out", 8 | }, 9 | .examples = .{ 10 | .path = "../examples", 11 | }, 12 | }, 13 | .paths = .{"."}, 14 | } 15 | -------------------------------------------------------------------------------- /0.13.0/common.zig: -------------------------------------------------------------------------------- 1 | const builtin = @import("builtin"); 2 | const std = @import("std"); 3 | 4 | pub fn addExamples( 5 | b: *std.Build, 6 | optimize: std.builtin.Mode, 7 | win32: *std.Build.Module, 8 | examples: std.Build.LazyPath, 9 | ) void { 10 | const arches: []const ?[]const u8 = &[_]?[]const u8{ 11 | null, 12 | "x86", 13 | "x86_64", 14 | "aarch64", 15 | }; 16 | const examples_step = b.step("examples", "Build/run examples. Use -j1 to run one at a time"); 17 | 18 | try addExample(b, arches, optimize, win32, examples, "helloworld", .Console, examples_step); 19 | try addExample(b, arches, optimize, win32, examples, "wasapi", .Console, examples_step); 20 | try addExample(b, arches, optimize, win32, examples, "net", .Console, examples_step); 21 | try addExample(b, arches, optimize, win32, examples, "tests", .Console, examples_step); 22 | 23 | try addExample(b, arches, optimize, win32, examples, "helloworld-window", .Windows, examples_step); 24 | try addExample(b, arches, optimize, win32, examples, "d2dcircle", .Windows, examples_step); 25 | try addExample(b, arches, optimize, win32, examples, "opendialog", .Windows, examples_step); 26 | try addExample(b, arches, optimize, win32, examples, "unionpointers", .Windows, examples_step); 27 | try addExample(b, arches, optimize, win32, examples, "testwindow", .Windows, examples_step); 28 | } 29 | 30 | fn addExample( 31 | b: *std.Build, 32 | arches: []const ?[]const u8, 33 | optimize: std.builtin.Mode, 34 | win32: *std.Build.Module, 35 | examples: std.Build.LazyPath, 36 | root: []const u8, 37 | subsystem: std.Target.SubSystem, 38 | examples_step: *std.Build.Step, 39 | ) !void { 40 | const basename = b.fmt("{s}.zig", .{root}); 41 | for (arches) |cross_arch_opt| { 42 | const name = if (cross_arch_opt) |arch| b.fmt("{s}-{s}", .{ root, arch }) else root; 43 | 44 | const arch_os_abi = if (cross_arch_opt) |arch| b.fmt("{s}-windows", .{arch}) else "native"; 45 | const target_query = std.Target.Query.parse(.{ .arch_os_abi = arch_os_abi }) catch unreachable; 46 | const target = b.resolveTargetQuery(target_query); 47 | const exe = b.addExecutable(.{ 48 | .name = name, 49 | .root_source_file = examples.path(b, basename), 50 | .target = target, 51 | .optimize = optimize, 52 | .single_threaded = true, 53 | .win32_manifest = if (subsystem == .Windows) examples.path(b, "win32.manifest") else null, 54 | }); 55 | exe.subsystem = subsystem; 56 | exe.root_module.addImport("win32", win32); 57 | examples_step.dependOn(&exe.step); 58 | exe.pie = true; 59 | 60 | const desc_suffix: []const u8 = if (cross_arch_opt) |_| "" else " for the native target"; 61 | const build_desc = b.fmt("Build {s}{s}", .{ name, desc_suffix }); 62 | b.step(b.fmt("{s}-build", .{name}), build_desc).dependOn(&exe.step); 63 | 64 | const run_cmd = b.addRunArtifact(exe); 65 | const run_desc = b.fmt("Run {s}{s}", .{ name, desc_suffix }); 66 | b.step(name, run_desc).dependOn(&run_cmd.step); 67 | 68 | if (builtin.os.tag == .windows) { 69 | if (cross_arch_opt == null) { 70 | examples_step.dependOn(&run_cmd.step); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ComOverloads.txt: -------------------------------------------------------------------------------- 1 | AI.MachineLearning.WinML IMLOperatorKernelContext GetOutputTensor 1 WithShape 2 | AI.MachineLearning.WinML IMLOperatorKernelContext GetOutputTensor 2 Default 3 | Data.HtmlHelp IITPropList Set 0 String 4 | Data.HtmlHelp IITPropList Set 1 Pointer 5 | Data.HtmlHelp IITPropList Set 2 Dword 6 | Data.HtmlHelp IITPropList SetPersist 6 All 7 | Data.HtmlHelp IITPropList SetPersist 7 One 8 | Data.HtmlHelp IITWordWheel Lookup 5 _TODO_A 9 | Data.HtmlHelp IITWordWheel Lookup 6 _TODO_B 10 | Data.HtmlHelp IITWordWheel Lookup 7 _TODO_C 11 | Data.HtmlHelp IITResultSet Add 3 _TODO_A 12 | Data.HtmlHelp IITResultSet Add 4 _TODO_B 13 | Data.HtmlHelp IITResultSet Add 5 _TODO_C 14 | Data.HtmlHelp IITResultSet Add 6 _TODO_D 15 | Data.HtmlHelp IITResultSet Set 8 _TODO_A 16 | Data.HtmlHelp IITResultSet Set 9 _TODO_B 17 | Data.HtmlHelp IITResultSet Set 10 _TODO_C 18 | Data.HtmlHelp IITResultSet Set 11 _TODO_D 19 | Data.HtmlHelp IITResultSet GetColumn 19 _TODO_A 20 | Data.HtmlHelp IITResultSet GetColumn 20 _TODO_B 21 | Graphics.Direct2D ID2D1SvgStrokeDashArray UpdateDashes 1 Units 22 | Graphics.Direct2D ID2D1SvgStrokeDashArray UpdateDashes 2 Floats 23 | Graphics.Direct2D ID2D1SvgStrokeDashArray GetDashes 3 Units 24 | Graphics.Direct2D ID2D1SvgStrokeDashArray GetDashes 4 Floats 25 | Graphics.Direct2D ID2D1SvgElement SetAttributeValue 23 Obj 26 | Graphics.Direct2D ID2D1SvgElement SetAttributeValue 24 Pod 27 | Graphics.Direct2D ID2D1SvgElement SetAttributeValue 25 String 28 | Graphics.Direct2D ID2D1SvgElement GetAttributeValue 26 Obj 29 | Graphics.Direct2D ID2D1SvgElement GetAttributeValue 27 Pod 30 | Graphics.Direct2D ID2D1SvgElement GetAttributeValue 28 String 31 | Graphics.DirectComposition IDCompositionVisual SetOffsetX 0 _TODO_A 32 | Graphics.DirectComposition IDCompositionVisual SetOffsetX 1 _TODO_B 33 | Graphics.DirectComposition IDCompositionVisual SetOffsetY 2 _TODO_A 34 | Graphics.DirectComposition IDCompositionVisual SetOffsetY 3 _TODO_B 35 | Graphics.DirectComposition IDCompositionVisual SetTransform 4 _TODO_A 36 | Graphics.DirectComposition IDCompositionVisual SetTransform 5 _TODO_B 37 | Graphics.DirectComposition IDCompositionVisual SetClip 10 _TODO_A 38 | Graphics.DirectComposition IDCompositionVisual SetClip 11 _TODO_B 39 | Graphics.DirectComposition IDCompositionTranslateTransform SetOffsetX 0 _TODO_A 40 | Graphics.DirectComposition IDCompositionTranslateTransform SetOffsetX 1 _TODO_B 41 | Graphics.DirectComposition IDCompositionTranslateTransform SetOffsetY 2 _TODO_A 42 | Graphics.DirectComposition IDCompositionTranslateTransform SetOffsetY 3 _TODO_B 43 | Graphics.DirectComposition IDCompositionScaleTransform SetScaleX 0 _TODO_A 44 | Graphics.DirectComposition IDCompositionScaleTransform SetScaleX 1 _TODO_B 45 | Graphics.DirectComposition IDCompositionScaleTransform SetScaleY 2 _TODO_A 46 | Graphics.DirectComposition IDCompositionScaleTransform SetScaleY 3 _TODO_B 47 | Graphics.DirectComposition IDCompositionScaleTransform SetCenterX 4 _TODO_A 48 | Graphics.DirectComposition IDCompositionScaleTransform SetCenterX 5 _TODO_B 49 | Graphics.DirectComposition IDCompositionScaleTransform SetCenterY 6 _TODO_A 50 | Graphics.DirectComposition IDCompositionScaleTransform SetCenterY 7 _TODO_B 51 | Graphics.DirectComposition IDCompositionRotateTransform SetAngle 0 _TODO_A 52 | Graphics.DirectComposition IDCompositionRotateTransform SetAngle 1 _TODO_B 53 | Graphics.DirectComposition IDCompositionRotateTransform SetCenterX 2 _TODO_A 54 | Graphics.DirectComposition IDCompositionRotateTransform SetCenterX 3 _TODO_B 55 | Graphics.DirectComposition IDCompositionRotateTransform SetCenterY 4 _TODO_A 56 | Graphics.DirectComposition IDCompositionRotateTransform SetCenterY 5 _TODO_B 57 | Graphics.DirectComposition IDCompositionSkewTransform SetAngleX 0 _TODO_A 58 | Graphics.DirectComposition IDCompositionSkewTransform SetAngleX 1 _TODO_B 59 | Graphics.DirectComposition IDCompositionSkewTransform SetAngleY 2 _TODO_A 60 | Graphics.DirectComposition IDCompositionSkewTransform SetAngleY 3 _TODO_B 61 | Graphics.DirectComposition IDCompositionSkewTransform SetCenterX 4 _TODO_A 62 | Graphics.DirectComposition IDCompositionSkewTransform SetCenterX 5 _TODO_B 63 | Graphics.DirectComposition IDCompositionSkewTransform SetCenterY 6 _TODO_A 64 | Graphics.DirectComposition IDCompositionSkewTransform SetCenterY 7 _TODO_B 65 | Graphics.DirectComposition IDCompositionMatrixTransform SetMatrixElement 1 _TODO_A 66 | Graphics.DirectComposition IDCompositionMatrixTransform SetMatrixElement 2 _TODO_B 67 | Graphics.DirectComposition IDCompositionEffectGroup SetOpacity 0 _TODO_A 68 | Graphics.DirectComposition IDCompositionEffectGroup SetOpacity 1 _TODO_B 69 | Graphics.DirectComposition IDCompositionTranslateTransform3D SetOffsetX 0 _TODO_A 70 | Graphics.DirectComposition IDCompositionTranslateTransform3D SetOffsetX 1 _TODO_B 71 | Graphics.DirectComposition IDCompositionTranslateTransform3D SetOffsetY 2 _TODO_A 72 | Graphics.DirectComposition IDCompositionTranslateTransform3D SetOffsetY 3 _TODO_B 73 | Graphics.DirectComposition IDCompositionTranslateTransform3D SetOffsetZ 4 _TODO_A 74 | Graphics.DirectComposition IDCompositionTranslateTransform3D SetOffsetZ 5 _TODO_B 75 | Graphics.DirectComposition IDCompositionScaleTransform3D SetScaleX 0 _TODO_A 76 | Graphics.DirectComposition IDCompositionScaleTransform3D SetScaleX 1 _TODO_B 77 | Graphics.DirectComposition IDCompositionScaleTransform3D SetScaleY 2 _TODO_A 78 | Graphics.DirectComposition IDCompositionScaleTransform3D SetScaleY 3 _TODO_B 79 | Graphics.DirectComposition IDCompositionScaleTransform3D SetScaleZ 4 _TODO_A 80 | Graphics.DirectComposition IDCompositionScaleTransform3D SetScaleZ 5 _TODO_B 81 | Graphics.DirectComposition IDCompositionScaleTransform3D SetCenterX 6 _TODO_A 82 | Graphics.DirectComposition IDCompositionScaleTransform3D SetCenterX 7 _TODO_B 83 | Graphics.DirectComposition IDCompositionScaleTransform3D SetCenterY 8 _TODO_A 84 | Graphics.DirectComposition IDCompositionScaleTransform3D SetCenterY 9 _TODO_B 85 | Graphics.DirectComposition IDCompositionScaleTransform3D SetCenterZ 10 _TODO_A 86 | Graphics.DirectComposition IDCompositionScaleTransform3D SetCenterZ 11 _TODO_B 87 | Graphics.DirectComposition IDCompositionRotateTransform3D SetAngle 0 _TODO_A 88 | Graphics.DirectComposition IDCompositionRotateTransform3D SetAngle 1 _TODO_B 89 | Graphics.DirectComposition IDCompositionRotateTransform3D SetAxisX 2 _TODO_A 90 | Graphics.DirectComposition IDCompositionRotateTransform3D SetAxisX 3 _TODO_B 91 | Graphics.DirectComposition IDCompositionRotateTransform3D SetAxisY 4 _TODO_A 92 | Graphics.DirectComposition IDCompositionRotateTransform3D SetAxisY 5 _TODO_B 93 | Graphics.DirectComposition IDCompositionRotateTransform3D SetAxisZ 6 _TODO_A 94 | Graphics.DirectComposition IDCompositionRotateTransform3D SetAxisZ 7 _TODO_B 95 | Graphics.DirectComposition IDCompositionRotateTransform3D SetCenterX 8 _TODO_A 96 | Graphics.DirectComposition IDCompositionRotateTransform3D SetCenterX 9 _TODO_B 97 | Graphics.DirectComposition IDCompositionRotateTransform3D SetCenterY 10 _TODO_A 98 | Graphics.DirectComposition IDCompositionRotateTransform3D SetCenterY 11 _TODO_B 99 | Graphics.DirectComposition IDCompositionRotateTransform3D SetCenterZ 12 _TODO_A 100 | Graphics.DirectComposition IDCompositionRotateTransform3D SetCenterZ 13 _TODO_B 101 | Graphics.DirectComposition IDCompositionMatrixTransform3D SetMatrixElement 1 _TODO_A 102 | Graphics.DirectComposition IDCompositionMatrixTransform3D SetMatrixElement 2 _TODO_B 103 | Graphics.DirectComposition IDCompositionRectangleClip SetLeft 0 _TODO_A 104 | Graphics.DirectComposition IDCompositionRectangleClip SetLeft 1 _TODO_B 105 | Graphics.DirectComposition IDCompositionRectangleClip SetTop 2 _TODO_A 106 | Graphics.DirectComposition IDCompositionRectangleClip SetTop 3 _TODO_B 107 | Graphics.DirectComposition IDCompositionRectangleClip SetRight 4 _TODO_A 108 | Graphics.DirectComposition IDCompositionRectangleClip SetRight 5 _TODO_B 109 | Graphics.DirectComposition IDCompositionRectangleClip SetBottom 6 _TODO_A 110 | Graphics.DirectComposition IDCompositionRectangleClip SetBottom 7 _TODO_B 111 | Graphics.DirectComposition IDCompositionRectangleClip SetTopLeftRadiusX 8 _TODO_A 112 | Graphics.DirectComposition IDCompositionRectangleClip SetTopLeftRadiusX 9 _TODO_B 113 | Graphics.DirectComposition IDCompositionRectangleClip SetTopLeftRadiusY 10 _TODO_A 114 | Graphics.DirectComposition IDCompositionRectangleClip SetTopLeftRadiusY 11 _TODO_B 115 | Graphics.DirectComposition IDCompositionRectangleClip SetTopRightRadiusX 12 _TODO_A 116 | Graphics.DirectComposition IDCompositionRectangleClip SetTopRightRadiusX 13 _TODO_B 117 | Graphics.DirectComposition IDCompositionRectangleClip SetTopRightRadiusY 14 _TODO_A 118 | Graphics.DirectComposition IDCompositionRectangleClip SetTopRightRadiusY 15 _TODO_B 119 | Graphics.DirectComposition IDCompositionRectangleClip SetBottomLeftRadiusX 16 _TODO_A 120 | Graphics.DirectComposition IDCompositionRectangleClip SetBottomLeftRadiusX 17 _TODO_B 121 | Graphics.DirectComposition IDCompositionRectangleClip SetBottomLeftRadiusY 18 _TODO_A 122 | Graphics.DirectComposition IDCompositionRectangleClip SetBottomLeftRadiusY 19 _TODO_B 123 | Graphics.DirectComposition IDCompositionRectangleClip SetBottomRightRadiusX 20 _TODO_A 124 | Graphics.DirectComposition IDCompositionRectangleClip SetBottomRightRadiusX 21 _TODO_B 125 | Graphics.DirectComposition IDCompositionRectangleClip SetBottomRightRadiusY 22 _TODO_A 126 | Graphics.DirectComposition IDCompositionRectangleClip SetBottomRightRadiusY 23 _TODO_B 127 | Graphics.DirectComposition IDCompositionVisual3 SetOffsetZ 1 _TODO_A 128 | Graphics.DirectComposition IDCompositionVisual3 SetOffsetZ 2 _TODO_B 129 | Graphics.DirectComposition IDCompositionVisual3 SetOpacity 3 _TODO_A 130 | Graphics.DirectComposition IDCompositionVisual3 SetOpacity 4 _TODO_B 131 | Graphics.DirectComposition IDCompositionVisual3 SetTransform 5 _TODO_A 132 | Graphics.DirectComposition IDCompositionVisual3 SetTransform 6 _TODO_B 133 | Graphics.DirectComposition IDCompositionGaussianBlurEffect SetStandardDeviation 0 _TODO_A 134 | Graphics.DirectComposition IDCompositionGaussianBlurEffect SetStandardDeviation 1 _TODO_B 135 | Graphics.DirectComposition IDCompositionBrightnessEffect SetWhitePointX 2 _TODO_A 136 | Graphics.DirectComposition IDCompositionBrightnessEffect SetWhitePointX 3 _TODO_B 137 | Graphics.DirectComposition IDCompositionBrightnessEffect SetWhitePointY 4 _TODO_A 138 | Graphics.DirectComposition IDCompositionBrightnessEffect SetWhitePointY 5 _TODO_B 139 | Graphics.DirectComposition IDCompositionBrightnessEffect SetBlackPointX 6 _TODO_A 140 | Graphics.DirectComposition IDCompositionBrightnessEffect SetBlackPointX 7 _TODO_B 141 | Graphics.DirectComposition IDCompositionBrightnessEffect SetBlackPointY 8 _TODO_A 142 | Graphics.DirectComposition IDCompositionBrightnessEffect SetBlackPointY 9 _TODO_B 143 | Graphics.DirectComposition IDCompositionColorMatrixEffect SetMatrixElement 1 _TODO_A 144 | Graphics.DirectComposition IDCompositionColorMatrixEffect SetMatrixElement 2 _TODO_B 145 | Graphics.DirectComposition IDCompositionShadowEffect SetStandardDeviation 0 _TODO_A 146 | Graphics.DirectComposition IDCompositionShadowEffect SetStandardDeviation 1 _TODO_B 147 | Graphics.DirectComposition IDCompositionShadowEffect SetRed 3 _TODO_A 148 | Graphics.DirectComposition IDCompositionShadowEffect SetRed 4 _TODO_B 149 | Graphics.DirectComposition IDCompositionShadowEffect SetGreen 5 _TODO_A 150 | Graphics.DirectComposition IDCompositionShadowEffect SetGreen 6 _TODO_B 151 | Graphics.DirectComposition IDCompositionShadowEffect SetBlue 7 _TODO_A 152 | Graphics.DirectComposition IDCompositionShadowEffect SetBlue 8 _TODO_B 153 | Graphics.DirectComposition IDCompositionShadowEffect SetAlpha 9 _TODO_A 154 | Graphics.DirectComposition IDCompositionShadowEffect SetAlpha 10 _TODO_B 155 | Graphics.DirectComposition IDCompositionHueRotationEffect SetAngle 0 _TODO_A 156 | Graphics.DirectComposition IDCompositionHueRotationEffect SetAngle 1 _TODO_B 157 | Graphics.DirectComposition IDCompositionSaturationEffect SetSaturation 0 _TODO_A 158 | Graphics.DirectComposition IDCompositionSaturationEffect SetSaturation 1 _TODO_B 159 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetRedYIntercept 0 _TODO_A 160 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetRedYIntercept 1 _TODO_B 161 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetRedSlope 2 _TODO_A 162 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetRedSlope 3 _TODO_B 163 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetGreenYIntercept 5 _TODO_A 164 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetGreenYIntercept 6 _TODO_B 165 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetGreenSlope 7 _TODO_A 166 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetGreenSlope 8 _TODO_B 167 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetBlueYIntercept 10 _TODO_A 168 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetBlueYIntercept 11 _TODO_B 169 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetBlueSlope 12 _TODO_A 170 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetBlueSlope 13 _TODO_B 171 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetAlphaYIntercept 15 _TODO_A 172 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetAlphaYIntercept 16 _TODO_B 173 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetAlphaSlope 17 _TODO_A 174 | Graphics.DirectComposition IDCompositionLinearTransferEffect SetAlphaSlope 18 _TODO_B 175 | Graphics.DirectComposition IDCompositionTableTransferEffect SetRedTableValue 9 _TODO_A 176 | Graphics.DirectComposition IDCompositionTableTransferEffect SetRedTableValue 10 _TODO_B 177 | Graphics.DirectComposition IDCompositionTableTransferEffect SetGreenTableValue 11 _TODO_A 178 | Graphics.DirectComposition IDCompositionTableTransferEffect SetGreenTableValue 12 _TODO_B 179 | Graphics.DirectComposition IDCompositionTableTransferEffect SetBlueTableValue 13 _TODO_A 180 | Graphics.DirectComposition IDCompositionTableTransferEffect SetBlueTableValue 14 _TODO_B 181 | Graphics.DirectComposition IDCompositionTableTransferEffect SetAlphaTableValue 15 _TODO_A 182 | Graphics.DirectComposition IDCompositionTableTransferEffect SetAlphaTableValue 16 _TODO_B 183 | Graphics.DirectComposition IDCompositionArithmeticCompositeEffect SetCoefficient1 2 _TODO_A 184 | Graphics.DirectComposition IDCompositionArithmeticCompositeEffect SetCoefficient1 3 _TODO_B 185 | Graphics.DirectComposition IDCompositionArithmeticCompositeEffect SetCoefficient2 4 _TODO_A 186 | Graphics.DirectComposition IDCompositionArithmeticCompositeEffect SetCoefficient2 5 _TODO_B 187 | Graphics.DirectComposition IDCompositionArithmeticCompositeEffect SetCoefficient3 6 _TODO_A 188 | Graphics.DirectComposition IDCompositionArithmeticCompositeEffect SetCoefficient3 7 _TODO_B 189 | Graphics.DirectComposition IDCompositionArithmeticCompositeEffect SetCoefficient4 8 _TODO_A 190 | Graphics.DirectComposition IDCompositionArithmeticCompositeEffect SetCoefficient4 9 _TODO_B 191 | Graphics.DirectComposition IDCompositionAffineTransform2DEffect SetTransformMatrixElement 3 _TODO_A 192 | Graphics.DirectComposition IDCompositionAffineTransform2DEffect SetTransformMatrixElement 4 _TODO_B 193 | Graphics.DirectComposition IDCompositionAffineTransform2DEffect SetSharpness 5 _TODO_A 194 | Graphics.DirectComposition IDCompositionAffineTransform2DEffect SetSharpness 6 _TODO_B 195 | Graphics.DirectWrite IDWriteFactory3 CreateFontFaceReference 2 _TODO_A 196 | Graphics.DirectWrite IDWriteFactory3 CreateFontFaceReference 3 _TODO_B 197 | Graphics.DirectWrite IDWriteFontSet GetPropertyValues 4 _TODO_A 198 | Graphics.DirectWrite IDWriteFontSet GetPropertyValues 5 _TODO_B 199 | Graphics.DirectWrite IDWriteFontSet GetPropertyValues 6 _TODO_C 200 | Graphics.DirectWrite IDWriteFontSet GetMatchingFonts 8 _TODO_A 201 | Graphics.DirectWrite IDWriteFontSet GetMatchingFonts 9 _TODO_B 202 | Graphics.DirectWrite IDWriteFontSetBuilder AddFontFaceReference 0 _TODO_A 203 | Graphics.DirectWrite IDWriteFontSetBuilder AddFontFaceReference 1 _TODO_B 204 | Graphics.DirectWrite IDWriteGdiInterop1 GetFontSignature 1 _TODO_A 205 | Graphics.DirectWrite IDWriteGdiInterop1 GetFontSignature 2 _TODO_B 206 | Graphics.DirectWrite IDWriteFontFace4 GetGlyphImageFormats 0 _TODO_A 207 | Graphics.DirectWrite IDWriteFontFace4 GetGlyphImageFormats 1 _TODO_B 208 | Graphics.DirectWrite IDWriteFactory4 ComputeGlyphOrigins 1 _TODO_A 209 | Graphics.DirectWrite IDWriteFactory4 ComputeGlyphOrigins 2 _TODO_B 210 | Graphics.DirectWrite IDWriteFontSet1 GetFilteredFonts 2 _TODO_A 211 | Graphics.DirectWrite IDWriteFontSet1 GetFilteredFonts 3 _TODO_B 212 | Graphics.DirectWrite IDWriteFontSet1 GetFilteredFonts 4 _TODO_C 213 | Graphics.DirectWrite IDWriteFontSet1 GetFilteredFontIndices 5 _TODO_A 214 | Graphics.DirectWrite IDWriteFontSet1 GetFilteredFontIndices 6 _TODO_C 215 | Graphics.DirectWrite IDWriteFontSet1 GetFontAxisRanges 7 _TODO_A 216 | Graphics.DirectWrite IDWriteFontSet1 GetFontAxisRanges 8 _TODO_B 217 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zigwin32gen 2 | 3 | Generates Zig bindings for Win32 using https://github.com/marlersoft/win32json. If you just want to use the bindings, see https://github.com/marlersoft/zigwin32. 4 | 5 | ## How to generate the Windows Zig bindings 6 | 7 | Download zig, currently tested on version: 0.12.0-dev.2701+d18f52197 8 | 9 | First, clone the zigwin32 repo from the root of this repository to provide a location for the generated files: 10 | 11 | ``` 12 | git clone https://github.com/marlersoft/zigwin32 13 | ``` 14 | 15 | Then run the following command to generate the latest bindings: 16 | 17 | ``` 18 | zig build 19 | ``` 20 | 21 | On the first run, running this command will provide an error message saying that the `win32json` dependency is missing, and will provide you with a git command to clone it and checkout the expected version. 22 | 23 | Once the build task completes, you can view your generated code in the `zigwin32` subrepository that you cloned earlier. 24 | 25 | ## win32json vs C/C++ Win32 SDK 26 | 27 | The win32metadata project from which win32json is generated does not expose the exact same interface as the classic C/C++ header files did. One thing I'd like is to support is to be able to take a Windows C/C++ example and port it to Zig without changing any names/types or the way it interfaces with the Windows API. To support this I'll need to: 28 | 29 | * Support a set of modules with the same structure as the Window headers. For example, importing the `windows` module should have all the same declarations as the `windows.h` header. 30 | * Generate structs with the same field names (hungarian notation) 31 | * Support code that can be compiled for either Ascii or Wide characters. Provide mappings like `CreateFile` to `CreateFileA` or `CreateFileW` based on this configuration. 32 | 33 | 34 | For example, intead of `DWORD` or `UINT32`, I would generate `u32`. Instead of field names with hungarian notation, I would generate them with snake case. These would only be cosmetic changes, where it would look more natural to use the Windows API directly alongside other Zig APIs. The bigger advantage to this is not having to track all the different ways to declare the same type, for example, a developer having to know that `DWORD`, `UINT32`, `ULONG`, `UINT` are all the same as `u32`. 35 | * I may want to be able support different "views" into the api that change things like how to import the api. These extra views should not add to compile-time unless they are being used. 36 | * I may want to consider also generating wrapper APIs? It's clear that wrapping APIs should exist, but I'm not sure if this wrapping API should be created/maintained manually or autogenerated from the Windows API data. 37 | * I may want to make it possible to generate subsets of the complete API. One example of where this could be used it to generate bindings to be included in the standard library, where only a small portion of the complete API is required. 38 | 39 | extra.txt 40 | ================================================================================ 41 | This file contains extra metadata, namely, 42 | 43 | * whether pointer types are "union pointers" 44 | * whether pointer types are "not null" 45 | 46 | "Union Pointers" are pointers that in addition to actual pointer values also 47 | accept special reserved values that aren't actually pointers. 48 | 49 | It's important for Zig to know when this is the case because Zig inserts 50 | runtime alignment checks when casting non-pointer values to pointers. To address 51 | this, anytime a pointer type is actually a "union pointer", we annotate that 52 | pointer type with `align(1)` to ellide this runtime alignment check. Note that 53 | the reason for using `align(1)` instead of a union type is because modifying 54 | a parameter type to be a union instead of a pointer can modify the ABI. 55 | See https://github.com/dotnet/coreclr/pull/23974#issuecomment-482961995 for an 56 | example of this. 57 | 58 | > See Issue "CreateWindowEx lpClassName union": https://github.com/microsoft/win32metadata/issues/623 59 | 60 | We also add include extra information about which pointers are "not null". By 61 | default, all pointer types in the metadata are treated as being "optional" 62 | (i.e. `?*T`). We can override this default by providing a "NotNull" modifier 63 | in extra.txt. 64 | 65 | The "NotNull" modifier is a sequence of 0/1 flags. Each flag modifies a pointer 66 | in the type to be "non-optional". 67 | For example, consider the type `?*?*?*T`. The integer value `0` would leave 68 | the type unmodified. 69 | The modifier `1` would modify the first pointer in the type to make it `*?*?*T`. 70 | The modifier `10` would modify the second pointer in the type to become `?**?*T`. 71 | The integer value `111` would modify all 3 pointer types to become `***T`. 72 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const builtin = @import("builtin"); 2 | const std = @import("std"); 3 | const Build = std.Build; 4 | const Step = std.Build.Step; 5 | const CrossTarget = std.zig.CrossTarget; 6 | const buildcommon = @import("0.13.0/common.zig"); 7 | 8 | comptime { 9 | const required_zig = "0.14.0"; 10 | const v = std.SemanticVersion.parse(required_zig) catch unreachable; 11 | if (builtin.zig_version.order(v) != .eq) @compileError( 12 | "zig version " ++ required_zig ++ " is required to ensure zigwin32 output is always the same", 13 | ); 14 | } 15 | 16 | pub fn build(b: *Build) !void { 17 | const default_steps = "install diff test"; 18 | b.default_step = b.step( 19 | "default", 20 | "The default step, equivalent to: " ++ default_steps, 21 | ); 22 | const test_step = b.step("test", "Run all the tests (except the examples)"); 23 | const unittest_step = b.step("unittest", "Unit test the generated bindings"); 24 | test_step.dependOn(unittest_step); 25 | const desc_line_prefix = [_]u8{' '} ** 31; 26 | const diff_step = b.step( 27 | "diff", 28 | ("Updates 'diffrepo' then installs the latest generated\n" ++ 29 | desc_line_prefix ++ 30 | "files so they can be diffed via git."), 31 | ); 32 | addDefaultStepDeps(b, default_steps); 33 | 34 | { 35 | const release_step = b.step("release", "Generate the bindings and run tests for a release"); 36 | release_step.dependOn(test_step); 37 | release_step.dependOn(b.getInstallStep()); 38 | } 39 | 40 | const pass1_step = b.step( 41 | "pass1", 42 | "Only perform pass1 of zig binding generation.\n" ++ desc_line_prefix ++ 43 | "(generates pass1.json in .zig-cache)", 44 | ); 45 | const gen_step = b.step("gen", "Generate the bindings (in .zig-cache)"); 46 | const optimize = b.standardOptimizeOption(.{}); 47 | 48 | const win32json_dep = b.dependency("win32json", .{}); 49 | 50 | const pass1_out_file = blk: { 51 | const pass1_exe = b.addExecutable(.{ 52 | .name = "pass1", 53 | .root_source_file = b.path("src/pass1.zig"), 54 | .optimize = optimize, 55 | .target = b.graph.host, 56 | }); 57 | 58 | const run = b.addRunArtifact(pass1_exe); 59 | run.addDirectoryArg(win32json_dep.path("")); 60 | const out_file = run.addOutputFileArg("pass1.json"); 61 | 62 | pass1_step.dependOn(&run.step); 63 | break :blk out_file; 64 | }; 65 | 66 | const zigexports = blk: { 67 | const exe = b.addExecutable(.{ 68 | .name = "genzigexports", 69 | .root_source_file = b.path("src/genzigexports.zig"), 70 | .target = b.graph.host, 71 | }); 72 | exe.root_module.addImport("win32_stub", b.createModule(.{ 73 | .root_source_file = b.path("src/static/win32.zig"), 74 | })); 75 | 76 | const run = b.addRunArtifact(exe); 77 | break :blk run.addOutputFileArg("zigexports.zig"); 78 | }; 79 | 80 | const gen_out_dir = blk: { 81 | const exe = b.addExecutable(.{ 82 | .name = "genzig", 83 | .root_source_file = b.path("src/genzig.zig"), 84 | .optimize = optimize, 85 | .target = b.graph.host, 86 | }); 87 | exe.root_module.addImport( 88 | "zigexports", 89 | b.createModule(.{ .root_source_file = zigexports }), 90 | ); 91 | const run = b.addRunArtifact(exe); 92 | run.addFileArg(b.path("extra.txt")); 93 | run.addDirectoryArg(win32json_dep.path("")); 94 | run.addFileArg(pass1_out_file); 95 | run.addFileArg(b.path("ComOverloads.txt")); 96 | const out_dir = run.addOutputDirectoryArg("."); 97 | gen_step.dependOn(&run.step); 98 | break :blk out_dir; 99 | }; 100 | 101 | b.step( 102 | "show-path", 103 | "Print the zigwin32 cache directory", 104 | ).dependOn(&PrintLazyPath.create(b, gen_out_dir).step); 105 | 106 | b.installDirectory(.{ 107 | .source_dir = gen_out_dir, 108 | .install_dir = .prefix, 109 | .install_subdir = ".", 110 | }); 111 | 112 | { 113 | const diff_exe = b.addExecutable(.{ 114 | .name = "diff", 115 | .root_source_file = b.path("src/diff.zig"), 116 | .target = b.graph.host, 117 | .optimize = .Debug, 118 | }); 119 | const diff = b.addRunArtifact(diff_exe); 120 | // fetches from zigwin32 github and also modifies the contents 121 | // of the 'diffrepo' subdirectory so definitely has side effects 122 | diff.has_side_effects = true; 123 | diff.addArg("--zigbuild"); 124 | // make this a normal string arg, we don't want the build system 125 | // trying to hash this as an input or something 126 | diff.addArg(b.pathFromRoot("diffrepo")); 127 | diff.addDirectoryArg(gen_out_dir); 128 | if (b.args) |args| { 129 | diff.addArgs(args); 130 | } 131 | diff_step.dependOn(&diff.step); 132 | } 133 | 134 | { 135 | const unittest = b.addTest(.{ 136 | .root_source_file = gen_out_dir.path(b, "win32.zig"), 137 | .target = b.graph.host, 138 | .optimize = optimize, 139 | }); 140 | unittest.pie = true; 141 | unittest_step.dependOn(&unittest.step); 142 | } 143 | 144 | const win32 = b.createModule(.{ 145 | .root_source_file = gen_out_dir.path(b, "win32.zig"), 146 | }); 147 | 148 | buildcommon.addExamples(b, optimize, win32, b.path("examples")); 149 | 150 | { 151 | const exe = b.addExecutable(.{ 152 | .name = "comoverload", 153 | .root_source_file = b.path("test/comoverload.zig"), 154 | .target = b.graph.host, 155 | }); 156 | exe.root_module.addImport("win32", win32); 157 | const run = b.addRunArtifact(exe); 158 | b.step("comoverload", "").dependOn(&run.step); 159 | test_step.dependOn(&run.step); 160 | } 161 | { 162 | const compile = b.addSystemCommand(&.{ 163 | b.graph.zig_exe, 164 | "build-exe", 165 | "--dep", 166 | "win32", 167 | }); 168 | compile.addPrefixedFileArg("-Mroot=", b.path("test/badcomoverload.zig")); 169 | compile.addPrefixedFileArg("-Mwin32=", gen_out_dir.path(b, "win32.zig")); 170 | compile.addCheck(.{ 171 | .expect_stderr_match = "COM method 'GetAttributeValue' must be called using one of the following overload names: GetAttributeValueString, GetAttributeValueObj, GetAttributeValuePod", 172 | }); 173 | test_step.dependOn(&compile.step); 174 | } 175 | } 176 | 177 | const PrintLazyPath = struct { 178 | step: Step, 179 | lazy_path: Build.LazyPath, 180 | pub fn create( 181 | b: *Build, 182 | lazy_path: Build.LazyPath, 183 | ) *PrintLazyPath { 184 | const print = b.allocator.create(PrintLazyPath) catch unreachable; 185 | print.* = .{ 186 | .step = Step.init(.{ 187 | .id = .custom, 188 | .name = "print the given lazy path", 189 | .owner = b, 190 | .makeFn = make, 191 | }), 192 | .lazy_path = lazy_path, 193 | }; 194 | lazy_path.addStepDependencies(&print.step); 195 | return print; 196 | } 197 | fn make(step: *Step, opt: std.Build.Step.MakeOptions) !void { 198 | _ = opt; 199 | const print: *PrintLazyPath = @fieldParentPtr("step", step); 200 | try std.io.getStdOut().writer().print( 201 | "{s}\n", 202 | .{print.lazy_path.getPath(step.owner)}, 203 | ); 204 | } 205 | }; 206 | 207 | fn addDefaultStepDeps(b: *std.Build, default_steps: []const u8) void { 208 | var it = std.mem.tokenizeScalar(u8, default_steps, ' '); 209 | while (it.next()) |step_name| { 210 | const step = b.top_level_steps.get(step_name) orelse std.debug.panic( 211 | "step '{s}' not added yet", 212 | .{step_name}, 213 | ); 214 | b.default_step.dependOn(&step.step); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = .zigwin32gen, 3 | .version = "0.0.0", 4 | .fingerprint = 0xab047bbd59488dae, 5 | .minimum_zig_version = "0.14.0", 6 | .dependencies = .{ 7 | .win32json = .{ 8 | .url = "git+https://github.com/marlersoft/win32json#d1f6a1fba4a7714f65b9c8a2696e7530561366ef", 9 | .hash = "N-V-__8AALasBATgDWZkgeN6G_QBIVFt9gD36O5YqZv6itzS", 10 | }, 11 | }, 12 | .paths = .{"build.zig.zon"}, 13 | } 14 | -------------------------------------------------------------------------------- /examples/basewin.zig: -------------------------------------------------------------------------------- 1 | //! This example is ported from : https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/begin/LearnWin32/Direct2DCircle/cpp/basewin.h 2 | 3 | const WINAPI = @import("std").os.windows.WINAPI; 4 | const win32 = struct { 5 | usingnamespace @import("win32").zig; 6 | usingnamespace @import("win32").foundation; 7 | usingnamespace @import("win32").system.system_services; 8 | usingnamespace @import("win32").system.library_loader; 9 | usingnamespace @import("win32").ui.windows_and_messaging; 10 | }; 11 | const L = win32.L; 12 | const HWND = win32.HWND; 13 | 14 | const windowlongptr = @import("win32").windowlongptr; 15 | 16 | // NOTE: can't do usingnamespace for menu_and_resources because it has conflicts with windows_and_messaging 17 | // I think this particular one is a problem with win32metadata. 18 | // NOTE: should Zig allow symbol conflicts so long as they are not referenced? 19 | const mnr = @import("win32").ui.menus_and_resources; 20 | 21 | pub fn BaseWindow(comptime DERIVED_TYPE: type) type { 22 | return struct { 23 | fn WindowProc(hwnd: HWND, uMsg: u32, wParam: win32.WPARAM, lParam: win32.LPARAM) callconv(WINAPI) win32.LRESULT { 24 | var pThis: ?*DERIVED_TYPE = null; 25 | if (uMsg == win32.WM_NCCREATE) { 26 | const pCreate: *win32.CREATESTRUCT = @ptrFromInt(@as(usize, @bitCast(lParam))); 27 | pThis = @ptrCast(@alignCast(pCreate.lpCreateParams)); 28 | _ = windowlongptr.SetWindowLongPtr(hwnd, win32.GWL_USERDATA, @bitCast(@intFromPtr(pThis))); 29 | pThis.?.base.m_hwnd = hwnd; 30 | } else { 31 | //pThis = @intToPtr(?*DERIVED_TYPE, @bitCast(usize, windowlongptr.GetWindowLongPtr(hwnd, win32.GWL_USERDATA))); 32 | pThis = @ptrFromInt(@as(usize, @bitCast(windowlongptr.GetWindowLongPtr(hwnd, win32.GWL_USERDATA)))); 33 | } 34 | if (pThis) |this| { 35 | return this.HandleMessage(uMsg, wParam, lParam); 36 | } else { 37 | return win32.DefWindowProc(hwnd, uMsg, wParam, lParam); 38 | } 39 | } 40 | 41 | pub fn Create( 42 | self: *@This(), 43 | lpWindowName: [*:0]const u16, 44 | dwStyle: win32.WINDOW_STYLE, 45 | options: struct { 46 | dwExStyle: win32.WINDOW_EX_STYLE = .{}, 47 | x: i32 = win32.CW_USEDEFAULT, 48 | y: i32 = win32.CW_USEDEFAULT, 49 | nWidth: i32 = win32.CW_USEDEFAULT, 50 | nHeight: i32 = win32.CW_USEDEFAULT, 51 | hWndParent: ?HWND = null, 52 | hMenu: ?win32.HMENU = null, 53 | }, 54 | ) win32.BOOL { 55 | const wc = win32.WNDCLASS{ 56 | .style = .{}, 57 | .lpfnWndProc = WindowProc, 58 | .cbClsExtra = 0, 59 | .cbWndExtra = 0, 60 | .hInstance = win32.GetModuleHandle(null), 61 | .hIcon = null, 62 | .hCursor = null, 63 | .hbrBackground = null, 64 | // TODO: autogen bindings don't allow for null, should win32metadata allow Option for fields? Or should all strings allow NULL? 65 | .lpszMenuName = L("Placeholder"), 66 | .lpszClassName = DERIVED_TYPE.ClassName(), 67 | }; 68 | 69 | _ = win32.RegisterClass(&wc); 70 | 71 | self.m_hwnd = win32.CreateWindowEx( 72 | options.dwExStyle, 73 | DERIVED_TYPE.ClassName(), 74 | lpWindowName, 75 | dwStyle, 76 | options.x, 77 | options.y, 78 | options.nWidth, 79 | options.nHeight, 80 | options.hWndParent, 81 | options.hMenu, 82 | win32.GetModuleHandle(null), 83 | @ptrCast(self), 84 | ); 85 | 86 | return if (self.m_hwnd != null) win32.TRUE else win32.FALSE; 87 | } 88 | 89 | pub fn Window(self: @This()) ?HWND { 90 | return self.m_hwnd; 91 | } 92 | 93 | m_hwnd: ?HWND = null, 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /examples/d2dcircle.zig: -------------------------------------------------------------------------------- 1 | //! This example is ported from : https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/begin/LearnWin32/Direct2DCircle/cpp/main.cpp 2 | pub const UNICODE = true; 3 | 4 | const WINAPI = @import("std").os.windows.WINAPI; 5 | const win32 = struct { 6 | usingnamespace @import("win32").zig; 7 | usingnamespace @import("win32").foundation; 8 | usingnamespace @import("win32").system.system_services; 9 | usingnamespace @import("win32").ui.windows_and_messaging; 10 | usingnamespace @import("win32").graphics.gdi; 11 | usingnamespace @import("win32").graphics.direct2d; 12 | usingnamespace @import("win32").graphics.direct2d.common; 13 | usingnamespace @import("win32").graphics.direct3d9; 14 | usingnamespace @import("win32").graphics.dxgi.common; 15 | usingnamespace @import("win32").system.com; 16 | }; 17 | const L = win32.L; 18 | const FAILED = win32.FAILED; 19 | const SUCCEEDED = win32.SUCCEEDED; 20 | const HRESULT = win32.HRESULT; 21 | const HINSTANCE = win32.HINSTANCE; 22 | const HWND = win32.HWND; 23 | const MSG = win32.MSG; 24 | const WPARAM = win32.WPARAM; 25 | const LPARAM = win32.LPARAM; 26 | const LRESULT = win32.LRESULT; 27 | const RECT = win32.RECT; 28 | const D2D_SIZE_U = win32.D2D_SIZE_U; 29 | const D2D_SIZE_F = win32.D2D_SIZE_F; 30 | const SafeReslease = win32.SafeRelease; 31 | 32 | const basewin = @import("basewin.zig"); 33 | const BaseWindow = basewin.BaseWindow; 34 | 35 | fn SafeRelease(ppT: anytype) void { 36 | if (ppT.*) |t| { 37 | _ = t.IUnknown.Release(); 38 | ppT.* = null; 39 | } 40 | } 41 | 42 | const MainWindow = struct { 43 | base: BaseWindow(@This()) = .{}, 44 | pFactory: ?*win32.ID2D1Factory = null, 45 | pRenderTarget: ?*win32.ID2D1HwndRenderTarget = null, 46 | pBrush: ?*win32.ID2D1SolidColorBrush = null, 47 | ellipse: win32.D2D1_ELLIPSE = undefined, 48 | 49 | pub inline fn CalculateLayout(self: *MainWindow) void { 50 | MainWindowCalculateLayout(self); 51 | } 52 | pub inline fn CreateGraphicsResources(self: *MainWindow) HRESULT { 53 | return MainWindowCreateGraphicsResources(self); 54 | } 55 | pub inline fn DiscardGraphicsResources(self: *MainWindow) void { 56 | MainWindowDiscardGraphicsResources(self); 57 | } 58 | pub inline fn OnPaint(self: *MainWindow) void { 59 | MainWindowOnPaint(self); 60 | } 61 | pub inline fn Resize(self: *MainWindow) void { 62 | MainWindowResize(self); 63 | } 64 | 65 | pub fn ClassName() [*:0]const u16 { 66 | return L("Circle Window Class"); 67 | } 68 | 69 | pub fn HandleMessage(self: *MainWindow, uMsg: u32, wParam: WPARAM, lParam: LPARAM) LRESULT { 70 | return MainWindowHandleMessage(self, uMsg, wParam, lParam); 71 | } 72 | }; 73 | 74 | // Recalculate drawing layout when the size of the window changes. 75 | 76 | fn MainWindowCalculateLayout(self: *MainWindow) void { 77 | if (self.pRenderTarget) |pRenderTarget| { 78 | // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 79 | // TODO: this call is causing a segfault when we return from this function!!! 80 | // I believe it is caused by this issue: https://github.com/ziglang/zig/issues/1481 81 | // Zig unable to handle a return type of extern struct { x: f32, y: f32 } for WINAPI 82 | _ = pRenderTarget; 83 | //const size: D2D_SIZE_F = pRenderTarget.ID2D1RenderTarget_GetSize(); 84 | const size = D2D_SIZE_F{ .width = 300, .height = 300 }; 85 | const x: f32 = size.width / 2; 86 | const y: f32 = size.height / 2; 87 | const radius = @min(x, y); 88 | self.ellipse = D2D1.Ellipse(D2D1.Point2F(x, y), radius, radius); 89 | } 90 | } 91 | 92 | fn MainWindowCreateGraphicsResources(self: *MainWindow) HRESULT { 93 | var hr = win32.S_OK; 94 | if (self.pRenderTarget == null) { 95 | var rc: RECT = undefined; 96 | _ = win32.GetClientRect(self.base.m_hwnd.?, &rc); 97 | 98 | const size = D2D_SIZE_U{ .width = @intCast(rc.right), .height = @intCast(rc.bottom) }; 99 | 100 | var target: *win32.ID2D1HwndRenderTarget = undefined; 101 | hr = self.pFactory.?.CreateHwndRenderTarget( 102 | &D2D1.RenderTargetProperties(), 103 | &D2D1.HwndRenderTargetProperties(self.base.m_hwnd.?, size), 104 | &target, 105 | ); 106 | 107 | if (SUCCEEDED(hr)) { 108 | self.pRenderTarget = target; 109 | const color = D2D1.ColorF(.{ .r = 1, .g = 1, .b = 0 }); 110 | var brush: *win32.ID2D1SolidColorBrush = undefined; 111 | hr = self.pRenderTarget.?.ID2D1RenderTarget.CreateSolidColorBrush(&color, null, &brush); 112 | 113 | if (SUCCEEDED(hr)) { 114 | self.pBrush = brush; 115 | self.CalculateLayout(); 116 | } 117 | } 118 | } 119 | return hr; 120 | } 121 | 122 | fn MainWindowDiscardGraphicsResources(self: *MainWindow) void { 123 | SafeRelease(&self.pRenderTarget); 124 | SafeRelease(&self.pBrush); 125 | } 126 | 127 | fn MainWindowOnPaint(self: *MainWindow) void { 128 | var hr = self.CreateGraphicsResources(); 129 | if (SUCCEEDED(hr)) { 130 | var ps: win32.PAINTSTRUCT = undefined; 131 | _ = win32.BeginPaint(self.base.m_hwnd.?, &ps); 132 | 133 | self.pRenderTarget.?.ID2D1RenderTarget.BeginDraw(); 134 | 135 | self.pRenderTarget.?.ID2D1RenderTarget.Clear(&D2D1.ColorFU32(.{ .rgb = D2D1.SkyBlue })); 136 | // TODO: how do I get a COM interface type to convert to a base type without 137 | // an explicit cast like this? 138 | self.pRenderTarget.?.ID2D1RenderTarget.FillEllipse(&self.ellipse, &self.pBrush.?.ID2D1Brush); 139 | 140 | hr = self.pRenderTarget.?.ID2D1RenderTarget.EndDraw(null, null); 141 | if (FAILED(hr) or hr == win32.D2DERR_RECREATE_TARGET) { 142 | self.DiscardGraphicsResources(); 143 | } 144 | _ = win32.EndPaint(self.base.m_hwnd.?, &ps); 145 | } 146 | } 147 | 148 | fn MainWindowResize(self: *MainWindow) void { 149 | if (self.pRenderTarget) |renderTarget| { 150 | var rc: RECT = undefined; 151 | _ = win32.GetClientRect(self.base.m_hwnd.?, &rc); 152 | 153 | const size = D2D_SIZE_U{ .width = @intCast(rc.right), .height = @intCast(rc.bottom) }; 154 | 155 | _ = renderTarget.Resize(&size); 156 | self.CalculateLayout(); 157 | _ = win32.InvalidateRect(self.base.m_hwnd.?, null, win32.FALSE); 158 | } 159 | } 160 | 161 | pub export fn wWinMain(_: HINSTANCE, __: ?HINSTANCE, ___: [*:0]u16, nCmdShow: u32) callconv(WINAPI) c_int { 162 | _ = __; 163 | _ = ___; 164 | 165 | var win = MainWindow{}; 166 | 167 | if (win32.TRUE != win.base.Create(L("Circle"), win32.WS_OVERLAPPEDWINDOW, .{})) { 168 | return 0; 169 | } 170 | 171 | _ = win32.ShowWindow(win.base.Window(), @bitCast(nCmdShow)); 172 | 173 | // Run the message loop. 174 | 175 | var msg: MSG = undefined; 176 | while (0 != win32.GetMessage(&msg, null, 0, 0)) { 177 | _ = win32.TranslateMessage(&msg); 178 | _ = win32.DispatchMessage(&msg); 179 | } 180 | 181 | return 0; 182 | } 183 | 184 | fn MainWindowHandleMessage(self: *MainWindow, uMsg: u32, wParam: WPARAM, lParam: LPARAM) LRESULT { 185 | switch (uMsg) { 186 | win32.WM_CREATE => { 187 | // TODO: Should I need to case &self.pFactory to **anyopaque? Maybe 188 | // D2D2CreateFactory probably doesn't have the correct type yet? 189 | if (FAILED(win32.D2D1CreateFactory( 190 | win32.D2D1_FACTORY_TYPE_SINGLE_THREADED, 191 | win32.IID_ID2D1Factory, 192 | null, 193 | @ptrCast(&self.pFactory), 194 | ))) { 195 | return -1; // Fail CreateWindowEx. 196 | } 197 | return 0; 198 | }, 199 | win32.WM_DESTROY => { 200 | self.DiscardGraphicsResources(); 201 | SafeRelease(&self.pFactory); 202 | win32.PostQuitMessage(0); 203 | return 0; 204 | }, 205 | win32.WM_PAINT => { 206 | self.OnPaint(); 207 | return 0; 208 | }, 209 | // Other messages not shown... 210 | win32.WM_SIZE => { 211 | self.Resize(); 212 | return 0; 213 | }, 214 | else => {}, 215 | } 216 | return win32.DefWindowProc(self.base.m_hwnd.?, uMsg, wParam, lParam); 217 | } 218 | 219 | // TODO: tthis D2D1 namespace is referenced in the C++ example but it doesn't exist in win32metadata 220 | const D2D1 = struct { 221 | // TODO: SkyBlue is missing from win32metadata? file an issue? 222 | pub const SkyBlue = 0x87CEEB; 223 | 224 | // TODO: this is missing 225 | pub fn ColorF(o: struct { r: f32, g: f32, b: f32, a: f32 = 1 }) win32.D2D_COLOR_F { 226 | return .{ .r = o.r, .g = o.g, .b = o.b, .a = o.a }; 227 | } 228 | 229 | // TODO: this is missing 230 | pub fn ColorFU32(o: struct { rgb: u32, a: f32 = 1 }) win32.D2D_COLOR_F { 231 | return .{ 232 | .r = @as(f32, @floatFromInt((o.rgb >> 16) & 0xff)) / 255, 233 | .g = @as(f32, @floatFromInt((o.rgb >> 8) & 0xff)) / 255, 234 | .b = @as(f32, @floatFromInt((o.rgb >> 0) & 0xff)) / 255, 235 | .a = o.a, 236 | }; 237 | } 238 | 239 | pub fn Point2F(x: f32, y: f32) win32.D2D_POINT_2F { 240 | return .{ .x = x, .y = y }; 241 | } 242 | 243 | pub fn Ellipse(center: win32.D2D_POINT_2F, radiusX: f32, radiusY: f32) win32.D2D1_ELLIPSE { 244 | return .{ 245 | .point = center, 246 | .radiusX = radiusX, 247 | .radiusY = radiusY, 248 | }; 249 | } 250 | 251 | // TODO: this is missing 252 | pub fn RenderTargetProperties() win32.D2D1_RENDER_TARGET_PROPERTIES { 253 | return .{ 254 | .type = win32.D2D1_RENDER_TARGET_TYPE_DEFAULT, 255 | .pixelFormat = PixelFormat(), 256 | .dpiX = 0, 257 | .dpiY = 0, 258 | .usage = win32.D2D1_RENDER_TARGET_USAGE_NONE, 259 | .minLevel = win32.D2D1_FEATURE_LEVEL_DEFAULT, 260 | }; 261 | } 262 | 263 | // TODO: this is missing 264 | pub fn PixelFormat() win32.D2D1_PIXEL_FORMAT { 265 | return .{ 266 | .format = win32.DXGI_FORMAT_UNKNOWN, 267 | .alphaMode = win32.D2D1_ALPHA_MODE_UNKNOWN, 268 | }; 269 | } 270 | 271 | // TODO: this is missing 272 | pub fn HwndRenderTargetProperties(hwnd: HWND, size: D2D_SIZE_U) win32.D2D1_HWND_RENDER_TARGET_PROPERTIES { 273 | return .{ 274 | .hwnd = hwnd, 275 | .pixelSize = size, 276 | .presentOptions = win32.D2D1_PRESENT_OPTIONS_NONE, 277 | }; 278 | } 279 | }; 280 | -------------------------------------------------------------------------------- /examples/helloworld-window.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const WINAPI = std.os.windows.WINAPI; 3 | 4 | pub const UNICODE = true; 5 | const win32 = struct { 6 | usingnamespace @import("win32").zig; 7 | usingnamespace @import("win32").foundation; 8 | usingnamespace @import("win32").system.system_services; 9 | usingnamespace @import("win32").ui.windows_and_messaging; 10 | usingnamespace @import("win32").graphics.gdi; 11 | }; 12 | const L = win32.L; 13 | const HWND = win32.HWND; 14 | 15 | fn fatal(comptime fmt: []const u8, args: anytype) noreturn { 16 | if (std.fmt.allocPrintZ(std.heap.page_allocator, fmt, args)) |msg| { 17 | _ = win32.MessageBoxA(null, msg, "Fatal Error", .{}); 18 | } else |e| switch (e) { 19 | error.OutOfMemory => _ = win32.MessageBoxA(null, "Out of memory", "Fatal Error", .{}), 20 | } 21 | std.process.exit(1); 22 | } 23 | 24 | pub export fn wWinMain( 25 | hInstance: win32.HINSTANCE, 26 | _: ?win32.HINSTANCE, 27 | pCmdLine: [*:0]u16, 28 | nCmdShow: u32, 29 | ) callconv(WINAPI) c_int { 30 | _ = pCmdLine; 31 | _ = nCmdShow; 32 | 33 | const CLASS_NAME = L("Sample Window Class"); 34 | const wc = win32.WNDCLASS{ 35 | .style = .{}, 36 | .lpfnWndProc = WindowProc, 37 | .cbClsExtra = 0, 38 | .cbWndExtra = 0, 39 | .hInstance = hInstance, 40 | .hIcon = null, 41 | .hCursor = null, 42 | .hbrBackground = null, 43 | // TODO: this field is not marked as options so we can't use null atm 44 | .lpszMenuName = L("Some Menu Name"), 45 | .lpszClassName = CLASS_NAME, 46 | }; 47 | 48 | if (0 == win32.RegisterClass(&wc)) 49 | fatal("RegisterClass failed, error={}", .{win32.GetLastError()}); 50 | 51 | const hwnd = win32.CreateWindowEx(.{}, CLASS_NAME, L("Hello Windows"), win32.WS_OVERLAPPEDWINDOW, win32.CW_USEDEFAULT, win32.CW_USEDEFAULT, // Position 52 | 400, 200, // Size 53 | null, // Parent window 54 | null, // Menu 55 | hInstance, // Instance handle 56 | null // Additional application data 57 | ) orelse fatal("CreateWindow failed, error={}", .{win32.GetLastError()}); 58 | 59 | _ = win32.ShowWindow(hwnd, .{ .SHOWNORMAL = 1 }); 60 | 61 | var msg: win32.MSG = undefined; 62 | while (win32.GetMessage(&msg, null, 0, 0) != 0) { 63 | _ = win32.TranslateMessage(&msg); 64 | _ = win32.DispatchMessage(&msg); 65 | } 66 | return @intCast(msg.wParam); 67 | } 68 | 69 | fn WindowProc( 70 | hwnd: HWND, 71 | uMsg: u32, 72 | wParam: win32.WPARAM, 73 | lParam: win32.LPARAM, 74 | ) callconv(WINAPI) win32.LRESULT { 75 | switch (uMsg) { 76 | win32.WM_DESTROY => { 77 | win32.PostQuitMessage(0); 78 | return 0; 79 | }, 80 | win32.WM_PAINT => { 81 | var ps: win32.PAINTSTRUCT = undefined; 82 | const hdc = win32.BeginPaint(hwnd, &ps); 83 | _ = win32.FillRect(hdc, &ps.rcPaint, @ptrFromInt(@intFromEnum(win32.COLOR_WINDOW) + 1)); 84 | _ = win32.TextOutA(hdc, 20, 20, "Hello", 5); 85 | _ = win32.EndPaint(hwnd, &ps); 86 | return 0; 87 | }, 88 | else => {}, 89 | } 90 | return win32.DefWindowProc(hwnd, uMsg, wParam, lParam); 91 | } 92 | -------------------------------------------------------------------------------- /examples/helloworld.zig: -------------------------------------------------------------------------------- 1 | const win32 = @import("win32").everything; 2 | 3 | pub export fn WinMainCRTStartup() callconv(@import("std").os.windows.WINAPI) noreturn { 4 | const hStdOut = win32.GetStdHandle(win32.STD_OUTPUT_HANDLE); 5 | if (hStdOut == win32.INVALID_HANDLE_VALUE) { 6 | //std.debug.warn("Error: GetStdHandle failed with {}\n", .{GetLastError()}); 7 | win32.ExitProcess(255); 8 | } 9 | writeAll(hStdOut, "Hello, World!") catch win32.ExitProcess(255); // fail 10 | win32.ExitProcess(0); 11 | } 12 | 13 | fn writeAll(hFile: win32.HANDLE, buffer: []const u8) !void { 14 | var written: usize = 0; 15 | while (written < buffer.len) { 16 | const next_write = @as(u32, @intCast(0xFFFFFFFF & (buffer.len - written))); 17 | var last_written: u32 = undefined; 18 | if (1 != win32.WriteFile(hFile, buffer.ptr + written, next_write, &last_written, null)) { 19 | // TODO: return from GetLastError 20 | return error.WriteFileFailed; 21 | } 22 | written += last_written; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/net.zig: -------------------------------------------------------------------------------- 1 | //! test basic network functionality 2 | const std = @import("std"); 3 | 4 | const win32 = struct { 5 | usingnamespace @import("win32").foundation; 6 | usingnamespace @import("win32").networking.win_sock; 7 | usingnamespace @import("win32").network_management.ip_helper; 8 | }; 9 | 10 | pub fn main() void { 11 | const s = win32.socket(@intFromEnum(win32.AF_INET), win32.SOCK_STREAM, @intFromEnum(win32.IPPROTO_TCP)); 12 | if (s == win32.INVALID_SOCKET) { 13 | std.log.err("socket failed, error={}", .{win32.GetLastError()}); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/opendialog.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | pub const UNICODE = true; 3 | 4 | const WINAPI = @import("std").os.windows.WINAPI; 5 | 6 | const win32 = @import("win32").everything; 7 | 8 | pub const panic = win32.messageBoxThenPanic(.{ .title = "Opendialog Example Panic" }); 9 | 10 | pub export fn wWinMain(__: win32.HINSTANCE, _: ?win32.HINSTANCE, ___: [*:0]u16, ____: u32) callconv(WINAPI) c_int { 11 | _ = __; 12 | _ = ___; 13 | _ = ____; 14 | 15 | { 16 | const hr = win32.CoInitializeEx(null, win32.COINIT{ 17 | .APARTMENTTHREADED = 1, 18 | .DISABLE_OLE1DDE = 1, 19 | }); 20 | if (win32.FAILED(hr)) win32.panicHresult("CoInitiailizeEx", hr); 21 | } 22 | defer win32.CoUninitialize(); 23 | 24 | const dialog = blk: { 25 | var dialog: ?*win32.IFileOpenDialog = undefined; 26 | const hr = win32.CoCreateInstance( 27 | win32.CLSID_FileOpenDialog, 28 | null, 29 | win32.CLSCTX_ALL, 30 | win32.IID_IFileOpenDialog, 31 | @ptrCast(&dialog), 32 | ); 33 | if (win32.FAILED(hr)) win32.panicHresult("create FileOpenDialog", hr); 34 | break :blk dialog.?; 35 | }; 36 | defer _ = dialog.IUnknown.Release(); 37 | 38 | { 39 | const hr = dialog.IModalWindow.Show(null); 40 | if (win32.FAILED(hr)) win32.panicHresult("show dialog", hr); 41 | } 42 | 43 | var pItem: ?*win32.IShellItem = undefined; 44 | { 45 | const hr = dialog.IFileDialog.GetResult(&pItem); 46 | if (win32.FAILED(hr)) win32.panicHresult("get dialog result", hr); 47 | } 48 | defer _ = pItem.?.IUnknown.Release(); 49 | 50 | const file_path = blk: { 51 | var file_path: ?[*:0]u16 = undefined; 52 | const hr = pItem.?.GetDisplayName(win32.SIGDN_FILESYSPATH, &file_path); 53 | if (win32.FAILED(hr)) win32.panicHresult("GetDisplayName", hr); 54 | break :blk file_path.?; 55 | }; 56 | defer win32.CoTaskMemFree(file_path); 57 | _ = win32.MessageBoxW(null, file_path, win32.L("File Path"), win32.MB_OK); 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /examples/tests.zig: -------------------------------------------------------------------------------- 1 | const win32 = @import("win32").everything; 2 | 3 | pub fn main() void { 4 | { 5 | const event = win32.CreateEventW(null, 0, 0, null) orelse @panic("CreateEvent failed"); 6 | defer win32.closeHandle(event); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/testwindow.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const WINAPI = std.os.windows.WINAPI; 3 | 4 | const win32 = @import("win32").everything; 5 | const L = win32.L; 6 | const HWND = win32.HWND; 7 | 8 | pub const panic = win32.messageBoxThenPanic(.{ .title = "Zigwin32 TestWindow Panic" }); 9 | 10 | pub export fn wWinMain( 11 | hInstance: win32.HINSTANCE, 12 | _: ?win32.HINSTANCE, 13 | pCmdLine: [*:0]u16, 14 | nCmdShow: u32, 15 | ) callconv(WINAPI) c_int { 16 | _ = pCmdLine; 17 | _ = nCmdShow; 18 | 19 | std.debug.assert(0x5678 == win32.loword(@as(i32, 0x12345678))); 20 | std.debug.assert(0x1234 == win32.hiword(@as(i32, 0x12345678))); 21 | 22 | const CLASS_NAME = L("ZigTestWindow"); 23 | const wc = win32.WNDCLASSW{ 24 | .style = .{}, 25 | .lpfnWndProc = WindowProc, 26 | .cbClsExtra = 0, 27 | .cbWndExtra = @sizeOf(usize), 28 | .hInstance = hInstance, 29 | .hIcon = null, 30 | .hCursor = null, 31 | .hbrBackground = null, 32 | // TODO: this field is not marked as options so we can't use null atm 33 | .lpszMenuName = L("Some Menu Name"), 34 | .lpszClassName = CLASS_NAME, 35 | }; 36 | 37 | if (0 == win32.RegisterClassW(&wc)) 38 | std.debug.panic("RegisterClass failed, error={}", .{win32.GetLastError()}); 39 | 40 | const hwnd = win32.CreateWindowExW( 41 | .{}, 42 | CLASS_NAME, 43 | L("Zigwin32 Test Window"), 44 | win32.WS_OVERLAPPEDWINDOW, 45 | win32.CW_USEDEFAULT, // x 46 | win32.CW_USEDEFAULT, // y 47 | 0, // width 48 | 0, // height 49 | null, // parent window 50 | null, // menu 51 | hInstance, 52 | null, // Additional application data 53 | ) orelse std.debug.panic("CreateWindow failed, error={}", .{win32.GetLastError()}); 54 | 55 | const dpi = win32.dpiFromHwnd(hwnd); 56 | 57 | // just test the api works 58 | _ = win32.scaleFromDpi(f32, dpi); 59 | _ = win32.scaleFromDpi(f64, dpi); 60 | _ = win32.scaleFromDpi(f128, dpi); 61 | _ = win32.scaleDpi(i32, 100, dpi); 62 | _ = win32.scaleDpi(f32, 100, dpi); 63 | 64 | if (0 == win32.SetWindowPos( 65 | hwnd, 66 | null, 67 | 0, 68 | 0, 69 | win32.scaleDpi(i32, 600, dpi), 70 | win32.scaleDpi(i32, 400, dpi), 71 | .{ .NOMOVE = 1 }, 72 | )) std.debug.panic("SetWindowPos failed, error={}", .{win32.GetLastError()}); 73 | 74 | _ = win32.ShowWindow(hwnd, .{ .SHOWNORMAL = 1 }); 75 | 76 | var msg: win32.MSG = undefined; 77 | while (win32.GetMessageW(&msg, null, 0, 0) != 0) { 78 | _ = win32.TranslateMessage(&msg); 79 | _ = win32.DispatchMessageW(&msg); 80 | } 81 | return @intCast(msg.wParam); 82 | } 83 | 84 | fn WindowProc( 85 | hwnd: HWND, 86 | uMsg: u32, 87 | wParam: win32.WPARAM, 88 | lParam: win32.LPARAM, 89 | ) callconv(WINAPI) win32.LRESULT { 90 | switch (uMsg) { 91 | win32.WM_CREATE => { 92 | if (win32.has_window_longptr) { 93 | std.debug.assert(0 == win32.setWindowLongPtrW(hwnd, 0, 0x1234)); 94 | std.debug.assert(0x1234 == win32.getWindowLongPtrW(hwnd, 0)); 95 | } 96 | }, 97 | win32.WM_DESTROY => { 98 | win32.PostQuitMessage(0); 99 | return 0; 100 | }, 101 | win32.WM_DPICHANGED => win32.invalidateHwnd(hwnd), 102 | win32.WM_PAINT => { 103 | // some of these methods aren't really doing anything, just 104 | // testing various apis. 105 | _ = win32.pointFromLparam(lParam); 106 | 107 | const client_size = win32.getClientSize(hwnd); 108 | _ = client_size; 109 | 110 | const hdc, const ps = win32.beginPaint(hwnd); 111 | defer win32.endPaint(hwnd, &ps); 112 | 113 | { 114 | const tmp_hdc = win32.CreateCompatibleDC(hdc); 115 | win32.deleteDc(tmp_hdc); 116 | } 117 | 118 | { 119 | const brush = win32.createSolidBrush(0xff00ffff); 120 | defer win32.deleteObject(brush); 121 | win32.fillRect(hdc, ps.rcPaint, brush); 122 | } 123 | 124 | var y: i32 = 10; 125 | 126 | { 127 | const msg = win32.L("A window for testing things."); 128 | win32.textOutW(hdc, 20, y, msg); 129 | y += win32.getTextExtentW(hdc, msg).cy; 130 | } 131 | { 132 | const msg = "Testing TextOutA"; 133 | win32.textOutA(hdc, 20, y, msg); 134 | y += win32.getTextExtentA(hdc, msg).cy; 135 | } 136 | 137 | { 138 | var buf: [100]u8 = undefined; 139 | const text = std.fmt.bufPrint( 140 | @ptrCast(&buf), 141 | "dpi is {}", 142 | .{win32.dpiFromHwnd(hwnd)}, 143 | ) catch unreachable; 144 | win32.textOutA(hdc, 20, y, text); 145 | } 146 | 147 | _ = win32.EndPaint(hwnd, &ps); 148 | return 0; 149 | }, 150 | else => {}, 151 | } 152 | return win32.DefWindowProcW(hwnd, uMsg, wParam, lParam); 153 | } 154 | -------------------------------------------------------------------------------- /examples/unionpointers.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const UNICODE = true; 4 | const win32 = struct { 5 | usingnamespace @import("win32").zig; 6 | usingnamespace @import("win32").foundation; 7 | usingnamespace @import("win32").system.system_services; 8 | usingnamespace @import("win32").ui.windows_and_messaging; 9 | usingnamespace @import("win32").graphics.gdi; 10 | }; 11 | const L = win32.L; 12 | const HWND = win32.HWND; 13 | 14 | pub const panic = win32.messageBoxThenPanic(.{ .title = "Unionpointers Example Panic" }); 15 | 16 | pub export fn wWinMain( 17 | hInstance: win32.HINSTANCE, 18 | _: ?win32.HINSTANCE, 19 | pCmdLine: [*:0]u16, 20 | nCmdShow: u32, 21 | ) callconv(std.os.windows.WINAPI) c_int { 22 | _ = pCmdLine; 23 | _ = nCmdShow; 24 | 25 | for (0..20) |atom| { 26 | // we don't care if this works, we're just verifying @ptrFromInt(atom) 27 | // doesn't trigger a runtime panic 28 | if (win32.CreateWindowEx( 29 | .{}, 30 | @ptrFromInt(atom), 31 | L("Test Window"), 32 | .{}, 33 | 0, 34 | 0, 35 | 0, 36 | 0, 37 | null, 38 | null, 39 | hInstance, 40 | null, 41 | )) |hwnd| { 42 | std.log.info("atom={} CreateWindow success", .{atom}); 43 | if (0 == win32.DestroyWindow(hwnd)) win32.panicWin32( 44 | "DestroyWindow", 45 | win32.GetLastError(), 46 | ); 47 | } else { 48 | std.log.info("atom={} CreateWindow failed, error={} (this is fine)", .{ atom, win32.GetLastError() }); 49 | } 50 | } 51 | 52 | { 53 | const CLASS_NAME = L("Sample Window Class"); 54 | const wc = win32.WNDCLASS{ 55 | .style = .{}, 56 | .lpfnWndProc = WindowProc, 57 | .cbClsExtra = 0, 58 | .cbWndExtra = 0, 59 | .hInstance = hInstance, 60 | .hIcon = null, 61 | .hCursor = null, 62 | .hbrBackground = null, 63 | // TODO: this field is not marked as options so we can't use null atm 64 | .lpszMenuName = L("Some Menu Name"), 65 | .lpszClassName = CLASS_NAME, 66 | }; 67 | const atom = win32.RegisterClass(&wc); 68 | if (0 == atom) win32.panicWin32("RegisterClass", win32.GetLastError()); 69 | const hwnd = win32.CreateWindowEx( 70 | .{}, 71 | @ptrFromInt(atom), 72 | L("Test Window"), 73 | .{}, 74 | 0, 75 | 0, 76 | 0, 77 | 0, 78 | null, // Parent window 79 | null, // Menu 80 | hInstance, // Instance handle 81 | null, // Additional application data 82 | ) orelse win32.panicWin32("CreateWindow", win32.GetLastError()); 83 | if (0 == win32.DestroyWindow(hwnd)) win32.panicWin32("DestroyWindow", win32.GetLastError()); 84 | } 85 | 86 | { 87 | // Well this sucks...ptr and const cast? 88 | const old_cursor = win32.SetCursor(@constCast(@ptrCast(win32.IDC_ARROW))); 89 | defer _ = win32.SetCursor(old_cursor); 90 | _ = win32.SetCursor(@constCast(@ptrCast(win32.IDC_IBEAM))); 91 | _ = win32.SetCursor(@constCast(@ptrCast(win32.IDC_WAIT))); 92 | } 93 | 94 | std.log.info("success!", .{}); 95 | return 0; 96 | } 97 | 98 | fn WindowProc( 99 | hwnd: HWND, 100 | uMsg: u32, 101 | wParam: win32.WPARAM, 102 | lParam: win32.LPARAM, 103 | ) callconv(std.os.windows.WINAPI) win32.LRESULT { 104 | switch (uMsg) { 105 | win32.WM_DESTROY => { 106 | win32.PostQuitMessage(0); 107 | return 0; 108 | }, 109 | else => {}, 110 | } 111 | return win32.DefWindowProc(hwnd, uMsg, wParam, lParam); 112 | } 113 | -------------------------------------------------------------------------------- /examples/wasapi.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const log = std.log.info; 4 | 5 | const win32 = struct { 6 | usingnamespace @import("win32").foundation; 7 | usingnamespace @import("win32").media.audio; 8 | usingnamespace @import("win32").media.audio.direct_music; 9 | usingnamespace @import("win32").storage.structured_storage; 10 | usingnamespace @import("win32").system.com; 11 | usingnamespace @import("win32").system.com.structured_storage; 12 | usingnamespace @import("win32").ui.shell.properties_system; 13 | usingnamespace @import("win32").zig; 14 | }; 15 | 16 | pub fn getDefaultDevice() !void { 17 | var enumerator: *win32.IMMDeviceEnumerator = undefined; 18 | 19 | { 20 | const status = win32.CoCreateInstance( 21 | win32.CLSID_MMDeviceEnumerator, 22 | null, 23 | win32.CLSCTX_ALL, 24 | win32.IID_IMMDeviceEnumerator, 25 | @ptrCast(&enumerator), 26 | ); 27 | if (win32.FAILED(status)) { 28 | log("CoCreateInstance FAILED: {d}", .{status}); 29 | return error.Fail; 30 | } 31 | } 32 | defer _ = enumerator.IUnknown.Release(); 33 | 34 | log("pre enumerator: {}", .{enumerator}); 35 | 36 | var device: ?*win32.IMMDevice = undefined; 37 | { 38 | const status = enumerator.GetDefaultAudioEndpoint( 39 | win32.EDataFlow.eCapture, 40 | win32.ERole.eCommunications, 41 | &device, 42 | ); 43 | if (win32.FAILED(status)) { 44 | log("DEVICE STATUS: {d}", .{status}); 45 | return error.Fail; 46 | } 47 | } 48 | defer _ = device.?.IUnknown.Release(); // No such method 49 | 50 | var properties: ?*win32.IPropertyStore = undefined; 51 | { 52 | const status = device.?.OpenPropertyStore(win32.STGM_READ, &properties); 53 | if (win32.FAILED(status)) { 54 | log("DEVICE PROPS: {d}", .{status}); 55 | return error.Fail; 56 | } 57 | } 58 | 59 | var count: u32 = 0; 60 | { 61 | const status = properties.?.GetCount(&count); 62 | if (win32.FAILED(status)) { 63 | log("GetCount failed: {d}", .{status}); 64 | return error.Fail; 65 | } 66 | } 67 | 68 | var index: u32 = 0; 69 | while (index < count - 1) : (index += 1) { 70 | var propKey: win32.PROPERTYKEY = undefined; 71 | 72 | log("index: {d}", .{index}); 73 | { 74 | const status = properties.?.GetAt(index, &propKey); 75 | if (win32.FAILED(status)) { 76 | log("Failed to getAt {x}", .{status}); 77 | return error.Fail; 78 | } 79 | } 80 | log("Looping propeties with: {}", .{propKey}); 81 | 82 | var propValue: win32.PROPVARIANT = undefined; 83 | // The following line fails with a stack trace (pasted below) 84 | const status = properties.?.GetValue(&propKey, &propValue); 85 | _ = status; 86 | } 87 | 88 | // log("post device: {s}", .{device.?.IMMDevice_GetId()}); 89 | } 90 | 91 | pub fn main() !u8 { 92 | const config_value = win32.COINIT{ .APARTMENTTHREADED = 1, .DISABLE_OLE1DDE = 1 }; 93 | { 94 | _ = config_value; 95 | const status = win32.CoInitialize(null); // CoInitializeEx(null, @intToEnum(COINIT, config_value)); 96 | if (win32.FAILED(status)) { 97 | log("CoInitialize FAILED: {d}", .{status}); 98 | return error.Fail; 99 | } 100 | } 101 | // TODO: I'm not sure if we should do this or not 102 | //defer CoUninitialize(); 103 | 104 | try getDefaultDevice(); 105 | return 0; 106 | } 107 | -------------------------------------------------------------------------------- /examples/win32.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true/pm 6 | PerMonitorV2 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /extra.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Format 3 | # -------------------------------------------------------------------------------- 4 | # : 5 | # Constant 6 | # Function + 7 | # 8 | # : 9 | # '(' + ')' 10 | # : 11 | # UnionPointer 12 | # NotNull '=' [01]+ 13 | # 14 | Networking.WinSock Function accept return(NotNull=1) 15 | Networking.WinSock Function shutdown return(NotNull=1) 16 | Networking.WinSock Function socket return(NotNull=1) 17 | Networking.WinSock Function WSASocketA return(NotNull=1) 18 | Networking.WinSock Function WSASocketW return(NotNull=1) 19 | 20 | Storage.FileSystem Function CreateFileA return(NotNull=1) 21 | Storage.FileSystem Function CreateFileW return(NotNull=1) 22 | 23 | System.Com Function CoCreateInstance riid(NotNull=1) 24 | System.Com Function IUnknown.QueryInterface riid(NotNull=1) 25 | 26 | System.Console Function GetStdHandle return(NotNull=1) 27 | 28 | System.Diagnostics.ToolHelp Function CreateToolhelp32Snapshot return(NotNull=1) 29 | 30 | System.LibraryLoader Function ENUMRESLANGPROCW lpType(UnionPointer) 31 | System.LibraryLoader Function ENUMRESNAMEPROCW lpType(UnionPointer) 32 | System.LibraryLoader Function EnumResourceLanguagesW lpType(UnionPointer) 33 | System.LibraryLoader Function EnumResourceLanguagesExW lpType(UnionPointer) 34 | System.LibraryLoader Function EnumResourceNamesW lpType(UnionPointer) 35 | System.LibraryLoader Function EnumResourceNamesExW lpType(UnionPointer) 36 | System.LibraryLoader Function FindResourceW lpType(UnionPointer) lpName(UnionPointer) 37 | System.LibraryLoader Function FindResourceExW lpType(UnionPointer) lpName(UnionPointer) 38 | System.LibraryLoader Function UpdateResourceW lpType(UnionPointer) lpName(UnionPointer) 39 | 40 | System.Mailslots Function CreateMailslotA return(NotNull=1) 41 | System.Mailslots Function CreateMailslotW return(NotNull=1) 42 | 43 | System.Pipes Function CreateNamedPipeA return(NotNull=1) 44 | System.Pipes Function CreateNamedPipeW return(NotNull=1) 45 | 46 | UI.Controls Constant TD_WARNING_ICON(UnionPointer) 47 | UI.Controls Constant TD_ERROR_ICON(UnionPointer) 48 | UI.Controls Constant TD_INFORMATION_ICON(UnionPointer) 49 | UI.Controls Constant TD_SHIELD_ICON(UnionPointer) 50 | 51 | UI.WindowsAndMessaging Constant IDC_ARROW(UnionPointer) 52 | UI.WindowsAndMessaging Constant IDC_IBEAM(UnionPointer) 53 | UI.WindowsAndMessaging Constant IDC_WAIT(UnionPointer) 54 | UI.WindowsAndMessaging Constant IDC_CROSS(UnionPointer) 55 | UI.WindowsAndMessaging Constant IDC_UPARROW(UnionPointer) 56 | UI.WindowsAndMessaging Constant IDC_SIZE(UnionPointer) 57 | UI.WindowsAndMessaging Constant IDC_ICON(UnionPointer) 58 | UI.WindowsAndMessaging Constant IDC_SIZENWSE(UnionPointer) 59 | UI.WindowsAndMessaging Constant IDC_SIZENESW(UnionPointer) 60 | UI.WindowsAndMessaging Constant IDC_SIZEWE(UnionPointer) 61 | UI.WindowsAndMessaging Constant IDC_SIZENS(UnionPointer) 62 | UI.WindowsAndMessaging Constant IDC_SIZEALL(UnionPointer) 63 | UI.WindowsAndMessaging Constant IDC_NO(UnionPointer) 64 | UI.WindowsAndMessaging Constant IDC_HAND(UnionPointer) 65 | UI.WindowsAndMessaging Constant IDC_APPSTARTING(UnionPointer) 66 | UI.WindowsAndMessaging Constant IDC_HELP(UnionPointer) 67 | UI.WindowsAndMessaging Constant IDC_PIN(UnionPointer) 68 | UI.WindowsAndMessaging Constant IDC_PERSON(UnionPointer) 69 | UI.WindowsAndMessaging Constant IDI_APPLICATION(UnionPointer) 70 | UI.WindowsAndMessaging Constant IDI_HAND(UnionPointer) 71 | UI.WindowsAndMessaging Constant IDI_QUESTION(UnionPointer) 72 | UI.WindowsAndMessaging Constant IDI_EXCLAMATION(UnionPointer) 73 | UI.WindowsAndMessaging Constant IDI_ASTERISK(UnionPointer) 74 | UI.WindowsAndMessaging Constant IDI_WINLOGO(UnionPointer) 75 | UI.WindowsAndMessaging Constant IDI_SHIELD(UnionPointer) 76 | UI.WindowsAndMessaging Constant RT_VERSION(UnionPointer) 77 | UI.WindowsAndMessaging Constant RT_DLGINCLUDE(UnionPointer) 78 | UI.WindowsAndMessaging Constant RT_PLUGPLAY(UnionPointer) 79 | UI.WindowsAndMessaging Constant RT_VXD(UnionPointer) 80 | UI.WindowsAndMessaging Constant RT_ANICURSOR(UnionPointer) 81 | UI.WindowsAndMessaging Constant RT_ANIICON(UnionPointer) 82 | UI.WindowsAndMessaging Constant RT_HTML(UnionPointer) 83 | UI.WindowsAndMessaging Constant RT_CURSOR(UnionPointer) 84 | UI.WindowsAndMessaging Constant RT_BITMAP(UnionPointer) 85 | UI.WindowsAndMessaging Constant RT_ICON(UnionPointer) 86 | UI.WindowsAndMessaging Constant RT_MENU(UnionPointer) 87 | UI.WindowsAndMessaging Constant RT_DIALOG(UnionPointer) 88 | UI.WindowsAndMessaging Constant RT_FONTDIR(UnionPointer) 89 | UI.WindowsAndMessaging Constant RT_FONT(UnionPointer) 90 | UI.WindowsAndMessaging Constant RT_ACCELERATOR(UnionPointer) 91 | UI.WindowsAndMessaging Constant RT_MESSAGETABLE(UnionPointer) 92 | 93 | UI.WindowsAndMessaging Function CreateWindowExA lpClassName(UnionPointer) 94 | UI.WindowsAndMessaging Function CreateWindowExW lpClassName(UnionPointer) 95 | UI.WindowsAndMessaging Function UnregisterClassA lpClassName(UnionPointer) 96 | UI.WindowsAndMessaging Function UnregisterClassW lpClassName(UnionPointer) 97 | UI.WindowsAndMessaging Function LoadCursorW lpCursorName(UnionPointer) 98 | UI.WindowsAndMessaging Function LoadIconW lpIconName(UnionPointer) 99 | UI.WindowsAndMessaging Function LoadImageA name(UnionPointer) 100 | UI.WindowsAndMessaging Function LoadImageW name(UnionPointer) 101 | UI.WindowsAndMessaging Function SENDASYNCPROC param0(NotNull=1) 102 | UI.WindowsAndMessaging Function DLGPROC param0(NotNull=1) 103 | UI.WindowsAndMessaging Function PROPENUMPROCA param0(NotNull=1) 104 | UI.WindowsAndMessaging Function PROPENUMPROCW param0(NotNull=1) 105 | UI.WindowsAndMessaging Function PROPENUMPROCEXA param0(NotNull=1) 106 | UI.WindowsAndMessaging Function PROPENUMPROCEXW param0(NotNull=1) 107 | UI.WindowsAndMessaging Function TIMERPROC param0(NotNull=1) 108 | UI.WindowsAndMessaging Function WNDENUMPROC param0(NotNull=1) 109 | UI.WindowsAndMessaging Function WNDPROC param0(NotNull=1) 110 | -------------------------------------------------------------------------------- /src/StringPool.zig: -------------------------------------------------------------------------------- 1 | /// Takes an allocator and manages a set of strings. 2 | /// Every string in the pool is owned by the pool. 3 | const StringPool = @This(); 4 | 5 | const std = @import("std"); 6 | const StringHashMap = std.hash_map.StringHashMap; 7 | 8 | pub const Val = struct { 9 | slice: []const u8, 10 | pub fn eql(self: Val, other: Val) bool { 11 | return self.slice.ptr == other.slice.ptr; 12 | } 13 | pub fn format( 14 | self: @This(), 15 | comptime fmt: []const u8, 16 | options: std.fmt.FormatOptions, 17 | writer: anytype, 18 | ) !void { 19 | _ = fmt; 20 | _ = options; 21 | return writer.writeAll(self.slice); 22 | } 23 | }; 24 | 25 | allocator: std.mem.Allocator, 26 | map: StringHashMap(Val), 27 | pub fn init(allocator: std.mem.Allocator) @This() { 28 | return @This(){ 29 | .allocator = allocator, 30 | .map = StringHashMap(Val).init(allocator), 31 | }; 32 | } 33 | 34 | pub fn deinit(self: *StringPool) void { 35 | var it = self.map.iterator(); 36 | while (it.next()) |entry| { 37 | self.allocator.free(entry.value_ptr.slice); 38 | } 39 | self.map.deinit(); 40 | } 41 | 42 | /// If the pool already contains this a string that matches the contents 43 | /// of the given string, return the existing string from this pool. 44 | /// Otherwise, create a copy of this string, add it to the pool and return 45 | /// the new copy. 46 | pub fn add(self: *@This(), s: []const u8) error{OutOfMemory}!Val { 47 | if (self.map.get(s)) |entry| { 48 | return entry; 49 | } 50 | const newString = try self.allocator.alloc(u8, s.len); 51 | @memcpy(newString, s); 52 | const val = Val{ .slice = newString }; 53 | _ = try self.map.put(newString, val); 54 | return val; 55 | } 56 | 57 | pub fn addFormatted(self: *@This(), comptime fmt: []const u8, args: anytype) error{OutOfMemory}!Val { 58 | const s = try std.fmt.allocPrint(self.allocator, fmt, args); 59 | errdefer self.allocator.free(s); 60 | 61 | const val = try self.add(s); 62 | if (val.slice.ptr != s.ptr) { 63 | self.allocator.free(s); 64 | } 65 | return val; 66 | } 67 | 68 | pub const HashContext = struct { 69 | pub fn hash(self: HashContext, s: Val) u64 { 70 | _ = self; 71 | return std.hash.Wyhash.hash(0, @as([*]const u8, @ptrCast(&s.slice.ptr))[0..@sizeOf(usize)]); 72 | } 73 | pub fn eql(self: HashContext, a: Val, b: Val) bool { 74 | _ = self; 75 | return a.slice.ptr == b.slice.ptr; 76 | } 77 | }; 78 | pub const ArrayHashContext = struct { 79 | pub fn hash(self: @This(), s: Val) u32 { 80 | _ = self; 81 | return @as(u32, @truncate(std.hash.Wyhash.hash(0, @as([*]const u8, @ptrCast(&s.slice.ptr))[0..@sizeOf(usize)]))); 82 | } 83 | pub fn eql(self: @This(), a: Val, b: Val, index: usize) bool { 84 | _ = self; 85 | _ = index; 86 | return a.slice.ptr == b.slice.ptr; 87 | } 88 | }; 89 | 90 | pub fn HashMap(comptime V: type) type { 91 | return std.HashMap(Val, V, HashContext, std.hash_map.default_max_load_percentage); 92 | } 93 | pub fn HashMapUnmanaged(comptime V: type) type { 94 | return std.HashMapUnmanaged(Val, V, HashContext, std.hash_map.default_max_load_percentage); 95 | } 96 | 97 | pub fn asciiLessThanIgnoreCase(_: void, lhs: Val, rhs: Val) bool { 98 | return std.ascii.lessThanIgnoreCase(lhs.slice, rhs.slice); 99 | } 100 | 101 | test "stringpool" { 102 | var pool = StringPool.init(std.testing.allocator); 103 | defer pool.deinit(); 104 | const s = try pool.add("hello"); 105 | { 106 | var buf: [5]u8 = undefined; 107 | std.mem.copy(u8, buf[0..], "hello"); 108 | const s2 = try pool.add(buf[0..]); 109 | try std.testing.expect(s.slice.ptr == s2.slice.ptr); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/cameltosnake.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | // TODO: should this be in std lib? 4 | pub fn camelToSnakeAlloc(a: std.mem.Allocator, camel: []const u8) ![]u8 { 5 | const snake = try a.alloc(u8, camelToSnakeLen(camel)); 6 | errdefer a.free(snake); 7 | camelToSnake(snake, camel); 8 | return snake; 9 | } 10 | 11 | pub fn camelToSnakeLen(camel: []const u8) usize { 12 | var snake_len = camel.len; 13 | { 14 | var i: usize = 1; 15 | while (i < camel.len) : (i += 1) { 16 | if (std.ascii.isUpper(camel[i]) and std.ascii.isLower(camel[i - 1])) { 17 | snake_len += 1; 18 | } 19 | } 20 | } 21 | return snake_len; 22 | } 23 | 24 | pub fn camelToSnake(snake: []u8, camel: []const u8) void { 25 | if (camel.len == 0) return; 26 | 27 | snake[0] = asciiCharToLower(camel[0]); 28 | 29 | var snake_index: usize = 1; 30 | { 31 | var i: usize = 1; 32 | while (i < camel.len) : (i += 1) { 33 | const is_upper = std.ascii.isUpper(camel[i]); 34 | if (is_upper and std.ascii.isLower(camel[i - 1])) { 35 | snake[snake_index] = '_'; 36 | snake_index += 1; 37 | } 38 | snake[snake_index] = asciiCharToLower(camel[i]); 39 | snake_index += 1; 40 | } 41 | } 42 | std.debug.assert(snake_index == snake.len); 43 | } 44 | 45 | fn testCamelToSnake(camel: []const u8, expected_snake: []const u8) void { 46 | const actual_snake = camelToSnakeAlloc(std.testing.allocator, camel) catch @panic("out of memory"); 47 | defer std.testing.allocator.free(actual_snake); 48 | std.debug.print("test '{s}' expect '{s}'\n", .{ camel, expected_snake }); 49 | std.debug.print("actual '{s}'\n", .{actual_snake}); 50 | std.testing.expect(std.mem.eql(u8, expected_snake, actual_snake)); 51 | } 52 | 53 | test "cameltosnake" { 54 | testCamelToSnake("", ""); 55 | testCamelToSnake("a", "a"); 56 | testCamelToSnake("A", "a"); 57 | 58 | testCamelToSnake("abc", "abc"); 59 | 60 | testCamelToSnake("Abc", "abc"); 61 | testCamelToSnake("aBc", "a_bc"); 62 | testCamelToSnake("abC", "ab_c"); 63 | 64 | testCamelToSnake("AbC", "ab_c"); 65 | testCamelToSnake("aBC", "a_bc"); 66 | 67 | testCamelToSnake("ABC", "abc"); 68 | } 69 | 70 | // TODO: this should already exist somewhere? 71 | fn asciiCharToLower(c: u8) u8 { 72 | return c + (if (std.ascii.isUpper(c)) @as(u8, 'a' - 'A') else 0); 73 | } 74 | -------------------------------------------------------------------------------- /src/common.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const metadata = @import("metadata.zig"); 3 | 4 | const path_sep = std.fs.path.sep_str; 5 | 6 | pub fn oom(e: error{OutOfMemory}) noreturn { 7 | @panic(@errorName(e)); 8 | } 9 | 10 | pub fn fatal(comptime fmt: []const u8, args: anytype) noreturn { 11 | std.log.err(fmt, args); 12 | std.process.exit(0xff); 13 | } 14 | 15 | pub fn getcwd(a: std.mem.Allocator) ![]u8 { 16 | var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; 17 | const path = try std.os.getcwd(&path_buf); 18 | const path_allocated = try a.alloc(u8, path.len); 19 | @memcpy(path_allocated, path); 20 | return path_allocated; 21 | } 22 | 23 | pub fn readApiList(api_dir: std.fs.Dir, api_list: *std.ArrayList([]const u8)) !void { 24 | var dir_it = api_dir.iterate(); 25 | while (try dir_it.next()) |entry| { 26 | if (!std.mem.endsWith(u8, entry.name, ".json")) { 27 | std.log.err("expected all files to end in '.json' but got '{s}'\n", .{entry.name}); 28 | return error.AlreadyReported; 29 | } 30 | try api_list.append(try api_list.allocator.dupe(u8, entry.name)); 31 | } 32 | } 33 | 34 | pub fn asciiLessThanIgnoreCase(_: void, lhs: []const u8, rhs: []const u8) bool { 35 | return std.ascii.lessThanIgnoreCase(lhs, rhs); 36 | } 37 | 38 | fn SliceFormatter(comptime T: type, comptime spec: []const u8) type { 39 | return struct { 40 | slice: []const T, 41 | pub fn format( 42 | self: @This(), 43 | comptime fmt: []const u8, 44 | options: std.fmt.FormatOptions, 45 | writer: anytype, 46 | ) !void { 47 | _ = fmt; 48 | _ = options; 49 | var first: bool = true; 50 | for (self.slice) |e| { 51 | if (first) { 52 | first = false; 53 | } else { 54 | try writer.writeAll(", "); 55 | } 56 | try std.fmt.format(writer, "{" ++ spec ++ "}", .{e}); 57 | } 58 | } 59 | }; 60 | } 61 | pub fn formatSliceT(comptime T: type, comptime spec: []const u8, slice: []const T) SliceFormatter(T, spec) { 62 | return .{ .slice = slice }; 63 | } 64 | // TODO: implement this 65 | //pub fn formatSlice(slice: anytype) SliceFormatter(T) { 66 | // return .{ .slice = slice }; 67 | //} 68 | 69 | pub fn jsonPanic() noreturn { 70 | @panic("an assumption about the json format was violated"); 71 | } 72 | pub fn jsonPanicMsg(comptime msg: []const u8, args: anytype) noreturn { 73 | std.debug.panic("an assumption about the json format was violated: " ++ msg, args); 74 | } 75 | 76 | pub fn jsonEnforce(cond: bool) void { 77 | if (!cond) { 78 | jsonPanic(); 79 | } 80 | } 81 | pub fn jsonEnforceMsg(cond: bool, comptime msg: []const u8, args: anytype) void { 82 | if (!cond) { 83 | jsonPanicMsg(msg, args); 84 | } 85 | } 86 | 87 | const JsonFormatter = struct { 88 | value: std.json.Value, 89 | pub fn format( 90 | self: JsonFormatter, 91 | comptime fmt: []const u8, 92 | options: std.fmt.FormatOptions, 93 | writer: anytype, 94 | ) !void { 95 | _ = fmt; 96 | _ = options; 97 | switch (self.value) { 98 | // avoid issues where std.json adds quotes to big numbers 99 | // (potential fix: https://github.com/ziglang/zig/pull/16707) 100 | .integer => |i| try std.fmt.formatIntValue(i, "", .{}, writer), 101 | .number_string => |s| try writer.writeAll(s), 102 | else => try std.json.stringify(self.value, .{}, writer), 103 | } 104 | } 105 | }; 106 | pub fn fmtJson(value: anytype) JsonFormatter { 107 | if (@TypeOf(value) == std.json.ObjectMap) { 108 | return .{ .value = .{ .object = value } }; 109 | } 110 | if (@TypeOf(value) == std.json.Array) { 111 | return .{ .value = .{ .array = value } }; 112 | } 113 | if (@TypeOf(value) == []std.json.Value) { 114 | return .{ .value = .{ .array = std.json.Array{ .items = value, .capacity = value.len, .allocator = undefined } } }; 115 | } 116 | return .{ .value = value }; 117 | } 118 | 119 | pub const ComInterface = struct { 120 | name: []const u8, 121 | api: []const u8, 122 | pub fn format( 123 | self: ComInterface, 124 | comptime fmt: []const u8, 125 | options: std.fmt.FormatOptions, 126 | writer: anytype, 127 | ) !void { 128 | _ = fmt; 129 | _ = options; 130 | try writer.print( 131 | "{{\"Kind\":\"ApiRef\",\"Name\":\"{s}\",\"TargetKind\":\"Com\",\"Api\":\"{s}\",\"Parents\":[]}}", 132 | .{ self.name, self.api }, 133 | ); 134 | } 135 | }; 136 | 137 | pub fn getComInterface(type_ref: metadata.TypeRef) ComInterface { 138 | const api_ref = switch (type_ref) { 139 | .ApiRef => |r| r, 140 | else => jsonPanic(), 141 | }; 142 | jsonEnforce(api_ref.TargetKind == .Com); 143 | jsonEnforce(api_ref.Parents.len == 0); 144 | return .{ 145 | .api = api_ref.Api, 146 | .name = api_ref.Name, 147 | }; 148 | } 149 | 150 | // TODO: this should be in std, maybe method on HashMap? 151 | pub fn allocMapValues(alloc: std.mem.Allocator, comptime T: type, map: anytype) ![]T { 152 | var values = try alloc.alloc(T, map.count()); 153 | errdefer alloc.free(values); 154 | { 155 | var i: usize = 0; 156 | var it = map.iterator(); 157 | while (it.next()) |entry| : (i += 1) { 158 | values[i] = entry.value_ptr.*; 159 | } 160 | std.debug.assert(i == map.count()); 161 | } 162 | return values; 163 | } 164 | -------------------------------------------------------------------------------- /src/diff.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn fatal(comptime fmt: []const u8, args: anytype) noreturn { 4 | std.log.err(fmt, args); 5 | std.process.exit(0xff); 6 | } 7 | 8 | fn usage(zigbuild: bool) !void { 9 | const options = "[--nofetch|-n]"; 10 | const stderr = std.io.getStdErr().writer(); 11 | const parts: struct { 12 | diffrepo: []const u8, 13 | generated: []const u8, 14 | } = blk: { 15 | if (zigbuild) { 16 | try stderr.print("Usage: zig build diff -- {s}\n", .{options}); 17 | break :blk .{ 18 | .diffrepo = "the 'diffrepo' subdirectory", 19 | .generated = "the latest generated release", 20 | }; 21 | } 22 | try stderr.print("Usage: diff.exe {s} DIFF_REPO GENERATED_PATH\n", .{options}); 23 | break :blk .{ 24 | .diffrepo = "DIFF_REPO", 25 | .generated = "GENERATED_PATH", 26 | }; 27 | }; 28 | try stderr.print( 29 | \\ 30 | \\Updates {s} and installs {s} 31 | \\on top for diffing purposes. 32 | \\ 33 | \\ --nofetch | -n Disable fetching the latest 'main' branch for 34 | \\ {0s}. 35 | \\ 36 | , 37 | .{ parts.diffrepo, parts.generated }, 38 | ); 39 | } 40 | 41 | pub fn main() !void { 42 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); 43 | const arena = arena_instance.allocator(); 44 | 45 | var opt: struct { 46 | // indicates user is running from 'zig build diff' which affects 47 | // the usage output. 48 | zigbuild: bool = false, 49 | fetch: bool = true, 50 | } = .{}; 51 | 52 | const cmd_pos_args = blk: { 53 | const all_args = try std.process.argsAlloc(arena); 54 | // don't care about freeing args 55 | var pos_arg_count: usize = 0; 56 | var arg_index: usize = 1; 57 | while (arg_index < all_args.len) { 58 | const arg = all_args[arg_index]; 59 | arg_index += 1; 60 | if (!std.mem.startsWith(u8, arg, "-")) { 61 | all_args[pos_arg_count] = arg; 62 | pos_arg_count += 1; 63 | } else if (std.mem.eql(u8, arg, "--zigbuild")) { 64 | opt.zigbuild = true; 65 | } else if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) { 66 | try usage(opt.zigbuild); 67 | std.process.exit(1); 68 | } else if (std.mem.eql(u8, arg, "--nofetch") or std.mem.eql(u8, arg, "-n")) { 69 | opt.fetch = false; 70 | } else fatal("unknown cmline option '{s}'", .{arg}); 71 | } 72 | break :blk all_args[0..pos_arg_count]; 73 | }; 74 | if (cmd_pos_args.len == 0) { 75 | try usage(opt.zigbuild); 76 | std.process.exit(1); 77 | } 78 | if (cmd_pos_args.len != 2) fatal("expected 2 positional cmdline arguments but got {}", .{cmd_pos_args.len}); 79 | const diff_repo = cmd_pos_args[0]; 80 | const generated_path = cmd_pos_args[1]; 81 | 82 | try makeRepo(diff_repo); 83 | 84 | if (opt.fetch) { 85 | try run(arena, "git fetch", &.{ "git", "-C", diff_repo, "fetch", "origin", "main" }); 86 | } 87 | try run(arena, "git clean", &.{ "git", "-C", diff_repo, "clean", "-xffd" }); 88 | try run(arena, "git reset", &.{ "git", "-C", diff_repo, "reset", "--hard" }); 89 | try run(arena, "git checkout", &.{ "git", "-C", diff_repo, "checkout", "origin/main" }); 90 | try run(arena, "git clean", &.{ "git", "-C", diff_repo, "clean", "-xffd" }); 91 | 92 | { 93 | var dir = try std.fs.cwd().openDir(diff_repo, .{ .iterate = true }); 94 | defer dir.close(); 95 | var it = dir.iterate(); 96 | while (try it.next()) |entry| { 97 | if (std.mem.eql(u8, entry.name, ".git")) continue; 98 | std.log.info("rm -rf '{s}/{s}'", .{ diff_repo, entry.name }); 99 | try dir.deleteTree(entry.name); 100 | } 101 | } 102 | 103 | std.log.info("copying generated files from '{s}'...", .{generated_path}); 104 | try copyDir( 105 | std.fs.cwd(), 106 | generated_path, 107 | std.fs.cwd(), 108 | diff_repo, 109 | ); 110 | 111 | try run(arena, "git status", &.{ "git", "-C", diff_repo, "status" }); 112 | } 113 | 114 | pub fn makeRepo(path: []const u8) !void { 115 | std.fs.cwd().access(path, .{}) catch |err| switch (err) { 116 | error.FileNotFound => try gitInit(path), 117 | else => |e| return e, 118 | }; 119 | } 120 | fn gitInit(repo: []const u8) !void { 121 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 122 | defer arena.deinit(); 123 | const allocator = arena.allocator(); 124 | 125 | const tmp_repo = try std.mem.concat(allocator, u8, &.{ repo, ".initializing" }); 126 | defer allocator.free(tmp_repo); 127 | try std.fs.cwd().deleteTree(tmp_repo); 128 | try std.fs.cwd().makeDir(tmp_repo); 129 | try run(allocator, "git init", &.{ 130 | "git", 131 | "-C", 132 | tmp_repo, 133 | "init", 134 | }); 135 | const zigwin32_repo_url = "https://github.com/marlersoft/zigwin32"; 136 | try run(allocator, "git init", &.{ 137 | "git", 138 | "-C", 139 | tmp_repo, 140 | "remote", 141 | "add", 142 | "origin", 143 | zigwin32_repo_url, 144 | }); 145 | try std.fs.cwd().rename(tmp_repo, repo); 146 | } 147 | 148 | const FormatArgv = struct { 149 | argv: []const []const u8, 150 | pub fn format( 151 | self: @This(), 152 | comptime fmt: []const u8, 153 | options: std.fmt.FormatOptions, 154 | writer: anytype, 155 | ) !void { 156 | _ = fmt; 157 | _ = options; 158 | var prefix: []const u8 = ""; 159 | for (self.argv) |arg| { 160 | try writer.print("{s}{s}", .{ prefix, arg }); 161 | prefix = " "; 162 | } 163 | } 164 | }; 165 | pub fn fmtArgv(argv: []const []const u8) FormatArgv { 166 | return .{ .argv = argv }; 167 | } 168 | 169 | pub fn childProcFailed(term: std.process.Child.Term) bool { 170 | return switch (term) { 171 | .Exited => |code| code != 0, 172 | .Signal => true, 173 | .Stopped => true, 174 | .Unknown => true, 175 | }; 176 | } 177 | const FormatTerm = struct { 178 | term: std.process.Child.Term, 179 | pub fn format( 180 | self: @This(), 181 | comptime fmt: []const u8, 182 | options: std.fmt.FormatOptions, 183 | writer: anytype, 184 | ) !void { 185 | _ = fmt; 186 | _ = options; 187 | switch (self.term) { 188 | .Exited => |code| try writer.print("exited with code {}", .{code}), 189 | .Signal => |sig| try writer.print("exited with signal {}", .{sig}), 190 | .Stopped => |sig| try writer.print("stopped with signal {}", .{sig}), 191 | .Unknown => |sig| try writer.print("terminated abnormally with signal {}", .{sig}), 192 | } 193 | } 194 | }; 195 | pub fn fmtTerm(term: std.process.Child.Term) FormatTerm { 196 | return .{ .term = term }; 197 | } 198 | 199 | pub fn run( 200 | allocator: std.mem.Allocator, 201 | name: []const u8, 202 | argv: []const []const u8, 203 | ) !void { 204 | var child = std.process.Child.init(argv, allocator); 205 | std.log.info("{}", .{fmtArgv(child.argv)}); 206 | try child.spawn(); 207 | const term = try child.wait(); 208 | if (childProcFailed(term)) { 209 | fatal("{s} {}", .{ name, fmtTerm(term) }); 210 | } 211 | } 212 | 213 | fn copyDir( 214 | src_parent_dir: std.fs.Dir, 215 | src_path: []const u8, 216 | dst_parent_dir: std.fs.Dir, 217 | dst_path: []const u8, 218 | ) !void { 219 | var dst_dir = try dst_parent_dir.openDir(dst_path, .{}); 220 | defer dst_dir.close(); 221 | var src_dir = try src_parent_dir.openDir(src_path, .{ .iterate = true }); 222 | defer src_dir.close(); 223 | var it = src_dir.iterate(); 224 | while (try it.next()) |entry| { 225 | switch (entry.kind) { 226 | .directory => { 227 | try dst_dir.makeDir(entry.name); 228 | try copyDir(src_dir, entry.name, dst_dir, entry.name); 229 | }, 230 | .file => try src_dir.copyFile( 231 | entry.name, 232 | dst_dir, 233 | entry.name, 234 | .{}, 235 | ), 236 | else => |kind| fatal("unsupported file kind '{s}'", .{@tagName(kind)}), 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/extra.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const StringPool = @import("StringPool.zig"); 4 | 5 | pub const Root = StringPool.HashMapUnmanaged(Api); 6 | 7 | pub const Functions = StringPool.HashMapUnmanaged(Function); 8 | pub const Constants = StringPool.HashMapUnmanaged(TypeModifier); 9 | pub const Api = struct { 10 | functions: Functions = .{}, 11 | constants: Constants = .{}, 12 | }; 13 | pub const NullModifier = u3; 14 | pub const TypeModifier = struct { 15 | union_pointer: bool = false, 16 | null_modifier: NullModifier = 0, 17 | }; 18 | const Function = struct { 19 | ret: ?TypeModifier = null, 20 | params: StringPool.HashMapUnmanaged(TypeModifier) = .{}, 21 | }; 22 | 23 | pub fn oom(e: error{OutOfMemory}) noreturn { 24 | @panic(@errorName(e)); 25 | } 26 | fn parseError(filename: []const u8, lineno: u32, comptime fmt: []const u8, args: anytype) noreturn { 27 | std.io.getStdErr().writer().print("{s}:{}: parse error: ", .{ filename, lineno }) catch |e| std.debug.panic("write to stderr failed with {s}", .{@errorName(e)}); 28 | std.io.getStdErr().writer().print(fmt, args) catch |e| std.debug.panic("write to stderr failed with {s}", .{@errorName(e)}); 29 | std.process.exit(0xff); 30 | } 31 | 32 | pub fn read( 33 | api_name_set: StringPool.HashMapUnmanaged(void), 34 | string_pool: *StringPool, 35 | allocator: std.mem.Allocator, 36 | filename: []const u8, 37 | content: []const u8, 38 | ) Root { 39 | var root: Root = .{}; 40 | 41 | const return_id = string_pool.add("return") catch |e| oom(e); 42 | 43 | var line_it = std.mem.splitAny(u8, content, "\r\n"); 44 | var lineno: u32 = 0; 45 | while (line_it.next()) |line| { 46 | lineno += 1; 47 | 48 | var field_it = std.mem.tokenizeScalar(u8, line, ' '); 49 | const first_field = field_it.next() orelse continue; 50 | if (first_field.len == 0 or first_field[0] == '#') continue; 51 | const api_name = string_pool.add(first_field) catch |e| oom(e); 52 | if (api_name_set.get(api_name)) |_| {} else parseError(filename, lineno, "unknown api '{}'", .{api_name}); 53 | 54 | const api = blk: { 55 | const entry = root.getOrPut(allocator, api_name) catch |e| oom(e); 56 | if (!entry.found_existing) { 57 | entry.value_ptr.* = .{}; 58 | } 59 | break :blk entry.value_ptr; 60 | }; 61 | 62 | const kind = field_it.next() orelse parseError(filename, lineno, "missing kind specifier", .{}); 63 | 64 | if (std.mem.eql(u8, kind, "Function")) { 65 | const func_name = string_pool.add( 66 | field_it.next() orelse parseError(filename, lineno, "missing function name", .{}), 67 | ) catch |e| oom(e); 68 | 69 | const func = blk: { 70 | const entry = api.functions.getOrPut(allocator, func_name) catch |e| oom(e); 71 | if (entry.found_existing) parseError(filename, lineno, "duplicate function '{s} {s}'", .{ api_name, func_name }); 72 | entry.value_ptr.* = .{}; 73 | break :blk entry.value_ptr; 74 | }; 75 | 76 | var next_index = field_it.index; 77 | var mod_count: u32 = 0; 78 | while (parseNamedModifier(filename, lineno, line, next_index)) |named_mod| { 79 | mod_count += 1; 80 | const name = string_pool.add(named_mod.name) catch |e| oom(e); 81 | if (name.eql(return_id)) { 82 | if (func.ret) |_| parseError(filename, lineno, "duplicate return specifier", .{}); 83 | func.ret = named_mod.modifier; 84 | } else { 85 | const entry = func.params.getOrPut(allocator, name) catch |e| oom(e); 86 | if (entry.found_existing) parseError(filename, lineno, "duplicate parameter '{s}'", .{name}); 87 | entry.value_ptr.* = named_mod.modifier; 88 | } 89 | next_index = named_mod.end; 90 | } 91 | if (mod_count == 0) parseError(filename, lineno, "missing return/parameter specifiers", .{}); 92 | } else if (std.mem.eql(u8, kind, "Constant")) { 93 | const named_mod = parseNamedModifier(filename, lineno, line, field_it.index) orelse parseError( 94 | filename, 95 | lineno, 96 | "missing name/modifiers", 97 | .{}, 98 | ); 99 | if (skipWhitespace(line, named_mod.end) != line.len) parseError(filename, lineno, "unexpected data: '{s}'", .{line[named_mod.end..]}); 100 | const name = string_pool.add(named_mod.name) catch |e| oom(e); 101 | const entry = api.constants.getOrPut(allocator, name) catch |e| oom(e); 102 | if (entry.found_existing) parseError(filename, lineno, "duplicate constant '{s}'", .{name}); 103 | entry.value_ptr.* = named_mod.modifier; 104 | } else parseError(filename, lineno, "unknown kind '{s}'", .{kind}); 105 | } 106 | return root; 107 | } 108 | 109 | fn parseNamedModifier( 110 | filename: []const u8, 111 | lineno: u32, 112 | line: []const u8, 113 | start: usize, 114 | ) ?struct { 115 | end: usize, 116 | name: []const u8, 117 | modifier: TypeModifier, 118 | } { 119 | const name_start = skipWhitespace(line, start); 120 | if (name_start == line.len) return null; 121 | const name_end = scanId(line, name_start); 122 | const name = line[name_start..name_end]; 123 | if (name.len == 0) parseError(filename, lineno, "expected id [a-zA-Z0-9_] but got '{s}'", .{line[name_start..]}); 124 | if (!matches(line, name_end, '(')) parseError(filename, lineno, "expected '(' but got '{s}'", .{line[name_end..]}); 125 | const result = parseModifier(filename, lineno, line, name_end + 1); 126 | return .{ 127 | .end = result.end, 128 | .name = name, 129 | .modifier = result.modifier, 130 | }; 131 | } 132 | 133 | fn parseModifier(filename: []const u8, lineno: u32, line: []const u8, start: usize) struct { 134 | end: usize, 135 | modifier: TypeModifier, 136 | } { 137 | var modifier: TypeModifier = .{}; 138 | var next_index = start; 139 | while (true) { 140 | const id_start = skipWhitespace(line, next_index); 141 | if (id_start == line.len) parseError(filename, lineno, "missing ')'", .{}); 142 | if (matches(line, id_start, ')')) 143 | return .{ .end = next_index + 1, .modifier = modifier }; 144 | 145 | const id_end = scanId(line, id_start); 146 | const id = line[id_start..id_end]; 147 | if (id.len == 0) parseError(filename, lineno, "expected id [a-zA-Z0-9_] but got '{s}'", .{line[id_start..]}); 148 | 149 | if (std.mem.eql(u8, id, "NotNull")) { 150 | if (!matches(line, id_end, '=')) parseError(filename, lineno, "expected '=' after NotNull but got '{s}'", .{line[id_start..]}); 151 | 152 | next_index = id_end + 1; 153 | var flags: NullModifier = 0; 154 | var flag_count: u32 = 0; 155 | while (true) { 156 | const on = if (matches(line, next_index, '0')) 157 | false 158 | else if (matches(line, next_index, '1')) 159 | true 160 | else 161 | break; 162 | next_index += 1; 163 | flag_count += 1; 164 | if (flag_count > @typeInfo(NullModifier).int.bits) parseError(filename, lineno, "NullModifier type doesn't have enough bits", .{}); 165 | flags = flags << 1; 166 | if (on) flags |= 1; 167 | } 168 | if (flag_count == 0) parseError(filename, lineno, "expected 1's and 0's after 'NotNull=' but got '{s}'", .{line[id_start..]}); 169 | modifier.null_modifier = flags; 170 | } else if (std.mem.eql(u8, id, "UnionPointer")) { 171 | modifier.union_pointer = true; 172 | next_index = id_end; 173 | } else parseError(filename, lineno, "unknown type modifier '{s}'", .{id}); 174 | } 175 | } 176 | 177 | fn skipWhitespace(str: []const u8, start: usize) usize { 178 | var i = start; 179 | while (i < str.len) : (i += 1) { 180 | if (str[i] != ' ') break; 181 | } 182 | return i; 183 | } 184 | 185 | fn isIdChar(c: u8) bool { 186 | return switch (c) { 187 | 'a'...'z', 'A'...'Z', '0'...'9', '_' => true, 188 | else => false, 189 | }; 190 | } 191 | fn scanId(str: []const u8, start: usize) usize { 192 | var i = start; 193 | while (i < str.len) : (i += 1) { 194 | if (!isIdChar(str[i])) break; 195 | } 196 | return i; 197 | } 198 | 199 | fn matches(str: []const u8, index: usize, c: u8) bool { 200 | return index < str.len and str[index] == c; 201 | } 202 | -------------------------------------------------------------------------------- /src/genzigexports.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const win32_stub = @import("win32_stub"); 3 | const zig = win32_stub.zig; 4 | 5 | // make win32/zig.zig happy 6 | pub const UNICODE = true; 7 | 8 | pub fn main() !void { 9 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); 10 | const arena = arena_instance.allocator(); 11 | const all_args = try std.process.argsAlloc(arena); 12 | // don't care about freeing args 13 | 14 | const cmd_args = all_args[1..]; 15 | if (cmd_args.len != 1) { 16 | std.log.err("expected 1 cmdline argument but got {}", .{cmd_args.len}); 17 | std.process.exit(0xff); 18 | } 19 | const out_file_path = cmd_args[0]; 20 | 21 | const out_file = try std.fs.cwd().createFile(out_file_path, .{}); 22 | defer out_file.close(); 23 | 24 | var bw = std.io.bufferedWriter(out_file.writer()); 25 | try generate(bw.writer().any()); 26 | try bw.flush(); 27 | } 28 | fn generate(writer: std.io.AnyWriter) !void { 29 | try writer.writeAll("pub const Kind = enum { constant, type, function };\n"); 30 | try writer.writeAll("pub const Decl = struct { kind: Kind, name: []const u8};\n"); 31 | try writer.writeAll("pub const declarations = [_]Decl{\n"); 32 | inline for (comptime std.meta.declarations(zig)) |decl| { 33 | const field_type_info = @typeInfo(@TypeOf(@field(zig, decl.name))); 34 | const kind = switch (comptime field_type_info) { 35 | .bool, .int, .@"enum" => "constant", 36 | .@"fn" => "function", 37 | .type => "type", 38 | else => @compileError( 39 | "zig.zig decl '" ++ decl.name ++ "' has unsupported type info: " ++ @tagName(field_type_info), 40 | ), 41 | }; 42 | try writer.print(" .{{ .kind = .{s}, .name = \"{s}\" }},\n", .{ kind, decl.name }); 43 | } 44 | try writer.writeAll("};\n"); 45 | } 46 | -------------------------------------------------------------------------------- /src/handletypes.zig: -------------------------------------------------------------------------------- 1 | //! workaround github.com/microsoft/win32metadata/issues/395 2 | const std = @import("std"); 3 | pub const std_handle_types = std.StaticStringMap([]const u8).initComptime(.{ 4 | .{ "HANDLE", "os.windows.HANDLE" }, 5 | .{ "HINSTANCE", "os.windows.HINSTANCE" }, 6 | .{ "SOCKET", "os.windows.ws2_32.SOCKET" }, 7 | }); 8 | pub const handle_types = list: { 9 | @setEvalBranchQuota(3000); 10 | break :list std.StaticStringMap(void).initComptime(.{ 11 | .{"HICON"}, 12 | .{"HCURSOR"}, 13 | .{"HBRUSH"}, 14 | .{"HSYNTHETICPOINTERDEVICE"}, 15 | .{"HPROPSHEETPAGE"}, 16 | .{"HIMAGELIST"}, 17 | .{"HTREEITEM"}, 18 | .{"HCOLORSPACE"}, 19 | .{"HCRYPTASYNC"}, 20 | .{"HCERTCHAINENGINE"}, 21 | .{"HCOMDB"}, 22 | .{"HKEY"}, 23 | .{"HWINWATCH"}, 24 | .{"HWINSTA"}, 25 | .{"HDESK"}, 26 | .{"HSTRING"}, 27 | .{"HSZ"}, 28 | .{"HCONV"}, 29 | .{"HCONVLIST"}, 30 | .{"HDDEDATA"}, 31 | .{"HRASCONN"}, 32 | .{"HAMSICONTEXT"}, 33 | .{"HAMSISESSION"}, 34 | .{"HCMNOTIFICATION"}, 35 | .{"HKL"}, 36 | .{"HIFTIMESTAMPCHANGE"}, 37 | .{"HWSAEVENT"}, 38 | .{"HTASK"}, 39 | .{"HWINEVENTHOOK"}, 40 | .{"HUIANODE"}, 41 | .{"HUIAPATTERNOBJECT"}, 42 | .{"HUIATEXTRANGE"}, 43 | .{"HUIAEVENT"}, 44 | .{"HMIDI"}, 45 | .{"HMIDIIN"}, 46 | .{"HMIDIOUT"}, 47 | .{"HMIDISTRM"}, 48 | .{"HMIXER"}, 49 | .{"HMIXEROBJ"}, 50 | .{"HWAVE"}, 51 | .{"HWAVEOUT"}, 52 | .{"HWAVEIN"}, 53 | .{"HMMIO"}, 54 | .{"HDRVR"}, 55 | .{"HACMDRIVERID"}, 56 | .{"HACMDRIVER"}, 57 | .{"HACMSTREAM"}, 58 | .{"HACMOBJ"}, 59 | .{"HIC"}, 60 | .{"HVIDEO"}, 61 | .{"HSWDEVICE"}, 62 | .{"HINTERACTIONCONTEXT"}, 63 | .{"HRAWINPUT"}, 64 | .{"HRECOALT"}, 65 | .{"HRECOCONTEXT"}, 66 | .{"HRECOGNIZER"}, 67 | .{"HRECOLATTICE"}, 68 | .{"HRECOWORDLIST"}, 69 | .{"HIMC"}, 70 | .{"HIMCC"}, 71 | .{"HSAVEDUILANGUAGES"}, 72 | .{"HRSRC"}, 73 | .{"HSURF"}, 74 | .{"HPOWERNOTIFY"}, 75 | .{"HUMPD"}, 76 | .{"HSTR"}, 77 | .{"HSPRITE"}, 78 | .{"HSEMAPHORE"}, 79 | .{"HLSURF"}, 80 | .{"HFASTMUTEX"}, 81 | .{"HDRVOBJ"}, 82 | .{"HDEV"}, 83 | .{"HBM"}, 84 | .{"HPCON"}, 85 | .{"HMENU"}, 86 | .{"HACCEL"}, 87 | .{"HDROP"}, 88 | .{"HPSXA"}, 89 | .{"HDC"}, 90 | .{"HGDIOBJ"}, 91 | .{"HBITMAP"}, 92 | .{"HRGN"}, 93 | .{"HPEN"}, 94 | .{"HBRUSH"}, 95 | .{"HFONT"}, 96 | .{"HMETAFILE"}, 97 | .{"HENHMETAFILE"}, 98 | .{"HMONITOR"}, 99 | .{"HPALETTE"}, 100 | .{"HWND"}, 101 | .{"HHOOK"}, 102 | .{"HGESTUREINFO"}, 103 | .{"HTOUCHINPUT"}, 104 | .{"HGLRC"}, 105 | .{"HFILTER"}, 106 | .{"HPTPROVIDER"}, 107 | .{"HPSS"}, 108 | .{"HPSSWALK"}, 109 | .{"HSTRING_BUFFER"}, 110 | .{"JET_SESID"}, 111 | .{"PSID"}, 112 | .{"AUTHZ_AUDIT_EVENT_HANDLE"}, 113 | .{"HeapHandle"}, 114 | .{"HDPA"}, 115 | .{"HDSA"}, 116 | }); 117 | }; 118 | -------------------------------------------------------------------------------- /src/jsonextra.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn ArrayHashMap(comptime T: type) type { 4 | return struct { 5 | map: std.StringArrayHashMap(T), 6 | 7 | const Self = @This(); 8 | pub fn get(self: Self, name: []const u8) ?T { 9 | return self.map.get(name); 10 | } 11 | 12 | pub fn jsonParse( 13 | allocator: std.mem.Allocator, 14 | source: anytype, 15 | options: std.json.ParseOptions, 16 | ) std.json.ParseError(@TypeOf(source.*))!Self { 17 | var map = std.StringArrayHashMap(T).init(allocator); 18 | errdefer map.deinit(); 19 | 20 | if (.object_begin != try source.next()) return error.UnexpectedToken; 21 | while (true) { 22 | const api_name = switch (try source.next()) { 23 | .string => |s| s, 24 | .object_end => return .{ .map = map }, 25 | else => return error.UnexpectedToken, 26 | }; 27 | const value = try std.json.innerParse(T, allocator, source, options); 28 | try map.put(api_name, value); 29 | } 30 | } 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/metadata.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const metadata = @This(); 3 | 4 | pub const Api = struct { 5 | Constants: []const Constant, 6 | Types: []const Type, 7 | Functions: []const Function, 8 | UnicodeAliases: []const []const u8, 9 | 10 | pub fn parse( 11 | allocator: std.mem.Allocator, 12 | api_path: []const u8, 13 | filename: []const u8, 14 | content: []const u8, 15 | ) Api { 16 | var diagnostics = std.json.Diagnostics{}; 17 | var scanner = std.json.Scanner.initCompleteInput(allocator, content); 18 | defer scanner.deinit(); 19 | scanner.enableDiagnostics(&diagnostics); 20 | return std.json.parseFromTokenSourceLeaky( 21 | Api, 22 | allocator, 23 | &scanner, 24 | .{}, 25 | ) catch |err| { 26 | std.log.err( 27 | "{s}{c}{s}:{}:{}: {s}", 28 | .{ 29 | api_path, std.fs.path.sep, filename, 30 | diagnostics.getLine(), diagnostics.getColumn(), @errorName(err), 31 | }, 32 | ); 33 | @panic("json error"); 34 | }; 35 | } 36 | }; 37 | 38 | pub const ValueType = enum { 39 | Byte, 40 | UInt16, 41 | Int32, 42 | UInt32, 43 | Int64, 44 | UInt64, 45 | Single, 46 | Double, 47 | String, 48 | PropertyKey, 49 | }; 50 | 51 | pub const TypeRefNative = enum { 52 | Void, 53 | Boolean, 54 | SByte, 55 | Byte, 56 | Int16, 57 | UInt16, 58 | Int32, 59 | UInt32, 60 | Int64, 61 | UInt64, 62 | Char, 63 | Single, 64 | Double, 65 | String, 66 | IntPtr, 67 | UIntPtr, 68 | Guid, 69 | }; 70 | 71 | pub const Constant = struct { 72 | Name: []const u8, 73 | Type: TypeRef, 74 | ValueType: ValueType, 75 | Value: std.json.Value, 76 | Attrs: ConstantAttrs, 77 | }; 78 | pub const ConstantAttrs = struct { 79 | pub fn jsonParse( 80 | allocator: std.mem.Allocator, 81 | source: anytype, 82 | options: std.json.ParseOptions, 83 | ) std.json.ParseError(@TypeOf(source.*))!ConstantAttrs { 84 | return try parseAttrsArray(ConstantAttrs, allocator, source, options); 85 | } 86 | }; 87 | 88 | pub const EnumIntegerBase = enum { Byte, SByte, UInt16, UInt32, Int32, UInt64 }; 89 | 90 | const TypeKind = enum { 91 | NativeTypedef, 92 | Enum, 93 | Struct, 94 | Union, 95 | ComClassID, 96 | Com, 97 | FunctionPointer, 98 | }; 99 | const type_kinds = std.StaticStringMap(TypeKind).initComptime(.{ 100 | .{ "NativeTypedef", .NativeTypedef }, 101 | .{ "Enum", .Enum }, 102 | .{ "Struct", .Struct }, 103 | .{ "Union", .Union }, 104 | .{ "ComClassID", .ComClassID }, 105 | .{ "Com", .Com }, 106 | .{ "FunctionPointer", .FunctionPointer }, 107 | }); 108 | pub const Type = struct { 109 | Name: []const u8, 110 | Architectures: Architectures, 111 | Platform: ?Platform, 112 | Kind: union(enum) { 113 | NativeTypedef: NativeTypedef, 114 | Enum: Enum, 115 | Struct: StructOrUnion, 116 | Union: StructOrUnion, 117 | ComClassID: ComClassID, 118 | Com: Com, 119 | FunctionPointer: FunctionPointer, 120 | }, 121 | 122 | pub const Enum = struct { 123 | Flags: bool, 124 | Scoped: bool, 125 | Values: []EnumField, 126 | IntegerBase: ?EnumIntegerBase, 127 | }; 128 | pub const EnumField = struct { 129 | Name: []const u8, 130 | Value: std.json.Value, 131 | }; 132 | 133 | pub const ComClassID = struct { 134 | Guid: []const u8, 135 | }; 136 | 137 | pub fn jsonParse( 138 | allocator: std.mem.Allocator, 139 | source: anytype, 140 | options: std.json.ParseOptions, 141 | ) std.json.ParseError(@TypeOf(source.*))!Type { 142 | if (.object_begin != try source.next()) return error.UnexpectedToken; 143 | try expectFieldName(source, "Name"); 144 | const name = switch (try source.next()) { 145 | .string => |s| s, 146 | else => return error.UnexpectedToken, 147 | }; 148 | try expectFieldName(source, "Architectures"); 149 | const arches = try Architectures.jsonParse(allocator, source, options); 150 | try expectFieldName(source, "Platform"); 151 | const platform = try std.json.innerParse(?Platform, allocator, source, options); 152 | switch (try jsonParseUnionKind(TypeKind, "Type", source, type_kinds)) { 153 | .NativeTypedef => return .{ .Name = name, .Architectures = arches, .Platform = platform, .Kind = .{ 154 | .NativeTypedef = try parseUnionObject(NativeTypedef, allocator, source, options), 155 | } }, 156 | .Enum => return .{ .Name = name, .Architectures = arches, .Platform = platform, .Kind = .{ 157 | .Enum = try parseUnionObject(Enum, allocator, source, options), 158 | } }, 159 | .Struct => return .{ .Name = name, .Architectures = arches, .Platform = platform, .Kind = .{ 160 | .Struct = try parseUnionObject(StructOrUnion, allocator, source, options), 161 | } }, 162 | .Union => return .{ .Name = name, .Architectures = arches, .Platform = platform, .Kind = .{ 163 | .Union = try parseUnionObject(StructOrUnion, allocator, source, options), 164 | } }, 165 | .ComClassID => return .{ .Name = name, .Architectures = arches, .Platform = platform, .Kind = .{ 166 | .ComClassID = try parseUnionObject(ComClassID, allocator, source, options), 167 | } }, 168 | .Com => return .{ .Name = name, .Architectures = arches, .Platform = platform, .Kind = .{ 169 | .Com = try parseUnionObject(Com, allocator, source, options), 170 | } }, 171 | .FunctionPointer => return .{ .Name = name, .Architectures = arches, .Platform = platform, .Kind = .{ 172 | .FunctionPointer = try parseUnionObject(FunctionPointer, allocator, source, options), 173 | } }, 174 | } 175 | } 176 | }; 177 | 178 | pub const FunctionPointer = struct { 179 | SetLastError: bool, 180 | ReturnType: TypeRef, 181 | //ReturnAttrs: ReturnAttrs, 182 | ReturnAttrs: ParamAttrs, 183 | Attrs: FunctionAttrs, 184 | Params: []const Param, 185 | }; 186 | 187 | pub const StructOrUnion = struct { 188 | Size: u32, 189 | PackingSize: u32, 190 | Fields: []const StructOrUnionField, 191 | NestedTypes: []const Type, 192 | Comment: ?[]const u8 = null, 193 | }; 194 | pub const StructOrUnionField = struct { 195 | Name: []const u8, 196 | Type: TypeRef, 197 | Attrs: FieldAttrs, 198 | }; 199 | 200 | pub const FieldAttrs = struct { 201 | Const: bool = false, 202 | Obselete: bool = false, 203 | Optional: bool = false, 204 | NotNullTerminated: bool = false, 205 | NullNullTerminated: bool = false, 206 | pub fn jsonParse( 207 | allocator: std.mem.Allocator, 208 | source: anytype, 209 | options: std.json.ParseOptions, 210 | ) std.json.ParseError(@TypeOf(source.*))!FieldAttrs { 211 | return parseAttrsArray(FieldAttrs, allocator, source, options); 212 | } 213 | }; 214 | 215 | pub const NativeTypedef = struct { 216 | AlsoUsableFor: ?[]const u8, 217 | Def: TypeRef, 218 | FreeFunc: ?[]const u8, 219 | InvalidHandleValue: ?i64, 220 | }; 221 | 222 | pub const Com = struct { 223 | Guid: ?[]const u8, 224 | Attrs: ComAttrs, 225 | Interface: ?TypeRef, 226 | Methods: []const ComMethod, 227 | }; 228 | pub const ComAttrs = struct { 229 | Agile: bool = false, 230 | pub fn jsonParse( 231 | allocator: std.mem.Allocator, 232 | source: anytype, 233 | options: std.json.ParseOptions, 234 | ) std.json.ParseError(@TypeOf(source.*))!ComAttrs { 235 | return parseAttrsArray(ComAttrs, allocator, source, options); 236 | } 237 | }; 238 | 239 | pub const ComMethod = struct { 240 | Name: []const u8, 241 | SetLastError: bool, 242 | ReturnType: TypeRef, 243 | //ReturnAttrs: ReturnAttrs, 244 | ReturnAttrs: ParamAttrs, 245 | Architectures: Architectures, 246 | Platform: ?Platform, 247 | Attrs: FunctionAttrs, 248 | Params: []const Param, 249 | }; 250 | //pub const ComMethodAttrs = struct { 251 | // SpecialName: bool = false, 252 | // PreserveSig: bool = false, 253 | // pub fn jsonParse( 254 | // allocator: std.mem.Allocator, 255 | // source: anytype, 256 | // options: std.json.ParseOptions, 257 | // ) std.json.ParseError(@TypeOf(source.*))!ComMethodAttrs { 258 | // return parseAttrsArray(ComMethodAttrs, allocator, source, options); 259 | // } 260 | //}; 261 | 262 | //pub const ComMethodParam = struct { 263 | // Name: []const u8, 264 | // Type: TypeRef, 265 | // Attrs: ComMethodParamAttrs, 266 | //}; 267 | //pub const ComMethodParamAttrs = struct { 268 | // In: bool = false, 269 | // Out: bool = false, 270 | // Const: bool = false, 271 | // Optional: bool = false, 272 | // ComOutPtr: bool = false, 273 | // RetVal: bool = false, 274 | // Reserved: bool = false, 275 | // NotNullTerminated: bool = false, 276 | // NullNullTerminated: bool = false, 277 | // MemorySize: ?MemorySize = null, 278 | // FreeWith: ?FreeWith = null, 279 | // 280 | // pub fn jsonParse( 281 | // allocator: std.mem.Allocator, 282 | // source: anytype, 283 | // options: std.json.ParseOptions, 284 | // ) std.json.ParseError(@TypeOf(source.*))!ComMethodParamAttrs { 285 | // return parseAttrsArray(ComMethodParamAttrs, allocator, source, options); 286 | // } 287 | //}; 288 | 289 | pub const Function = struct { 290 | Name: []const u8, 291 | SetLastError: bool, 292 | DllImport: []const u8, 293 | ReturnType: TypeRef, 294 | //ReturnAttrs: ReturnAttrs, 295 | ReturnAttrs: ParamAttrs, 296 | Architectures: Architectures, 297 | Platform: ?Platform, 298 | Attrs: FunctionAttrs, 299 | Params: []const Param, 300 | }; 301 | pub const Platform = enum { 302 | windowsServer2000, 303 | windowsServer2003, 304 | windowsServer2008, 305 | windowsServer2012, 306 | windowsServer2016, 307 | windowsServer2020, 308 | @"windows5.0", 309 | @"windows5.1.2600", 310 | @"windows6.0.6000", 311 | @"windows6.1", 312 | @"windows8.0", 313 | @"windows8.1", 314 | @"windows10.0.10240", 315 | @"windows10.0.10586", 316 | @"windows10.0.14393", 317 | @"windows10.0.15063", 318 | @"windows10.0.16299", 319 | @"windows10.0.17134", 320 | @"windows10.0.17763", 321 | @"windows10.0.18362", 322 | @"windows10.0.19041", 323 | }; 324 | 325 | pub const Architectures = struct { 326 | filter: ?Filter = null, 327 | 328 | pub const Filter = struct { 329 | X86: bool = false, 330 | X64: bool = false, 331 | Arm64: bool = false, 332 | //pub fn allAreSet(self: Filter) bool { 333 | //return self.X86 and self.X64 and self.Arm64; 334 | //} 335 | pub fn eql(self: Filter, other: Filter) bool { 336 | return self.X86 == other.X86 and 337 | self.X64 == other.X64 and 338 | self.Arm64 == other.Arm64; 339 | } 340 | pub fn unionWith(self: Filter, other: Filter) ?Filter { 341 | const new_filter: Filter = .{ 342 | .X86 = self.X86 or other.X86, 343 | .X64 = self.X64 or other.X64, 344 | .Arm64 = self.Arm64 or other.Arm64, 345 | }; 346 | if (new_filter.X86 and new_filter.X64 and new_filter.Arm64) 347 | return null; 348 | return new_filter; 349 | } 350 | }; 351 | 352 | pub fn eql(self: Architectures, other: Architectures) bool { 353 | const self_filter = self.filter orelse return other.filter == null; 354 | const other_filter = other.filter orelse return false; 355 | return self_filter.eql(other_filter); 356 | } 357 | 358 | pub fn unionWith(self: Architectures, other: Architectures) Architectures { 359 | const self_filter = self.filter orelse return .{}; 360 | const other_filter = other.filter orelse return .{}; 361 | return .{ .filter = self_filter.unionWith(other_filter) }; 362 | } 363 | 364 | pub fn jsonParse( 365 | allocator: std.mem.Allocator, 366 | source: anytype, 367 | options: std.json.ParseOptions, 368 | ) std.json.ParseError(@TypeOf(source.*))!Architectures { 369 | _ = allocator; 370 | _ = options; 371 | if (.array_begin != try source.next()) return error.UnexpectedToken; 372 | const filter_struct_info = @typeInfo(Filter).@"struct"; 373 | var result: Architectures = .{}; 374 | while (true) { 375 | switch (try source.next()) { 376 | .array_end => return result, 377 | .string => |s| { 378 | if (result.filter == null) { 379 | result.filter = .{}; 380 | } 381 | 382 | inline for (filter_struct_info.fields) |field| { 383 | if (field.type == bool and std.mem.eql(u8, s, field.name)) { 384 | @field(result.filter.?, field.name) = true; 385 | break; 386 | } 387 | } else { 388 | std.log.err("unknown Architecture attribute '{s}'", .{s}); 389 | return error.UnexpectedToken; 390 | } 391 | }, 392 | else => |token| { 393 | std.log.err( 394 | "expected string or array_close but got {s}", 395 | .{@tagName(token)}, 396 | ); 397 | return error.UnexpectedToken; 398 | }, 399 | } 400 | } 401 | } 402 | }; 403 | 404 | const MemorySize = struct { 405 | BytesParamIndex: u16, 406 | }; 407 | const FreeWith = struct { 408 | Func: []const u8, 409 | }; 410 | 411 | pub const FunctionAttrs = struct { 412 | SpecialName: bool = false, 413 | PreserveSig: bool = false, 414 | DoesNotReturn: bool = false, 415 | pub fn jsonParse( 416 | allocator: std.mem.Allocator, 417 | source: anytype, 418 | options: std.json.ParseOptions, 419 | ) std.json.ParseError(@TypeOf(source.*))!FunctionAttrs { 420 | return parseAttrsArray(FunctionAttrs, allocator, source, options); 421 | } 422 | }; 423 | 424 | //pub const ReturnAttrs = struct { 425 | // Optional: bool = false, 426 | // pub fn jsonParse( 427 | // allocator: std.mem.Allocator, 428 | // source: anytype, 429 | // options: std.json.ParseOptions, 430 | // ) std.json.ParseError(@TypeOf(source.*))!ReturnAttrs { 431 | // return parseAttrsArray(ReturnAttrs, allocator, source, options); 432 | // } 433 | //}; 434 | pub const ParamAttrs = struct { 435 | Const: bool = false, 436 | In: bool = false, 437 | Out: bool = false, 438 | Optional: bool = false, 439 | NotNullTerminated: bool = false, 440 | NullNullTerminated: bool = false, 441 | RetVal: bool = false, 442 | ComOutPtr: bool = false, 443 | DoNotRelease: bool = false, 444 | Reserved: bool = false, 445 | MemorySize: ?MemorySize = null, 446 | FreeWith: ?FreeWith = null, 447 | 448 | pub fn jsonParse( 449 | allocator: std.mem.Allocator, 450 | source: anytype, 451 | options: std.json.ParseOptions, 452 | ) std.json.ParseError(@TypeOf(source.*))!ParamAttrs { 453 | return parseAttrsArray(ParamAttrs, allocator, source, options); 454 | } 455 | }; 456 | 457 | pub const Param = struct { 458 | Name: []const u8, 459 | Type: TypeRef, 460 | Attrs: ParamAttrs, 461 | }; 462 | 463 | const TargetKind = enum { 464 | Default, 465 | Com, 466 | FunctionPointer, 467 | }; 468 | 469 | const TypeRefKind = enum { 470 | Native, 471 | ApiRef, 472 | PointerTo, 473 | Array, 474 | LPArray, 475 | MissingClrType, 476 | }; 477 | const type_ref_kinds = std.StaticStringMap(TypeRefKind).initComptime(.{ 478 | .{ "Native", .Native }, 479 | .{ "ApiRef", .ApiRef }, 480 | .{ "PointerTo", .PointerTo }, 481 | .{ "Array", .Array }, 482 | .{ "LPArray", .LPArray }, 483 | .{ "MissingClrType", .MissingClrType }, 484 | }); 485 | 486 | const Native = struct { 487 | Name: TypeRefNative, 488 | }; 489 | const ApiRef = struct { 490 | Name: []const u8, 491 | TargetKind: TargetKind, 492 | Api: []const u8, 493 | Parents: []const []const u8, 494 | }; 495 | const PointerTo = struct { 496 | Child: *const TypeRef, 497 | }; 498 | const PointerToAttrs = struct { 499 | pub fn jsonParse( 500 | allocator: std.mem.Allocator, 501 | source: anytype, 502 | options: std.json.ParseOptions, 503 | ) std.json.ParseError(@TypeOf(source.*))!PointerToAttrs { 504 | return parseAttrsArray(PointerToAttrs, allocator, source, options); 505 | } 506 | }; 507 | const Array = struct { 508 | Shape: ?ArrayShape, 509 | Child: *const TypeRef, 510 | }; 511 | const ArrayShape = struct { 512 | Size: u32, 513 | }; 514 | const LPArray = struct { 515 | NullNullTerm: bool, 516 | CountConst: i32, 517 | CountParamIndex: i32, 518 | Child: *const TypeRef, 519 | }; 520 | const MissingClrType = struct { 521 | Name: []const u8, 522 | Namespace: []const u8, 523 | }; 524 | 525 | pub const TypeRef = union(TypeRefKind) { 526 | Native: Native, 527 | ApiRef: ApiRef, 528 | PointerTo: PointerTo, 529 | Array: Array, 530 | LPArray: LPArray, 531 | MissingClrType: MissingClrType, 532 | 533 | pub fn jsonParse( 534 | allocator: std.mem.Allocator, 535 | source: anytype, 536 | options: std.json.ParseOptions, 537 | ) std.json.ParseError(@TypeOf(source.*))!TypeRef { 538 | if (.object_begin != try source.next()) return error.UnexpectedToken; 539 | switch (try jsonParseUnionKind(TypeRefKind, "TypeRef", source, type_ref_kinds)) { 540 | .Native => return .{ 541 | .Native = try parseUnionObject(Native, allocator, source, options), 542 | }, 543 | .ApiRef => return .{ 544 | .ApiRef = try parseUnionObject(ApiRef, allocator, source, options), 545 | }, 546 | .PointerTo => return .{ 547 | .PointerTo = try parseUnionObject(PointerTo, allocator, source, options), 548 | }, 549 | .Array => return .{ 550 | .Array = try parseUnionObject(Array, allocator, source, options), 551 | }, 552 | .LPArray => return .{ 553 | .LPArray = try parseUnionObject(LPArray, allocator, source, options), 554 | }, 555 | .MissingClrType => return .{ 556 | .MissingClrType = try parseUnionObject(MissingClrType, allocator, source, options), 557 | }, 558 | } 559 | } 560 | }; 561 | 562 | fn parseAttrsArray( 563 | comptime Attrs: type, 564 | allocator: std.mem.Allocator, 565 | source: anytype, 566 | options: std.json.ParseOptions, 567 | ) std.json.ParseError(@TypeOf(source.*))!Attrs { 568 | const structInfo = switch (@typeInfo(Attrs)) { 569 | .@"struct" => |i| i, 570 | else => @compileError("Unable to parse attribute array into non-struct type '" ++ @typeName(Attrs) ++ "'"), 571 | }; 572 | 573 | if (.array_begin != try source.next()) return error.UnexpectedToken; 574 | var result: Attrs = .{}; 575 | 576 | while (true) { 577 | switch (try source.next()) { 578 | .array_end => return result, 579 | .string => |s| { 580 | inline for (structInfo.fields) |field| { 581 | if (field.type == bool and std.mem.eql(u8, s, field.name)) { 582 | @field(result, field.name) = true; 583 | break; 584 | } 585 | } else { 586 | std.log.err( 587 | "unknown attribute '{s}' for type {s}", 588 | .{ s, @typeName(Attrs) }, 589 | ); 590 | return error.UnexpectedToken; 591 | } 592 | }, 593 | .object_begin => { 594 | const kind = blk: { 595 | const field_name = switch (try source.next()) { 596 | .string => |s| s, 597 | else => return error.UnexpectedToken, 598 | }; 599 | if (!std.mem.eql(u8, field_name, "Kind")) 600 | return error.UnexpectedToken; 601 | break :blk switch (try source.next()) { 602 | .string => |s| s, 603 | else => return error.UnexpectedToken, 604 | }; 605 | }; 606 | if (@hasField(Attrs, "MemorySize")) { 607 | if (std.mem.eql(u8, kind, "MemorySize")) { 608 | result.MemorySize = try parseUnionObject( 609 | MemorySize, 610 | allocator, 611 | source, 612 | options, 613 | ); 614 | continue; 615 | } 616 | } 617 | if (@hasField(Attrs, "FreeWith")) { 618 | if (std.mem.eql(u8, kind, "FreeWith")) { 619 | result.FreeWith = try parseUnionObject( 620 | FreeWith, 621 | allocator, 622 | source, 623 | options, 624 | ); 625 | continue; 626 | } 627 | } 628 | std.log.err( 629 | "unknown object attribute object kind '{s}' for type {s}", 630 | .{ kind, @typeName(Attrs) }, 631 | ); 632 | return error.UnknownField; 633 | }, 634 | else => |token| { 635 | std.log.err( 636 | "expected token string, object or array_close but got {s} for attr type {s}", 637 | .{ @tagName(token), @typeName(Attrs) }, 638 | ); 639 | return error.UnexpectedToken; 640 | }, 641 | } 642 | } 643 | } 644 | 645 | pub fn jsonParseUnionKind( 646 | comptime KindEnum: type, 647 | type_name: []const u8, 648 | source: anytype, 649 | map: std.StaticStringMap(KindEnum), 650 | ) std.json.ParseError(@TypeOf(source.*))!KindEnum { 651 | switch (try source.next()) { 652 | .string => |field_name| if (!std.mem.eql(u8, field_name, "Kind")) { 653 | std.log.err( 654 | "expected first field of {s} to be 'Kind' but got '{s}'", 655 | .{ type_name, field_name }, 656 | ); 657 | return error.UnexpectedToken; 658 | }, 659 | else => return error.UnexpectedToken, 660 | } 661 | const kind_str = switch (try source.next()) { 662 | .string => |s| s, 663 | else => return error.UnexpectedToken, 664 | }; 665 | return map.get(kind_str) orelse { 666 | std.log.err("unknown {s} Kind '{s}'", .{ type_name, kind_str }); 667 | return error.UnexpectedToken; 668 | }; 669 | } 670 | 671 | pub fn parseUnionObject( 672 | comptime T: type, 673 | allocator: std.mem.Allocator, 674 | source: anytype, 675 | options: std.json.ParseOptions, 676 | ) !T { 677 | const structInfo = switch (@typeInfo(T)) { 678 | .@"struct" => |i| i, 679 | else => @compileError("Unable to parse into non-struct type '" ++ @typeName(T) ++ "'"), 680 | }; 681 | 682 | var r: T = undefined; 683 | var fields_seen = [_]bool{false} ** structInfo.fields.len; 684 | 685 | while (true) { 686 | var name_token: ?std.json.Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?); 687 | const field_name = switch (name_token.?) { 688 | inline .string, .allocated_string => |slice| slice, 689 | .object_end => { // No more fields. 690 | break; 691 | }, 692 | else => { 693 | return error.UnexpectedToken; 694 | }, 695 | }; 696 | 697 | inline for (structInfo.fields, 0..) |field, i| { 698 | if (field.is_comptime) @compileError("comptime fields are not supported: " ++ @typeName(T) ++ "." ++ field.name); 699 | if (std.mem.eql(u8, field.name, field_name)) { 700 | // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object. 701 | // (Recursing into innerParse() might trigger more allocations.) 702 | freeAllocated(allocator, name_token.?); 703 | name_token = null; 704 | if (fields_seen[i]) { 705 | switch (options.duplicate_field_behavior) { 706 | .use_first => { 707 | // Parse and ignore the redundant value. 708 | // We don't want to skip the value, because we want type checking. 709 | _ = try std.json.innerParse(field.type, allocator, source, options); 710 | break; 711 | }, 712 | .@"error" => return error.DuplicateField, 713 | .use_last => {}, 714 | } 715 | } 716 | @field(r, field.name) = try std.json.innerParse(field.type, allocator, source, options); 717 | fields_seen[i] = true; 718 | break; 719 | } 720 | } else { 721 | // Didn't match anything. 722 | std.log.err("unknown field '{s}' on type {s}", .{ field_name, @typeName(T) }); 723 | freeAllocated(allocator, name_token.?); 724 | if (options.ignore_unknown_fields) { 725 | try source.skipValue(); 726 | } else { 727 | return error.UnknownField; 728 | } 729 | } 730 | } 731 | inline for (structInfo.fields, 0..) |field, i| { 732 | if (!fields_seen[i] and !std.mem.eql(u8, field.name, "Comment")) { 733 | std.log.err("field '{s}' has not been set", .{field.name}); 734 | return error.MissingField; 735 | } 736 | } 737 | return r; 738 | } 739 | 740 | fn freeAllocated(allocator: std.mem.Allocator, token: std.json.Token) void { 741 | switch (token) { 742 | .allocated_number, .allocated_string => |slice| { 743 | allocator.free(slice); 744 | }, 745 | else => {}, 746 | } 747 | } 748 | 749 | fn expectFieldName( 750 | source: anytype, 751 | name: []const u8, 752 | ) !void { 753 | switch (try source.next()) { 754 | .string => |s| { 755 | if (!std.mem.eql(u8, s, name)) { 756 | std.log.err( 757 | "expected field '{s}' but got '{s}'", 758 | .{ name, s }, 759 | ); 760 | return error.UnexpectedToken; 761 | } 762 | }, 763 | else => return error.UnexpectedToken, 764 | } 765 | } 766 | -------------------------------------------------------------------------------- /src/pass1.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const metadata = @import("metadata.zig"); 3 | 4 | const common = @import("common.zig"); 5 | const fatal = common.fatal; 6 | 7 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 8 | const allocator = arena.allocator(); 9 | 10 | const BufferedWriter = std.io.BufferedWriter(4096, std.fs.File.Writer); 11 | const OutWriter = BufferedWriter.Writer; 12 | 13 | pub fn main() !u8 { 14 | const all_args = try std.process.argsAlloc(allocator); 15 | // don't care about freeing args 16 | 17 | const cmd_args = all_args[1..]; 18 | if (cmd_args.len != 2) { 19 | std.log.err("expected 2 arguments but got {}", .{cmd_args.len}); 20 | return 1; 21 | } 22 | const win32json_path = cmd_args[0]; 23 | const out_filename = cmd_args[1]; 24 | 25 | const api_path = try std.fs.path.join(allocator, &.{ win32json_path, "api" }); 26 | var api_dir = try std.fs.cwd().openDir(api_path, .{ .iterate = true }); 27 | defer api_dir.close(); 28 | 29 | var api_list = std.ArrayList([]const u8).init(allocator); 30 | defer { 31 | for (api_list.items) |api_name| { 32 | allocator.free(api_name); 33 | } 34 | api_list.deinit(); 35 | } 36 | try common.readApiList(api_dir, &api_list); 37 | 38 | // sort so our data is always in the same order 39 | std.mem.sort([]const u8, api_list.items, {}, common.asciiLessThanIgnoreCase); 40 | 41 | const out_file = try std.fs.cwd().createFile(out_filename, .{}); 42 | defer out_file.close(); 43 | var buffered_writer = BufferedWriter{ 44 | .unbuffered_writer = out_file.writer(), 45 | }; 46 | const out = buffered_writer.writer(); 47 | 48 | try out.writeAll("{\n"); 49 | var json_obj_prefix: []const u8 = ""; 50 | 51 | for (api_list.items) |api_json_basename| { 52 | const name = api_json_basename[0 .. api_json_basename.len - 5]; 53 | try out.print(" {s}\"{s}\": {{\n", .{ json_obj_prefix, name }); 54 | var file = try api_dir.openFile(api_json_basename, .{}); 55 | defer file.close(); 56 | try pass1OnFile(out, api_path, api_json_basename, file); 57 | try out.writeAll(" }\n"); 58 | json_obj_prefix = ","; 59 | } 60 | 61 | try out.writeAll("}\n"); 62 | try buffered_writer.flush(); 63 | std.log.info("wrote {s}", .{out_filename}); 64 | return 0; 65 | } 66 | 67 | fn pass1OnFile(out: OutWriter, api_dir: []const u8, filename: []const u8, file: std.fs.File) !void { 68 | var json_arena_instance = std.heap.ArenaAllocator.init(allocator); 69 | defer json_arena_instance.deinit(); 70 | const json_arena = json_arena_instance.allocator(); 71 | 72 | const content = try file.readToEndAlloc(json_arena, std.math.maxInt(usize)); 73 | // no need to free, owned by json_arena 74 | const parse_start = std.time.milliTimestamp(); 75 | 76 | const api = metadata.Api.parse(json_arena, api_dir, filename, content); 77 | // no need to free, owned by json_arena 78 | const parse_time = std.time.milliTimestamp() - parse_start; 79 | std.log.info("{} ms: parse time for '{s}'", .{ parse_time, filename }); 80 | 81 | try pass1OnJson(out, api); 82 | } 83 | 84 | fn writeType(out: OutWriter, json_obj_prefix: []const u8, name: []const u8, kind: []const u8) !void { 85 | try out.print(" {s}\"{s}\": {{\"Kind\":\"{s}\"}}\n", .{ json_obj_prefix, name, kind }); 86 | } 87 | 88 | fn pass1OnJson(out: OutWriter, api: metadata.Api) !void { 89 | var json_obj_prefix: []const u8 = ""; 90 | 91 | for (api.Types) |t| { 92 | switch (t.Kind) { 93 | .NativeTypedef => |n| try generateNativeTypedef(out, json_obj_prefix, t, n), 94 | .Enum => try writeType(out, json_obj_prefix, t.Name, "Enum"), 95 | .Struct => try writeType(out, json_obj_prefix, t.Name, "Struct"), 96 | .Union => try writeType(out, json_obj_prefix, t.Name, "Union"), 97 | .ComClassID => continue, 98 | .Com => |com| try writeComType(out, json_obj_prefix, t, com), 99 | .FunctionPointer => try writeType(out, json_obj_prefix, t.Name, "FunctionPointer"), 100 | } 101 | json_obj_prefix = ","; 102 | } 103 | } 104 | 105 | fn generateNativeTypedef( 106 | out: OutWriter, 107 | json_obj_prefix: []const u8, 108 | t: metadata.Type, 109 | native_typedef: metadata.NativeTypedef, 110 | ) !void { 111 | // HANDLE PSTR and PWSTR specially because win32metadata is not properly declaring them as arrays, only pointers 112 | // not sure if this is a real issue with the metadata or intentional 113 | const special: enum { pstr, pwstr, other } = blk: { 114 | if (std.mem.eql(u8, t.Name, "PSTR")) break :blk .pstr; 115 | if (std.mem.eql(u8, t.Name, "PWSTR")) break :blk .pwstr; 116 | break :blk .other; 117 | }; 118 | if (special == .pstr or special == .pwstr) { 119 | try writeType(out, json_obj_prefix, t.Name, "Pointer"); 120 | return; 121 | } 122 | 123 | // we should be able to ignore also_usable_for_node because the def_type should be the same as the type being defined 124 | //switch (also_usable_for_node) { 125 | // .string => |also_usable_for| { 126 | // if (also_usable_type_api_map.get(also_usable_for)) |api| { 127 | // try sdk_file.addApiImport(arches, also_usable_for, api, json.Array { .items = &[_]json.Value{}, .capacity = 0, .allocator = allocator }); 128 | // try writer.linef("//TODO: type '{s}' is \"AlsoUsableFor\" '{s}' which means this type is implicitly", .{tmp_name, also_usable_for}); 129 | // try writer.linef("// convertible to '{s}' but not the other way around. I don't know how to do this", .{also_usable_for}); 130 | // try writer.line("// in Zig so for now I'm just defining it as an alias"); 131 | // try writer.linef("pub const {s} = {s};", .{tmp_name, also_usable_for}); 132 | // //try writer.linef("pub const {s} = extern struct {{ base: {s} }};", .{tmp_name, also_usable_for}); 133 | // } else std.debug.panic("AlsoUsableFor type '{s}' is missing from alsoUsableForApiMap", .{also_usable_for}); 134 | // return; 135 | // }, 136 | // .Null => {}, 137 | // else => jsonPanic(), 138 | //} 139 | 140 | // NOTE: for now, I'm just hardcoding a few types to redirect to the ones defined in 'std' 141 | // this allows apps to use values of these types interchangeably with bindings in std 142 | if (@import("handletypes.zig").std_handle_types.get(t.Name)) |_| { 143 | try writeType(out, json_obj_prefix, t.Name, "Pointer"); 144 | return; 145 | } 146 | // workaround https://github.com/microsoft/win32metadata/issues/395 147 | if (@import("handletypes.zig").handle_types.get(t.Name)) |_| { 148 | try writeType(out, json_obj_prefix, t.Name, "Pointer"); 149 | return; 150 | } 151 | 152 | switch (native_typedef.Def) { 153 | .Native => |native| if (isIntegral(native.Name)) { 154 | try writeType(out, json_obj_prefix, t.Name, "Integral"); 155 | } else std.debug.panic("unhandled Native kind in NativeTypedef '{s}'", .{@tagName(native.Name)}), 156 | .PointerTo => try writeType(out, json_obj_prefix, t.Name, "Pointer"), 157 | else => |kind| std.debug.panic("unhandled NativeTypedef kind '{s}'", .{@tagName(kind)}), 158 | } 159 | } 160 | 161 | fn isIntegral(native: metadata.TypeRefNative) bool { 162 | return switch (native) { 163 | .Void => false, 164 | .Boolean => false, 165 | .SByte => true, 166 | .Byte => true, 167 | .Int16 => true, 168 | .UInt16 => true, 169 | .Int32 => true, 170 | .UInt32 => true, 171 | .Int64 => true, 172 | .UInt64 => true, 173 | .Char => false, 174 | .Single => false, 175 | .Double => false, 176 | .String => false, 177 | .IntPtr => true, 178 | .UIntPtr => true, 179 | .Guid => false, 180 | }; 181 | } 182 | 183 | fn writeComType( 184 | out: OutWriter, 185 | json_obj_prefix: []const u8, 186 | t: metadata.Type, 187 | com: metadata.Com, 188 | ) !void { 189 | const iface: ?common.ComInterface = blk: { 190 | if (com.Interface) |iface| 191 | break :blk common.getComInterface(iface); 192 | if (!std.mem.eql(u8, t.Name, "IUnknown")) { 193 | std.log.warn("com type '{s}' does not have an interface (file bug if we're on the latest metadata version)", .{t.Name}); 194 | } 195 | break :blk null; 196 | }; 197 | 198 | try out.print( 199 | " {s}\"{s}\": {{\"Kind\":\"Com\",\"Interface\":{?}}}\n", 200 | .{ json_obj_prefix, t.Name, iface }, 201 | ); 202 | } 203 | -------------------------------------------------------------------------------- /src/pass1data.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const metadata = @import("metadata.zig"); 3 | const jsonextra = @import("jsonextra.zig"); 4 | 5 | pub const Root = jsonextra.ArrayHashMap(TypeMap); 6 | 7 | pub fn parseRoot( 8 | allocator: std.mem.Allocator, 9 | json_filename: []const u8, 10 | content: []const u8, 11 | ) Root { 12 | var diagnostics = std.json.Diagnostics{}; 13 | var scanner = std.json.Scanner.initCompleteInput(allocator, content); 14 | defer scanner.deinit(); 15 | scanner.enableDiagnostics(&diagnostics); 16 | return std.json.parseFromTokenSourceLeaky( 17 | Root, 18 | allocator, 19 | &scanner, 20 | .{}, 21 | ) catch |err| { 22 | std.log.err( 23 | "{s}:{}:{}: {s}", 24 | .{ 25 | json_filename, 26 | diagnostics.getLine(), 27 | diagnostics.getColumn(), 28 | @errorName(err), 29 | }, 30 | ); 31 | @panic("json error"); 32 | }; 33 | } 34 | 35 | pub const TypeMap = jsonextra.ArrayHashMap(Type); 36 | 37 | pub const TypeKind = enum { 38 | Integral, 39 | Enum, 40 | Struct, 41 | Union, 42 | Pointer, 43 | FunctionPointer, 44 | Com, 45 | }; 46 | const type_kinds = std.StaticStringMap(TypeKind).initComptime(.{ 47 | .{ "Integral", .Integral }, 48 | .{ "Enum", .Enum }, 49 | .{ "Struct", .Struct }, 50 | .{ "Union", .Union }, 51 | .{ "Pointer", .Pointer }, 52 | .{ "FunctionPointer", .FunctionPointer }, 53 | .{ "Com", .Com }, 54 | }); 55 | 56 | const EmptyStruct = struct {}; 57 | pub const Type = union(TypeKind) { 58 | Integral: EmptyStruct, 59 | Enum: EmptyStruct, 60 | Struct: EmptyStruct, 61 | Union: EmptyStruct, 62 | Pointer: EmptyStruct, 63 | FunctionPointer: EmptyStruct, 64 | Com: Com, 65 | 66 | pub fn jsonParse( 67 | allocator: std.mem.Allocator, 68 | source: anytype, 69 | options: std.json.ParseOptions, 70 | ) std.json.ParseError(@TypeOf(source.*))!Type { 71 | if (.object_begin != try source.next()) return error.UnexpectedToken; 72 | switch (try metadata.jsonParseUnionKind(TypeKind, "Type", source, type_kinds)) { 73 | .Integral => return .{ .Integral = try metadata.parseUnionObject(EmptyStruct, allocator, source, options) }, 74 | .Enum => return .{ .Enum = try metadata.parseUnionObject(EmptyStruct, allocator, source, options) }, 75 | .Struct => return .{ .Struct = try metadata.parseUnionObject(EmptyStruct, allocator, source, options) }, 76 | .Union => return .{ .Union = try metadata.parseUnionObject(EmptyStruct, allocator, source, options) }, 77 | .Pointer => return .{ .Pointer = try metadata.parseUnionObject(EmptyStruct, allocator, source, options) }, 78 | .FunctionPointer => return .{ .FunctionPointer = try metadata.parseUnionObject(EmptyStruct, allocator, source, options) }, 79 | .Com => return .{ .Com = try metadata.parseUnionObject(Com, allocator, source, options) }, 80 | } 81 | } 82 | }; 83 | 84 | pub const Com = struct { 85 | Interface: ?metadata.TypeRef, 86 | }; 87 | -------------------------------------------------------------------------------- /src/static/.gitignore: -------------------------------------------------------------------------------- 1 | .zig-cache/ 2 | -------------------------------------------------------------------------------- /src/static/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Jonathan Marler 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /src/static/README.md: -------------------------------------------------------------------------------- 1 | # zigwin32 2 | 3 | A complete autogenerated set of Zig bindings for the Win32 API. 4 | 5 | These bindings are generated by https://github.com/marlersoft/zigwin32gen 6 | -------------------------------------------------------------------------------- /src/static/build.zig: -------------------------------------------------------------------------------- 1 | const builtin = @import("builtin"); 2 | const std = @import("std"); 3 | 4 | pub fn build(b: *std.Build) void { 5 | _ = b.addModule("win32", .{ 6 | .root_source_file = b.path("win32.zig"), 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /src/static/win32.zig: -------------------------------------------------------------------------------- 1 | /// This file is just a stub so that we can reflect on win32/zig.zig in order to 2 | /// get its list of exports before generating everything.zig. The definitions don't 3 | /// need to be correct, they just need to exist and "make sense" enough to 4 | /// get win32/zig.zig to compile. 5 | pub const zig = @import("win32/zig.zig"); 6 | pub const foundation = struct { 7 | pub const BOOL = i32; 8 | pub const WIN32_ERROR = enum {}; 9 | pub const HRESULT = i32; 10 | pub const HWND = *opaque {}; 11 | pub const HANDLE = @import("std").os.windows.HANDLE; 12 | pub const LPARAM = isize; 13 | pub const POINT = struct {}; 14 | pub const SIZE = struct {}; 15 | pub const RECT = struct {}; 16 | }; 17 | pub const graphics = struct { 18 | pub const gdi = struct { 19 | pub const HDC = *opaque {}; 20 | pub const HGDIOBJ = *opaque {}; 21 | pub const HBRUSH = HGDIOBJ; 22 | pub const PAINTSTRUCT = struct {}; 23 | }; 24 | }; 25 | pub const ui = struct { 26 | pub const windows_and_messaging = struct { 27 | pub const MESSAGEBOX_STYLE = struct { 28 | ICONASTERISK: u1 = 0, 29 | }; 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /src/static/win32/windowlongptr.zig: -------------------------------------------------------------------------------- 1 | //! The SetWindowLongPtr and GetWindowLongPtr variants are missing because they are 64-bit only 2 | //! See: https://github.com/microsoft/win32metadata/issues/142 (SetWindowLongPtr/GetWindowLongPtr are missing) 3 | const win32 = @import("../win32.zig"); 4 | 5 | pub const SetWindowLongPtrA = if (@sizeOf(usize) == 8) 6 | win32.ui.windows_and_messaging.SetWindowLongPtrA 7 | else 8 | win32.ui.windows_and_messaging.SetWindowLongA; 9 | 10 | pub const SetWindowLongPtrW = if (@sizeOf(usize) == 8) 11 | win32.ui.windows_and_messaging.SetWindowLongPtrW 12 | else 13 | win32.ui.windows_and_messaging.SetWindowLongW; 14 | 15 | pub const GetWindowLongPtrA = if (@sizeOf(usize) == 8) 16 | win32.ui.windows_and_messaging.GetWindowLongPtrA 17 | else 18 | win32.ui.windows_and_messaging.GetWindowLongA; 19 | 20 | pub const GetWindowLongPtrW = if (@sizeOf(usize) == 8) 21 | win32.ui.windows_and_messaging.GetWindowLongPtrW 22 | else 23 | win32.ui.windows_and_messaging.GetWindowLongW; 24 | 25 | pub const SetWindowLongPtr = switch (@import("zig.zig").unicode_mode) { 26 | .ansi => SetWindowLongPtrA, 27 | .wide => SetWindowLongPtrW, 28 | .unspecified => if (@import("builtin").is_test) *opaque{} else @compileError("'SetWindowLongPtr' requires that UNICODE be set to true or false in the root module"), 29 | }; 30 | 31 | pub const GetWindowLongPtr = switch (@import("zig.zig").unicode_mode) { 32 | .ansi => GetWindowLongPtrA, 33 | .wide => GetWindowLongPtrW, 34 | .unspecified => if (@import("builtin").is_test) *opaque{} else @compileError("'GetWindowLongPtr' requires that UNICODE be set to true or false in the root module"), 35 | }; 36 | -------------------------------------------------------------------------------- /src/static/win32/zig.zig: -------------------------------------------------------------------------------- 1 | //! This module is maintained by hand and is copied to the generated code directory 2 | const std = @import("std"); 3 | const builtin = @import("builtin"); 4 | const testing = std.testing; 5 | 6 | const mod_root = @import("../win32.zig"); 7 | const win32 = struct { 8 | const BOOL = mod_root.foundation.BOOL; 9 | const WIN32_ERROR = mod_root.foundation.WIN32_ERROR; 10 | const HRESULT = mod_root.foundation.HRESULT; 11 | const HWND = mod_root.foundation.HWND; 12 | const HANDLE = mod_root.foundation.HANDLE; 13 | const LPARAM = mod_root.foundation.LPARAM; 14 | const POINT = mod_root.foundation.POINT; 15 | const SIZE = mod_root.foundation.SIZE; 16 | const RECT = mod_root.foundation.RECT; 17 | 18 | const HDC = mod_root.graphics.gdi.HDC; 19 | const HGDIOBJ = mod_root.graphics.gdi.HGDIOBJ; 20 | const HBRUSH = mod_root.graphics.gdi.HBRUSH; 21 | const PAINTSTRUCT = mod_root.graphics.gdi.PAINTSTRUCT; 22 | 23 | const GetLastError = mod_root.foundation.GetLastError; 24 | const CloseHandle = mod_root.foundation.CloseHandle; 25 | const FormatMessageA = mod_root.system.diagnostics.debug.FormatMessageA; 26 | const DeleteObject = mod_root.graphics.gdi.DeleteObject; 27 | const DeleteDC = mod_root.graphics.gdi.DeleteDC; 28 | const InvalidateRect = mod_root.graphics.gdi.InvalidateRect; 29 | const BeginPaint = mod_root.graphics.gdi.BeginPaint; 30 | const EndPaint = mod_root.graphics.gdi.EndPaint; 31 | const CreateSolidBrush = mod_root.graphics.gdi.CreateSolidBrush; 32 | const FillRect = mod_root.graphics.gdi.FillRect; 33 | const TextOutA = mod_root.graphics.gdi.TextOutA; 34 | const TextOutW = mod_root.graphics.gdi.TextOutW; 35 | const GetTextExtentPoint32A = mod_root.graphics.gdi.GetTextExtentPoint32A; 36 | const GetTextExtentPoint32W = mod_root.graphics.gdi.GetTextExtentPoint32W; 37 | const MESSAGEBOX_STYLE = mod_root.ui.windows_and_messaging.MESSAGEBOX_STYLE; 38 | const MessageBoxA = mod_root.ui.windows_and_messaging.MessageBoxA; 39 | const GetWindowLongPtrA = mod_root.ui.windows_and_messaging.GetWindowLongPtrA; 40 | const GetWindowLongPtrW = mod_root.ui.windows_and_messaging.GetWindowLongPtrW; 41 | const SetWindowLongPtrA = mod_root.ui.windows_and_messaging.SetWindowLongPtrA; 42 | const SetWindowLongPtrW = mod_root.ui.windows_and_messaging.SetWindowLongPtrW; 43 | const GetClientRect = mod_root.ui.windows_and_messaging.GetClientRect; 44 | const GetDpiForWindow = mod_root.ui.hi_dpi.GetDpiForWindow; 45 | }; 46 | 47 | const root = @import("root"); 48 | pub const UnicodeMode = enum { ansi, wide, unspecified }; 49 | pub const unicode_mode: UnicodeMode = if (@hasDecl(root, "UNICODE")) (if (root.UNICODE) .wide else .ansi) else .unspecified; 50 | 51 | const is_zig_0_11 = std.mem.eql(u8, builtin.zig_version_string, "0.11.0"); 52 | const zig_version_0_13 = std.SemanticVersion{ .major = 0, .minor = 13, .patch = 0 }; 53 | 54 | pub const L = std.unicode.utf8ToUtf16LeStringLiteral; 55 | 56 | pub const TCHAR = switch (unicode_mode) { 57 | .ansi => u8, 58 | .wide => u16, 59 | .unspecified => if (builtin.is_test) void else @compileError("'TCHAR' requires that UNICODE be set to true or false in the root module"), 60 | }; 61 | pub const _T = switch (unicode_mode) { 62 | .ansi => (struct { 63 | pub fn _T(comptime str: []const u8) *const [str.len:0]u8 { 64 | return str; 65 | } 66 | })._T, 67 | .wide => L, 68 | .unspecified => if (builtin.is_test) void else @compileError("'_T' requires that UNICODE be set to true or false in the root module"), 69 | }; 70 | 71 | pub const Arch = enum { X86, X64, Arm64 }; 72 | pub const arch: Arch = switch (builtin.target.cpu.arch) { 73 | .x86 => .X86, 74 | .x86_64 => .X64, 75 | .arm, .armeb, .aarch64 => .Arm64, 76 | else => @compileError("unhandled arch " ++ @tagName(builtin.target.cpu.arch)), 77 | }; 78 | 79 | // TODO: this should probably be in the standard lib somewhere? 80 | pub const Guid = extern union { 81 | Ints: extern struct { 82 | a: u32, 83 | b: u16, 84 | c: u16, 85 | d: [8]u8, 86 | }, 87 | Bytes: [16]u8, 88 | 89 | const big_endian_hex_offsets = [16]u6{ 0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34 }; 90 | const little_endian_hex_offsets = [16]u6{ 6, 4, 2, 0, 11, 9, 16, 14, 19, 21, 24, 26, 28, 30, 32, 34 }; 91 | 92 | const hex_offsets = if (is_zig_0_11) switch (builtin.target.cpu.arch.endian()) { 93 | .Big => big_endian_hex_offsets, 94 | .Little => little_endian_hex_offsets, 95 | } else switch (builtin.target.cpu.arch.endian()) { 96 | .big => big_endian_hex_offsets, 97 | .little => little_endian_hex_offsets, 98 | }; 99 | 100 | pub fn initString(s: []const u8) Guid { 101 | var guid = Guid{ .Bytes = undefined }; 102 | for (hex_offsets, 0..) |hex_offset, i| { 103 | //guid.Bytes[i] = decodeHexByte(s[offset..offset+2]); 104 | guid.Bytes[i] = decodeHexByte([2]u8{ s[hex_offset], s[hex_offset + 1] }); 105 | } 106 | return guid; 107 | } 108 | }; 109 | comptime { 110 | std.debug.assert(@sizeOf(Guid) == 16); 111 | } 112 | 113 | // TODO: is this in the standard lib somewhere? 114 | fn hexVal(c: u8) u4 { 115 | if (c <= '9') return @as(u4, @intCast(c - '0')); 116 | if (c >= 'a') return @as(u4, @intCast(c + 10 - 'a')); 117 | return @as(u4, @intCast(c + 10 - 'A')); 118 | } 119 | 120 | // TODO: is this in the standard lib somewhere? 121 | fn decodeHexByte(hex: [2]u8) u8 { 122 | return @as(u8, @intCast(hexVal(hex[0]))) << 4 | hexVal(hex[1]); 123 | } 124 | 125 | test "Guid" { 126 | if (is_zig_0_11) { 127 | try testing.expect(std.mem.eql(u8, switch (builtin.target.cpu.arch.endian()) { 128 | .Big => "\x01\x23\x45\x67\x89\xAB\xEF\x10\x32\x54\x76\x98\xba\xdc\xfe\x91", 129 | .Little => "\x67\x45\x23\x01\xAB\x89\x10\xEF\x32\x54\x76\x98\xba\xdc\xfe\x91", 130 | }, &Guid.initString("01234567-89AB-EF10-3254-7698badcfe91").Bytes)); 131 | } else { 132 | try testing.expect(std.mem.eql(u8, switch (builtin.target.cpu.arch.endian()) { 133 | .big => "\x01\x23\x45\x67\x89\xAB\xEF\x10\x32\x54\x76\x98\xba\xdc\xfe\x91", 134 | .little => "\x67\x45\x23\x01\xAB\x89\x10\xEF\x32\x54\x76\x98\xba\xdc\xfe\x91", 135 | }, &Guid.initString("01234567-89AB-EF10-3254-7698badcfe91").Bytes)); 136 | } 137 | } 138 | 139 | pub const PropertyKey = extern struct { 140 | fmtid: Guid, 141 | pid: u32, 142 | pub fn init(fmtid: []const u8, pid: u32) PropertyKey { 143 | return .{ 144 | .fmtid = Guid.initString(fmtid), 145 | .pid = pid, 146 | }; 147 | } 148 | }; 149 | 150 | pub fn FAILED(hr: win32.HRESULT) bool { 151 | return hr < 0; 152 | } 153 | pub fn SUCCEEDED(hr: win32.HRESULT) bool { 154 | return hr >= 0; 155 | } 156 | 157 | // These constants were removed from the metadata to allow each projection 158 | // to define them however they like (see https://github.com/microsoft/win32metadata/issues/530) 159 | pub const FALSE: win32.BOOL = 0; 160 | pub const TRUE: win32.BOOL = 1; 161 | 162 | /// Returns a formatter that will print the given error in the following format: 163 | /// 164 | /// ([...]) 165 | /// 166 | /// For example: 167 | /// 168 | /// 2 (The system cannot find the file specified.) 169 | /// 5 (Access is denied.) 170 | /// 171 | /// The error is formatted using FormatMessage into a stack allocated buffer 172 | /// of 300 bytes. If the message exceeds 300 bytes (Messages can be arbitrarily 173 | /// long) then "..." is appended to the message. The message may contain newlines 174 | /// and carriage returns but any trailing ones are trimmed. 175 | /// 176 | /// Provide the 's' fmt specifier to omit the error code. 177 | pub fn fmtError(error_code: u32) FormatError(300) { 178 | return .{ .error_code = error_code }; 179 | } 180 | pub fn FormatError(comptime max_len: usize) type { 181 | return struct { 182 | error_code: u32, 183 | pub fn format( 184 | self: @This(), 185 | comptime fmt: []const u8, 186 | options: std.fmt.FormatOptions, 187 | writer: anytype, 188 | ) @TypeOf(writer).Error!void { 189 | _ = options; 190 | 191 | const with_code = comptime blk: { 192 | if (std.mem.eql(u8, fmt, "")) break :blk true; 193 | if (std.mem.eql(u8, fmt, "s")) break :blk false; 194 | @compileError("expected '{}' or '{s}' but got '{" ++ fmt ++ "}'"); 195 | }; 196 | if (with_code) try writer.print("{} (", .{self.error_code}); 197 | var buf: [max_len]u8 = undefined; 198 | const len = win32.FormatMessageA( 199 | .{ .FROM_SYSTEM = 1, .IGNORE_INSERTS = 1 }, 200 | null, 201 | self.error_code, 202 | 0, 203 | @ptrCast(&buf), 204 | buf.len, 205 | null, 206 | ); 207 | if (len == 0) { 208 | try writer.writeAll("unknown error"); 209 | } 210 | const msg = std.mem.trimRight(u8, buf[0..len], "\r\n"); 211 | try writer.writeAll(msg); 212 | if (len + 1 >= buf.len) { 213 | try writer.writeAll("..."); 214 | } 215 | if (with_code) try writer.writeAll(")"); 216 | } 217 | }; 218 | } 219 | 220 | threadlocal var thread_is_panicing = false; 221 | 222 | pub const PanicType = switch (builtin.zig_version.order(zig_version_0_13)) { 223 | .lt, .eq => fn ([]const u8, ?*std.builtin.StackTrace, ?usize) noreturn, 224 | .gt => type, 225 | }; 226 | 227 | /// Returns a panic handler that can be set in your root module that will show the panic 228 | /// message to the user in a message box, then call the default builtin panic handler. 229 | /// It also handles re-entrancy by skipping the message box if the current thread 230 | /// is already panicing. 231 | pub fn messageBoxThenPanic( 232 | opt: struct { 233 | title: [:0]const u8, 234 | style: win32.MESSAGEBOX_STYLE = .{ .ICONASTERISK = 1 }, 235 | // TODO: add option/logic to include the stacktrace in the messagebox 236 | }, 237 | ) PanicType { 238 | switch (comptime builtin.zig_version.order(zig_version_0_13)) { 239 | .lt, .eq => return struct { 240 | pub fn panic( 241 | msg: []const u8, 242 | error_return_trace: ?*std.builtin.StackTrace, 243 | ret_addr: ?usize, 244 | ) noreturn { 245 | if (!thread_is_panicing) { 246 | thread_is_panicing = true; 247 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 248 | const msg_z: [:0]const u8 = if (std.fmt.allocPrintZ( 249 | arena.allocator(), 250 | "{s}", 251 | .{msg}, 252 | )) |msg_z| msg_z else |_| "failed allocate error message"; 253 | _ = win32.MessageBoxA(null, msg_z, opt.title, opt.style); 254 | } 255 | std.builtin.default_panic(msg, error_return_trace, ret_addr); 256 | } 257 | }.panic, 258 | .gt => {}, 259 | } 260 | return std.debug.FullPanic(struct { 261 | pub fn panic( 262 | msg: []const u8, 263 | ret_addr: ?usize, 264 | ) noreturn { 265 | if (!thread_is_panicing) { 266 | thread_is_panicing = true; 267 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 268 | const msg_z: [:0]const u8 = if (std.fmt.allocPrintZ( 269 | arena.allocator(), 270 | "{s}", 271 | .{msg}, 272 | )) |msg_z| msg_z else |_| "failed allocate error message"; 273 | _ = win32.MessageBoxA(null, msg_z, opt.title, opt.style); 274 | } 275 | std.debug.defaultPanic(msg, ret_addr); 276 | } 277 | }.panic); 278 | } 279 | 280 | /// Calls std.debug.panic with a message that indicates what failed and the 281 | /// associated win32 error code. 282 | pub fn panicWin32(what: []const u8, err: win32.WIN32_ERROR) noreturn { 283 | std.debug.panic("{s} failed, error={}", .{ what, err }); 284 | } 285 | 286 | /// Calls std.debug.panic with a message that indicates what failed and the 287 | /// associated hresult error code. 288 | pub fn panicHresult(what: []const u8, hresult: win32.HRESULT) noreturn { 289 | std.debug.panic("{s} failed, hresult=0x{x}", .{ what, @as(u32, @bitCast(hresult)) }); 290 | } 291 | 292 | /// calls CloseHandle, panics on failure 293 | pub fn closeHandle(handle: win32.HANDLE) void { 294 | if (0 == win32.CloseHandle(handle)) panicWin32( 295 | "CloseHandle", 296 | win32.GetLastError(), 297 | ); 298 | } 299 | 300 | pub fn xFromLparam(lparam: win32.LPARAM) i16 { 301 | return @bitCast(loword(lparam)); 302 | } 303 | pub fn yFromLparam(lparam: win32.LPARAM) i16 { 304 | return @bitCast(hiword(lparam)); 305 | } 306 | pub fn pointFromLparam(lparam: win32.LPARAM) win32.POINT { 307 | return win32.POINT{ .x = xFromLparam(lparam), .y = yFromLparam(lparam) }; 308 | } 309 | 310 | pub fn loword(value: anytype) u16 { 311 | switch (comptime builtin.zig_version.order(zig_version_0_13)) { 312 | .gt => switch (@typeInfo(@TypeOf(value))) { 313 | .int => |int| switch (int.signedness) { 314 | .signed => return loword(@as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = int.bits } }), @bitCast(value))), 315 | .unsigned => return if (int.bits <= 16) value else @intCast(0xffff & value), 316 | }, 317 | else => {}, 318 | }, 319 | .lt, .eq => switch (@typeInfo(@TypeOf(value))) { 320 | .Int => |int| switch (int.signedness) { 321 | .signed => return loword(@as(@Type(.{ .Int = .{ .signedness = .unsigned, .bits = int.bits } }), @bitCast(value))), 322 | .unsigned => return if (int.bits <= 16) value else @intCast(0xffff & value), 323 | }, 324 | else => {}, 325 | }, 326 | } 327 | @compileError("unsupported type " ++ @typeName(@TypeOf(value))); 328 | } 329 | pub fn hiword(value: anytype) u16 { 330 | switch (comptime builtin.zig_version.order(zig_version_0_13)) { 331 | .gt => switch (@typeInfo(@TypeOf(value))) { 332 | .int => |int| switch (int.signedness) { 333 | .signed => return hiword(@as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = int.bits } }), @bitCast(value))), 334 | .unsigned => return @intCast(0xffff & (value >> 16)), 335 | }, 336 | else => {}, 337 | }, 338 | .lt, .eq => switch (@typeInfo(@TypeOf(value))) { 339 | .Int => |int| switch (int.signedness) { 340 | .signed => return hiword(@as(@Type(.{ .Int = .{ .signedness = .unsigned, .bits = int.bits } }), @bitCast(value))), 341 | .unsigned => return @intCast(0xffff & (value >> 16)), 342 | }, 343 | else => {}, 344 | }, 345 | } 346 | @compileError("unsupported type " ++ @typeName(@TypeOf(value))); 347 | } 348 | 349 | pub const has_window_longptr = switch (arch) { 350 | .X86 => false, 351 | .X64, .Arm64 => true, 352 | }; 353 | 354 | pub const getWindowLongPtr = switch (unicode_mode) { 355 | .ansi => getWindowLongPtrA, 356 | .wide => getWindowLongPtrW, 357 | .unspecified => if (builtin.is_test) struct {} else @compileError( 358 | "getWindowLongPtr requires that UNICODE be set to true or false in the root module", 359 | ), 360 | }; 361 | 362 | pub const setWindowLongPtr = switch (unicode_mode) { 363 | .ansi => setWindowLongPtrA, 364 | .wide => setWindowLongPtrW, 365 | .unspecified => if (builtin.is_test) struct {} else @compileError( 366 | "setWindowLongPtr requires that UNICODE be set to true or false in the root module", 367 | ), 368 | }; 369 | 370 | pub fn getWindowLongPtrA(hwnd: win32.HWND, index: i32) usize { 371 | if (!has_window_longptr) @compileError("this arch does not have GetWindowLongPtr"); 372 | return @bitCast(win32.GetWindowLongPtrA(hwnd, @enumFromInt(index))); 373 | } 374 | pub fn getWindowLongPtrW(hwnd: win32.HWND, index: i32) usize { 375 | if (!has_window_longptr) @compileError("this arch does not have GetWindowLongPtr"); 376 | return @bitCast(win32.GetWindowLongPtrW(hwnd, @enumFromInt(index))); 377 | } 378 | pub fn setWindowLongPtrA(hwnd: win32.HWND, index: i32, value: usize) usize { 379 | if (!has_window_longptr) @compileError("this arch does not have SetWindowLongPtr"); 380 | return @bitCast(win32.SetWindowLongPtrA(hwnd, @enumFromInt(index), @bitCast(value))); 381 | } 382 | pub fn setWindowLongPtrW(hwnd: win32.HWND, index: i32, value: usize) usize { 383 | if (!has_window_longptr) @compileError("this arch does not have SetWindowLongPtr"); 384 | return @bitCast(win32.SetWindowLongPtrW(hwnd, @enumFromInt(index), @bitCast(value))); 385 | } 386 | 387 | /// calls DpiForWindow, panics on failure 388 | pub fn dpiFromHwnd(hwnd: win32.HWND) u32 { 389 | const value = win32.GetDpiForWindow(hwnd); 390 | if (value == 0) panicWin32("GetDpiForWindow", win32.GetLastError()); 391 | return value; 392 | } 393 | 394 | /// Converts the given DPI to a floating point scale where 96 returns 1.0, 120 return 1.25 and so on. 395 | pub fn scaleFromDpi(comptime Float: type, dpi: u32) Float { 396 | return @as(Float, @floatFromInt(dpi)) / @as(Float, 96.0); 397 | } 398 | 399 | pub fn scaleDpi(comptime T: type, value: T, dpi: u32) T { 400 | std.debug.assert(dpi >= 96); 401 | return switch (comptime builtin.zig_version.order(zig_version_0_13)) { 402 | .gt => switch (@typeInfo(T)) { 403 | .float => value * scaleFromDpi(T, dpi), 404 | .int => @intFromFloat(@round(@as(f32, @floatFromInt(value)) * scaleFromDpi(f32, dpi))), 405 | else => @compileError("scale_dpi does not support type " ++ @typeName(@TypeOf(value))), 406 | }, 407 | .lt, .eq => switch (@typeInfo(T)) { 408 | .Float => value * scaleFromDpi(T, dpi), 409 | .Int => @intFromFloat(@round(@as(f32, @floatFromInt(value)) * scaleFromDpi(f32, dpi))), 410 | else => @compileError("scale_dpi does not support type " ++ @typeName(@TypeOf(value))), 411 | }, 412 | }; 413 | } 414 | 415 | /// wrapper for GetClientRect, panics on failure 416 | pub fn getClientSize(hwnd: win32.HWND) win32.SIZE { 417 | var rect: win32.RECT = undefined; 418 | if (0 == win32.GetClientRect(hwnd, &rect)) 419 | panicWin32("GetClientRect", win32.GetLastError()); 420 | std.debug.assert(rect.left == 0); 421 | std.debug.assert(rect.top == 0); 422 | return .{ .cx = rect.right, .cy = rect.bottom }; 423 | } 424 | 425 | /// Converts comptime values to the given type. 426 | /// Note that this function is called at compile time rather than converting constant values earlier at code generation time. 427 | /// The reason for doing it a compile time is because genzig.zig generates all constants as they are encountered which can 428 | /// be before it knows the constant's type definition, so we delay the convession to compile-time where the compiler knows 429 | /// all type definition. 430 | pub fn typedConst(comptime T: type, comptime value: anytype) T { 431 | return switch (comptime builtin.zig_version.order(zig_version_0_13)) { 432 | .gt => typedConst2(T, T, value), 433 | .lt, .eq => typedConst2_0_13(T, T, value), 434 | }; 435 | } 436 | 437 | fn typedConst2(comptime ReturnType: type, comptime SwitchType: type, comptime value: anytype) ReturnType { 438 | const target_type_error = @as([]const u8, "typedConst cannot convert to " ++ @typeName(ReturnType)); 439 | const value_type_error = @as([]const u8, "typedConst cannot convert " ++ @typeName(@TypeOf(value)) ++ " to " ++ @typeName(ReturnType)); 440 | 441 | switch (@typeInfo(SwitchType)) { 442 | .int => |target_type_info| { 443 | if (value >= std.math.maxInt(SwitchType)) { 444 | if (target_type_info.signedness == .signed) { 445 | const UnsignedT = @Type(std.builtin.Type{ .int = .{ .signedness = .unsigned, .bits = target_type_info.bits } }); 446 | return @as(SwitchType, @bitCast(@as(UnsignedT, value))); 447 | } 448 | } 449 | return value; 450 | }, 451 | .pointer => |target_type_info| switch (target_type_info.size) { 452 | .one, .many, .c => { 453 | switch (@typeInfo(@TypeOf(value))) { 454 | .comptime_int, .int => { 455 | const usize_value = if (value >= 0) value else @as(usize, @bitCast(@as(isize, value))); 456 | return @as(ReturnType, @ptrFromInt(usize_value)); 457 | }, 458 | else => @compileError(value_type_error), 459 | } 460 | }, 461 | else => target_type_error, 462 | }, 463 | .optional => |target_type_info| switch (@typeInfo(target_type_info.child)) { 464 | .pointer => return typedConst2(ReturnType, target_type_info.child, value), 465 | else => target_type_error, 466 | }, 467 | .@"enum" => |_| switch (@typeInfo(@TypeOf(value))) { 468 | .Int => return @as(ReturnType, @enumFromInt(value)), 469 | else => target_type_error, 470 | }, 471 | else => @compileError(target_type_error), 472 | } 473 | } 474 | fn typedConst2_0_13(comptime ReturnType: type, comptime SwitchType: type, comptime value: anytype) ReturnType { 475 | const target_type_error = @as([]const u8, "typedConst cannot convert to " ++ @typeName(ReturnType)); 476 | const value_type_error = @as([]const u8, "typedConst cannot convert " ++ @typeName(@TypeOf(value)) ++ " to " ++ @typeName(ReturnType)); 477 | 478 | switch (@typeInfo(SwitchType)) { 479 | .Int => |target_type_info| { 480 | if (value >= std.math.maxInt(SwitchType)) { 481 | if (target_type_info.signedness == .signed) { 482 | const UnsignedT = @Type(std.builtin.Type{ .Int = .{ .signedness = .unsigned, .bits = target_type_info.bits } }); 483 | return @as(SwitchType, @bitCast(@as(UnsignedT, value))); 484 | } 485 | } 486 | return value; 487 | }, 488 | .Pointer => |target_type_info| switch (target_type_info.size) { 489 | .One, .Many, .C => { 490 | switch (@typeInfo(@TypeOf(value))) { 491 | .ComptimeInt, .Int => { 492 | const usize_value = if (value >= 0) value else @as(usize, @bitCast(@as(isize, value))); 493 | return @as(ReturnType, @ptrFromInt(usize_value)); 494 | }, 495 | else => @compileError(value_type_error), 496 | } 497 | }, 498 | else => target_type_error, 499 | }, 500 | .Optional => |target_type_info| switch (@typeInfo(target_type_info.child)) { 501 | .Pointer => return typedConst2_0_13(ReturnType, target_type_info.child, value), 502 | else => target_type_error, 503 | }, 504 | .Enum => |_| switch (@typeInfo(@TypeOf(value))) { 505 | .Int => return @as(ReturnType, @enumFromInt(value)), 506 | else => target_type_error, 507 | }, 508 | else => @compileError(target_type_error), 509 | } 510 | } 511 | test "typedConst" { 512 | try testing.expectEqual(@as(usize, @bitCast(@as(isize, -1))), @intFromPtr(typedConst(?*opaque {}, -1))); 513 | try testing.expectEqual(@as(usize, @bitCast(@as(isize, -12))), @intFromPtr(typedConst(?*opaque {}, -12))); 514 | try testing.expectEqual(@as(u32, 0xffffffff), typedConst(u32, 0xffffffff)); 515 | try testing.expectEqual(@as(i32, @bitCast(@as(u32, 0x80000000))), typedConst(i32, 0x80000000)); 516 | } 517 | 518 | // ============================================================================= 519 | // GDI function wrappers 520 | // ============================================================================= 521 | 522 | /// calls DeleteObject, panics on failure 523 | pub fn deleteObject(obj: win32.HGDIOBJ) void { 524 | if (0 == win32.DeleteObject(obj)) panicWin32("DeleteObject", win32.GetLastError()); 525 | } 526 | 527 | /// calls DeleteDC, panics on failure 528 | pub fn deleteDc(hdc: win32.HDC) void { 529 | if (0 == win32.DeleteDC(hdc)) panicWin32("DeleteDC", win32.GetLastError()); 530 | } 531 | 532 | /// calls InvalidateRect, panics on failure 533 | pub fn invalidateHwnd(hwnd: win32.HWND) void { 534 | if (0 == win32.InvalidateRect(hwnd, null, 0)) panicWin32("InvalidateRect", win32.GetLastError()); 535 | } 536 | 537 | /// calls BeginPaint, panics on failure 538 | pub fn beginPaint(hwnd: win32.HWND) struct { win32.HDC, win32.PAINTSTRUCT } { 539 | var paintstruct: win32.PAINTSTRUCT = undefined; 540 | const hdc = win32.BeginPaint(hwnd, &paintstruct) orelse panicWin32( 541 | "BeginPaint", 542 | win32.GetLastError(), 543 | ); 544 | return .{ hdc, paintstruct }; 545 | } 546 | /// calls EndPaint, panics on failure 547 | pub fn endPaint(hwnd: win32.HWND, paintstruct: *const win32.PAINTSTRUCT) void { 548 | if (0 == win32.EndPaint(hwnd, paintstruct)) panicWin32( 549 | "EndPaint", 550 | win32.GetLastError(), 551 | ); 552 | } 553 | 554 | /// calls CreateSolidBrush, panics on failure 555 | pub fn createSolidBrush(color: u32) win32.HBRUSH { 556 | return win32.CreateSolidBrush(color) orelse panicWin32( 557 | "CreateSolidBrush", 558 | win32.GetLastError(), 559 | ); 560 | } 561 | 562 | /// calls FillRect, panics on failure 563 | pub fn fillRect(hdc: win32.HDC, rect: win32.RECT, brush: win32.HBRUSH) void { 564 | if (0 == win32.FillRect(hdc, &rect, brush)) panicWin32( 565 | "FillRect", 566 | win32.GetLastError(), 567 | ); 568 | } 569 | 570 | /// calls TextOutA, panics on failure 571 | pub fn textOutA(hdc: win32.HDC, x: i32, y: i32, msg: []const u8) void { 572 | if (0 == win32.TextOutA(hdc, x, y, @ptrCast(msg.ptr), @intCast(msg.len))) panicWin32( 573 | "TextOut", 574 | win32.GetLastError(), 575 | ); 576 | } 577 | 578 | /// calls TextOutW, panics on failure 579 | pub fn textOutW(hdc: win32.HDC, x: i32, y: i32, msg: []const u16) void { 580 | if (0 == win32.TextOutW(hdc, x, y, @ptrCast(msg.ptr), @intCast(msg.len))) panicWin32( 581 | "TextOut", 582 | win32.GetLastError(), 583 | ); 584 | } 585 | 586 | /// calls GetTextExtentPoint32A, panics on failure 587 | pub fn getTextExtentA(hdc: win32.HDC, str: []const u8) win32.SIZE { 588 | var size: win32.SIZE = undefined; 589 | if (0 == win32.GetTextExtentPoint32A(hdc, @ptrCast(str.ptr), @intCast(str.len), &size)) panicWin32( 590 | "GetTextExtentPoint32A", 591 | win32.GetLastError(), 592 | ); 593 | return size; 594 | } 595 | 596 | /// calls GetTextExtentPoint32W, panics on failure 597 | pub fn getTextExtentW(hdc: win32.HDC, str: []const u16) win32.SIZE { 598 | var size: win32.SIZE = undefined; 599 | if (0 == win32.GetTextExtentPoint32W(hdc, @ptrCast(str.ptr), @intCast(str.len), &size)) panicWin32( 600 | "GetTextExtentPoint32W", 601 | win32.GetLastError(), 602 | ); 603 | return size; 604 | } 605 | -------------------------------------------------------------------------------- /src/static/zig.mod: -------------------------------------------------------------------------------- 1 | id: o6ogpor87xc23o863qaqfciqqdnt48nlj0395dk1xt4m9b34 2 | name: win32 3 | main: win32.zig 4 | license: MIT 5 | description: "Zig bindings for Win32 generated by https://github.com/marlersoft/zigwin32gen" 6 | dependencies: 7 | -------------------------------------------------------------------------------- /test/badcomoverload.zig: -------------------------------------------------------------------------------- 1 | const win32 = struct { 2 | usingnamespace @import("win32").graphics.direct2d; 3 | }; 4 | 5 | pub fn main() void { 6 | var elem: *win32.ID2D1SvgElement = undefined; 7 | elem.GetAttributeValue(0, 0, 0); 8 | } 9 | -------------------------------------------------------------------------------- /test/comoverload.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const WINAPI = std.os.windows.WINAPI; 3 | const win32 = struct { 4 | usingnamespace @import("win32").foundation; 5 | usingnamespace @import("win32").graphics.direct2d; 6 | usingnamespace @import("win32").zig; 7 | }; 8 | 9 | fn GetAttributeValueString( 10 | self: *const win32.ID2D1SvgElement, 11 | name: ?[*:0]const u16, 12 | @"type": win32.D2D1_SVG_ATTRIBUTE_STRING_TYPE, 13 | value: [*:0]u16, 14 | valueCount: u32, 15 | ) callconv(WINAPI) win32.HRESULT { 16 | _ = self; 17 | _ = name; 18 | _ = @"type"; 19 | _ = value; 20 | _ = valueCount; 21 | return 0; 22 | } 23 | 24 | pub fn main() void { 25 | var vtable: win32.ID2D1SvgElement.VTable = undefined; 26 | vtable.GetAttributeValueString = &GetAttributeValueString; 27 | var element_instance: win32.ID2D1SvgElement = .{ 28 | .vtable = &vtable, 29 | }; 30 | const element = &element_instance; 31 | var value_buf: [10:0]u16 = undefined; 32 | std.debug.assert(0 == element.GetAttributeValueString( 33 | win32.L("Hello"), 34 | .SVG, 35 | &value_buf, 36 | value_buf.len, 37 | )); 38 | } 39 | -------------------------------------------------------------------------------- /update-merged-branch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Updates origin/merged with the latest set of commits from all release branches 4 | # 5 | import sys 6 | import os 7 | import subprocess 8 | import functools 9 | 10 | SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 11 | ZIGWIN32_DIR = os.path.join(SCRIPT_DIR, "zigwin32") 12 | 13 | def gitRun(git_args, **kwargs): 14 | if "allow_fail" in kwargs: 15 | allow_fail = kwargs["allow_fail"] 16 | del kwargs["allow_fail"] 17 | else: 18 | allow_fail = False 19 | args = ["git", "-C", ZIGWIN32_DIR] + git_args 20 | print("[RUN] {}".format(" ".join(args))) 21 | result = subprocess.run(args, **kwargs) 22 | capture_output = kwargs.get('capture_output', False) 23 | if capture_output: 24 | sys.stderr.buffer.write(result.stderr) 25 | if (not allow_fail) and result.returncode != 0: 26 | sys.exit(result.returncode) 27 | if capture_output: 28 | if allow_fail: 29 | return { "returncode": result.returncode, "stdout": result.stdout } 30 | return result.stdout 31 | if allow_fail: 32 | return result.returncode 33 | 34 | class Branch: 35 | def __init__(self, full): 36 | self.full = full 37 | name = os.path.basename(full) 38 | self.name = name 39 | preview_index = name.index("-preview") 40 | self.parts = name[:preview_index].split(".") 41 | suffix = name[preview_index+8:] 42 | if suffix: 43 | if not suffix.startswith("."): 44 | sys.exit(f"expected '.' after '-preview' but got '{name}'") 45 | self.suffix = int(suffix[1:]) 46 | else: 47 | self.suffix = None 48 | def __repr__(self): 49 | return self.name 50 | def compare(self, right): 51 | i = 0 52 | while True: 53 | if i == len(self.parts): 54 | if i == len(right.parts): 55 | break; 56 | sys.exit("todo or error?") 57 | self_i = int(self.parts[i]) 58 | right_i = int(right.parts[i]) 59 | if self_i < right_i: 60 | return -1 61 | if self_i > right_i: 62 | return 1 63 | i += 1 64 | 65 | if self.suffix: 66 | if not right.suffix: 67 | sys.exit("todo or error?") 68 | return self.suffix - right.suffix 69 | else: 70 | if right.suffix: 71 | sys.exit("todo or error?") 72 | return 0 73 | 74 | def hasCommit(refspec, sha): 75 | result = gitRun(["merge-base", refspec, sha], capture_output=True, allow_fail=True) 76 | returncode = result["returncode"] 77 | merge_base = result["stdout"].decode("utf8").strip() 78 | if (returncode == 0) and (merge_base == sha): 79 | #if returncode == 0: 80 | return True 81 | elif (returncode != 0) and (len(merge_base) == 0): 82 | return False 83 | return False 84 | sys.exit(f"error: unexpected merge-base output returncode={returncode}, stdout='{merge_base}'") 85 | 86 | def getShas(refspec): 87 | return gitRun(["log", "--reverse", "--pretty=%H", refspec, "--"], capture_output=True).decode("utf8").strip().split() 88 | 89 | def findMissingSha(branches): 90 | for branch_index, branch in enumerate(branches): 91 | print(f"Checking if {branch} is in origin/merged") 92 | shas = getShas(branch.full) 93 | # shas are ordered oldest to newest 94 | for sha_index, sha in enumerate(shas): 95 | if not hasCommit("origin/merged", sha): 96 | return (branch_index, shas, sha_index) 97 | print(f" {sha} YES") 98 | return (None, None, None) 99 | 100 | def main(): 101 | gitRun(["fetch"]) 102 | gitRun(["reset", "--hard", "HEAD"]) 103 | gitRun(["clean", "-xfd"]) 104 | 105 | raw_branches = gitRun(["branch", "--remotes", "--format", "%(refname)"], capture_output=True).decode("utf8").split() 106 | branches = [] 107 | for raw_branch in raw_branches: 108 | name = os.path.basename(raw_branch) 109 | if "-preview" in name: 110 | branches.append(Branch(raw_branch)) 111 | else: 112 | print(f"Ignoring Branch {name}") 113 | branches.sort(key=functools.cmp_to_key(Branch.compare)) 114 | 115 | (branch_index, shas, sha_index) = findMissingSha(branches) 116 | if branch_index == None: 117 | print("Success: origin/merged already contains all commits") 118 | return 119 | 120 | print("First missing Sha:") 121 | print(f" Branch '{branches[branch_index]}' ({branch_index+1} out of {len(branches)})") 122 | print(f" Sha {sha_index+1} out of {len(shas)}: {shas[sha_index]}") 123 | 124 | # detach in case we are on the merged branch we are about to delete 125 | gitRun(["checkout", "--detach"]) 126 | gitRun(["branch", "-D", "merged"], allow_fail=True) 127 | gitRun(["checkout", "origin/merged", "-b", "merged"]) 128 | 129 | for branch in branches[branch_index:]: 130 | print(f"Processing {branch}...") 131 | shas = getShas(branch.full) 132 | for sha_index, sha in enumerate(shas): 133 | if hasCommit("HEAD", sha): 134 | print(f" {sha} already merged") 135 | continue 136 | body = gitRun([ 137 | "log", 138 | "--format=%B", 139 | "-n", "1", 140 | sha, 141 | ], capture_output=True).decode("utf8") 142 | gitRun([ 143 | "merge", 144 | "-m", f"{branch} commit {sha_index+1}/{len(shas)}: {body}", 145 | "--allow-unrelated-histories", sha, 146 | "--strategy-option", "theirs", 147 | ]) 148 | 149 | gitRun(["push", "origin", "merged", "-f"]) 150 | print("Success: updated origin/merged with latest set of commits") 151 | 152 | main() 153 | --------------------------------------------------------------------------------