@zogien/native-sdk 0.11.0-fork.3 → 0.11.0-fork.5
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 +53 -34
- package/build/cli.zig +22 -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/core/references/bridge-security-native-capabilities.md +4 -0
- 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/linux/cef_host.cpp +22 -0
- package/src/platform/linux/gtk_host.c +317 -34
- package/src/platform/linux/gtk_host.h +37 -0
- package/src/platform/linux/root.zig +114 -20
- package/src/platform/macos/appkit_host.h +53 -0
- package/src/platform/macos/appkit_host.m +667 -67
- package/src/platform/macos/cef_host.mm +21 -0
- package/src/platform/macos/root.zig +146 -29
- package/src/platform/null_platform.zig +136 -0
- package/src/platform/root.zig +7 -0
- package/src/platform/types.zig +90 -0
- package/src/platform/windows/dialog_test.cpp +303 -0
- package/src/platform/windows/host_loop_test.cpp +169 -0
- package/src/platform/windows/root.zig +262 -39
- package/src/platform/windows/session_end_test.cpp +303 -0
- package/src/platform/windows/webview2_host.cpp +1435 -306
- 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 +8 -0
- package/src/runtime/builtin_bridge.zig +108 -13
- package/src/runtime/channel_sources.zig +49 -1
- package/src/runtime/core.zig +24 -3
- package/src/runtime/dialog_tests.zig +367 -0
- package/src/runtime/dialogs.zig +385 -0
- package/src/runtime/effects.zig +83 -3
- package/src/runtime/effects_host_tests.zig +69 -1
- package/src/runtime/flow.zig +20 -6
- package/src/runtime/flow_reference.zig +20 -6
- package/src/runtime/platform_bridge_tests.zig +1 -0
- package/src/runtime/root.zig +9 -0
- package/src/runtime/single_instance.zig +893 -0
- package/src/runtime/system_services.zig +64 -82
- package/src/runtime/ui_app.zig +67 -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 +44 -2
- 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
|
@@ -143,8 +143,8 @@ extern fn native_sdk_windows_create(app_name: [*]const u8, app_name_len: usize,
|
|
|
143
143
|
extern fn native_sdk_windows_destroy(host: *WindowsHost) void;
|
|
144
144
|
extern fn native_sdk_windows_run(host: *WindowsHost, callback: WindowsCallback, context: ?*anyopaque) void;
|
|
145
145
|
extern fn native_sdk_windows_stop(host: *WindowsHost) void;
|
|
146
|
-
extern fn native_sdk_windows_wake(host: *WindowsHost)
|
|
147
|
-
extern fn native_sdk_windows_request_frame(host: *WindowsHost)
|
|
146
|
+
extern fn native_sdk_windows_wake(host: *WindowsHost) c_int;
|
|
147
|
+
extern fn native_sdk_windows_request_frame(host: *WindowsHost) c_int;
|
|
148
148
|
extern fn native_sdk_windows_decode_image(bytes: [*]const u8, bytes_len: usize, pixels: [*]u8, pixels_len: usize, max_pixels: usize, out_width: *usize, out_height: *usize) c_int;
|
|
149
149
|
extern fn native_sdk_windows_load_webview(host: *WindowsHost, source: [*]const u8, source_len: usize, source_kind: c_int, asset_root: [*]const u8, asset_root_len: usize, asset_entry: [*]const u8, asset_entry_len: usize, asset_origin: [*]const u8, asset_origin_len: usize, spa_fallback: c_int) void;
|
|
150
150
|
extern fn native_sdk_windows_load_window_webview(host: *WindowsHost, window_id: u64, source: [*]const u8, source_len: usize, source_kind: c_int, asset_root: [*]const u8, asset_root_len: usize, asset_entry: [*]const u8, asset_entry_len: usize, asset_origin: [*]const u8, asset_origin_len: usize, spa_fallback: c_int) c_int;
|
|
@@ -197,6 +197,9 @@ extern fn native_sdk_windows_reveal_path(host: *WindowsHost, path: [*]const u8,
|
|
|
197
197
|
extern fn native_sdk_windows_show_open_dialog(host: *WindowsHost, opts: *const WindowsOpenDialogOpts, buffer: [*]u8, buffer_len: usize) WindowsOpenDialogResult;
|
|
198
198
|
extern fn native_sdk_windows_show_save_dialog(host: *WindowsHost, opts: *const WindowsSaveDialogOpts, buffer: [*]u8, buffer_len: usize) usize;
|
|
199
199
|
extern fn native_sdk_windows_show_message_dialog(host: *WindowsHost, opts: *const WindowsMessageDialogOpts) c_int;
|
|
200
|
+
extern fn native_sdk_windows_request_dialog(host: *WindowsHost, request: *const WindowsDialogRequest) c_int;
|
|
201
|
+
extern fn native_sdk_windows_cancel_dialog(host: *WindowsHost, id: u64) void;
|
|
202
|
+
extern fn native_sdk_windows_poll_dialog_result(host: *WindowsHost, buffer: [*]u8, buffer_len: usize, result: *WindowsDialogResult) c_int;
|
|
200
203
|
extern fn native_sdk_windows_show_notification(host: *WindowsHost, title: [*]const u8, title_len: usize, subtitle: [*]const u8, subtitle_len: usize, body: [*]const u8, body_len: usize, notification_id: [*]const u8, notification_id_len: usize, action_label: [*]const u8, action_label_len: usize, action_command: [*]const u8, action_command_len: usize) c_int;
|
|
201
204
|
extern fn native_sdk_windows_create_tray(host: *WindowsHost, icon_path: [*]const u8, icon_path_len: usize, tooltip: [*]const u8, tooltip_len: usize, activation_command: [*]const u8, activation_command_len: usize, alternate_activation_command: [*]const u8, alternate_activation_command_len: usize, open_command: [*]const u8, open_command_len: usize) c_int;
|
|
202
205
|
extern fn native_sdk_windows_update_tray_menu(host: *WindowsHost, item_ids: [*]const u32, labels: [*]const [*]const u8, label_lens: [*]const usize, separators: [*]const c_int, enabled_flags: [*]const c_int, details: [*]const [*]const u8, detail_lens: [*]const usize, roles: [*]const c_int, keys: [*]const [*]const u8, key_lens: [*]const usize, modifiers: [*]const u32, count: usize) c_int;
|
|
@@ -277,6 +280,23 @@ const WindowsMessageDialogOpts = extern struct {
|
|
|
277
280
|
tertiary_button_len: usize,
|
|
278
281
|
};
|
|
279
282
|
|
|
283
|
+
const WindowsDialogRequest = extern struct {
|
|
284
|
+
id: u64,
|
|
285
|
+
parent_window_id: u64,
|
|
286
|
+
kind: c_int,
|
|
287
|
+
open_opts: ?*const WindowsOpenDialogOpts,
|
|
288
|
+
save_opts: ?*const WindowsSaveDialogOpts,
|
|
289
|
+
message_opts: ?*const WindowsMessageDialogOpts,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const WindowsDialogResult = extern struct {
|
|
293
|
+
id: u64,
|
|
294
|
+
outcome: c_int,
|
|
295
|
+
button: c_int,
|
|
296
|
+
count: usize,
|
|
297
|
+
bytes_written: usize,
|
|
298
|
+
};
|
|
299
|
+
|
|
280
300
|
/// The startup-window twin of the runtime's create-time `.hide` gate:
|
|
281
301
|
/// on windows, hide-on-close is honest only when the app declares a
|
|
282
302
|
/// tray (status item) — SW_HIDE removes the taskbar entry and windows
|
|
@@ -477,6 +497,9 @@ pub const WindowsPlatform = struct {
|
|
|
477
497
|
.show_open_dialog_fn = showOpenDialog,
|
|
478
498
|
.show_save_dialog_fn = showSaveDialog,
|
|
479
499
|
.show_message_dialog_fn = showMessageDialog,
|
|
500
|
+
.request_dialog_fn = if (self.web_engine == .system) requestDialog else null,
|
|
501
|
+
.cancel_dialog_fn = if (self.web_engine == .system) cancelDialog else null,
|
|
502
|
+
.poll_dialog_result_fn = if (self.web_engine == .system) pollDialogResult else null,
|
|
480
503
|
.show_notification_fn = showNotification,
|
|
481
504
|
.create_tray_fn = createTray,
|
|
482
505
|
.update_tray_menu_fn = updateTrayMenu,
|
|
@@ -901,13 +924,14 @@ fn emitWindowEvent(context: ?*anyopaque, window_id: platform_mod.WindowId, name:
|
|
|
901
924
|
native_sdk_windows_emit_window_event(self.host, window_id, name.ptr, name.len, detail_json.ptr, detail_json.len);
|
|
902
925
|
}
|
|
903
926
|
|
|
904
|
-
/// Thread-safe: `PostMessageW`
|
|
905
|
-
/// enqueue-only shape the wake contract requires — never the
|
|
927
|
+
/// Thread-safe: `PostMessageW` to the host's loop window (the
|
|
928
|
+
/// enqueue-only shape the wake contract requires — no lock, never the
|
|
906
929
|
/// synchronous `SendMessage`), whose window procedure emits `.wake` on
|
|
907
|
-
/// the loop thread.
|
|
930
|
+
/// the loop thread. A wake that could not be posted is an error, so the
|
|
931
|
+
/// caller re-arms instead of assuming the nudge happened.
|
|
908
932
|
fn wake(context: ?*anyopaque) anyerror!void {
|
|
909
933
|
const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
|
|
910
|
-
native_sdk_windows_wake(self.host);
|
|
934
|
+
if (native_sdk_windows_wake(self.host) == 0) return error.WakeNotPosted;
|
|
911
935
|
}
|
|
912
936
|
|
|
913
937
|
/// Teardown abandoned an in-flight channel wake call: latch the flag
|
|
@@ -918,13 +942,14 @@ fn noteChannelWakeAbandoned(context: ?*anyopaque) void {
|
|
|
918
942
|
self.channel_wake_abandoned.store(true, .seq_cst);
|
|
919
943
|
}
|
|
920
944
|
|
|
921
|
-
/// Thread-safe like `wake`: `PostMessage`
|
|
922
|
-
/// window procedure emits one `.frame` event on the loop thread
|
|
923
|
-
///
|
|
924
|
-
///
|
|
945
|
+
/// Thread-safe like `wake`: `PostMessage` to the loop window, whose
|
|
946
|
+
/// window procedure emits one `.frame` event on the loop thread;
|
|
947
|
+
/// requests coalesce while one is queued. The automation arrival
|
|
948
|
+
/// watcher calls this when a command lands so consumption never depends
|
|
949
|
+
/// on the host's own frame pump.
|
|
925
950
|
fn requestFrame(context: ?*anyopaque) anyerror!void {
|
|
926
951
|
const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
|
|
927
|
-
native_sdk_windows_request_frame(self.host);
|
|
952
|
+
if (native_sdk_windows_request_frame(self.host) == 0) return error.FrameRequestNotPosted;
|
|
928
953
|
}
|
|
929
954
|
|
|
930
955
|
/// Headless image codec for session replay: the SAME WIC decode a live
|
|
@@ -1481,12 +1506,9 @@ fn closeWebView(context: ?*anyopaque, window_id: platform_mod.WindowId, label: [
|
|
|
1481
1506
|
if (native_sdk_windows_close_webview(self.host, window_id, label.ptr, label.len) == 0) return error.WebViewNotFound;
|
|
1482
1507
|
}
|
|
1483
1508
|
|
|
1484
|
-
fn
|
|
1485
|
-
const
|
|
1486
|
-
|
|
1487
|
-
var ext_buf: [1024]u8 = undefined;
|
|
1488
|
-
const ext_str = flattenFilters(options.filters, &ext_buf);
|
|
1489
|
-
const opts = WindowsOpenDialogOpts{
|
|
1509
|
+
fn windowsOpenDialogOpts(options: platform_mod.OpenDialogOptions, ext_buf: []u8) WindowsOpenDialogOpts {
|
|
1510
|
+
const ext_str = flattenFilters(options.filters, ext_buf);
|
|
1511
|
+
return .{
|
|
1490
1512
|
.title = options.title.ptr,
|
|
1491
1513
|
.title_len = options.title.len,
|
|
1492
1514
|
.default_path = options.default_path.ptr,
|
|
@@ -1496,17 +1518,11 @@ fn showOpenDialog(context: ?*anyopaque, options: platform_mod.OpenDialogOptions,
|
|
|
1496
1518
|
.allow_directories = if (options.allow_directories) 1 else 0,
|
|
1497
1519
|
.allow_multiple = if (options.allow_multiple) 1 else 0,
|
|
1498
1520
|
};
|
|
1499
|
-
const result = native_sdk_windows_show_open_dialog(self.host, &opts, buffer.ptr, buffer.len);
|
|
1500
|
-
if (result.bytes_written > buffer.len) return error.NoSpaceLeft;
|
|
1501
|
-
return .{ .count = result.count, .paths = buffer[0..result.bytes_written] };
|
|
1502
1521
|
}
|
|
1503
1522
|
|
|
1504
|
-
fn
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
var ext_buf: [1024]u8 = undefined;
|
|
1508
|
-
const ext_str = flattenFilters(options.filters, &ext_buf);
|
|
1509
|
-
const opts = WindowsSaveDialogOpts{
|
|
1523
|
+
fn windowsSaveDialogOpts(options: platform_mod.SaveDialogOptions, ext_buf: []u8) WindowsSaveDialogOpts {
|
|
1524
|
+
const ext_str = flattenFilters(options.filters, ext_buf);
|
|
1525
|
+
return .{
|
|
1510
1526
|
.title = options.title.ptr,
|
|
1511
1527
|
.title_len = options.title.len,
|
|
1512
1528
|
.default_path = options.default_path.ptr,
|
|
@@ -1516,16 +1532,10 @@ fn showSaveDialog(context: ?*anyopaque, options: platform_mod.SaveDialogOptions,
|
|
|
1516
1532
|
.extensions = ext_str.ptr,
|
|
1517
1533
|
.extensions_len = ext_str.len,
|
|
1518
1534
|
};
|
|
1519
|
-
const written = native_sdk_windows_show_save_dialog(self.host, &opts, buffer.ptr, buffer.len);
|
|
1520
|
-
if (written > buffer.len) return error.NoSpaceLeft;
|
|
1521
|
-
if (written == 0) return null;
|
|
1522
|
-
return buffer[0..written];
|
|
1523
1535
|
}
|
|
1524
1536
|
|
|
1525
|
-
fn
|
|
1526
|
-
|
|
1527
|
-
if (self.web_engine != .system) return error.UnsupportedService;
|
|
1528
|
-
const opts = WindowsMessageDialogOpts{
|
|
1537
|
+
fn windowsMessageDialogOpts(options: platform_mod.MessageDialogOptions) WindowsMessageDialogOpts {
|
|
1538
|
+
return .{
|
|
1529
1539
|
.style = @intFromEnum(options.style),
|
|
1530
1540
|
.title = options.title.ptr,
|
|
1531
1541
|
.title_len = options.title.len,
|
|
@@ -1540,7 +1550,97 @@ fn showMessageDialog(context: ?*anyopaque, options: platform_mod.MessageDialogOp
|
|
|
1540
1550
|
.tertiary_button = options.tertiary_button.ptr,
|
|
1541
1551
|
.tertiary_button_len = options.tertiary_button.len,
|
|
1542
1552
|
};
|
|
1543
|
-
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
fn showOpenDialog(context: ?*anyopaque, options: platform_mod.OpenDialogOptions, buffer: []u8) anyerror!platform_mod.OpenDialogResult {
|
|
1556
|
+
const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
|
|
1557
|
+
if (self.web_engine != .system) return error.UnsupportedService;
|
|
1558
|
+
var ext_buf: [1024]u8 = undefined;
|
|
1559
|
+
const opts = windowsOpenDialogOpts(options, &ext_buf);
|
|
1560
|
+
const result = native_sdk_windows_show_open_dialog(self.host, &opts, buffer.ptr, buffer.len);
|
|
1561
|
+
if (result.bytes_written > buffer.len) return error.NoSpaceLeft;
|
|
1562
|
+
return .{ .count = result.count, .paths = buffer[0..result.bytes_written] };
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
fn showSaveDialog(context: ?*anyopaque, options: platform_mod.SaveDialogOptions, buffer: []u8) anyerror!?[]const u8 {
|
|
1566
|
+
const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
|
|
1567
|
+
if (self.web_engine != .system) return error.UnsupportedService;
|
|
1568
|
+
var ext_buf: [1024]u8 = undefined;
|
|
1569
|
+
const opts = windowsSaveDialogOpts(options, &ext_buf);
|
|
1570
|
+
const written = native_sdk_windows_show_save_dialog(self.host, &opts, buffer.ptr, buffer.len);
|
|
1571
|
+
if (written > buffer.len) return error.NoSpaceLeft;
|
|
1572
|
+
if (written == 0) return null;
|
|
1573
|
+
return buffer[0..written];
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
fn showMessageDialog(context: ?*anyopaque, options: platform_mod.MessageDialogOptions) anyerror!platform_mod.MessageDialogResult {
|
|
1577
|
+
const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
|
|
1578
|
+
if (self.web_engine != .system) return error.UnsupportedService;
|
|
1579
|
+
const opts = windowsMessageDialogOpts(options);
|
|
1580
|
+
// Negative: a session end left the dialog unanswered (the host
|
|
1581
|
+
// cancelled it to unwind this dispatch); no button stands in for it.
|
|
1582
|
+
const pressed = native_sdk_windows_show_message_dialog(self.host, &opts);
|
|
1583
|
+
if (pressed < 0) return error.SessionEnding;
|
|
1584
|
+
return @enumFromInt(pressed);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// The host copies every option before returning, so they may live on this
|
|
1588
|
+
// stack; the result comes back through pollDialogResult on a later wake.
|
|
1589
|
+
fn requestDialog(context: ?*anyopaque, request: platform_mod.DialogRequest) anyerror!void {
|
|
1590
|
+
const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
|
|
1591
|
+
var ext_buf: [1024]u8 = undefined;
|
|
1592
|
+
var open_opts: WindowsOpenDialogOpts = undefined;
|
|
1593
|
+
var save_opts: WindowsSaveDialogOpts = undefined;
|
|
1594
|
+
var message_opts: WindowsMessageDialogOpts = undefined;
|
|
1595
|
+
var native_request = WindowsDialogRequest{
|
|
1596
|
+
.id = request.id,
|
|
1597
|
+
.parent_window_id = request.parent_window_id orelse 0,
|
|
1598
|
+
.kind = @intFromEnum(std.meta.activeTag(request.options)),
|
|
1599
|
+
.open_opts = null,
|
|
1600
|
+
.save_opts = null,
|
|
1601
|
+
.message_opts = null,
|
|
1602
|
+
};
|
|
1603
|
+
switch (request.options) {
|
|
1604
|
+
.open => |options| {
|
|
1605
|
+
open_opts = windowsOpenDialogOpts(options, &ext_buf);
|
|
1606
|
+
native_request.open_opts = &open_opts;
|
|
1607
|
+
},
|
|
1608
|
+
.save => |options| {
|
|
1609
|
+
save_opts = windowsSaveDialogOpts(options, &ext_buf);
|
|
1610
|
+
native_request.save_opts = &save_opts;
|
|
1611
|
+
},
|
|
1612
|
+
.message => |options| {
|
|
1613
|
+
message_opts = windowsMessageDialogOpts(options);
|
|
1614
|
+
native_request.message_opts = &message_opts;
|
|
1615
|
+
},
|
|
1616
|
+
}
|
|
1617
|
+
if (native_sdk_windows_request_dialog(self.host, &native_request) == 0) return error.DialogUnavailable;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
fn cancelDialog(context: ?*anyopaque, id: platform_mod.DialogRequestId) void {
|
|
1621
|
+
const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
|
|
1622
|
+
native_sdk_windows_cancel_dialog(self.host, id);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
fn pollDialogResult(context: ?*anyopaque, buffer: []u8) ?platform_mod.DialogResult {
|
|
1626
|
+
const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
|
|
1627
|
+
var result: WindowsDialogResult = undefined;
|
|
1628
|
+
if (native_sdk_windows_poll_dialog_result(self.host, buffer.ptr, buffer.len, &result) == 0) return null;
|
|
1629
|
+
return .{
|
|
1630
|
+
.id = result.id,
|
|
1631
|
+
.outcome = switch (result.outcome) {
|
|
1632
|
+
0 => .selected,
|
|
1633
|
+
1 => .cancelled,
|
|
1634
|
+
else => .failed,
|
|
1635
|
+
},
|
|
1636
|
+
.paths = buffer[0..@min(result.bytes_written, buffer.len)],
|
|
1637
|
+
.count = result.count,
|
|
1638
|
+
.button = switch (result.button) {
|
|
1639
|
+
1 => .secondary,
|
|
1640
|
+
2 => .tertiary,
|
|
1641
|
+
else => .primary,
|
|
1642
|
+
},
|
|
1643
|
+
};
|
|
1544
1644
|
}
|
|
1545
1645
|
|
|
1546
1646
|
fn showNotification(context: ?*anyopaque, options: platform_mod.NotificationOptions) anyerror!void {
|
|
@@ -2274,7 +2374,9 @@ test "windows tray carries lifecycle commands rich rows and key equivalents into
|
|
|
2274
2374
|
try std.testing.expect(std.mem.indexOf(u8, host_source, "display_label += shortcutSuffix(item.key, item.modifiers);") != null);
|
|
2275
2375
|
try std.testing.expect(std.mem.indexOf(u8, host_source, "const std::vector<TrayItem> displayed_items = host->tray_items;") != null);
|
|
2276
2376
|
try std.testing.expect(std.mem.indexOf(u8, host_source, "emitTrayActionForCommandId(host, displayed_items, command_id);") != null);
|
|
2277
|
-
try std.testing.expect(std.mem.indexOf(u8, host_source, "
|
|
2377
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "out->kind = kTrayAction;") != null);
|
|
2378
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "out->tray_item_id = item.id;") != null);
|
|
2379
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "event.tray_item_id = match.tray_item_id;") != null);
|
|
2278
2380
|
try std.testing.expect(std.mem.indexOf(u8, host_source, "tray_event == NIN_SELECT || tray_event == NIN_KEYSELECT || tray_event == WM_LBUTTONUP") != null);
|
|
2279
2381
|
}
|
|
2280
2382
|
|
|
@@ -2336,6 +2438,87 @@ test "windows WM_CLOSE hide consults the live tray state and downgrades to a rea
|
|
|
2336
2438
|
try std.testing.expect(std.mem.indexOf(u8, close_hook, "downgraded to a real close") != null);
|
|
2337
2439
|
}
|
|
2338
2440
|
|
|
2441
|
+
test "windows session end runs the stop hook once and bypasses the close policy" {
|
|
2442
|
+
// C++ this suite cannot execute, pinned textually like the WM_CLOSE
|
|
2443
|
+
// hook above. A confirmed WM_ENDSESSION (logoff, system shutdown, a
|
|
2444
|
+
// Restart Manager close) never goes through the close-policy hide;
|
|
2445
|
+
// the query only says yes; the run loop's exit and the session end
|
|
2446
|
+
// share one exactly-once delivery.
|
|
2447
|
+
const host_source = @embedFile("webview2_host.cpp");
|
|
2448
|
+
const query_at = std.mem.indexOf(u8, host_source, "case WM_QUERYENDSESSION:") orelse return error.TestExpectedEqual;
|
|
2449
|
+
const end_at = std.mem.indexOfPos(u8, host_source, query_at, "case WM_ENDSESSION:") orelse return error.TestExpectedEqual;
|
|
2450
|
+
const close_at = std.mem.indexOfPos(u8, host_source, end_at, "case WM_CLOSE:") orelse return error.TestExpectedEqual;
|
|
2451
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source[query_at..end_at], "return TRUE;") != null);
|
|
2452
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source[end_at..close_at], "if (host && wparam) endSession(host);") != null);
|
|
2453
|
+
// While a session end unwinds a dispatch, closes are ignored before
|
|
2454
|
+
// the close policy is consulted.
|
|
2455
|
+
const ending_close_at = std.mem.indexOfPos(u8, host_source, close_at, "if (host && host->session_ending) return 0;") orelse return error.TestExpectedEqual;
|
|
2456
|
+
const hide_at = std.mem.indexOfPos(u8, host_source, close_at, "ShowWindow(hwnd, SW_HIDE);") orelse return error.TestExpectedEqual;
|
|
2457
|
+
try std.testing.expect(ending_close_at < hide_at);
|
|
2458
|
+
|
|
2459
|
+
// Outside a runtime dispatch the shutdown runs synchronously, then
|
|
2460
|
+
// the tray icon goes and the process ends. Inside one, the runtime
|
|
2461
|
+
// queues the shutdown behind it, so the host marks the session
|
|
2462
|
+
// ending, hands the shutdown over, stops the loop, and cancels the
|
|
2463
|
+
// dialogs holding the dispatch, without ending the process.
|
|
2464
|
+
const session_at = std.mem.indexOf(u8, host_source, "static void endSession(Host *host) {") orelse return error.TestExpectedEqual;
|
|
2465
|
+
const session_end = std.mem.indexOfPos(u8, host_source, session_at, "\n}\n") orelse return error.TestExpectedEqual;
|
|
2466
|
+
const session_fn = host_source[session_at..session_end];
|
|
2467
|
+
const order = [_][]const u8{
|
|
2468
|
+
"if (host->shutdown_delivered) return;",
|
|
2469
|
+
"if (host->runtime_dispatch_depth == 0) {",
|
|
2470
|
+
"deliverShutdown(host);",
|
|
2471
|
+
"removeNotificationIcon(host);",
|
|
2472
|
+
"TerminateProcess(GetCurrentProcess(), 0);",
|
|
2473
|
+
"host->session_ending = true;",
|
|
2474
|
+
"deliverShutdown(host);",
|
|
2475
|
+
"host->running = false;",
|
|
2476
|
+
"EnumThreadWindows(GetCurrentThreadId(), closeOwnedDialog,",
|
|
2477
|
+
};
|
|
2478
|
+
var cursor: usize = 0;
|
|
2479
|
+
for (order) |needle| cursor = (std.mem.indexOfPos(u8, session_fn, cursor, needle) orelse return error.TestExpectedEqual) + needle.len;
|
|
2480
|
+
try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, session_fn, "TerminateProcess("));
|
|
2481
|
+
|
|
2482
|
+
const deliver_fn_at = std.mem.indexOf(u8, host_source, "static void deliverShutdown(Host *host) {") orelse return error.TestExpectedEqual;
|
|
2483
|
+
const flag_at = std.mem.indexOfPos(u8, host_source, deliver_fn_at, "host->shutdown_delivered = true;") orelse return error.TestExpectedEqual;
|
|
2484
|
+
const callback_at = std.mem.indexOfPos(u8, host_source, deliver_fn_at, "host->callback(host->callback_context, &shutdown);") orelse return error.TestExpectedEqual;
|
|
2485
|
+
// The flag latches before the callback: a session end arriving while
|
|
2486
|
+
// the stop hook runs (a modal loop inside it) must not deliver again.
|
|
2487
|
+
try std.testing.expect(flag_at < callback_at);
|
|
2488
|
+
try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, host_source, "kind = kShutdown;"));
|
|
2489
|
+
|
|
2490
|
+
// The dispatch depth sees every call into the runtime: events through
|
|
2491
|
+
// the counting trampoline the run loop installs, Bridge messages
|
|
2492
|
+
// through their one delivery site.
|
|
2493
|
+
const run_at = std.mem.indexOf(u8, host_source, "void native_sdk_windows_run(") orelse return error.TestExpectedEqual;
|
|
2494
|
+
const run_end = std.mem.indexOfPos(u8, host_source, run_at, "\n}\n") orelse return error.TestExpectedEqual;
|
|
2495
|
+
const run_fn = host_source[run_at..run_end];
|
|
2496
|
+
try std.testing.expect(std.mem.indexOf(u8, run_fn, "host->callback = dispatchEventToRuntime;") != null);
|
|
2497
|
+
try std.testing.expect(std.mem.indexOf(u8, run_fn, "deliverShutdown(host);") != null);
|
|
2498
|
+
const trampoline_at = std.mem.indexOf(u8, host_source, "static void dispatchEventToRuntime(") orelse return error.TestExpectedEqual;
|
|
2499
|
+
const scope_at = std.mem.indexOfPos(u8, host_source, trampoline_at, "RuntimeDispatchScope scope(host);") orelse return error.TestExpectedEqual;
|
|
2500
|
+
const forward_at = std.mem.indexOfPos(u8, host_source, trampoline_at, "host->runtime_callback(host->runtime_context, event);") orelse return error.TestExpectedEqual;
|
|
2501
|
+
try std.testing.expect(scope_at < forward_at);
|
|
2502
|
+
// Nothing enters the runtime after the shutdown was handed over, on
|
|
2503
|
+
// every path: the stop hook's own events and a closing dialog's.
|
|
2504
|
+
const gate_at = std.mem.indexOfPos(u8, host_source, trampoline_at, "if (host->shutdown_delivered && event->kind != kShutdown) return;") orelse return error.TestExpectedEqual;
|
|
2505
|
+
try std.testing.expect(gate_at < forward_at);
|
|
2506
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "if (!host->bridge_callback || host->shutdown_delivered) return;") != null);
|
|
2507
|
+
try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, host_source, "host->bridge_callback("));
|
|
2508
|
+
const bridge_at = std.mem.indexOf(u8, host_source, "host->bridge_callback(") orelse return error.TestExpectedEqual;
|
|
2509
|
+
const bridge_scope_at = std.mem.lastIndexOf(u8, host_source[0..bridge_at], "RuntimeDispatchScope scope(host);") orelse return error.TestExpectedEqual;
|
|
2510
|
+
try std.testing.expect(bridge_at - bridge_scope_at < 128);
|
|
2511
|
+
|
|
2512
|
+
// Dialogs the unwinding cancels, through any chain of owners, report
|
|
2513
|
+
// no answer rather than a button, and new ones are not shown;
|
|
2514
|
+
// session_end_test.cpp drives this on a Windows host.
|
|
2515
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "for (HWND owner = GetWindow(hwnd, GW_OWNER); owner; owner = GetWindow(owner, GW_OWNER)) {") != null);
|
|
2516
|
+
const message_at = std.mem.indexOf(u8, host_source, "int native_sdk_windows_show_message_dialog(Host *host, const WindowsMessageDialogOpts *opts) {") orelse return error.TestExpectedEqual;
|
|
2517
|
+
const message_end = std.mem.indexOfPos(u8, host_source, message_at, "\n}\n") orelse return error.TestExpectedEqual;
|
|
2518
|
+
try std.testing.expectEqual(@as(usize, 3), std.mem.count(u8, host_source[message_at..message_end], "if (host->session_ending) return -1;"));
|
|
2519
|
+
try std.testing.expectEqual(@as(usize, 2), std.mem.count(u8, host_source, "|| buffer_len == 0 || host->session_ending) return"));
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2339
2522
|
test "windows passive show never foregrounds or focuses the window" {
|
|
2340
2523
|
const host_source = @embedFile("webview2_host.cpp");
|
|
2341
2524
|
const show_at = std.mem.indexOf(u8, host_source, "int native_sdk_windows_show_window(") orelse return error.TestExpectedEqual;
|
|
@@ -2720,11 +2903,51 @@ test "windows window focus includes focused native child views" {
|
|
|
2720
2903
|
test "windows webview focus reports the focused child label" {
|
|
2721
2904
|
// WebView2 owns the page HWND and its input; GotFocus is the
|
|
2722
2905
|
// controller-level edge that mirrors keyboard ownership back into
|
|
2723
|
-
// the runtime's per-view register.
|
|
2906
|
+
// the runtime's per-view register. The callback queues the focused
|
|
2907
|
+
// view, and the loop window reports it once the callback returns.
|
|
2724
2908
|
const host_source = @embedFile("webview2_host.cpp");
|
|
2725
2909
|
try std.testing.expect(std.mem.indexOf(u8, host_source, "add_GotFocus(Callback<ICoreWebView2FocusChangedEventHandler>") != null);
|
|
2726
|
-
try std.testing.expect(std.mem.indexOf(u8, host_source, "event.kind =
|
|
2727
|
-
try std.testing.expect(std.mem.indexOf(u8, host_source, "event.
|
|
2910
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "event.kind = DeferredWebViewEvent::Kind::Focus;") != null);
|
|
2911
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "event.label = focused->second.label;") != null);
|
|
2912
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "focused.kind = kViewFocused;") != null);
|
|
2913
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "focused.view_label = event.label.c_str();") != null);
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
test "windows webview2 keeps its user data with the app's per-user data" {
|
|
2917
|
+
// WebView2's default user data folder sits beside the executable,
|
|
2918
|
+
// which is read-only under Program Files and is swapped away with
|
|
2919
|
+
// the program files on update. Every environment the host creates
|
|
2920
|
+
// names the folder beside the app_dirs roots instead, and an earlier
|
|
2921
|
+
// "<exe>.WebView2" folder moves there once.
|
|
2922
|
+
const host_source = @embedFile("webview2_host.cpp");
|
|
2923
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "factory(nullptr, webViewUserDataFolder(host), nullptr,") != null);
|
|
2924
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "factory(nullptr, nullptr, nullptr,") == null);
|
|
2925
|
+
const resolve_at = std.mem.indexOf(u8, host_source, "static const wchar_t *webViewUserDataFolder(Host *host) {") orelse return error.TestExpectedEqual;
|
|
2926
|
+
const resolve = host_source[resolve_at..];
|
|
2927
|
+
try std.testing.expect(std.mem.indexOf(u8, resolve, "GetEnvironmentVariableW(L\"LOCALAPPDATA\"") != null);
|
|
2928
|
+
try std.testing.expect(std.mem.indexOf(u8, resolve, "const std::wstring folder = app_root + L\"\\\\WebView2\";") != null);
|
|
2929
|
+
try std.testing.expect(std.mem.indexOf(u8, resolve, "MoveFileExW(legacy.c_str(), folder.c_str(), 0)") != null);
|
|
2930
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "return path + L\".WebView2\";") != null);
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
test "windows webview assets resolve against the packaged resources beside bin" {
|
|
2934
|
+
// `native package` stages bin\<exe> and the app-relative asset tree
|
|
2935
|
+
// in resources\; a relative asset root names that tree when the app
|
|
2936
|
+
// runs from a package and the working directory otherwise.
|
|
2937
|
+
const host_source = @embedFile("webview2_host.cpp");
|
|
2938
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source, "std::string path = assetRootPath(webview.asset_root);") != null);
|
|
2939
|
+
const resources_at = std.mem.indexOf(u8, host_source, "static const std::wstring &packagedResourcesDirectory() {") orelse return error.TestExpectedEqual;
|
|
2940
|
+
const resources = host_source[resources_at..];
|
|
2941
|
+
try std.testing.expect(std.mem.indexOf(u8, resources, "CompareStringOrdinal(bin.c_str() + bin_slash + 1, -1, L\"bin\", -1, TRUE)") != null);
|
|
2942
|
+
try std.testing.expect(std.mem.indexOf(u8, resources, "return bin.substr(0, bin_slash) + L\"\\\\resources\";") != null);
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
test "windows names the process by its bundle id for the taskbar" {
|
|
2946
|
+
// The installer's Start menu shortcut carries the bundle id as its
|
|
2947
|
+
// AppUserModelID; the process must claim the same id.
|
|
2948
|
+
const host_source = @embedFile("webview2_host.cpp");
|
|
2949
|
+
const create_at = std.mem.indexOf(u8, host_source, "host->bundle_id = slice(bundle_id, bundle_id_len);") orelse return error.TestExpectedEqual;
|
|
2950
|
+
try std.testing.expect(std.mem.indexOf(u8, host_source[create_at..], "SetCurrentProcessExplicitAppUserModelID(app_user_model_id.c_str());") != null);
|
|
2728
2951
|
}
|
|
2729
2952
|
|
|
2730
2953
|
test "windows audio event maps kinds and payload" {
|