Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
version: [0.16.0]
version: [0.16.0, 0.17.0]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ Like `zig build run_minimal`, this will build and run the `minimal` example.

### Zig `0.16.0` and later

This package targets Zig `0.16.0` and up. Nightly is
still not recommended — the build-system API can change between dev builds
and break the binding without warning.
This package supports Zig `0.16.0` and `0.17.0`; CI builds and tests both.
Nightly is still not recommended — the build-system API can change between
dev builds and break the binding without warning.

1. Add to `build.zig.zon`

Expand Down
15 changes: 14 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,23 @@ pub fn addEmbeddedDir(b: *Build, module: *Module, options: EmbedDirOptions) !voi
var dir = try root.openDir(io, options.path, .{ .iterate = true });
defer dir.close(io);

// Zig 0.17 caches the configure phase. Declare every scanned directory so
// adding, deleting, or renaming a file re-runs this scan; file contents are
// already tracked by `addCopyFile`. The API is not recursive.
const track_dirs = @hasDecl(Build, "dependOnDirectoryContents");
if (track_dirs) b.dependOnDirectoryContents(b.path(options.path));

var files: std.ArrayList([]const u8) = .empty;
defer files.deinit(b.allocator);
var walker = try dir.walk(b.allocator);
defer walker.deinit();
while (try walker.next(io)) |entry| {
if (track_dirs and entry.kind == .directory) {
b.dependOnDirectoryContents(b.path(b.pathJoin(&.{ options.path, entry.path })));
}
if (entry.kind != .file) continue;
const path = b.dupe(entry.path);
// `b.dupe` returns `[]const u8` on Zig 0.17; the path is rewritten in place on Windows.
const path = try b.allocator.dupe(u8, entry.path);
if (builtin.os.tag == .windows) {
for (path) |*char| {
if (char.* == '\\') char.* = '/';
Expand Down Expand Up @@ -376,6 +386,9 @@ fn buildExamples(b: *Build, options: BuildExamplesOptions) !void {
};
defer examples_dir.close(io);

// Re-run configuration when an example directory is added or removed.
b.dependOnDirectoryContents(b.path(examples_path));

var iter = examples_dir.iterate();
while (try iter.next(io)) |entry| {
if (entry.kind != .directory) continue;
Expand Down
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
.minimum_zig_version = "0.16.0",
.dependencies = .{
.webui = .{
.hash = "webui-2.5.0-beta.4-pxqD5aCPRABtMDhL6OiX-rWtAXSBTzb1iY0YYfKxR-rF",
.url = "https://github.com/webui-dev/webui/archive/52f9e75b92faf9a23fd150b3c60051c4ec85fc69.tar.gz",
.hash = "webui-2.5.0-beta.4-pxqD5SCVRAAXiIiARY9CWyEhzl_sHMmRW8tzspnErwfT",
.url = "https://github.com/webui-dev/webui/archive/f1b28eeeecfc2d63bdb4d4857c6f137468c6d6f6.tar.gz",
},
},
.paths = .{
Expand Down
7 changes: 7 additions & 0 deletions examples/compat.zig
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ pub const FixedBufferStream = struct {
}
};

/// Format into `buffer` with a NUL terminator. `std.fmt.bufPrintZ` was removed
/// in Zig 0.17, and its replacement `std.mem.printSentinel` does not exist on 0.16.
pub fn bufPrintZ(buffer: []u8, comptime format: []const u8, args: anytype) ![:0]u8 {
if (@hasDecl(std.mem, "printSentinel")) return std.mem.printSentinel(buffer, format, args, 0);
return std.fmt.bufPrintSentinel(buffer, format, args, 0);
}

// ===== Allocator compat ======================================================

/// `std.heap.GeneralPurposeAllocator` was renamed to `std.heap.DebugAllocator`
Expand Down
48 changes: 24 additions & 24 deletions examples/comprehensive/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ fn getAppStatus(e: *webui.Event) void {
const url = win.getUrl() catch "unknown";

var buffer: [1024]u8 = undefined;
const json = std.fmt.bufPrintZ(buffer[0..],
const json = compat.bufPrintZ(buffer[0..],
\\{{"status":"running","users":{},"messages":{},"files":{},"port":{},"url":"{s}","clientId":{},"timestamp":{}}}
, .{ app_state.users_online, app_state.messages_sent, app_state.files_uploaded, port, url, e.client_id, compat.timestamp() }) catch "{\"error\":\"format_error\"}";

Expand All @@ -172,7 +172,7 @@ fn userAction(e: *webui.Event, action: [:0]const u8, data: [:0]const u8) void {
if (std.mem.eql(u8, action, "login")) {
// Check if user is already online
if (online_users.contains(data)) {
result = std.fmt.bufPrintZ(response[0..], "User '{s}' is already online. Online users: {}", .{ data, app_state.users_online }) catch "Error";
result = compat.bufPrintZ(response[0..], "User '{s}' is already online. Online users: {}", .{ data, app_state.users_online }) catch "Error";
} else {
// Add new user
const username_copy = allocator.dupe(u8, data) catch {
Expand All @@ -187,24 +187,24 @@ fn userAction(e: *webui.Event, action: [:0]const u8, data: [:0]const u8) void {
return;
};
app_state.users_online += 1;
result = std.fmt.bufPrintZ(response[0..], "User '{s}' logged in. Online users: {}", .{ data, app_state.users_online }) catch "Error";
result = compat.bufPrintZ(response[0..], "User '{s}' logged in. Online users: {}", .{ data, app_state.users_online }) catch "Error";
}
} else if (std.mem.eql(u8, action, "logout")) {
// Check if user is online
if (online_users.fetchRemove(data)) |kv| {
allocator.free(kv.key);
if (app_state.users_online > 0) app_state.users_online -= 1;
result = std.fmt.bufPrintZ(response[0..], "User '{s}' logged out. Online users: {}", .{ data, app_state.users_online }) catch "Error";
result = compat.bufPrintZ(response[0..], "User '{s}' logged out. Online users: {}", .{ data, app_state.users_online }) catch "Error";
} else {
result = std.fmt.bufPrintZ(response[0..], "User '{s}' was not online. Online users: {}", .{ data, app_state.users_online }) catch "Error";
result = compat.bufPrintZ(response[0..], "User '{s}' was not online. Online users: {}", .{ data, app_state.users_online }) catch "Error";
}
} else if (std.mem.eql(u8, action, "message")) {
app_state.messages_sent += 1;
result = std.fmt.bufPrintZ(response[0..], "Message sent. Total messages: {}", .{app_state.messages_sent}) catch "Error";
result = compat.bufPrintZ(response[0..], "Message sent. Total messages: {}", .{app_state.messages_sent}) catch "Error";
} else if (std.mem.eql(u8, action, "upload")) {
// For simulation purposes, just acknowledge the upload request
const filename = if (data.len > 0) data else "demo_file.txt";
result = std.fmt.bufPrintZ(response[0..], "Upload request received for '{s}'. Use the file input for actual upload.", .{filename}) catch "Error";
result = compat.bufPrintZ(response[0..], "Upload request received for '{s}'. Use the file input for actual upload.", .{filename}) catch "Error";
} else {
result = "Unknown action";
}
Expand All @@ -217,7 +217,7 @@ fn sendNotification(e: *webui.Event, message: [:0]const u8, level: [:0]const u8)

// Send notification to all clients
var js_code: [512]u8 = undefined;
const script = std.fmt.bufPrintZ(js_code[0..], "showNotification('{s}', '{s}');", .{ message, level }) catch return;
const script = compat.bufPrintZ(js_code[0..], "showNotification('{s}', '{s}');", .{ message, level }) catch return;

win.run(script);

Expand All @@ -238,22 +238,22 @@ fn processData(e: *webui.Event, operation: [:0]const u8, input_data: [:0]const u
while (i < input_data.len and i < 512) : (i += 1) {
reversed[i] = input_data[input_data.len - 1 - i];
}
output = std.fmt.bufPrintZ(result[0..], "Reversed: {s}", .{reversed[0..i]}) catch "Error";
output = compat.bufPrintZ(result[0..], "Reversed: {s}", .{reversed[0..i]}) catch "Error";
} else if (std.mem.eql(u8, operation, "uppercase")) {
// Convert to uppercase
var upper: [512]u8 = undefined;
for (input_data, 0..) |c, i| {
if (i >= 512) break;
upper[i] = std.ascii.toUpper(c);
}
output = std.fmt.bufPrintZ(result[0..], "Uppercase: {s}", .{upper[0..@min(input_data.len, 512)]}) catch "Error";
output = compat.bufPrintZ(result[0..], "Uppercase: {s}", .{upper[0..@min(input_data.len, 512)]}) catch "Error";
} else if (std.mem.eql(u8, operation, "hash")) {
// Simple hash (sum of bytes)
var hash: u32 = 0;
for (input_data) |c| {
hash = hash *% 31 +% c;
}
output = std.fmt.bufPrintZ(result[0..], "Hash: {X}", .{hash}) catch "Error";
output = compat.bufPrintZ(result[0..], "Hash: {X}", .{hash}) catch "Error";
} else {
output = "Unknown operation";
}
Expand All @@ -268,17 +268,17 @@ fn executeCommand(e: *webui.Event, command: [:0]const u8, args: [:0]const u8) vo
var result: [:0]const u8 = "";

if (std.mem.eql(u8, command, "echo")) {
result = std.fmt.bufPrintZ(response[0..], "Echo: {s}", .{args}) catch "Error";
result = compat.bufPrintZ(response[0..], "Echo: {s}", .{args}) catch "Error";
} else if (std.mem.eql(u8, command, "time")) {
const timestamp = compat.timestamp();
result = std.fmt.bufPrintZ(response[0..], "Current time: {}", .{timestamp}) catch "Error";
result = compat.bufPrintZ(response[0..], "Current time: {}", .{timestamp}) catch "Error";
} else if (std.mem.eql(u8, command, "random")) {
var prng = std.Random.DefaultPrng.init(@intCast(compat.timestamp()));
const random_num = prng.random().int(u32);
result = std.fmt.bufPrintZ(response[0..], "Random number: {}", .{random_num}) catch "Error";
result = compat.bufPrintZ(response[0..], "Random number: {}", .{random_num}) catch "Error";
} else if (std.mem.eql(u8, command, "memory")) {
// Simple memory info (simulated)
result = std.fmt.bufPrintZ(response[0..], "Memory usage: {}MB", .{50 + @rem(compat.timestamp(), 100)}) catch "Error";
result = compat.bufPrintZ(response[0..], "Memory usage: {}MB", .{50 + @rem(compat.timestamp(), 100)}) catch "Error";
} else {
result = "Unknown command";
}
Expand All @@ -290,7 +290,7 @@ fn getSystemInfo(e: *webui.Event) void {
const builtin = @import("builtin");

var buffer: [1024]u8 = undefined;
const info = std.fmt.bufPrintZ(buffer[0..],
const info = compat.bufPrintZ(buffer[0..],
\\{{"os":"{s}","arch":"{s}","zigVersion":"{s}","webuiVersion":"2.5.0","timestamp":{}}}
, .{ @tagName(builtin.os.tag), @tagName(builtin.cpu.arch), @import("builtin").zig_version_string, compat.timestamp() }) catch "{}";

Expand Down Expand Up @@ -318,7 +318,7 @@ fn testPerformance(e: *webui.Event, iterations: i64, operation: [:0]const u8) vo
const duration_ms = @as(f64, @floatFromInt(end_time - start_time)) / 1_000_000.0;

var response: [256]u8 = undefined;
const msg = std.fmt.bufPrintZ(response[0..], "Performance test completed: {} iterations of {s} in {d:.2}ms", .{ iterations, operation, duration_ms }) catch "Error";
const msg = compat.bufPrintZ(response[0..], "Performance test completed: {} iterations of {s} in {d:.2}ms", .{ iterations, operation, duration_ms }) catch "Error";

e.returnString(msg);
std.debug.print("Performance test: {} iterations in {d:.2}ms\n", .{ iterations, duration_ms });
Expand Down Expand Up @@ -357,22 +357,22 @@ fn manageSettings(e: *webui.Event, action: [:0]const u8, key: [:0]const u8, valu
return;
};

result = std.fmt.bufPrintZ(response[0..], "Setting '{s}' set to '{s}'", .{ key, value }) catch "Error";
result = compat.bufPrintZ(response[0..], "Setting '{s}' set to '{s}'", .{ key, value }) catch "Error";
} else if (std.mem.eql(u8, action, "get")) {
// Get setting from memory
if (settings_map.get(key)) |stored_value| {
result = std.fmt.bufPrintZ(response[0..], "Setting '{s}' = '{s}'", .{ key, stored_value }) catch "Error";
result = compat.bufPrintZ(response[0..], "Setting '{s}' = '{s}'", .{ key, stored_value }) catch "Error";
} else {
result = std.fmt.bufPrintZ(response[0..], "Setting '{s}' not found (no value set)", .{key}) catch "Error";
result = compat.bufPrintZ(response[0..], "Setting '{s}' not found (no value set)", .{key}) catch "Error";
}
} else if (std.mem.eql(u8, action, "delete")) {
// Delete setting from memory
if (settings_map.fetchRemove(key)) |kv| {
allocator.free(kv.key);
allocator.free(kv.value);
result = std.fmt.bufPrintZ(response[0..], "Setting '{s}' deleted", .{key}) catch "Error";
result = compat.bufPrintZ(response[0..], "Setting '{s}' deleted", .{key}) catch "Error";
} else {
result = std.fmt.bufPrintZ(response[0..], "Setting '{s}' not found (nothing to delete)", .{key}) catch "Error";
result = compat.bufPrintZ(response[0..], "Setting '{s}' not found (nothing to delete)", .{key}) catch "Error";
}
} else {
result = "Unknown settings action";
Expand Down Expand Up @@ -468,7 +468,7 @@ fn uploadFile(e: *webui.Event, filename: [:0]const u8, content: [:0]const u8) vo
// Write content to file
compat.writeFile(file_path, content) catch |err| {
std.debug.print("Failed to write file {s}: {}\n", .{ file_path, err });
result = std.fmt.bufPrintZ(response[0..], "Error: Failed to write file '{s}' ({s})", .{ filename, @errorName(err) }) catch "Error";
result = compat.bufPrintZ(response[0..], "Error: Failed to write file '{s}' ({s})", .{ filename, @errorName(err) }) catch "Error";
e.returnString(result);
return;
};
Expand All @@ -477,6 +477,6 @@ fn uploadFile(e: *webui.Event, filename: [:0]const u8, content: [:0]const u8) vo
app_state.files_uploaded += 1;

// Return success message
result = std.fmt.bufPrintZ(response[0..], "File '{s}' uploaded successfully as '{s}'. Size: {} bytes. Total files: {}", .{ filename, safe_filename[0..safe_len :0], content.len, app_state.files_uploaded }) catch "Error";
result = compat.bufPrintZ(response[0..], "File '{s}' uploaded successfully as '{s}'. Size: {} bytes. Total files: {}", .{ filename, safe_filename[0..safe_len :0], content.len, app_state.files_uploaded }) catch "Error";
e.returnString(result);
}
6 changes: 3 additions & 3 deletions examples/custom_spa_server_on_free_port/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,16 @@ pub fn main() !void {
// now use the port:
var buf1: [64]u8 = undefined;
var buf2: [64]u8 = undefined;
const port_argument1: []u8 = try std.fmt.bufPrintZ(&buf1, "{d}", .{backend_port});
const port_argument2: []u8 = try std.fmt.bufPrintZ(&buf2, "{d}", .{webui_port});
const port_argument1: []u8 = try compat.bufPrintZ(&buf1, "{d}", .{backend_port});
const port_argument2: []u8 = try compat.bufPrintZ(&buf2, "{d}", .{webui_port});
const argv = [_][]const u8{ "python", "./free_port_web_server.py", port_argument1, port_argument2 };

// start the SPA web server:
startPythonWebServer(&argv);

// Show a new window served by our custom web server (spawned above):
var buf: [64]u8 = undefined;
home_url = try std.fmt.bufPrintZ(&buf, "http://localhost:{d}/index.html", .{backend_port});
home_url = try compat.bufPrintZ(&buf, "http://localhost:{d}/index.html", .{backend_port});
try nwin.show(home_url);

// Wait until all windows get closed
Expand Down
3 changes: 2 additions & 1 deletion examples/event_handling/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ pub fn main() !void {
std.debug.print(" Windows: ipconfig | findstr IPv4\n", .{});
std.debug.print(" Mac/Linux: ifconfig | grep inet\n", .{});
std.debug.print(" Or check in network settings\n", .{});
std.debug.print("\n" ++ "=" ** 60 ++ "\n", .{});
const separator: [60]u8 = @splat('=');
std.debug.print("\n{s}\n", .{&separator});
std.debug.print("\n📋 How to test multi-client functionality:\n", .{});
std.debug.print("\n🔗 Multi-Client Connection Methods:\n", .{});
std.debug.print(" 1. SAME COMPUTER - New Tab/Window:\n", .{});
Expand Down
5 changes: 3 additions & 2 deletions examples/public_network_access/main.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Public Network Access Example
const std = @import("std");
const webui = @import("webui");
const compat = @import("compat");

// embed the html
const private_html = @embedFile("private.html");
Expand Down Expand Up @@ -32,9 +33,9 @@ fn private_window_events(e: *webui.Event) void {
const public_win_url: [:0]const u8 = public_window.getUrl() catch return;

var buf = std.mem.zeroes([1024]u8);
const js_1 = std.fmt.bufPrintZ(&buf, "document.getElementById('urlSpan1').innerHTML = 'http://localhost:{}';", .{public_win_port}) catch unreachable;
const js_1 = compat.bufPrintZ(&buf, "document.getElementById('urlSpan1').innerHTML = 'http://localhost:{}';", .{public_win_port}) catch unreachable;
private_window.run(js_1);
const js_2 = std.fmt.bufPrintZ(&buf, "document.getElementById('urlSpan2').innerHTML = '{s}';", .{public_win_url}) catch unreachable;
const js_2 = compat.bufPrintZ(&buf, "document.getElementById('urlSpan2').innerHTML = '{s}';", .{public_win_url}) catch unreachable;
private_window.run(js_2);
}
}
Expand Down
12 changes: 6 additions & 6 deletions examples/web_app_multi_client/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ fn events(e: *webui.Event) void {
// userNumber
{
var buffer = std.mem.zeroes([2048]u8);
const js = std.fmt.bufPrintZ(
const js = compat.bufPrintZ(
&buffer,
"document.getElementById(\"userNumber\").innerText = \"{}\";",
.{client_id},
Expand All @@ -98,7 +98,7 @@ fn events(e: *webui.Event) void {
// connectionNumber
{
var buffer = std.mem.zeroes([2048]u8);
const js = std.fmt.bufPrintZ(
const js = compat.bufPrintZ(
&buffer,
"document.getElementById(\"connectionNumber\").innerText = \"{}\";",
.{connection_id},
Expand All @@ -111,7 +111,7 @@ fn events(e: *webui.Event) void {
const val = if (private_input_arr[client_id]) |val| val else "";

var buffer = std.mem.zeroes([2048]u8);
const js = std.fmt.bufPrintZ(
const js = compat.bufPrintZ(
&buffer,
"document.getElementById(\"privateInput\").value = \"{s}\";",
.{val},
Expand All @@ -123,7 +123,7 @@ fn events(e: *webui.Event) void {
{
const val = if (public_input) |val| val else "";
var buffer = std.mem.zeroes([2048]u8);
const js = std.fmt.bufPrintZ(
const js = compat.bufPrintZ(
&buffer,
"document.getElementById(\"publicInput\").value = \"{s}\";",
.{val},
Expand All @@ -137,7 +137,7 @@ fn events(e: *webui.Event) void {
// userCount
{
var buffer = std.mem.zeroes([2048]u8);
const js = std.fmt.bufPrintZ(
const js = compat.bufPrintZ(
&buffer,
"document.getElementById(\"userCount\").innerText = \"{}\";",
.{users_count},
Expand All @@ -148,7 +148,7 @@ fn events(e: *webui.Event) void {
// tabCount
{
var buffer = std.mem.zeroes([2048]u8);
const js = std.fmt.bufPrintZ(
const js = compat.bufPrintZ(
&buffer,
"document.getElementById(\"tabCount\").innerText = \"{}\";",
.{tab_count},
Expand Down
Loading
Loading