From 9281adc5359069ace9438a4fa91f088203c91bfb Mon Sep 17 00:00:00 2001 From: ItsMeSamey Date: Wed, 25 Mar 2026 20:04:21 +1000 Subject: [PATCH 01/14] updated to 0.16 --- .gitignore | 2 + build.zig | 75 +++- build.zig.zon | 14 +- examples/shared/demo_runner.zig | 22 +- src/backends/linux_browser_host.zig | 100 ++--- src/backends/linux_webview/symbols.zig | 29 +- src/backends/linux_webview_host.zig | 51 ++- src/backends/macos_browser_host.zig | 40 +- src/backends/windows_browser_host.zig | 57 ++- src/backends/windows_webview/bindings.zig | 25 +- src/backends/windows_webview_host.zig | 91 +++-- src/bridge/template.zig | 96 +++-- src/env_compat.zig | 23 ++ src/io_compat.zig | 5 + src/main.zig | 17 +- src/network/https_server.zig | 92 +++-- src/network/tls_runtime.zig | 6 +- src/network/x509_self_signed.zig | 16 +- src/ported/browser_discovery.zig | 20 +- src/ported/runtime_env.zig | 6 +- src/ported/webui.zig | 122 ++---- src/root/app.zig | 3 +- src/root/net_io.zig | 230 +++++++----- src/root/process_signals.zig | 29 +- src/root/rpc_reflect.zig | 2 +- src/root/rpc_registry.zig | 21 +- src/root/rpc_runtime.zig | 15 +- src/root/server_routes.zig | 9 +- src/root/tests.zig | 439 ++++++++++++---------- src/root/window.zig | 12 +- src/root/window_state.zig | 172 +++++---- src/thread_compat.zig | 75 ++++ src/time_compat.zig | 21 ++ tools/bridge_gen.zig | 47 +-- tools/js_asset_gen.zig | 63 ++-- tools/parity_report.zig | 41 +- tools/vfs_gen.zig | 50 +-- 37 files changed, 1249 insertions(+), 889 deletions(-) create mode 100644 src/env_compat.zig create mode 100644 src/io_compat.zig create mode 100644 src/thread_compat.zig create mode 100644 src/time_compat.zig diff --git a/.gitignore b/.gitignore index b932c1a..f4df2fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .zig-cache/ +zig-pkg/ +.scratch/ zig-out/ zig-out-clean-dyn/ zig-out-clean-macos-dyn/ diff --git a/build.zig b/build.zig index 719cfac..477100c 100644 --- a/build.zig +++ b/build.zig @@ -144,6 +144,7 @@ pub fn build(b: *Build) void { const enable_webui_log = b.option(bool, "enable-webui-log", "Enable runtime log defaults") orelse false; const minify_embedded_js = b.option(bool, "minify-embedded-js", "Minify embedded JS helpers with pure Zig asset processing (default: true)") orelse true; const minify_written_js = b.option(bool, "minify-written-js", "Minify written JS helper assets with pure Zig asset processing (default: false)") orelse false; + const test_filter = b.option([]const u8, "test-filter", "Optional compile-time test name filter for `zig build test`"); const selected_example = b.option(ExampleChoice, "example", "Example to run with `zig build run` (default: all)") orelse .all; const run_mode_option = b.option([]const u8, "run-mode", "Runtime launch order for examples. Presets: `webview`, `browser` (app-window), `web-tab`, `web-url`. Or ordered tokens (`webview,browser,web-url`, `browser,webview`, etc). Default: webview,browser,web-url"); const run_mode = run_mode_option orelse "webview,browser,web-url"; @@ -168,19 +169,46 @@ pub fn build(b: *Build) void { build_opts.addOption([]const u8, "runtime_helpers_embed_path", runtime_helpers_assets.embed_path); build_opts.addOption([]const u8, "runtime_helpers_written_path", runtime_helpers_assets.written_path); - const websocket_dep = b.dependency("websocket", .{ + const zhttp_dep = b.dependency("zhttp", .{ .target = target, .optimize = optimize, }); - const websocket_mod = websocket_dep.module("websocket"); + const zhttp_mod = zhttp_dep.module("zhttp"); + const zhttp_request_mod = b.createModule(.{ + .root_source_file = zhttp_dep.path("src/request.zig"), + .target = target, + .optimize = optimize, + .link_libc = false, + }); const tls_dep = b.dependency("tls", .{ .target = target, .optimize = optimize, }); const tls_mod = tls_dep.module("tls"); - const websocket_build_opts = b.addOptions(); - websocket_build_opts.addOption(bool, "websocket_blocking", false); - websocket_mod.addOptions("build", websocket_build_opts); + const time_compat_mod = b.createModule(.{ + .root_source_file = b.path("src/time_compat.zig"), + .target = target, + .optimize = optimize, + .link_libc = false, + }); + const thread_compat_mod = b.createModule(.{ + .root_source_file = b.path("src/thread_compat.zig"), + .target = target, + .optimize = optimize, + .link_libc = false, + }); + const env_compat_mod = b.createModule(.{ + .root_source_file = b.path("src/env_compat.zig"), + .target = target, + .optimize = optimize, + .link_libc = false, + }); + const io_compat_mod = b.createModule(.{ + .root_source_file = b.path("src/io_compat.zig"), + .target = target, + .optimize = optimize, + .link_libc = false, + }); const lib_module = b.createModule(.{ .root_source_file = b.path("src/root.zig"), @@ -189,8 +217,13 @@ pub fn build(b: *Build) void { .link_libc = false, }); lib_module.addOptions("build_options", build_opts); - lib_module.addImport("websocket", websocket_mod); + lib_module.addImport("zhttp", zhttp_mod); + lib_module.addImport("zhttp_request", zhttp_request_mod); lib_module.addImport("tls", tls_mod); + lib_module.addImport("time_compat", time_compat_mod); + lib_module.addImport("thread_compat", thread_compat_mod); + lib_module.addImport("env_compat", env_compat_mod); + lib_module.addImport("io_compat", io_compat_mod); const webui_lib = b.addLibrary(.{ .name = "webui", @@ -207,8 +240,13 @@ pub fn build(b: *Build) void { .link_libc = false, }); webui_mod.addOptions("build_options", build_opts); - webui_mod.addImport("websocket", websocket_mod); + webui_mod.addImport("zhttp", zhttp_mod); + webui_mod.addImport("zhttp_request", zhttp_request_mod); webui_mod.addImport("tls", tls_mod); + webui_mod.addImport("time_compat", time_compat_mod); + webui_mod.addImport("thread_compat", thread_compat_mod); + webui_mod.addImport("env_compat", env_compat_mod); + webui_mod.addImport("io_compat", io_compat_mod); const example_shared_source_path = "examples/shared/demo_runner.zig"; const example_shared_mod: ?*Build.Module = if (pathExists(example_shared_source_path)) b.addModule("example_shared", .{ @@ -218,6 +256,8 @@ pub fn build(b: *Build) void { .link_libc = false, .imports = &.{ .{ .name = "webui", .module = webui_mod }, + .{ .name = "time_compat", .module = time_compat_mod }, + .{ .name = "env_compat", .module = env_compat_mod }, }, }) else null; @@ -230,14 +270,17 @@ pub fn build(b: *Build) void { .link_libc = false, .imports = &.{ .{ .name = "webui", .module = webui_mod }, + .{ .name = "time_compat", .module = time_compat_mod }, + .{ .name = "io_compat", .module = io_compat_mod }, }, }), }); exe.step.dependOn(runtime_helpers_assets.prepare_step); - exe.linkLibrary(webui_lib); + exe.root_module.linkLibrary(webui_lib); b.installArtifact(exe); const run_step = b.step("run", "Run Zig examples (default: all, override with -Dexample=)"); + const test_filters: []const []const u8 = if (test_filter) |name| &.{name} else &.{}; const bridge_template_mod = b.createModule(.{ .root_source_file = b.path("src/bridge/template.zig"), @@ -323,11 +366,14 @@ pub fn build(b: *Build) void { .link_libc = false, .imports = &.{ .{ .name = "webui", .module = webui_mod }, + .{ .name = "time_compat", .module = time_compat_mod }, + .{ .name = "io_compat", .module = io_compat_mod }, }, }), + .filters = test_filters, }); mod_tests.step.dependOn(runtime_helpers_assets.prepare_step); - mod_tests.linkLibrary(webui_lib); + mod_tests.root_module.linkLibrary(webui_lib); const run_mod_tests = b.addRunArtifact(mod_tests); var run_example_shared_tests_step: ?*Build.Step = null; @@ -341,11 +387,14 @@ pub fn build(b: *Build) void { .imports = &.{ .{ .name = "webui", .module = webui_mod }, .{ .name = "example_shared", .module = shared_mod }, + .{ .name = "time_compat", .module = time_compat_mod }, + .{ .name = "env_compat", .module = env_compat_mod }, }, }), + .filters = test_filters, }); example_shared_tests.step.dependOn(runtime_helpers_assets.prepare_step); - example_shared_tests.linkLibrary(webui_lib); + example_shared_tests.root_module.linkLibrary(webui_lib); const run_example_shared_tests = b.addRunArtifact(example_shared_tests); run_example_shared_tests_step = &run_example_shared_tests.step; } @@ -361,7 +410,7 @@ pub fn build(b: *Build) void { .filters = &.{"threaded dispatcher stress"}, }); dispatcher_stress_tests.step.dependOn(runtime_helpers_assets.prepare_step); - dispatcher_stress_tests.linkLibrary(webui_lib); + dispatcher_stress_tests.root_module.linkLibrary(webui_lib); const dispatcher_stress_step = b.step("dispatcher-stress", "Stress threaded dispatcher concurrency/lifetime paths"); var stress_iter: usize = 0; @@ -471,7 +520,7 @@ fn addExample( }), }); exe.step.dependOn(runtime_helpers_prepare_step); - exe.linkLibrary(webui_lib); + exe.root_module.linkLibrary(webui_lib); const install = b.addInstallArtifact(exe, .{}); @@ -591,6 +640,6 @@ fn isValidLinuxWebviewTarget(value: []const u8) bool { } fn pathExists(path: []const u8) bool { - std.fs.cwd().access(path, .{}) catch return false; + _ = path; return true; } diff --git a/build.zig.zon b/build.zig.zon index dfa2e85..4087609 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -25,20 +25,20 @@ .fingerprint = 0xac5d87f27f1c5d35, // Changing this has security and trust implications. // Tracks the earliest Zig version that the package considers to be a // supported use case. - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.16.0-dev.2905+5d71e3051", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. // Once all dependencies are fetched, `zig build` no longer requires // internet connectivity. .dependencies = .{ - .websocket = .{ - .url = "https://github.com/karlseguin/websocket.zig/archive/97fefafa59cc78ce177cff540b8685cd7f699276.tar.gz", - .hash = "websocket-0.1.0-ZPISdRlzAwBB_Bz2UMMqxYqF6YEVTIBoFsbzwPUJTHIc", - }, .tls = .{ - .url = "https://github.com/ianic/tls.zig/archive/9d56751.tar.gz", - .hash = "tls-0.1.0-ER2e0gNoBQBLuvbg_hPuGQYoCVsn-kHDuupr0hsapu4P", + .url = "https://github.com/ianic/tls.zig/archive/d91b50b05465f1669febd4d2009577a7da09b097.tar.gz", + .hash = "tls-0.1.0-ER2e0iOpBQA6rT-xledZ1M8qNQa3NCsEBcaRxUrnitrj", + }, + .zhttp = .{ + .url = "https://github.com/SmallThingz/zhttp/archive/15629692d25fd480d4986e8584fc98bf93c016ad.tar.gz", + .hash = "zhttp-0.0.0-I8z-vZFvAwDr_cmZ0DtJvyjXsJgQ4Q3UgK_XfaRFk9IB", }, }, .paths = .{ diff --git a/examples/shared/demo_runner.zig b/examples/shared/demo_runner.zig index 633c220..e9f19d1 100644 --- a/examples/shared/demo_runner.zig +++ b/examples/shared/demo_runner.zig @@ -1,5 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); +const time_compat = @import("time_compat"); +const env_compat = @import("env_compat"); const webui = @import("webui"); pub const ExampleKind = enum { @@ -181,7 +183,7 @@ fn surfaceName(surface: webui.LaunchSurface) []const u8 { } pub fn runExample(comptime kind: ExampleKind, comptime RpcMethods: type) !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var gpa = std.heap.DebugAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -272,17 +274,17 @@ pub fn runExample(comptime kind: ExampleKind, comptime RpcMethods: type) !void { std.debug.print("[{s}] running. Press Ctrl+C to stop.\n", .{tagFor(kind)}); } - const start_ms = std.time.milliTimestamp(); + const start_ms = time_compat.milliTimestamp(); while (!service.shouldExit()) { if (exit_ms) |ms| { - const now_ms = std.time.milliTimestamp(); + const now_ms = time_compat.milliTimestamp(); if (now_ms - start_ms >= @as(i64, @intCast(ms))) { service.shutdown(); break; } } - std.Thread.sleep(20 * std.time.ns_per_ms); + time_compat.sleep(20 * std.time.ns_per_ms); } service.shutdown(); @@ -299,23 +301,23 @@ fn callFrontendWithRetry( args: anytype, max_wait_ms: u32, ) !webui.ScriptEvalResult { - const deadline = std.time.milliTimestamp() + @as(i64, max_wait_ms); + const deadline = time_compat.milliTimestamp() + @as(i64, max_wait_ms); while (true) { const result = service.callFrontend(allocator, function_name, args, .{ .timeout_ms = 500 }) catch |err| switch (err) { else => { if (!std.mem.eql(u8, @errorName(err), "NoTargetConnections")) return err; - if (std.time.milliTimestamp() >= deadline) return err; - std.Thread.sleep(100 * std.time.ns_per_ms); + if (time_compat.milliTimestamp() >= deadline) return err; + time_compat.sleep(100 * std.time.ns_per_ms); continue; }, }; if (result.ok) return result; const timed_out = result.timed_out; - if (!timed_out or std.time.milliTimestamp() >= deadline) return result; + if (!timed_out or time_compat.milliTimestamp() >= deadline) return result; if (result.value) |value| allocator.free(value); if (result.error_message) |msg| allocator.free(msg); - std.Thread.sleep(100 * std.time.ns_per_ms); + time_compat.sleep(100 * std.time.ns_per_ms); } } @@ -328,7 +330,7 @@ fn onRawLog(_: ?*anyopaque, bytes: []const u8) void { } fn parseExitMs() ?u64 { - const raw = std.process.getEnvVarOwned(std.heap.page_allocator, "WEBUI_EXAMPLE_EXIT_MS") catch return null; + const raw = env_compat.getEnvVarOwned(std.heap.page_allocator, "WEBUI_EXAMPLE_EXIT_MS") catch return null; defer std.heap.page_allocator.free(raw); return std.fmt.parseInt(u64, raw, 10) catch null; } diff --git a/src/backends/linux_browser_host.zig b/src/backends/linux_browser_host.zig index 17bb5f8..50c3827 100644 --- a/src/backends/linux_browser_host.zig +++ b/src/backends/linux_browser_host.zig @@ -1,5 +1,6 @@ const std = @import("std"); const window_style_types = @import("../root/window_style.zig"); +const linux = std.os.linux; pub const LaunchResult = struct { pid: i64, @@ -18,15 +19,18 @@ pub fn launchTracked( try argv.append(browser_path); if (args.len > 0) try argv.appendSlice(args); - var child = std.process.Child.init(argv.items, allocator); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Ignore; - child.pgid = 0; + var threaded: std.Io.Threaded = .init(allocator, .{}); + defer threaded.deinit(); + const io = threaded.io(); - child.spawn() catch return null; + const child = std.process.spawn(io, .{ + .argv = argv.items, + .stdin = .ignore, + .stdout = .ignore, + .stderr = .ignore, + }) catch return null; return .{ - .pid = @as(i64, @intCast(child.id)), + .pid = @as(i64, @intCast(child.id.?)), .is_child_process = true, }; } @@ -54,14 +58,31 @@ pub fn isProcessAlive(_: std.mem.Allocator, pid_value: i64) bool { if (pid_value <= 0) return false; const pid: std.posix.pid_t = @intCast(pid_value); - std.posix.kill(pid, 0) catch |err| { + + // Probe child state without reaping so callers that still own `std.process.Child` + // can `wait()` safely later. + var child_info: linux.siginfo_t = std.mem.zeroes(linux.siginfo_t); + while (true) { + const wait_rc = linux.waitid(.PID, pid, &child_info, linux.W.EXITED | linux.W.NOHANG | linux.W.NOWAIT, null); + switch (linux.errno(wait_rc)) { + .SUCCESS => { + const state_pid = child_info.fields.common.first.piduid.pid; + if (state_pid == 0) break; // No child state change pending; still running. + return false; // Child is dead (zombie/exited), not reaped here. + }, + .INTR => continue, + .CHILD => break, // Not our child (or already reaped); fall back to signal probe. + else => break, + } + } + + std.posix.kill(pid, @enumFromInt(0)) catch |err| { return switch (err) { error.PermissionDenied => true, else => false, }; }; - - return !isZombieProcessLinux(pid); + return true; } /// Applies a coarse native window control action to the launched browser process. @@ -90,32 +111,29 @@ pub fn controlWindow(allocator: std.mem.Allocator, pid: i64, cmd: window_style_t } fn runCommandCaptureStdout(allocator: std.mem.Allocator, argv: []const []const u8) ![]u8 { - var child = std.process.Child.init(argv, allocator); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Ignore; - - try child.spawn(); - defer { - _ = child.wait() catch {}; - } - - const out = if (child.stdout) |*stdout_file| - try stdout_file.readToEndAlloc(allocator, 32 * 1024) - else - try allocator.dupe(u8, ""); - return out; + var threaded: std.Io.Threaded = .init(allocator, .{}); + defer threaded.deinit(); + const result = try std.process.run(allocator, threaded.io(), .{ + .argv = argv, + .stdout_limit = .limited(32 * 1024), + .stderr_limit = .limited(8 * 1024), + }); + defer allocator.free(result.stderr); + return result.stdout; } fn runCommandNoCapture(allocator: std.mem.Allocator, argv: []const []const u8) !bool { - var child = std.process.Child.init(argv, allocator); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Ignore; - - const term = child.spawnAndWait() catch return false; - return switch (term) { - .Exited => |code| code == 0, + var threaded: std.Io.Threaded = .init(allocator, .{}); + defer threaded.deinit(); + const result = std.process.run(allocator, threaded.io(), .{ + .argv = argv, + .stdout_limit = .limited(1024), + .stderr_limit = .limited(1024), + }) catch return false; + defer allocator.free(result.stdout); + defer allocator.free(result.stderr); + return switch (result.term) { + .exited => |code| code == 0, else => false, }; } @@ -135,19 +153,3 @@ fn firstLinuxWindowIdForPid(allocator: std.mem.Allocator, pid: i64) ?u64 { } return null; } - -fn isZombieProcessLinux(pid: std.posix.pid_t) bool { - var path_buf: [64]u8 = undefined; - const stat_path = std.fmt.bufPrint(&path_buf, "/proc/{d}/stat", .{pid}) catch return false; - - var stat_file = std.fs.openFileAbsolute(stat_path, .{}) catch return false; - defer stat_file.close(); - - var stat_buf: [1024]u8 = undefined; - const stat_len = stat_file.readAll(&stat_buf) catch return false; - const stat = stat_buf[0..stat_len]; - - const close_paren = std.mem.lastIndexOfScalar(u8, stat, ')') orelse return false; - if (close_paren + 2 >= stat.len) return false; - return stat[close_paren + 2] == 'Z'; -} diff --git a/src/backends/linux_webview/symbols.zig b/src/backends/linux_webview/symbols.zig index 7b6224a..5fb3e6f 100644 --- a/src/backends/linux_webview/symbols.zig +++ b/src/backends/linux_webview/symbols.zig @@ -52,7 +52,9 @@ pub const Symbols = struct { gtk_api: GtkApi, gtk_init_gtk3: ?*const fn (?*c_int, ?*anyopaque) callconv(.c) void = null, + gtk_init_check_gtk3: ?*const fn (?*c_int, ?*anyopaque) callconv(.c) c_int = null, gtk_init_gtk4: ?*const fn () callconv(.c) void = null, + gtk_init_check_gtk4: ?*const fn () callconv(.c) c_int = null, gtk_window_new_gtk3: ?*const fn (c_int) callconv(.c) ?*common.GtkWidget = null, gtk_window_new_gtk4: ?*const fn () callconv(.c) ?*common.GtkWidget = null, gtk_window_set_child: ?*const fn (*common.GtkWindow, ?*common.GtkWidget) callconv(.c) void = null, @@ -123,10 +125,6 @@ pub const Symbols = struct { ) callconv(.c) c_ulong, g_signal_handlers_disconnect_by_data: ?*const fn (*anyopaque, ?*anyopaque) callconv(.c) c_uint = null, g_idle_add: *const fn (*const fn (?*anyopaque) callconv(.c) c_int, ?*anyopaque) callconv(.c) c_uint, - g_main_loop_new: *const fn (?*anyopaque, c_int) callconv(.c) ?*common.GMainLoop, - g_main_loop_run: *const fn (*common.GMainLoop) callconv(.c) void, - g_main_loop_quit: *const fn (*common.GMainLoop) callconv(.c) void, - g_main_loop_unref: *const fn (*common.GMainLoop) callconv(.c) void, g_main_context_iteration: ?*const fn (?*anyopaque, c_int) callconv(.c) c_int = null, g_error_free: ?*const fn (?*common.GError) callconv(.c) void = null, g_list_append: ?*const fn (?*anyopaque, ?*anyopaque) callconv(.c) ?*anyopaque = null, @@ -168,14 +166,27 @@ pub const Symbols = struct { } /// Init toolkit. - pub fn initToolkit(self: *const Symbols) void { + pub fn initToolkit(self: *const Symbols) !void { if (self.gtk_api == .gtk4) { + if (self.gtk_init_check_gtk4) |init_check| { + if (init_check() == 0) return error.NativeBackendUnavailable; + return; + } if (self.gtk_init_gtk4) |init| { init(); return; } + return error.NativeBackendUnavailable; + } + if (self.gtk_init_check_gtk3) |init_check| { + if (init_check(null, null) == 0) return error.NativeBackendUnavailable; + return; + } + if (self.gtk_init_gtk3) |init| { + init(null, null); + return; } - if (self.gtk_init_gtk3) |init| init(null, null); + return error.NativeBackendUnavailable; } /// New top level window. @@ -522,7 +533,9 @@ pub const Symbols = struct { fn loadFunctions(self: *Symbols) !void { self.gtk_init_gtk3 = lookupOptionalSym(&self.gtk, @TypeOf(self.gtk_init_gtk3), "gtk_init"); + self.gtk_init_check_gtk3 = lookupOptionalSym(&self.gtk, @TypeOf(self.gtk_init_check_gtk3), "gtk_init_check"); self.gtk_init_gtk4 = lookupOptionalSym(&self.gtk, @TypeOf(self.gtk_init_gtk4), "gtk_init"); + self.gtk_init_check_gtk4 = lookupOptionalSym(&self.gtk, @TypeOf(self.gtk_init_check_gtk4), "gtk_init_check"); self.gtk_window_new_gtk3 = lookupOptionalSym(&self.gtk, @TypeOf(self.gtk_window_new_gtk3), "gtk_window_new"); self.gtk_window_new_gtk4 = lookupOptionalSym(&self.gtk, @TypeOf(self.gtk_window_new_gtk4), "gtk_window_new"); self.gtk_window_set_child = lookupOptionalSym(&self.gtk, @TypeOf(self.gtk_window_set_child), "gtk_window_set_child"); @@ -591,10 +604,6 @@ pub const Symbols = struct { ); self.g_object_unref = lookupOptionalSym(&self.gobject, @TypeOf(self.g_object_unref), "g_object_unref"); self.g_idle_add = try lookupSym(&self.glib, @TypeOf(self.g_idle_add), "g_idle_add"); - self.g_main_loop_new = try lookupSym(&self.glib, @TypeOf(self.g_main_loop_new), "g_main_loop_new"); - self.g_main_loop_run = try lookupSym(&self.glib, @TypeOf(self.g_main_loop_run), "g_main_loop_run"); - self.g_main_loop_quit = try lookupSym(&self.glib, @TypeOf(self.g_main_loop_quit), "g_main_loop_quit"); - self.g_main_loop_unref = try lookupSym(&self.glib, @TypeOf(self.g_main_loop_unref), "g_main_loop_unref"); self.g_main_context_iteration = lookupOptionalSym( &self.glib, @TypeOf(self.g_main_context_iteration), diff --git a/src/backends/linux_webview_host.zig b/src/backends/linux_webview_host.zig index 6c7a9a5..6ee6482 100644 --- a/src/backends/linux_webview_host.zig +++ b/src/backends/linux_webview_host.zig @@ -1,7 +1,11 @@ const std = @import("std"); +const builtin = @import("builtin"); const common = @import("linux_webview/common.zig"); const symbols_mod = @import("linux_webview/symbols.zig"); const rounded_shape = @import("linux_webview/rounded_shape.zig"); +const thread_compat = @import("thread_compat"); +const time_compat = @import("time_compat"); +const env_compat = @import("env_compat"); const WindowStyle = common.WindowStyle; const WindowControl = common.WindowControl; @@ -40,7 +44,7 @@ pub const Host = struct { style: WindowStyle, runtime_target: RuntimeTarget = .webview, - mutex: std.Thread.Mutex = .{}, + mutex: thread_compat.Mutex = .{}, queue: Queue, symbols: ?Symbols = null, @@ -60,6 +64,7 @@ pub const Host = struct { style: WindowStyle, runtime_target: RuntimeTarget, ) !*Host { + if (!linuxNativeToolkitSupported()) return error.NativeBackendUnavailable; if (!hasDisplaySession()) return error.NativeBackendUnavailable; const host = try allocator.create(Host); @@ -87,7 +92,7 @@ pub const Host = struct { var spins: usize = 0; while (spins < 2048 and !self.closed.load(.acquire)) : (spins += 1) { self.pump(); - std.Thread.sleep(std.time.ns_per_ms); + time_compat.sleep(std.time.ns_per_ms); } self.pump(); @@ -206,7 +211,7 @@ fn initOnCurrentThread(host: *Host) !void { } const symbols = &host.symbols.?; - symbols.initToolkit(); + try symbols.initToolkit(); const window_widget = symbols.newTopLevelWindow() orelse return error.NativeBackendUnavailable; var should_destroy_window = true; @@ -247,6 +252,7 @@ fn initOnCurrentThread(host: *Host) !void { /// Returns whether the native runtime dependency is available. pub fn runtimeAvailableFor(target: RuntimeTarget) bool { + if (!linuxNativeToolkitSupported()) return false; const cache_ptr = switch (target) { .webview => &probe_cache_webview, .webkitgtk_6 => &probe_cache_webkitgtk6, @@ -272,6 +278,12 @@ pub fn runtimeAvailableFor(target: RuntimeTarget) bool { return available; } +fn linuxNativeToolkitSupported() bool { + // GTK init crashes reliably in no-libc processes because GLib/GTK expect + // C runtime startup state provided by libc entrypoints. + return builtin.link_libc; +} + fn connectWindowSignals(symbols: *const Symbols, window_widget: *common.GtkWidget, host: *Host) void { _ = symbols.g_signal_connect_data(@ptrCast(window_widget), "destroy", @ptrCast(&onDestroy), host, null, 0); // Keep realize callback for all GTK variants so style/transparency can be @@ -480,7 +492,7 @@ fn hasDisplaySession() bool { } fn envVarNonEmpty(name: []const u8) bool { - const value = std.process.getEnvVarOwned(std.heap.page_allocator, name) catch return false; + const value = env_compat.getEnvVarOwned(std.heap.page_allocator, name) catch return false; defer std.heap.page_allocator.free(value); return value.len > 0; } @@ -517,18 +529,21 @@ fn writeIconTempFile(host: *Host, icon: WindowIcon) ![]u8 { cleanupIconTempFile(host); const ext = iconExtensionForMime(icon.mime_type); - const name = try std.fmt.allocPrint(host.allocator, "webui-icon-{d}{s}", .{ std.time.nanoTimestamp(), ext }); + const name = try std.fmt.allocPrint(host.allocator, "webui-icon-{d}{s}", .{ time_compat.nanoTimestamp(), ext }); defer host.allocator.free(name); - const dir_path = std.process.getEnvVarOwned(host.allocator, "XDG_RUNTIME_DIR") catch try host.allocator.dupe(u8, "/tmp"); + const dir_path = env_compat.getEnvVarOwned(host.allocator, "XDG_RUNTIME_DIR") catch try host.allocator.dupe(u8, "/tmp"); defer host.allocator.free(dir_path); const full_path = try std.fs.path.join(host.allocator, &.{ dir_path, name }); errdefer host.allocator.free(full_path); - var file = try std.fs.createFileAbsolute(full_path, .{ .truncate = true, .read = false }); - defer file.close(); - try file.writeAll(icon.bytes); + var threaded: std.Io.Threaded = .init(host.allocator, .{}); + defer threaded.deinit(); + const io = threaded.io(); + var file = try std.Io.Dir.createFileAbsolute(io, full_path, .{ .truncate = true, .read = false }); + defer file.close(io); + try file.writeStreamingAll(io, icon.bytes); host.icon_temp_path = full_path; return full_path; @@ -544,8 +559,24 @@ fn iconExtensionForMime(mime_type: []const u8) []const u8 { fn cleanupIconTempFile(host: *Host) void { if (host.icon_temp_path) |path| { - std.fs.deleteFileAbsolute(path) catch {}; + var threaded: std.Io.Threaded = .init(host.allocator, .{}); + defer threaded.deinit(); + std.Io.Dir.deleteFileAbsolute(threaded.io(), path) catch {}; host.allocator.free(path); host.icon_temp_path = null; } } + +test "linux webview runtime probe is disabled without libc" { + if (builtin.link_libc) return error.SkipZigTest; + try std.testing.expect(!runtimeAvailableFor(.webview)); + try std.testing.expect(!runtimeAvailableFor(.webkitgtk_6)); +} + +test "linux webview start fails fast without libc" { + if (builtin.link_libc) return error.SkipZigTest; + try std.testing.expectError( + error.NativeBackendUnavailable, + Host.start(std.testing.allocator, "test", .{}, .webview), + ); +} diff --git a/src/backends/macos_browser_host.zig b/src/backends/macos_browser_host.zig index c674bcc..a630978 100644 --- a/src/backends/macos_browser_host.zig +++ b/src/backends/macos_browser_host.zig @@ -18,14 +18,17 @@ pub fn launchTracked( try argv.append(browser_path); if (args.len > 0) try argv.appendSlice(args); - var child = std.process.Child.init(argv.items, allocator); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Ignore; - child.pgid = 0; - - child.spawn() catch return null; - return .{ .pid = @as(i64, @intCast(child.id)), .is_child_process = true }; + var threaded: std.Io.Threaded = .init(allocator, .{}); + defer threaded.deinit(); + const io = threaded.io(); + + const child = std.process.spawn(io, .{ + .argv = argv.items, + .stdin = .ignore, + .stdout = .ignore, + .stderr = .ignore, + }) catch return null; + return .{ .pid = @as(i64, @intCast(child.id.?)), .is_child_process = true }; } /// Opens a URL using an existing browser installation path. @@ -51,7 +54,7 @@ pub fn isProcessAlive(_: std.mem.Allocator, pid_value: i64) bool { if (pid_value <= 0) return false; const pid: std.posix.pid_t = @intCast(pid_value); - std.posix.kill(pid, 0) catch |err| { + std.posix.kill(pid, @enumFromInt(0)) catch |err| { return switch (err) { error.PermissionDenied => true, else => false, @@ -125,14 +128,17 @@ fn buildAppleScriptForZoomFallback(allocator: std.mem.Allocator, pid: i64) ![]u8 } fn runCommandNoCapture(allocator: std.mem.Allocator, argv: []const []const u8) !bool { - var child = std.process.Child.init(argv, allocator); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Ignore; - - const term = child.spawnAndWait() catch return false; - return switch (term) { - .Exited => |code| code == 0, + var threaded: std.Io.Threaded = .init(allocator, .{}); + defer threaded.deinit(); + const result = std.process.run(allocator, threaded.io(), .{ + .argv = argv, + .stdout_limit = .limited(1024), + .stderr_limit = .limited(1024), + }) catch return false; + defer allocator.free(result.stdout); + defer allocator.free(result.stderr); + return switch (result.term) { + .exited => |code| code == 0, else => false, }; } diff --git a/src/backends/windows_browser_host.zig b/src/backends/windows_browser_host.zig index d861d94..8dd80f3 100644 --- a/src/backends/windows_browser_host.zig +++ b/src/backends/windows_browser_host.zig @@ -113,20 +113,18 @@ fn buildPowershellLaunchScript( ) ![]u8 { var out = std.array_list.Managed(u8).init(allocator); errdefer out.deinit(); - - const w = out.writer(); - try w.writeAll("$ErrorActionPreference='Stop';$p=Start-Process -FilePath '"); + try out.appendSlice("$ErrorActionPreference='Stop';$p=Start-Process -FilePath '"); try appendPowershellSingleQuoted(&out, browser_path); - try w.writeAll("' -ArgumentList @("); + try out.appendSlice("' -ArgumentList @("); for (args, 0..) |arg, i| { - if (i != 0) try w.writeAll(","); - try w.writeAll("'"); + if (i != 0) try out.appendSlice(","); + try out.appendSlice("'"); try appendPowershellSingleQuoted(&out, arg); - try w.writeAll("'"); + try out.appendSlice("'"); } - try w.writeAll(") -PassThru -WindowStyle Hidden;[Console]::Out.Write($p.Id)"); + try out.appendSlice(") -PassThru -WindowStyle Hidden;[Console]::Out.Write($p.Id)"); return out.toOwnedSlice(); } @@ -141,32 +139,29 @@ fn appendPowershellSingleQuoted(out: *std.array_list.Managed(u8), value: []const } fn runCommandCaptureStdout(allocator: std.mem.Allocator, argv: []const []const u8) ![]u8 { - var child = std.process.Child.init(argv, allocator); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Ignore; - - try child.spawn(); - defer { - _ = child.wait() catch {}; - } - - const out = if (child.stdout) |*stdout_file| - try stdout_file.readToEndAlloc(allocator, 32 * 1024) - else - try allocator.dupe(u8, ""); - return out; + var threaded: std.Io.Threaded = .init(allocator, .{}); + defer threaded.deinit(); + const result = try std.process.run(allocator, threaded.io(), .{ + .argv = argv, + .stdout_limit = .limited(32 * 1024), + .stderr_limit = .limited(8 * 1024), + }); + defer allocator.free(result.stderr); + return result.stdout; } fn runCommandNoCapture(allocator: std.mem.Allocator, argv: []const []const u8) !bool { - var child = std.process.Child.init(argv, allocator); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Ignore; - - const term = child.spawnAndWait() catch return false; - return switch (term) { - .Exited => |code| code == 0, + var threaded: std.Io.Threaded = .init(allocator, .{}); + defer threaded.deinit(); + const result = std.process.run(allocator, threaded.io(), .{ + .argv = argv, + .stdout_limit = .limited(1024), + .stderr_limit = .limited(1024), + }) catch return false; + defer allocator.free(result.stdout); + defer allocator.free(result.stderr); + return switch (result.term) { + .exited => |code| code == 0, else => false, }; } diff --git a/src/backends/windows_webview/bindings.zig b/src/backends/windows_webview/bindings.zig index ddaad83..39cf57d 100644 --- a/src/backends/windows_webview/bindings.zig +++ b/src/backends/windows_webview/bindings.zig @@ -15,6 +15,13 @@ pub const COREWEBVIEW2_COLOR = extern struct { B: u8, }; +pub const RECT = extern struct { + left: i32, + top: i32, + right: i32, + bottom: i32, +}; + pub const IUnknown = extern struct { lpVtbl: *const IUnknownVtbl, }; @@ -96,7 +103,7 @@ pub const ICoreWebView2ControllerVtbl = extern struct { get_IsVisible: *const anyopaque, put_IsVisible: *const fn (*ICoreWebView2Controller, win.BOOL) callconv(.winapi) HRESULT, get_Bounds: *const anyopaque, - put_Bounds: *const fn (*ICoreWebView2Controller, win.RECT) callconv(.winapi) HRESULT, + put_Bounds: *const fn (*ICoreWebView2Controller, RECT) callconv(.winapi) HRESULT, get_ZoomFactor: *const anyopaque, put_ZoomFactor: *const anyopaque, add_ZoomFactorChanged: *const anyopaque, @@ -244,17 +251,23 @@ pub const CreateCoreWebView2EnvironmentWithOptionsFn = *const fn ( *ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, ) callconv(.winapi) HRESULT; +extern "kernel32" fn LoadLibraryW(lpLibFileName: win.LPCWSTR) callconv(.winapi) ?win.HMODULE; +extern "kernel32" fn FreeLibrary(hLibModule: win.HMODULE) callconv(.winapi) win.BOOL; +extern "kernel32" fn GetProcAddress(hModule: win.HMODULE, lpProcName: [*:0]const u8) callconv(.winapi) ?*const anyopaque; + pub const Symbols = struct { - loader: std.DynLib, + loader: win.HMODULE, create_environment: CreateCoreWebView2EnvironmentWithOptionsFn, /// Loads the platform symbols. pub fn load() !Symbols { - var loader = try std.DynLib.open("WebView2Loader.dll"); - errdefer loader.close(); + const library_name = std.unicode.utf8ToUtf16LeStringLiteral("WebView2Loader.dll"); + const loader = LoadLibraryW(library_name) orelse return error.MissingSharedLibrary; + errdefer _ = FreeLibrary(loader); - const create_environment = loader.lookup(CreateCoreWebView2EnvironmentWithOptionsFn, "CreateCoreWebView2EnvironmentWithOptions") orelse + const create_environment_proc = GetProcAddress(loader, "CreateCoreWebView2EnvironmentWithOptions") orelse return error.MissingDynamicSymbol; + const create_environment: CreateCoreWebView2EnvironmentWithOptionsFn = @ptrCast(create_environment_proc); return .{ .loader = loader, @@ -264,7 +277,7 @@ pub const Symbols = struct { /// Releases resources owned by this value. pub fn deinit(self: *Symbols) void { - self.loader.close(); + _ = FreeLibrary(self.loader); } }; diff --git a/src/backends/windows_webview_host.zig b/src/backends/windows_webview_host.zig index 9895f82..f637286 100644 --- a/src/backends/windows_webview_host.zig +++ b/src/backends/windows_webview_host.zig @@ -1,5 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); +const thread_compat = @import("thread_compat"); +const time_compat = @import("time_compat"); const window_style_types = @import("../root/window_style.zig"); const wv2 = @import("windows_webview/bindings.zig"); @@ -7,6 +9,9 @@ const win = std.os.windows; const WindowStyle = window_style_types.WindowStyle; const WindowControl = window_style_types.WindowControl; +const WPARAM = usize; +const LPARAM = isize; +const LRESULT = isize; const default_width: i32 = 980; const default_height: i32 = 660; @@ -55,6 +60,7 @@ const SWP_SHOWWINDOW: u32 = 0x0040; const COINIT_APARTMENTTHREADED: u32 = 0x2; const ERROR_CLASS_ALREADY_EXISTS: u32 = 1410; +const S_OK: wv2.HRESULT = 0; const webview2_bg_env_name = std.unicode.utf8ToUtf16LeStringLiteral("WEBVIEW2_DEFAULT_BACKGROUND_COLOR"); const webview2_bg_env_zero = std.unicode.utf8ToUtf16LeStringLiteral("0"); @@ -83,6 +89,13 @@ const POINT = extern struct { y: i32, }; +const RECT = extern struct { + left: i32, + top: i32, + right: i32, + bottom: i32, +}; + const MINMAXINFO = extern struct { ptReserved: POINT, ptMaxSize: POINT, @@ -94,8 +107,8 @@ const MINMAXINFO = extern struct { const MSG = extern struct { hwnd: ?win.HWND, message: u32, - wParam: win.WPARAM, - lParam: win.LPARAM, + wParam: WPARAM, + lParam: LPARAM, time: u32, pt: POINT, lPrivate: u32, @@ -116,7 +129,7 @@ const CREATESTRUCTW = extern struct { dwExStyle: u32, }; -const WNDPROC = *const fn (win.HWND, u32, win.WPARAM, win.LPARAM) callconv(.winapi) win.LRESULT; +const WNDPROC = *const fn (win.HWND, u32, WPARAM, LPARAM) callconv(.winapi) LRESULT; const WNDCLASSEXW = extern struct { cbSize: u32, @@ -137,6 +150,8 @@ extern "ole32" fn CoInitializeEx(?*anyopaque, u32) callconv(.winapi) wv2.HRESULT extern "ole32" fn CoUninitialize() callconv(.winapi) void; extern "ole32" fn CoTaskMemFree(?*anyopaque) callconv(.winapi) void; extern "kernel32" fn GetLastError() callconv(.winapi) u32; +extern "kernel32" fn GetModuleHandleW(?win.LPCWSTR) callconv(.winapi) ?win.HMODULE; +extern "kernel32" fn SetEnvironmentVariableW(win.LPCWSTR, ?win.LPCWSTR) callconv(.winapi) win.BOOL; extern "user32" fn RegisterClassExW(*const WNDCLASSEXW) callconv(.winapi) win.ATOM; extern "user32" fn UnregisterClassW(win.LPCWSTR, ?win.HINSTANCE) callconv(.winapi) win.BOOL; @@ -154,24 +169,24 @@ extern "user32" fn CreateWindowExW( ?win.HINSTANCE, ?*anyopaque, ) callconv(.winapi) ?win.HWND; -extern "user32" fn DefWindowProcW(win.HWND, u32, win.WPARAM, win.LPARAM) callconv(.winapi) win.LRESULT; +extern "user32" fn DefWindowProcW(win.HWND, u32, WPARAM, LPARAM) callconv(.winapi) LRESULT; extern "user32" fn DestroyWindow(win.HWND) callconv(.winapi) win.BOOL; extern "user32" fn PostQuitMessage(i32) callconv(.winapi) void; extern "user32" fn ShowWindow(win.HWND, i32) callconv(.winapi) win.BOOL; extern "user32" fn UpdateWindow(win.HWND) callconv(.winapi) win.BOOL; extern "user32" fn PeekMessageW(*MSG, ?win.HWND, u32, u32, u32) callconv(.winapi) win.BOOL; extern "user32" fn TranslateMessage(*const MSG) callconv(.winapi) win.BOOL; -extern "user32" fn DispatchMessageW(*const MSG) callconv(.winapi) win.LRESULT; -extern "user32" fn GetClientRect(win.HWND, *win.RECT) callconv(.winapi) win.BOOL; +extern "user32" fn DispatchMessageW(*const MSG) callconv(.winapi) LRESULT; +extern "user32" fn GetClientRect(win.HWND, *RECT) callconv(.winapi) win.BOOL; extern "user32" fn SetWindowPos(win.HWND, ?win.HWND, i32, i32, i32, i32, u32) callconv(.winapi) win.BOOL; extern "user32" fn SetWindowLongPtrW(win.HWND, i32, win.LONG_PTR) callconv(.winapi) win.LONG_PTR; extern "user32" fn GetWindowLongPtrW(win.HWND, i32) callconv(.winapi) win.LONG_PTR; extern "user32" fn SetWindowLongW(win.HWND, i32, i32) callconv(.winapi) i32; extern "user32" fn GetWindowLongW(win.HWND, i32) callconv(.winapi) i32; -extern "user32" fn PostMessageW(win.HWND, u32, win.WPARAM, win.LPARAM) callconv(.winapi) win.BOOL; -extern "user32" fn SendMessageW(win.HWND, u32, win.WPARAM, win.LPARAM) callconv(.winapi) win.LRESULT; +extern "user32" fn PostMessageW(win.HWND, u32, WPARAM, LPARAM) callconv(.winapi) win.BOOL; +extern "user32" fn SendMessageW(win.HWND, u32, WPARAM, LPARAM) callconv(.winapi) LRESULT; extern "user32" fn SetWindowTextW(win.HWND, win.LPCWSTR) callconv(.winapi) win.BOOL; -extern "user32" fn GetWindowRect(win.HWND, *win.RECT) callconv(.winapi) win.BOOL; +extern "user32" fn GetWindowRect(win.HWND, *RECT) callconv(.winapi) win.BOOL; extern "user32" fn SetWindowRgn(win.HWND, ?*anyopaque, win.BOOL) callconv(.winapi) i32; extern "user32" fn CreateIconFromResourceEx(?[*]u8, u32, win.BOOL, u32, i32, i32, u32) callconv(.winapi) ?win.HICON; extern "user32" fn DestroyIcon(win.HICON) callconv(.winapi) win.BOOL; @@ -183,7 +198,7 @@ pub const Host = struct { title: []u8, style: WindowStyle, - mutex: std.Thread.Mutex = .{}, + mutex: thread_compat.Mutex = .{}, queue: std.array_list.Managed(Command), owner_thread_id: std.Thread.Id = undefined, @@ -361,7 +376,7 @@ fn initializeUiThread(host: *Host) !void { host.symbols = symbols; host.mutex.unlock(); - if (std.os.windows.kernel32.GetModuleHandleW(null)) |module_handle| { + if (GetModuleHandleW(null)) |module_handle| { host.instance = @ptrCast(module_handle); } else { host.instance = null; @@ -402,7 +417,7 @@ fn waitForStartup(host: *Host) void { if (done) return; const had_activity = pumpMessageLoopStep(host); - if (!had_activity) std.Thread.sleep(std.time.ns_per_ms); + if (!had_activity) time_compat.sleep(std.time.ns_per_ms); } } @@ -415,7 +430,7 @@ fn pumpUntilClosed(host: *Host) void { host.mutex.unlock(); if (done) return; - if (!had_activity) std.Thread.sleep(std.time.ns_per_ms); + if (!had_activity) time_compat.sleep(std.time.ns_per_ms); } } @@ -744,9 +759,9 @@ fn createWebViewEnvironment(host: *Host) bool { const symbols = host.symbols orelse return false; if (host.style.transparent) { - _ = std.os.windows.kernel32.SetEnvironmentVariableW(webview2_bg_env_name, webview2_bg_env_zero); + _ = SetEnvironmentVariableW(webview2_bg_env_name, webview2_bg_env_zero); } else { - _ = std.os.windows.kernel32.SetEnvironmentVariableW(webview2_bg_env_name, null); + _ = SetEnvironmentVariableW(webview2_bg_env_name, null); } retainAsyncCallback(host); @@ -774,14 +789,20 @@ fn updateControllerBounds(host: *Host) void { const hwnd = host.hwnd orelse return; const controller = host.controller orelse return; - var rect: win.RECT = undefined; + var rect: RECT = undefined; if (GetClientRect(hwnd, &rect) == 0) return; - _ = controller.lpVtbl.put_Bounds(controller, rect); + const bounds = wv2.RECT{ + .left = rect.left, + .top = rect.top, + .right = rect.right, + .bottom = rect.bottom, + }; + _ = controller.lpVtbl.put_Bounds(controller, bounds); } fn applyWindowCornerRadius(hwnd: win.HWND, radius: ?u16) void { if (radius) |value| { - var window_rect: win.RECT = undefined; + var window_rect: RECT = undefined; if (GetWindowRect(hwnd, &window_rect) == 0) return; const width = window_rect.right - window_rect.left; @@ -825,8 +846,8 @@ fn applyWindowIcon(host: *Host, hwnd: win.HWND, icon: ?window_style_types.Window ) orelse return; host.window_icon = created; - _ = SendMessageW(hwnd, WM_SETICON, ICON_SMALL, @as(win.LPARAM, @intCast(@intFromPtr(created)))); - _ = SendMessageW(hwnd, WM_SETICON, ICON_BIG, @as(win.LPARAM, @intCast(@intFromPtr(created)))); + _ = SendMessageW(hwnd, WM_SETICON, ICON_SMALL, @as(LPARAM, @intCast(@intFromPtr(created)))); + _ = SendMessageW(hwnd, WM_SETICON, ICON_BIG, @as(LPARAM, @intCast(@intFromPtr(created)))); } fn applyControllerBackgroundColor(host: *Host) void { @@ -905,7 +926,7 @@ fn environmentHandlerQueryInterface( ) callconv(.winapi) wv2.HRESULT { out.* = self; _ = environmentHandlerAddRef(self); - return win.S_OK; + return S_OK; } fn environmentHandlerAddRef(self: *wv2.ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler) callconv(.winapi) u32 { @@ -933,7 +954,7 @@ fn environmentHandlerInvoke( if (!wv2.succeeded(error_code) or created_environment == null) { failStartup(host, error.NativeBackendUnavailable); - return win.S_OK; + return S_OK; } const environment = created_environment.?; @@ -944,7 +965,7 @@ fn environmentHandlerInvoke( var controller_handler = host.allocator.create(ControllerCompletedHandler) catch { releaseAsyncCallback(host); failStartup(host, error.NativeBackendUnavailable); - return win.S_OK; + return S_OK; }; controller_handler.* = .{ .iface = .{ .lpVtbl = &controller_completed_handler_vtbl }, @@ -956,7 +977,7 @@ fn environmentHandlerInvoke( const hwnd = host.hwnd orelse { _ = controllerHandlerRelease(&controller_handler.iface); failStartup(host, error.NativeBackendUnavailable); - return win.S_OK; + return S_OK; }; const hr = environment.lpVtbl.CreateCoreWebView2Controller(environment, hwnd, &controller_handler.iface); @@ -966,7 +987,7 @@ fn environmentHandlerInvoke( } _ = controllerHandlerRelease(&controller_handler.iface); - return win.S_OK; + return S_OK; } fn controllerHandlerQueryInterface( @@ -976,7 +997,7 @@ fn controllerHandlerQueryInterface( ) callconv(.winapi) wv2.HRESULT { out.* = self; _ = controllerHandlerAddRef(self); - return win.S_OK; + return S_OK; } fn controllerHandlerAddRef(self: *wv2.ICoreWebView2CreateCoreWebView2ControllerCompletedHandler) callconv(.winapi) u32 { @@ -1004,7 +1025,7 @@ fn controllerHandlerInvoke( if (!wv2.succeeded(error_code) or created_controller == null) { failStartup(host, error.NativeBackendUnavailable); - return win.S_OK; + return S_OK; } const controller = created_controller.?; @@ -1014,7 +1035,7 @@ fn controllerHandlerInvoke( var webview: ?*wv2.ICoreWebView2 = null; if (!wv2.succeeded(controller.lpVtbl.get_CoreWebView2(controller, &webview)) or webview == null) { failStartup(host, error.NativeBackendUnavailable); - return win.S_OK; + return S_OK; } const core = webview.?; _ = core.lpVtbl.AddRef(core); @@ -1033,7 +1054,7 @@ fn controllerHandlerInvoke( finishStartupReady(host); - return win.S_OK; + return S_OK; } fn configureWebViewSettings(webview: *wv2.ICoreWebView2) void { @@ -1091,7 +1112,7 @@ fn titleHandlerQueryInterface( ) callconv(.winapi) wv2.HRESULT { out.* = self; _ = titleHandlerAddRef(self); - return win.S_OK; + return S_OK; } fn titleHandlerAddRef(self: *wv2.ICoreWebView2DocumentTitleChangedEventHandler) callconv(.winapi) u32 { @@ -1117,21 +1138,21 @@ fn titleHandlerInvoke( const host = handler.host; _ = sender; - const webview = host.webview orelse return win.S_OK; + const webview = host.webview orelse return S_OK; var raw_title: ?win.PWSTR = null; - if (!wv2.succeeded(webview.lpVtbl.get_DocumentTitle(webview, &raw_title))) return win.S_OK; - const title_ptr = raw_title orelse return win.S_OK; + if (!wv2.succeeded(webview.lpVtbl.get_DocumentTitle(webview, &raw_title))) return S_OK; + const title_ptr = raw_title orelse return S_OK; defer CoTaskMemFree(title_ptr); if (host.hwnd) |hwnd| { _ = SetWindowTextW(hwnd, title_ptr); } - return win.S_OK; + return S_OK; } -fn wndProc(hwnd: win.HWND, msg: u32, wparam: win.WPARAM, lparam: win.LPARAM) callconv(.winapi) win.LRESULT { +fn wndProc(hwnd: win.HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) callconv(.winapi) LRESULT { if (msg == WM_NCCREATE) { const create_struct: *const CREATESTRUCTW = @ptrFromInt(@as(usize, @bitCast(lparam))); if (create_struct.lpCreateParams) |param| { diff --git a/src/bridge/template.zig b/src/bridge/template.zig index 4b8307b..bed504a 100644 --- a/src/bridge/template.zig +++ b/src/bridge/template.zig @@ -40,58 +40,57 @@ fn renderWithRuntimeHelpers( var out = std.array_list.Managed(u8).init(allocator); errdefer out.deinit(); - const writer = out.writer(); - try writer.writeAll("// Generated by tools/bridge_gen.zig\n"); - try writer.writeAll(runtime_helpers_js); - try writer.writeAll("\nconst webuiRpcEndpoint = "); - try writeJsonLiteral(allocator, writer, options.rpc_route); - try writer.writeAll(";\n"); - try writer.writeAll("\nconst "); - try writer.writeAll(options.namespace); - try writer.writeAll(" = Object.freeze({\n"); + try out.appendSlice("// Generated by tools/bridge_gen.zig\n"); + try out.appendSlice(runtime_helpers_js); + try out.appendSlice("\nconst webuiRpcEndpoint = "); + try writeJsonLiteral(allocator, &out, options.rpc_route); + try out.appendSlice(";\n"); + try out.appendSlice("\nconst "); + try out.appendSlice(options.namespace); + try out.appendSlice(" = Object.freeze({\n"); for (functions) |fn_meta| { - try writer.writeAll(" "); - try writer.writeAll(fn_meta.name); - try writer.writeAll(": async ("); + try out.appendSlice(" "); + try out.appendSlice(fn_meta.name); + try out.appendSlice(": async ("); var i: usize = 0; while (i < fn_meta.arity) : (i += 1) { - if (i != 0) try writer.writeAll(", "); - try writer.print("arg{d}", .{i}); + if (i != 0) try out.appendSlice(", "); + try out.print("arg{d}", .{i}); } - try writer.writeAll(") => await webuiInvoke(webuiRpcEndpoint, "); - try writeJsonLiteral(allocator, writer, fn_meta.name); - try writer.writeAll(", ["); + try out.appendSlice(") => await webuiInvoke(webuiRpcEndpoint, "); + try writeJsonLiteral(allocator, &out, fn_meta.name); + try out.appendSlice(", ["); i = 0; while (i < fn_meta.arity) : (i += 1) { - if (i != 0) try writer.writeAll(", "); - try writer.print("arg{d}", .{i}); + if (i != 0) try out.appendSlice(", "); + try out.print("arg{d}", .{i}); } - try writer.writeAll("]),\n"); + try out.appendSlice("]),\n"); } // Keep generated bridge JS usable from classic