@zogien/native-sdk 0.11.0-fork.2 → 0.11.0-fork.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/build/app.zig +47 -23
- package/build/cli.zig +22 -0
- package/native-sdk.d.ts +2 -0
- package/package.json +10 -10
- package/packages/core/package-lock.json +2 -2
- package/packages/core/package.json +1 -1
- package/schemas/app.schema.json +9 -1
- package/skill-data/ts-core/SKILL.md +2 -0
- package/src/app_runner/app_assets.zig +85 -1
- package/src/app_runner/root.zig +63 -3
- package/src/app_runner/ts_core_main.zig +8 -0
- package/src/platform/macos/appkit_host.h +9 -0
- package/src/platform/macos/appkit_host.m +366 -0
- package/src/platform/macos/root.zig +29 -6
- package/src/platform/null_platform.zig +66 -0
- package/src/platform/root.zig +1 -0
- package/src/platform/types.zig +46 -0
- package/src/platform/windows/root.zig +174 -0
- package/src/platform/windows/scriptc_mingw_compat.c +248 -0
- package/src/platform/windows/webview2_host.cpp +500 -26
- package/src/primitives/app_manifest/root.zig +1 -0
- package/src/primitives/app_manifest/tests.zig +35 -0
- package/src/primitives/app_manifest/types.zig +12 -0
- package/src/primitives/app_manifest/validation.zig +44 -6
- package/src/root.zig +1 -0
- package/src/runtime/bridge_responses.zig +2 -0
- package/src/runtime/channel_sources.zig +49 -1
- package/src/runtime/core.zig +5 -0
- package/src/runtime/effects.zig +12 -1
- package/src/runtime/flow.zig +6 -0
- package/src/runtime/flow_reference.zig +6 -0
- package/src/runtime/root.zig +4 -0
- package/src/runtime/single_instance.zig +893 -0
- package/src/runtime/tests.zig +1 -0
- package/src/runtime/window_controls.zig +73 -0
- package/src/runtime/window_controls_tests.zig +192 -0
- package/src/runtime/window_storage.zig +3 -0
- package/src/tooling/manifest.zig +111 -3
- package/src/tooling/package.zig +98 -8
- package/src/tooling/raw_manifest.zig +7 -0
- package/src/tooling/root.zig +2 -0
- package/src/tooling/templates.zig +53 -3
- package/src/tooling/update.zig +228 -10
- package/src/tooling/windows_installer.zig +245 -0
- package/src/tooling/zip_writer.zig +211 -0
- package/src/updater/engine.zig +936 -0
- package/src/updater/feed.zig +261 -12
- package/src/updater/macos.zig +187 -0
- package/src/updater/root.zig +41 -0
- package/src/updater/windows.zig +470 -0
- package/src/windows_install/config.zig +247 -0
- package/src/windows_install/main.zig +690 -0
- package/src/windows_install/payload.zig +146 -0
- package/src/windows_install/switch.zig +184 -0
- package/src/windows_install/tests.zig +11 -0
- package/src/windows_install/win32.zig +248 -0
- package/tools/native-sdk/main.zig +10 -9
|
@@ -125,6 +125,9 @@ const WindowsEvent = extern struct {
|
|
|
125
125
|
/// selection).
|
|
126
126
|
widget_id: u64,
|
|
127
127
|
menu_item_id: u32,
|
|
128
|
+
/// Window events: nonzero while the window is maximized. Every emit
|
|
129
|
+
/// carries the live value, so any frame event reports it.
|
|
130
|
+
maximized: c_int,
|
|
128
131
|
};
|
|
129
132
|
|
|
130
133
|
const WindowsCallback = *const fn (context: ?*anyopaque, event: *const WindowsEvent) callconv(.c) void;
|
|
@@ -166,6 +169,9 @@ extern fn native_sdk_windows_minimize_window(host: *WindowsHost, window_id: u64)
|
|
|
166
169
|
extern fn native_sdk_windows_hide_window(host: *WindowsHost, window_id: u64) c_int;
|
|
167
170
|
extern fn native_sdk_windows_show_window(host: *WindowsHost, window_id: u64) c_int;
|
|
168
171
|
extern fn native_sdk_windows_set_window_close_policy(host: *WindowsHost, window_id: u64, close_policy: c_int) c_int;
|
|
172
|
+
extern fn native_sdk_windows_maximize_window(host: *WindowsHost, window_id: u64) c_int;
|
|
173
|
+
extern fn native_sdk_windows_unmaximize_window(host: *WindowsHost, window_id: u64) c_int;
|
|
174
|
+
extern fn native_sdk_windows_request_close_window(host: *WindowsHost, window_id: u64) c_int;
|
|
169
175
|
extern fn native_sdk_windows_create_view(host: *WindowsHost, window_id: u64, label: [*]const u8, label_len: usize, kind: c_int, gpu_backend_request: c_int, parent: [*]const u8, parent_len: usize, x: f64, y: f64, width: f64, height: f64, layer: c_int, visible: c_int, enabled: c_int, role: [*]const u8, role_len: usize, accessibility_label: [*]const u8, accessibility_label_len: usize, text: [*]const u8, text_len: usize, command: [*]const u8, command_len: usize) c_int;
|
|
170
176
|
extern fn native_sdk_windows_update_view(host: *WindowsHost, window_id: u64, label: [*]const u8, label_len: usize, has_frame: c_int, x: f64, y: f64, width: f64, height: f64, has_layer: c_int, layer: c_int, has_visible: c_int, visible: c_int, has_enabled: c_int, enabled: c_int, has_role: c_int, role: [*]const u8, role_len: usize, has_accessibility_label: c_int, accessibility_label: [*]const u8, accessibility_label_len: usize, has_text: c_int, text: [*]const u8, text_len: usize, has_command: c_int, command: [*]const u8, command_len: usize) c_int;
|
|
171
177
|
extern fn native_sdk_windows_set_view_frame(host: *WindowsHost, window_id: u64, label: [*]const u8, label_len: usize, x: f64, y: f64, width: f64, height: f64) c_int;
|
|
@@ -438,6 +444,9 @@ pub const WindowsPlatform = struct {
|
|
|
438
444
|
.focus_window_fn = focusWindow,
|
|
439
445
|
.close_window_fn = closeWindow,
|
|
440
446
|
.minimize_window_fn = minimizeWindow,
|
|
447
|
+
.maximize_window_fn = maximizeWindow,
|
|
448
|
+
.unmaximize_window_fn = unmaximizeWindow,
|
|
449
|
+
.request_close_window_fn = requestCloseWindow,
|
|
441
450
|
.hide_window_fn = hideWindow,
|
|
442
451
|
.show_window_fn = showWindow,
|
|
443
452
|
.quit_app_fn = quitApp,
|
|
@@ -658,6 +667,7 @@ fn windowsCallback(context: ?*anyopaque, event: *const WindowsEvent) callconv(.c
|
|
|
658
667
|
.open = event.open != 0,
|
|
659
668
|
.focused = event.focused != 0,
|
|
660
669
|
.hidden = event.hidden != 0,
|
|
670
|
+
.maximized = event.maximized != 0,
|
|
661
671
|
} });
|
|
662
672
|
},
|
|
663
673
|
.view_focused => state.emit(.{ .view_focused = .{
|
|
@@ -1036,6 +1046,36 @@ fn minimizeWindow(context: ?*anyopaque, window_id: platform_mod.WindowId) anyerr
|
|
|
1036
1046
|
if (native_sdk_windows_minimize_window(self.host, window_id) == 0) return error.WindowNotFound;
|
|
1037
1047
|
}
|
|
1038
1048
|
|
|
1049
|
+
fn maximizeWindow(context: ?*anyopaque, window_id: platform_mod.WindowId) anyerror!void {
|
|
1050
|
+
const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
|
|
1051
|
+
return windowControlResult(native_sdk_windows_maximize_window(self.host, window_id));
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
fn unmaximizeWindow(context: ?*anyopaque, window_id: platform_mod.WindowId) anyerror!void {
|
|
1055
|
+
const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
|
|
1056
|
+
return windowControlResult(native_sdk_windows_unmaximize_window(self.host, window_id));
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
fn requestCloseWindow(context: ?*anyopaque, window_id: platform_mod.WindowId) anyerror!void {
|
|
1060
|
+
const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
|
|
1061
|
+
return switch (native_sdk_windows_request_close_window(self.host, window_id)) {
|
|
1062
|
+
1 => {},
|
|
1063
|
+
0 => error.WindowNotFound,
|
|
1064
|
+
else => error.CloseFailed,
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/// The maximize/unmaximize host status codes (see webview2_host.cpp).
|
|
1069
|
+
fn windowControlResult(status: c_int) anyerror!void {
|
|
1070
|
+
return switch (status) {
|
|
1071
|
+
1 => {},
|
|
1072
|
+
0 => error.WindowNotFound,
|
|
1073
|
+
-1 => error.WindowNotResizable,
|
|
1074
|
+
-2 => error.WindowHidden,
|
|
1075
|
+
else => error.Unexpected,
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1039
1079
|
fn hideWindow(context: ?*anyopaque, window_id: platform_mod.WindowId) anyerror!void {
|
|
1040
1080
|
const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
|
|
1041
1081
|
if (native_sdk_windows_hide_window(self.host, window_id) == 0) return error.WindowNotFound;
|
|
@@ -2687,6 +2727,43 @@ test "windows webview focus reports the focused child label" {
|
|
|
2687
2727
|
try std.testing.expect(std.mem.indexOf(u8, host_source, "event.view_label = focused->second.label.c_str();") != null);
|
|
2688
2728
|
}
|
|
2689
2729
|
|
|
2730
|
+
test "windows webview2 keeps its user data with the app's per-user data" {
|
|
2731
|
+
// WebView2's default user data folder sits beside the executable,
|
|
2732
|
+
// which is read-only under Program Files and is swapped away with
|
|
2733
|
+
// the program files on update. Every environment the host creates
|
|
2734
|
+
// names the folder beside the app_dirs roots instead, and an earlier
|
|
2735
|
+
// "<exe>.WebView2" folder moves there once.
|
|
2736
|
+
const host_source = @embedFile("webview2_host.cpp");
|
|
2737
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "factory(nullptr, webViewUserDataFolder(host), nullptr,") != null);
|
|
2738
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "factory(nullptr, nullptr, nullptr,") == null);
|
|
2739
|
+
const resolve_at = std.mem.indexOf(u8, host_source, "static const wchar_t *webViewUserDataFolder(Host *host) {") orelse return error.TestExpectedEqual;
|
|
2740
|
+
const resolve = host_source[resolve_at..];
|
|
2741
|
+
try std.testing.expect(std.mem.indexOf(u8, resolve, "GetEnvironmentVariableW(L\"LOCALAPPDATA\"") != null);
|
|
2742
|
+
try std.testing.expect(std.mem.indexOf(u8, resolve, "const std::wstring folder = app_root + L\"\\\\WebView2\";") != null);
|
|
2743
|
+
try std.testing.expect(std.mem.indexOf(u8, resolve, "MoveFileExW(legacy.c_str(), folder.c_str(), 0)") != null);
|
|
2744
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "return path + L\".WebView2\";") != null);
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
test "windows webview assets resolve against the packaged resources beside bin" {
|
|
2748
|
+
// `native package` stages bin\<exe> and the app-relative asset tree
|
|
2749
|
+
// in resources\; a relative asset root names that tree when the app
|
|
2750
|
+
// runs from a package and the working directory otherwise.
|
|
2751
|
+
const host_source = @embedFile("webview2_host.cpp");
|
|
2752
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "std::string path = assetRootPath(webview.asset_root);") != null);
|
|
2753
|
+
const resources_at = std.mem.indexOf(u8, host_source, "static const std::wstring &packagedResourcesDirectory() {") orelse return error.TestExpectedEqual;
|
|
2754
|
+
const resources = host_source[resources_at..];
|
|
2755
|
+
try std.testing.expect(std.mem.indexOf(u8, resources, "CompareStringOrdinal(bin.c_str() + bin_slash + 1, -1, L\"bin\", -1, TRUE)") != null);
|
|
2756
|
+
try std.testing.expect(std.mem.indexOf(u8, resources, "return bin.substr(0, bin_slash) + L\"\\\\resources\";") != null);
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
test "windows names the process by its bundle id for the taskbar" {
|
|
2760
|
+
// The installer's Start menu shortcut carries the bundle id as its
|
|
2761
|
+
// AppUserModelID; the process must claim the same id.
|
|
2762
|
+
const host_source = @embedFile("webview2_host.cpp");
|
|
2763
|
+
const create_at = std.mem.indexOf(u8, host_source, "host->bundle_id = slice(bundle_id, bundle_id_len);") orelse return error.TestExpectedEqual;
|
|
2764
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source[create_at..], "SetCurrentProcessExplicitAppUserModelID(app_user_model_id.c_str());") != null);
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2690
2767
|
test "windows audio event maps kinds and payload" {
|
|
2691
2768
|
var event = std.mem.zeroes(WindowsEvent);
|
|
2692
2769
|
event.audio_kind = 1;
|
|
@@ -2760,3 +2837,100 @@ fn flattenFilters(filters: []const platform_mod.FileFilter, buffer: []u8) []cons
|
|
|
2760
2837
|
test "windows platform module exports type" {
|
|
2761
2838
|
_ = WindowsPlatform;
|
|
2762
2839
|
}
|
|
2840
|
+
|
|
2841
|
+
test "windows event declares the same fields in the same order as the C host" {
|
|
2842
|
+
// WindowsEvent is written twice, here and in webview2_host.cpp, and
|
|
2843
|
+
// crosses the C ABI by layout. A field added on one side only, or at
|
|
2844
|
+
// a different position, shifts every later field without a compile
|
|
2845
|
+
// error, so the two declarations are compared by name and order.
|
|
2846
|
+
const field_names = comptime blk: {
|
|
2847
|
+
const fields = @typeInfo(WindowsEvent).@"struct".fields;
|
|
2848
|
+
var names: [fields.len][]const u8 = undefined;
|
|
2849
|
+
for (fields, 0..) |field, index| names[index] = field.name;
|
|
2850
|
+
break :blk names;
|
|
2851
|
+
};
|
|
2852
|
+
const host_source = @embedFile("webview2_host.cpp");
|
|
2853
|
+
const header = "struct WindowsEvent {";
|
|
2854
|
+
const start = (std.mem.indexOf(u8, host_source, header) orelse return error.TestExpectedEqual) + header.len;
|
|
2855
|
+
const end = std.mem.indexOfPos(u8, host_source, start, "};") orelse return error.TestExpectedEqual;
|
|
2856
|
+
var lines = std.mem.splitScalar(u8, host_source[start..end], '\n');
|
|
2857
|
+
var count: usize = 0;
|
|
2858
|
+
var in_comment = false;
|
|
2859
|
+
while (lines.next()) |raw| {
|
|
2860
|
+
const line = std.mem.trim(u8, raw, " \t\r");
|
|
2861
|
+
if (in_comment) {
|
|
2862
|
+
if (std.mem.indexOf(u8, line, "*/") != null) in_comment = false;
|
|
2863
|
+
continue;
|
|
2864
|
+
}
|
|
2865
|
+
if (std.mem.startsWith(u8, line, "/*")) {
|
|
2866
|
+
if (std.mem.indexOf(u8, line, "*/") == null) in_comment = true;
|
|
2867
|
+
continue;
|
|
2868
|
+
}
|
|
2869
|
+
if (!std.mem.endsWith(u8, line, ";")) continue;
|
|
2870
|
+
var declaration = line[0 .. line.len - 1];
|
|
2871
|
+
if (std.mem.indexOfScalar(u8, declaration, '[')) |bracket| declaration = declaration[0..bracket];
|
|
2872
|
+
const name_start = (std.mem.lastIndexOfAny(u8, declaration, " *") orelse return error.TestExpectedEqual) + 1;
|
|
2873
|
+
try std.testing.expect(count < field_names.len);
|
|
2874
|
+
try std.testing.expectEqualStrings(field_names[count], declaration[name_start..]);
|
|
2875
|
+
count += 1;
|
|
2876
|
+
}
|
|
2877
|
+
try std.testing.expectEqual(field_names.len, count);
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
/// One exported host function's source: from `start` up to the next
|
|
2881
|
+
/// exported `native_sdk_windows_` definition.
|
|
2882
|
+
fn hostFunction(host_source: []const u8, start: usize) ?[]const u8 {
|
|
2883
|
+
const end = std.mem.indexOfPos(u8, host_source, start + 1, "int native_sdk_windows_") orelse return null;
|
|
2884
|
+
return host_source[start..end];
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
test "windows maximize status codes map to explicit errors" {
|
|
2888
|
+
try windowControlResult(1);
|
|
2889
|
+
try std.testing.expectError(error.WindowNotFound, windowControlResult(0));
|
|
2890
|
+
try std.testing.expectError(error.WindowNotResizable, windowControlResult(-1));
|
|
2891
|
+
try std.testing.expectError(error.WindowHidden, windowControlResult(-2));
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
test "windows maximize refuses what the system would not maximize and reports every transition" {
|
|
2895
|
+
// The host verbs are C++ this suite cannot execute; pin their shape
|
|
2896
|
+
// textually, like the WM_CLOSE hook test above.
|
|
2897
|
+
const host_source = @embedFile("webview2_host.cpp");
|
|
2898
|
+
const maximize_at = std.mem.indexOf(u8, host_source, "int native_sdk_windows_maximize_window(") orelse return error.TestExpectedEqual;
|
|
2899
|
+
const maximize_fn = hostFunction(host_source, maximize_at) orelse return error.TestExpectedEqual;
|
|
2900
|
+
const resizable_at = std.mem.indexOf(u8, maximize_fn, "if (!found->second.resizable) return -1;") orelse return error.TestExpectedEqual;
|
|
2901
|
+
const visible_at = std.mem.indexOf(u8, maximize_fn, "if (!IsWindowVisible(found->second.hwnd)) return -2;") orelse return error.TestExpectedEqual;
|
|
2902
|
+
const show_at = std.mem.indexOf(u8, maximize_fn, "ShowWindow(found->second.hwnd, SW_MAXIMIZE);") orelse return error.TestExpectedEqual;
|
|
2903
|
+
try std.testing.expect(resizable_at < show_at and visible_at < show_at);
|
|
2904
|
+
|
|
2905
|
+
// Every frame event carries the live state, and the top-level window
|
|
2906
|
+
// procedure's WM_SIZE reports each transition, whoever caused it.
|
|
2907
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "event.maximized = window.hwnd && IsZoomed(window.hwnd) ? 1 : 0;") != null);
|
|
2908
|
+
const proc_at = std.mem.indexOf(u8, host_source, "static LRESULT CALLBACK windowProc(") orelse return error.TestExpectedEqual;
|
|
2909
|
+
const size_at = std.mem.indexOfPos(u8, host_source, proc_at, "case WM_SIZE:") orelse return error.TestExpectedEqual;
|
|
2910
|
+
const next_case_at = std.mem.indexOfPos(u8, host_source, size_at, "case WM_SETFOCUS:") orelse return error.TestExpectedEqual;
|
|
2911
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source[size_at..next_case_at], "emitMaximizedTransition(host, hwnd);") != null);
|
|
2912
|
+
|
|
2913
|
+
// A maximized chromeless window keeps the taskbar visible: without
|
|
2914
|
+
// a caption the system would size it to the whole monitor. The
|
|
2915
|
+
// min/max answer sets the size, and the position change re-derives
|
|
2916
|
+
// it from the monitor the window lands on, which the min/max answer
|
|
2917
|
+
// alone can get wrong on a secondary monitor larger than the primary.
|
|
2918
|
+
const minmax_at = std.mem.indexOfPos(u8, host_source, proc_at, "case WM_GETMINMAXINFO:") orelse return error.TestExpectedEqual;
|
|
2919
|
+
try std.testing.expect(std.mem.startsWith(u8, std.mem.trimStart(u8, host_source[minmax_at + "case WM_GETMINMAXINFO:".len ..], " \t\r\n"), "applyChromelessMaximizedBounds(host, hwnd, reinterpret_cast<MINMAXINFO *>(lparam));"));
|
|
2920
|
+
const changing_at = std.mem.indexOfPos(u8, host_source, proc_at, "case WM_WINDOWPOSCHANGING:") orelse return error.TestExpectedEqual;
|
|
2921
|
+
try std.testing.expect(std.mem.startsWith(u8, std.mem.trimStart(u8, host_source[changing_at + "case WM_WINDOWPOSCHANGING:".len ..], " \t\r\n"), "clampChromelessMaximizedFrame(host, hwnd, reinterpret_cast<WINDOWPOS *>(lparam));"));
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
test "windows close request posts the user's WM_CLOSE while the app's close stays direct" {
|
|
2925
|
+
const host_source = @embedFile("webview2_host.cpp");
|
|
2926
|
+
const request_at = std.mem.indexOf(u8, host_source, "int native_sdk_windows_request_close_window(") orelse return error.TestExpectedEqual;
|
|
2927
|
+
const request_fn = hostFunction(host_source, request_at) orelse return error.TestExpectedEqual;
|
|
2928
|
+
try std.testing.expect(std.mem.indexOf(u8, request_fn, "PostMessageW(found->second.hwnd, WM_CLOSE, 0, 0)") != null);
|
|
2929
|
+
try std.testing.expect(std.mem.indexOf(u8, request_fn, "DestroyWindow") == null);
|
|
2930
|
+
|
|
2931
|
+
// The app's own close keeps bypassing the WM_CLOSE close-policy hook.
|
|
2932
|
+
const close_at = std.mem.indexOf(u8, host_source, "int native_sdk_windows_close_window(") orelse return error.TestExpectedEqual;
|
|
2933
|
+
const close_fn = hostFunction(host_source, close_at) orelse return error.TestExpectedEqual;
|
|
2934
|
+
try std.testing.expect(std.mem.indexOf(u8, close_fn, "DestroyWindow(found->second.hwnd);") != null);
|
|
2935
|
+
try std.testing.expect(std.mem.indexOf(u8, close_fn, "WM_CLOSE") == null);
|
|
2936
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/* MinGW startup and CRT pieces for scriptc's Windows runtime on MSVC targets.
|
|
2
|
+
*
|
|
3
|
+
* The pinned scriptc builds its Windows runtime pack against Zig's MinGW
|
|
4
|
+
* sysroot (packages/runtime-win32-x64-msvc/scripts/build.mjs passes
|
|
5
|
+
* -target x86_64-windows-gnu). The runtime is already Win32-native for
|
|
6
|
+
* threads, child processes and its event loop; what remains is what MinGW
|
|
7
|
+
* layers over the UCRT. The MSVC build links the same UCRT statically, so
|
|
8
|
+
* this file restates exactly that, with the MinGW behaviour the runtime was
|
|
9
|
+
* compiled against: running its .ctors constructors, the dllimport slots for
|
|
10
|
+
* UCRT functions, stat64i32, the ___chkstk_ms stack probe, and <dirent.h>.
|
|
11
|
+
* POSIX names such as open/read/close come from MSVC's oldnames.lib instead.
|
|
12
|
+
*
|
|
13
|
+
* Delete this file once scriptc ships a runtime pack built for the MSVC CRT.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/* The import slots below only take the address of the UCRT's "insecure"
|
|
17
|
+
* functions (_wfopen, _wopen); nothing here calls them. */
|
|
18
|
+
#define _CRT_SECURE_NO_WARNINGS
|
|
19
|
+
|
|
20
|
+
#include <ctype.h>
|
|
21
|
+
#include <errno.h>
|
|
22
|
+
#include <io.h>
|
|
23
|
+
#include <process.h>
|
|
24
|
+
#include <stdint.h>
|
|
25
|
+
#include <stdio.h>
|
|
26
|
+
#include <stdlib.h>
|
|
27
|
+
#include <string.h>
|
|
28
|
+
#include <sys/stat.h>
|
|
29
|
+
#include <sys/utime.h>
|
|
30
|
+
|
|
31
|
+
#define WIN32_LEAN_AND_MEAN
|
|
32
|
+
#include <windows.h>
|
|
33
|
+
|
|
34
|
+
/* clang places a MinGW object's __attribute__((constructor)) functions in
|
|
35
|
+
* .ctors, which only MinGW's startup runs; the MSVC CRT runs .CRT$XCU. The
|
|
36
|
+
* runtime initializes state that way (scr_json.c's small-int table, for one),
|
|
37
|
+
* so walk this image's .ctors from a .CRT$XCU initializer, back to front as
|
|
38
|
+
* MinGW's __do_global_ctors does, skipping its 0/-1 list markers. */
|
|
39
|
+
typedef void (*scriptc_ctor_fn)(void);
|
|
40
|
+
extern IMAGE_DOS_HEADER __ImageBase;
|
|
41
|
+
|
|
42
|
+
static void __cdecl scriptc_run_mingw_ctors(void) {
|
|
43
|
+
const char *base = (const char *)&__ImageBase;
|
|
44
|
+
const IMAGE_NT_HEADERS *nt = (const IMAGE_NT_HEADERS *)(base + __ImageBase.e_lfanew);
|
|
45
|
+
const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nt);
|
|
46
|
+
for (WORD index = 0; index < nt->FileHeader.NumberOfSections; index++, section++) {
|
|
47
|
+
if (memcmp(section->Name, ".ctors\0\0", IMAGE_SIZEOF_SHORT_NAME) != 0) continue;
|
|
48
|
+
scriptc_ctor_fn *begin = (scriptc_ctor_fn *)(base + section->VirtualAddress);
|
|
49
|
+
scriptc_ctor_fn *end = begin + section->Misc.VirtualSize / sizeof(scriptc_ctor_fn);
|
|
50
|
+
while (end > begin) {
|
|
51
|
+
scriptc_ctor_fn ctor = *--end;
|
|
52
|
+
if (ctor && ctor != (scriptc_ctor_fn)(intptr_t)-1) ctor();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
__attribute__((section(".CRT$XCU"), used)) static void(__cdecl *const scriptc_mingw_ctors)(void) =
|
|
58
|
+
scriptc_run_mingw_ctors;
|
|
59
|
+
|
|
60
|
+
/* MinGW's headers declare UCRT functions dllimport, so the runtime calls them
|
|
61
|
+
* through __imp_ slots that the static UCRT does not define. lld would bind
|
|
62
|
+
* most of them to the static definitions anyway, but with one LNK4217 warning
|
|
63
|
+
* each (and tolower not at all), so define every slot the runtime pack's
|
|
64
|
+
* library objects reference. A slot added by a future pack shows up as an
|
|
65
|
+
* undefined symbol or a new LNK4217 warning in the MSVC link. */
|
|
66
|
+
#define SCRIPTC_UCRT_IMPORT(name) __typeof__(&name) const __imp_##name = &name;
|
|
67
|
+
/* The UCRT exports _assert, but its <assert.h> declares only _wassert. */
|
|
68
|
+
void __cdecl _assert(const char *message, const char *file, unsigned line);
|
|
69
|
+
SCRIPTC_UCRT_IMPORT(__acrt_iob_func)
|
|
70
|
+
SCRIPTC_UCRT_IMPORT(_assert)
|
|
71
|
+
SCRIPTC_UCRT_IMPORT(_chsize_s)
|
|
72
|
+
SCRIPTC_UCRT_IMPORT(_commit)
|
|
73
|
+
SCRIPTC_UCRT_IMPORT(_errno)
|
|
74
|
+
SCRIPTC_UCRT_IMPORT(_fstat64)
|
|
75
|
+
SCRIPTC_UCRT_IMPORT(_fullpath)
|
|
76
|
+
SCRIPTC_UCRT_IMPORT(_get_osfhandle)
|
|
77
|
+
SCRIPTC_UCRT_IMPORT(_getpid)
|
|
78
|
+
SCRIPTC_UCRT_IMPORT(_stricmp)
|
|
79
|
+
SCRIPTC_UCRT_IMPORT(_umask_s)
|
|
80
|
+
SCRIPTC_UCRT_IMPORT(_utime64)
|
|
81
|
+
SCRIPTC_UCRT_IMPORT(_wfopen)
|
|
82
|
+
SCRIPTC_UCRT_IMPORT(_wopen)
|
|
83
|
+
SCRIPTC_UCRT_IMPORT(tolower)
|
|
84
|
+
|
|
85
|
+
/* MinGW-UCRT aliases stat64i32 to the UCRT's _stat64i32. */
|
|
86
|
+
int __cdecl stat64i32(const char *path, struct _stat64i32 *buf) {
|
|
87
|
+
return _stat64i32(path, buf);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* MinGW's stack probe: rax holds the frame size; touch every page below the
|
|
91
|
+
* caller's stack pointer without moving it, preserving all registers. */
|
|
92
|
+
__attribute__((naked)) void ___chkstk_ms(void) {
|
|
93
|
+
__asm__ volatile(
|
|
94
|
+
"pushq %rcx\n\t"
|
|
95
|
+
"pushq %rax\n\t"
|
|
96
|
+
"cmpq $0x1000, %rax\n\t"
|
|
97
|
+
"leaq 24(%rsp), %rcx\n\t"
|
|
98
|
+
"jb 1f\n"
|
|
99
|
+
"2:\n\t"
|
|
100
|
+
"subq $0x1000, %rcx\n\t"
|
|
101
|
+
"testq %rcx, (%rcx)\n\t"
|
|
102
|
+
"subq $0x1000, %rax\n\t"
|
|
103
|
+
"cmpq $0x1000, %rax\n\t"
|
|
104
|
+
"ja 2b\n"
|
|
105
|
+
"1:\n\t"
|
|
106
|
+
"subq %rax, %rcx\n\t"
|
|
107
|
+
"testq %rcx, (%rcx)\n\t"
|
|
108
|
+
"popq %rax\n\t"
|
|
109
|
+
"popq %rcx\n\t"
|
|
110
|
+
"retq");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* <dirent.h>, ported from mingw-w64's public-domain misc/dirent.c (char
|
|
114
|
+
* variant). struct dirent and DIR keep mingw-w64's layout because the
|
|
115
|
+
* runtime was compiled against its header. */
|
|
116
|
+
|
|
117
|
+
struct dirent {
|
|
118
|
+
long d_ino;
|
|
119
|
+
unsigned short d_reclen;
|
|
120
|
+
unsigned short d_namlen;
|
|
121
|
+
char d_name[260];
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
typedef struct {
|
|
125
|
+
struct _finddata_t dd_dta;
|
|
126
|
+
struct dirent dd_dir;
|
|
127
|
+
intptr_t dd_handle;
|
|
128
|
+
int dd_stat;
|
|
129
|
+
char dd_name[1];
|
|
130
|
+
} DIR;
|
|
131
|
+
|
|
132
|
+
DIR *__cdecl opendir(const char *path) {
|
|
133
|
+
errno = 0;
|
|
134
|
+
if (!path) {
|
|
135
|
+
errno = EFAULT;
|
|
136
|
+
return NULL;
|
|
137
|
+
}
|
|
138
|
+
if (path[0] == '\0') {
|
|
139
|
+
errno = ENOTDIR;
|
|
140
|
+
return NULL;
|
|
141
|
+
}
|
|
142
|
+
DWORD attributes = GetFileAttributesA(path);
|
|
143
|
+
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
|
144
|
+
errno = ENOENT;
|
|
145
|
+
return NULL;
|
|
146
|
+
}
|
|
147
|
+
if (!(attributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
148
|
+
errno = ENOTDIR;
|
|
149
|
+
return NULL;
|
|
150
|
+
}
|
|
151
|
+
char full[MAX_PATH];
|
|
152
|
+
if (!_fullpath(full, path, MAX_PATH)) return NULL;
|
|
153
|
+
size_t len = strlen(full);
|
|
154
|
+
/* The search pattern: the absolute path, a separator, "*", and NUL. */
|
|
155
|
+
DIR *dir = malloc(sizeof(DIR) + len + 3);
|
|
156
|
+
if (!dir) {
|
|
157
|
+
errno = ENOMEM;
|
|
158
|
+
return NULL;
|
|
159
|
+
}
|
|
160
|
+
memcpy(dir->dd_name, full, len);
|
|
161
|
+
if (len > 0 && full[len - 1] != '/' && full[len - 1] != '\\') dir->dd_name[len++] = '\\';
|
|
162
|
+
dir->dd_name[len++] = '*';
|
|
163
|
+
dir->dd_name[len] = '\0';
|
|
164
|
+
dir->dd_handle = -1;
|
|
165
|
+
dir->dd_stat = 0;
|
|
166
|
+
dir->dd_dir.d_ino = 0;
|
|
167
|
+
dir->dd_dir.d_reclen = 0;
|
|
168
|
+
dir->dd_dir.d_namlen = 0;
|
|
169
|
+
memset(dir->dd_dir.d_name, 0, sizeof(dir->dd_dir.d_name));
|
|
170
|
+
return dir;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
struct dirent *__cdecl readdir(DIR *dir) {
|
|
174
|
+
errno = 0;
|
|
175
|
+
if (!dir) {
|
|
176
|
+
errno = EFAULT;
|
|
177
|
+
return NULL;
|
|
178
|
+
}
|
|
179
|
+
if (dir->dd_stat < 0) return NULL;
|
|
180
|
+
if (dir->dd_stat == 0) {
|
|
181
|
+
dir->dd_handle = _findfirst(dir->dd_name, &dir->dd_dta);
|
|
182
|
+
dir->dd_stat = dir->dd_handle == -1 ? -1 : 1;
|
|
183
|
+
} else if (_findnext(dir->dd_handle, &dir->dd_dta)) {
|
|
184
|
+
/* _findnext reports the end as ENOENT; the end is not an error. */
|
|
185
|
+
if (GetLastError() == ERROR_NO_MORE_FILES) errno = 0;
|
|
186
|
+
_findclose(dir->dd_handle);
|
|
187
|
+
dir->dd_handle = -1;
|
|
188
|
+
dir->dd_stat = -1;
|
|
189
|
+
} else {
|
|
190
|
+
dir->dd_stat++;
|
|
191
|
+
}
|
|
192
|
+
if (dir->dd_stat <= 0) return NULL;
|
|
193
|
+
/* Both buffers are MAX_PATH chars, so the name and its NUL always fit. */
|
|
194
|
+
size_t len = strlen(dir->dd_dta.name);
|
|
195
|
+
dir->dd_dir.d_namlen = (unsigned short)len;
|
|
196
|
+
memcpy(dir->dd_dir.d_name, dir->dd_dta.name, len + 1);
|
|
197
|
+
return &dir->dd_dir;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
int __cdecl closedir(DIR *dir) {
|
|
201
|
+
errno = 0;
|
|
202
|
+
if (!dir) {
|
|
203
|
+
errno = EFAULT;
|
|
204
|
+
return -1;
|
|
205
|
+
}
|
|
206
|
+
int rc = dir->dd_handle != -1 ? _findclose(dir->dd_handle) : 0;
|
|
207
|
+
free(dir);
|
|
208
|
+
return rc;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
void __cdecl rewinddir(DIR *dir) {
|
|
212
|
+
errno = 0;
|
|
213
|
+
if (!dir) {
|
|
214
|
+
errno = EFAULT;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (dir->dd_handle != -1) _findclose(dir->dd_handle);
|
|
218
|
+
dir->dd_handle = -1;
|
|
219
|
+
dir->dd_stat = 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
long __cdecl telldir(DIR *dir) {
|
|
223
|
+
errno = 0;
|
|
224
|
+
if (!dir) {
|
|
225
|
+
errno = EFAULT;
|
|
226
|
+
return -1;
|
|
227
|
+
}
|
|
228
|
+
return dir->dd_stat;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
void __cdecl seekdir(DIR *dir, long position) {
|
|
232
|
+
errno = 0;
|
|
233
|
+
if (!dir) {
|
|
234
|
+
errno = EFAULT;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (position < -1) {
|
|
238
|
+
errno = EINVAL;
|
|
239
|
+
} else if (position == -1) {
|
|
240
|
+
if (dir->dd_handle != -1) _findclose(dir->dd_handle);
|
|
241
|
+
dir->dd_handle = -1;
|
|
242
|
+
dir->dd_stat = -1;
|
|
243
|
+
} else {
|
|
244
|
+
rewinddir(dir);
|
|
245
|
+
while (dir->dd_stat < position && readdir(dir)) {
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|