Skip to content

Commit 49a0d3b

Browse files
committed
refactor: improve API naming and add macOS cross-compilation CI
- Rename dispatchNoContext/bindNoContext to dispatchSimple/bindSimple - Rename init to addInitScript to avoid conflict with Zig constructor convention - Make Status enum pub so callers can reference the type by name - Mark mapError as inline - Add macOS SDK cross-compilation steps to CI with caching
1 parent 958cded commit 49a0d3b

File tree

5 files changed

+94
-59
lines changed

5 files changed

+94
-59
lines changed

.github/workflows/doc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
- name: Setup Pages
3939
uses: actions/configure-pages@v3
4040
- uses: mlugg/setup-zig@v2
41-
- name: Install development headers
41+
- name: Install linux development libraries
4242
uses: awalsh128/cache-apt-pkgs-action@latest
4343
with:
4444
packages: libgtk-3-dev libwebkit2gtk-4.1-dev

.github/workflows/test.yml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- name: Checkout
2525
uses: actions/checkout@v4
2626
- uses: mlugg/setup-zig@v2
27-
- name: Install development headers
27+
- name: Install linux development libraries
2828
uses: awalsh128/cache-apt-pkgs-action@latest
2929
with:
3030
packages: libgtk-3-dev libwebkit2gtk-4.1-dev
@@ -38,6 +38,22 @@ jobs:
3838
- name: Build examples (ReleaseSafe)
3939
run: |
4040
zig build examples -Doptimize=ReleaseSafe
41-
- name: Build examples (Cross-compile for Windows ReleaseFast)
41+
- name: Build examples (Cross-compile for Windows x86_64 ReleaseFast)
4242
run: |
4343
zig build examples -Dtarget=x86_64-windows -Doptimize=ReleaseFast
44+
- name: Cache macOS SDK
45+
id: cache-macos-sdk
46+
uses: actions/cache@v4
47+
with:
48+
path: MacOSX15.5.sdk
49+
key: macos-sdk-15.5
50+
- name: Download macOS SDK
51+
if: steps.cache-macos-sdk.outputs.cache-hit != 'true'
52+
run: |
53+
curl -L https://github.com/joseluisq/macosx-sdks/releases/download/15.5/MacOSX15.5.sdk.tar.xz | tar -xJ
54+
- name: Build examples (Cross-compile for macOS aarch64 ReleaseFast)
55+
run: |
56+
zig build examples -Dtarget=aarch64-macos -Dmacos-sdk=${{ github.workspace }}/MacOSX15.5.sdk -Doptimize=ReleaseFast
57+
- name: Build examples (Cross-compile for macOS x86_64 ReleaseFast)
58+
run: |
59+
zig build examples -Dtarget=x86_64-macos -Dmacos-sdk=${{ github.workspace }}/MacOSX15.5.sdk -Doptimize=ReleaseFast

README.md

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Zig bindings for [webview/webview](https://github.com/webview/webview) — a tiny cross-platform library for building desktop applications with web technologies using a native browser widget.
44

5-
Zig API with error unions, comptime-powered typed callbacks, and JS ↔ Zig bindings via `bind` / `respond`.
5+
Idiomatic Zig API featuring error unions and comptime-powered typed callbacks.
66

77
## Requirements
88

@@ -38,7 +38,7 @@ pub fn main() !void {
3838
defer w.destroy() catch unreachable;
3939
try w.setTitle("Hello");
4040
try w.setSize(800, 600, .none);
41-
try w.navigate("https://example.com");
41+
try w.setHtml("Thanks for using webview!");
4242
try w.run();
4343
}
4444
```
@@ -67,15 +67,41 @@ Cross-compilation to macOS and Windows is supported.
6767
Obtain a macOS SDK (e.g. via [macosx-sdks](https://github.com/joseluisq/macosx-sdks)) and pass its path with `-Dmacos-sdk`:
6868

6969
```sh
70-
zig build -Dtarget=aarch64-macos -Dmacos-sdk=/path/to/MacOSX.sdk
70+
zig build examples -Dtarget=aarch64-macos -Dmacos-sdk=/path/to/MacOSX.sdk
7171
# or for x86_64
72-
zig build -Dtarget=x86_64-macos -Dmacos-sdk=/path/to/MacOSX.sdk
72+
zig build examples -Dtarget=x86_64-macos -Dmacos-sdk=/path/to/MacOSX.sdk
7373
```
7474

7575
**Targeting Windows**
7676

7777
The required WebView2 headers are already bundled in `deps/WebView2/`, so no extra setup is needed:
7878

7979
```sh
80-
zig build -Dtarget=x86_64-windows
80+
zig build examples -Dtarget=x86_64-windows
8181
```
82+
83+
## C API to Zig API Mapping
84+
85+
| C Function | Zig Method |
86+
|---|---|
87+
| `webview_create(debug, window)` | `Webview.create(debug, window) !*Webview` |
88+
| `webview_destroy(w)` | `w.destroy() !void` |
89+
| `webview_run(w)` | `w.run() !void` |
90+
| `webview_terminate(w)` | `w.terminate() !void` |
91+
| `webview_dispatch(w, fn, arg)` | `w.dispatchRaw(callback, arg) !void` |
92+
| `webview_dispatch(w, fn, arg)` | `w.dispatch(T, callback, arg) !void` |
93+
| `webview_dispatch(w, fn, null)` | `w.dispatchSimple(callback) !void` |
94+
| `webview_get_window(w)` | `w.getWindow() ?*anyopaque` |
95+
| `webview_get_native_handle(w, kind)` | `w.getNativeHandle(kind) ?*anyopaque` |
96+
| `webview_set_title(w, title)` | `w.setTitle(title) !void` |
97+
| `webview_set_size(w, width, height, hint)` | `w.setSize(width, height, hint) !void` |
98+
| `webview_navigate(w, url)` | `w.navigate(url) !void` |
99+
| `webview_set_html(w, html)` | `w.setHtml(html) !void` |
100+
| `webview_init(w, js)` | `w.addInitScript(js) !void` |
101+
| `webview_eval(w, js)` | `w.eval(js) !void` |
102+
| `webview_bind(w, name, fn, arg)` | `w.bindRaw(name, callback, arg) !void` |
103+
| `webview_bind(w, name, fn, arg)` | `w.bind(T, name, callback, arg) !void` |
104+
| `webview_bind(w, name, fn, null)` | `w.bindSimple(name, callback) !void` |
105+
| `webview_unbind(w, name)` | `w.unbind(name) !void` |
106+
| `webview_return(w, id, status, result)` | `w.respond(id, status, result) !void` |
107+
| `webview_version()` | `Webview.version() Version` |

build.zig

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,7 @@ fn addExamplesStep(b: *std.Build, options: BuildOptions, mod: *std.Build.Module)
119119
.{ .name = "webview", .module = mod },
120120
},
121121
});
122-
if (options.target.result.os.tag == .macos) {
123-
tryApplyMacOsSdk(b, example_mod, options);
124-
}
122+
tryApplyMacOsSdk(b, example_mod, options);
125123
const exe = b.addExecutable(.{
126124
.name = name,
127125
.root_module = example_mod,
@@ -157,8 +155,12 @@ fn createCModule(b: *std.Build, options: BuildOptions) *std.Build.Module {
157155
return c_mod;
158156
}
159157

160-
fn tryApplyMacOsSdk(b: *std.Build, mod: *std.Build.Module, options: BuildOptions) void {
161-
if (builtin.os.tag != .macos and options.macos_sdk != null) {
158+
/// Applies macOS SDK configuration for cross-compilation.
159+
///
160+
/// Automatically configures system include paths, library paths, and framework paths
161+
/// when cross-compiling for macOS from non-macOS hosts.
162+
pub fn tryApplyMacOsSdk(b: *std.Build, mod: *std.Build.Module, options: BuildOptions) void {
163+
if (options.target.result.os.tag == .macos and builtin.os.tag != .macos and options.macos_sdk != null) {
162164
const macos_sdk_path: std.Build.LazyPath = .{ .cwd_relative = options.macos_sdk.? };
163165
mod.addSystemIncludePath(macos_sdk_path.path(b, "usr/include"));
164166
mod.addLibraryPath(macos_sdk_path.path(b, "usr/lib"));

src/root.zig

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub const Webview = opaque {
3232
/// Signifies that something does not exist.
3333
NotFound,
3434
};
35-
fn mapError(err: c.webview_error_t) Error!void {
35+
inline fn mapError(err: c.webview_error_t) Error!void {
3636
switch (err) {
3737
c.WEBVIEW_ERROR_OK => {},
3838
c.WEBVIEW_ERROR_MISSING_DEPENDENCY => return Error.MissingDependency,
@@ -104,7 +104,9 @@ pub const Webview = opaque {
104104
/// Returns `Error.MissingDependency` if WebView2 is unavailable on Windows.
105105
pub fn create(debug: bool, window: ?*anyopaque) Error!*Webview {
106106
const w = c.webview_create(@intFromBool(debug), window);
107-
return @ptrCast(w orelse return Error.Unspecified);
107+
if (w == null) return Error.Unspecified;
108+
if (@intFromPtr(w) < 0) return mapError(@intFromPtr(w));
109+
return @ptrCast(w);
108110
}
109111

110112
/// Destroys a webview instance and closes the native window.
@@ -129,10 +131,10 @@ pub const Webview = opaque {
129131
/// this function can be used to schedule code to execute on the main/GUI
130132
/// thread and thereby make that execution safe in multi-threaded applications.
131133
///
132-
/// See also `dispatch` and `dispatchNoContext`
134+
/// See also `dispatch` and `dispatchSimple`
133135
pub fn dispatchRaw(
134136
self: *Webview,
135-
comptime callback: fn (w: *Webview, arg: ?*anyopaque) void,
137+
callback: fn (w: *Webview, arg: ?*anyopaque) void,
136138
arg: ?*anyopaque,
137139
) Error!void {
138140
const S = struct {
@@ -153,42 +155,36 @@ pub const Webview = opaque {
153155
/// Schedules a function to be invoked on the thread with the run/event loop,
154156
/// with a typed argument.
155157
///
156-
/// Like `dispatchRaw`, but the callback receives a `*T` pointer instead of
157-
/// `?*anyopaque`. When `T` is `void`, no argument is passed and `arg` should
158-
/// be `{}`.
158+
/// Like `dispatchRaw`, but the callback receives a `*T` pointer instead of `?*anyopaque`,
159+
/// providing type safety for the user-provided argument.
159160
///
160-
/// See also `dispatchNoContext`
161+
/// See also `dispatchSimple`
161162
pub fn dispatch(
162163
self: *Webview,
163164
comptime T: type,
164-
comptime callback: DispatchCallbackType(T),
165-
arg: if (T == void) void else *T,
165+
callback: DispatchCallbackType(T),
166+
arg: *T,
166167
) Error!void {
167168
const S = struct {
168169
fn cb(w: c.webview_t, a: ?*anyopaque) callconv(.c) void {
169-
if (comptime T == void) {
170-
callback(.from(w));
171-
} else {
172-
callback(.from(w), @ptrCast(@alignCast(a.?)));
173-
}
170+
callback(.from(w), @ptrCast(@alignCast(a.?)));
174171
}
175172
};
176-
return mapError(c.webview_dispatch(
177-
self.ptr(),
178-
S.cb,
179-
if (comptime T == void) null else @ptrCast(arg),
180-
));
173+
return mapError(c.webview_dispatch(self.ptr(), S.cb, @ptrCast(arg)));
181174
}
182175

183176
/// Schedules a function to be invoked on the thread with the run/event loop,
184177
/// without a user-provided argument.
185-
///
186-
/// Shorthand for `dispatch(void, callback, {})`.
187-
pub inline fn dispatchNoContext(
178+
pub fn dispatchSimple(
188179
self: *Webview,
189-
comptime callback: DispatchCallbackType(void),
180+
callback: DispatchCallbackType(void),
190181
) Error!void {
191-
return self.dispatch(void, callback, {});
182+
const S = struct {
183+
fn cb(w: c.webview_t, _: ?*anyopaque) callconv(.c) void {
184+
callback(.from(w));
185+
}
186+
};
187+
return mapError(c.webview_dispatch(self.ptr(), S.cb, null));
192188
}
193189

194190
/// Returns the native handle of the window associated with the webview
@@ -242,7 +238,7 @@ pub const Webview = opaque {
242238

243239
/// Injects JavaScript code to be executed immediately upon loading a page.
244240
/// The code will be executed before `window.onload`.
245-
pub fn init(self: *Webview, js: [:0]const u8) Error!void {
241+
pub fn addInitScript(self: *Webview, js: [:0]const u8) Error!void {
246242
return mapError(c.webview_init(self.ptr(), js.ptr));
247243
}
248244

@@ -263,7 +259,7 @@ pub const Webview = opaque {
263259
/// Returns `Error.Duplicate` if a binding already exists with the
264260
/// specified name.
265261
///
266-
/// See also `bind` and `bindNoContext`
262+
/// See also `bind` and `bindSimple`
267263
pub fn bindRaw(
268264
self: *Webview,
269265
name: [:0]const u8,
@@ -289,46 +285,41 @@ pub const Webview = opaque {
289285
///
290286
/// Like `bindRaw`, but the callback receives a `*T` pointer instead of `?*anyopaque`,
291287
/// providing type safety for the user-provided argument.
292-
/// When `T` is `void`, no argument is passed and `arg` should be `{}`.
293288
///
294289
/// Returns `Error.Duplicate` if a binding already exists with the specified name.
295290
///
296-
/// See also `bindNoContext`
291+
/// See also `bindSimple`
297292
pub fn bind(
298293
self: *Webview,
299294
comptime T: type,
300295
name: [:0]const u8,
301-
comptime callback: BindCallbackType(T),
302-
arg: if (T == void) void else *T,
296+
callback: BindCallbackType(T),
297+
arg: *T,
303298
) Error!void {
304299
const S = struct {
305300
fn cb(id: [*c]const u8, req: [*c]const u8, a: ?*anyopaque) callconv(.c) void {
306-
if (comptime T == void) {
307-
callback(std.mem.span(id), std.mem.span(req));
308-
} else {
309-
callback(std.mem.span(id), std.mem.span(req), @ptrCast(@alignCast(a.?)));
310-
}
301+
callback(std.mem.span(id), std.mem.span(req), @ptrCast(@alignCast(a.?)));
311302
}
312303
};
313-
return mapError(c.webview_bind(
314-
self.ptr(),
315-
name.ptr,
316-
S.cb,
317-
if (comptime T == void) null else @ptrCast(arg),
318-
));
304+
return mapError(c.webview_bind(self.ptr(), name.ptr, S.cb, @ptrCast(arg)));
319305
}
320306

321307
/// Binds a function to a new global JavaScript function without a user-provided argument.
322308
///
323309
/// Shorthand for `bind(void, name, callback, {})`.
324310
///
325311
/// Returns `Error.Duplicate` if a binding already exists with the specified name.
326-
pub inline fn bindNoContext(
312+
pub fn bindSimple(
327313
self: *Webview,
328314
name: [:0]const u8,
329-
comptime callback: BindCallbackType(void),
315+
callback: BindCallbackType(void),
330316
) Error!void {
331-
return self.bind(void, name, callback, {});
317+
const S = struct {
318+
fn cb(id: [*c]const u8, req: [*c]const u8, _: ?*anyopaque) callconv(.c) void {
319+
callback(std.mem.span(id), std.mem.span(req));
320+
}
321+
};
322+
return mapError(c.webview_bind(self.ptr(), name.ptr, S.cb, null));
332323
}
333324

334325
/// Removes a binding created with `bind`.
@@ -338,7 +329,7 @@ pub const Webview = opaque {
338329
return mapError(c.webview_unbind(self.ptr(), name.ptr));
339330
}
340331

341-
const Status = enum(i32) {
332+
pub const Status = enum(i32) {
342333
ok = 0,
343334
err = 1,
344335
_,

0 commit comments

Comments
 (0)