Skip to content

Commit b44e2d9

Browse files
committed
updated workflow, added a test
1 parent 5a55c8e commit b44e2d9

6 files changed

Lines changed: 51 additions & 115 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -17,109 +17,10 @@ jobs:
1717
uses: actions/checkout@v4
1818

1919
- name: Setup Zig
20-
shell: bash
21-
env:
22-
ZIG_VERSION: 0.15.2
23-
run: |
24-
set -euo pipefail
25-
python - <<'PY'
26-
import hashlib
27-
import json
28-
import os
29-
import pathlib
30-
import shutil
31-
import tarfile
32-
import urllib.request
33-
import zipfile
34-
35-
version = os.environ["ZIG_VERSION"]
36-
runner_os = os.environ["RUNNER_OS"]
37-
runner_arch = os.environ["RUNNER_ARCH"]
38-
39-
os_map = {
40-
"Linux": "linux",
41-
"macOS": "macos",
42-
"Windows": "windows",
43-
}
44-
arch_map = {
45-
"X64": "x86_64",
46-
"ARM64": "aarch64",
47-
"X86": "x86",
48-
}
49-
50-
os_part = os_map.get(runner_os)
51-
arch_part = arch_map.get(runner_arch.upper())
52-
if os_part is None or arch_part is None:
53-
raise SystemExit(
54-
f"Unsupported runner combination RUNNER_OS={runner_os} RUNNER_ARCH={runner_arch}"
55-
)
56-
57-
artifact_key = f"{arch_part}-{os_part}"
58-
with urllib.request.urlopen("https://ziglang.org/download/index.json") as resp:
59-
index = json.load(resp)
60-
61-
release = index.get(version)
62-
if release is None:
63-
raise SystemExit(f"Zig version {version} is not present in ziglang download index")
64-
65-
artifact = release.get(artifact_key)
66-
if artifact is None:
67-
raise SystemExit(f"No Zig artifact for key {artifact_key} in version {version}")
68-
69-
tarball_url = artifact["tarball"]
70-
expected_sha256 = artifact["shasum"]
71-
72-
out_root = pathlib.Path(os.environ["RUNNER_TEMP"]) / f"zig-{version}-{artifact_key}"
73-
if out_root.exists():
74-
shutil.rmtree(out_root)
75-
out_root.mkdir(parents=True)
76-
77-
archive_path = out_root / pathlib.Path(tarball_url).name
78-
print(f"Downloading {tarball_url}", flush=True)
79-
urllib.request.urlretrieve(tarball_url, archive_path)
80-
81-
digest = hashlib.sha256()
82-
with archive_path.open("rb") as f:
83-
for chunk in iter(lambda: f.read(1024 * 1024), b""):
84-
digest.update(chunk)
85-
actual_sha256 = digest.hexdigest()
86-
if actual_sha256.lower() != expected_sha256.lower():
87-
raise SystemExit(
88-
f"SHA256 mismatch for {archive_path.name}: {actual_sha256} != {expected_sha256}"
89-
)
90-
91-
extract_dir = out_root / "extract"
92-
extract_dir.mkdir()
93-
lower_name = archive_path.name.lower()
94-
if lower_name.endswith(".zip"):
95-
with zipfile.ZipFile(archive_path) as zf:
96-
zf.extractall(extract_dir)
97-
else:
98-
with tarfile.open(archive_path, "r:*") as tf:
99-
tf.extractall(extract_dir)
100-
101-
dirs = [p for p in extract_dir.iterdir() if p.is_dir()]
102-
if len(dirs) == 1:
103-
zig_root = dirs[0]
104-
else:
105-
candidates = [p for p in extract_dir.rglob("*") if p.is_file() and p.name in ("zig", "zig.exe")]
106-
if not candidates:
107-
raise SystemExit("Unable to locate zig executable after extraction")
108-
exe = candidates[0]
109-
zig_root = exe.parent.parent if exe.parent.name == "bin" else exe.parent
110-
111-
zig_exe_name = "zig.exe" if runner_os == "Windows" else "zig"
112-
zig_path = zig_root / zig_exe_name
113-
if not zig_path.exists():
114-
zig_path = zig_root / "bin" / zig_exe_name
115-
if not zig_path.exists():
116-
raise SystemExit(f"Unable to find zig executable under {zig_root}")
117-
118-
with pathlib.Path(os.environ["GITHUB_PATH"]).open("a", encoding="utf-8") as f:
119-
f.write(str(zig_path.parent) + "\n")
120-
121-
print(f"Installed Zig {version} at {zig_path}", flush=True)
122-
PY
20+
uses: mlugg/setup-zig@v2.2.1
21+
with:
22+
version: master
23+
cache-key: ${{ runner.os }}-${{ hashFiles('build.zig', 'build.zig.zon') }}
12324

12425
- name: Zig Version
12526
run: zig version

CONTRIBUTING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ Thanks for contributing to WebUI Zig.
44

55
## Development Setup
66

7-
- Zig `0.15.2+` (see `build.zig.zon`)
7+
- Zig `0.16.0-dev+`
8+
- CI tracks Zig `master` via `mlugg/setup-zig`
89
- Linux, macOS, or Windows
910

1011
Core commands:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
A Zig-first WebUI runtime with typed RPC, deterministic launch policy, native host launch paths, and browser/web fallbacks.
44

5-
![Zig](https://img.shields.io/badge/Zig-0.15.2%2B-f7a41d)
5+
![Zig](https://img.shields.io/badge/Zig-0.16--dev%20%2F%20master-f7a41d)
66
![Platforms](https://img.shields.io/badge/Platforms-Linux%20%7C%20macOS%20%7C%20Windows-2ea44f)
77
![Mode](https://img.shields.io/badge/Transport-WebView%20%2B%20Browser%20%2B%20Web-0366d6)
88

src/backends/macos_browser_host.zig

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,15 @@ fn buildAppleScriptForControl(
114114
),
115115
.restore => std.fmt.allocPrint(
116116
allocator,
117-
"tell application \"System Events\" to tell (first window of (first process whose unix id is {d})) to set value of attribute \"AXMinimized\" to false",
117+
"tell application \"System Events\" to tell (first window of (first process whose unix id is {d})) to try\n" ++
118+
"set value of attribute \"AXFullScreen\" to false\n" ++
119+
"end try\n" ++
120+
"try\n" ++
121+
"set value of attribute \"AXZoomed\" to false\n" ++
122+
"end try\n" ++
123+
"try\n" ++
124+
"set value of attribute \"AXMinimized\" to false\n" ++
125+
"end try",
118126
.{pid},
119127
),
120128
.maximize => std.fmt.allocPrint(
@@ -162,6 +170,15 @@ test "applescript control template contains target pid and command semantics" {
162170
try std.testing.expect(std.mem.indexOf(u8, script, "AXMinimized") != null);
163171
}
164172

173+
test "restore applescript clears fullscreen zoom and minimized state" {
174+
const script = try buildAppleScriptForControl(std.testing.allocator, 4242, .restore);
175+
defer std.testing.allocator.free(script);
176+
try std.testing.expect(std.mem.indexOf(u8, script, "unix id is 4242") != null);
177+
try std.testing.expect(std.mem.indexOf(u8, script, "AXFullScreen") != null);
178+
try std.testing.expect(std.mem.indexOf(u8, script, "AXZoomed") != null);
179+
try std.testing.expect(std.mem.indexOf(u8, script, "AXMinimized") != null);
180+
}
181+
165182
test "applescript zoom fallback template contains target pid" {
166183
const script = try buildAppleScriptForZoomFallback(std.testing.allocator, 3001);
167184
defer std.testing.allocator.free(script);

src/root/app.zig

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -293,15 +293,6 @@ pub const App = struct {
293293
state.state_mutex.unlock();
294294
state.terminateLaunchedBrowser(self.allocator);
295295
state.stopServer();
296-
if (state.event_callback.handler) |handler| {
297-
const event = Event{
298-
.window_id = state.id,
299-
.kind = .disconnected,
300-
.name = "disconnected",
301-
.payload = "",
302-
};
303-
handler(state.event_callback.context, &event);
304-
}
305296
}
306297
}
307298
};

src/root/tests.zig

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,32 @@ test "window lifecycle" {
239239
app.shutdown();
240240
}
241241

242+
test "app shutdown does not synthesize disconnected event without a frontend" {
243+
const Capture = struct {
244+
var disconnected_count: usize = 0;
245+
246+
fn onEvent(_: ?*anyopaque, event: *const Event) void {
247+
if (event.kind == .disconnected) disconnected_count += 1;
248+
}
249+
};
250+
251+
Capture.disconnected_count = 0;
252+
253+
var gpa = std.heap.DebugAllocator(.{}){};
254+
defer _ = gpa.deinit();
255+
256+
var app = try App.init(gpa.allocator(), .{});
257+
defer app.deinit();
258+
259+
var win = try app.newWindow(.{ .title = "ShutdownDisconnectNoFrontend" });
260+
win.onEvent(Capture.onEvent, null);
261+
try win.showHtml("<html><body>shutdown-disconnect-no-frontend</body></html>");
262+
try app.run();
263+
264+
app.shutdown();
265+
try std.testing.expectEqual(@as(usize, 0), Capture.disconnected_count);
266+
}
267+
242268
test "browser fallback serves window html over local http" {
243269
var gpa = std.heap.DebugAllocator(.{}){};
244270
defer _ = gpa.deinit();

0 commit comments

Comments
 (0)