ReviewOS

also looking at this

craft-native/craft

feat: add Linux and Windows named-window routing

#325
Merged glennmichael123 wants to merge codex/multi-window-linux-windows into main
29 files +2250 -200

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

docs/features/window-management.mdmodified+2-0
Changes to docs/features/window-management.md
@@ -32,6 +32,8 @@ The `craft-native` SDK returns a typed `Window` handle. Runtime-created
3232windows are available on macOS, Linux, and Windows and must provide either
3333`html` or `url` content. The common create, close, title, geometry, and event
3434routes are cross-platform; some advanced window controls remain macOS-only.
35On Linux, the window manager may ignore position requests, especially before
36the window is shown.
3537
3638```typescript
3739import { createWindow } from 'craft-native'
packages/zig/src/linux.zigmodified+8-6
Changes to packages/zig/src/linux.zig
@@ -20,6 +20,7 @@ pub extern "c" fn g_application_run(app: *anyopaque, argc: c_int, argv: [*c][*c]
2020pub extern "c" fn gtk_application_window_new(app: *anyopaque) *anyopaque;
2121pub extern "c" fn gtk_window_set_title(window: *anyopaque, title: [*:0]const u8) void;
2222pub extern "c" fn gtk_window_set_default_size(window: *anyopaque, width: c_int, height: c_int) void;
23pub extern "c" fn gtk_window_resize(window: *anyopaque, width: c_int, height: c_int) void;
2324pub extern "c" fn gtk_window_present(window: *anyopaque) void;
2425pub extern "c" fn gtk_window_close(window: *anyopaque) void;
2526pub extern "c" fn gtk_window_set_decorated(window: *anyopaque, decorated: c_int) void;
@@ -31,7 +32,7 @@ pub extern "c" fn gtk_window_unmaximize(window: *anyopaque) void;
3132pub extern "c" fn gtk_window_iconify(window: *anyopaque) void;
3233pub extern "c" fn gtk_widget_hide(widget: *anyopaque) void;
3334pub extern "c" fn gtk_widget_show(widget: *anyopaque) void;
34pub extern "c" fn gtk_window_set_position(window: *anyopaque, x: c_int, y: c_int) void;
35pub extern "c" fn gtk_window_move(window: *anyopaque, x: c_int, y: c_int) void;
3536pub extern "c" fn gtk_window_get_position(window: *anyopaque, x: *c_int, y: *c_int) void;
3637pub extern "c" fn gtk_window_get_size(window: *anyopaque, width: *c_int, height: *c_int) void;
3738pub extern "c" fn gtk_window_get_title(window: *anyopaque) ?[*:0]const u8;
@@ -493,12 +494,12 @@ fn handleWindowAction(action: []const u8, data: ?[]const u8) !void {
493494 const height = json_utils.getInt(u32, json, "height") orelse return error.InvalidParameter;
494495 if (width == 0 or height == 0 or width > @as(u32, std.math.maxInt(c_int)) or height > @as(u32, std.math.maxInt(c_int)))
495496 return error.InvalidParameter;
496 gtk_window_set_default_size(window, @intCast(width), @intCast(height));
497 gtk_window_resize(window, @intCast(width), @intCast(height));
497498 } else if (std.mem.eql(u8, action, "setPosition")) {
498499 const json = data orelse return error.MissingData;
499500 const x = json_utils.getInt(i32, json, "x") orelse return error.InvalidParameter;
500501 const y = json_utils.getInt(i32, json, "y") orelse return error.InvalidParameter;
501 gtk_window_set_position(window, x, y);
502 gtk_window_move(window, x, y);
502503 } else if (std.mem.eql(u8, action, "loadURL") or std.mem.eql(u8, action, "loadHTML")) {
503504 const json = data orelse return error.MissingData;
504505 const key: []const u8 = if (std.mem.eql(u8, action, "loadURL")) "url" else "html";
@@ -627,7 +628,7 @@ pub const Window = struct {
627628 const x: i32 = options.x orelse 100;
628629 const y: i32 = options.y orelse 100;
629630 if (options.x != null and options.y != null) {
630 gtk_window_set_position(window, @intCast(x), @intCast(y));
631 gtk_window_move(window, @intCast(x), @intCast(y));
631632 }
632633
633634 // Add WebView to window
@@ -681,11 +682,12 @@ pub const Window = struct {
681682 }
682683
683684 pub fn setSize(self: *Window, width: u32, height: u32) void {
684 gtk_window_set_default_size(self.gtk_window, @intCast(width), @intCast(height));
685 if (width == 0 or height == 0 or width > @as(u32, std.math.maxInt(c_int)) or height > @as(u32, std.math.maxInt(c_int))) return;
686 gtk_window_resize(self.gtk_window, @intCast(width), @intCast(height));
685687 }
686688
687689 pub fn setPosition(self: *Window, x: i32, y: i32) void {
688 gtk_window_set_position(self.gtk_window, @intCast(x), @intCast(y));
690 gtk_window_move(self.gtk_window, x, y);
689691 }
690692
691693 pub fn setTitle(self: *Window, title: []const u8) void {
scripts/verify-linux-multi-window.tsmodified+11-0
Changes to scripts/verify-linux-multi-window.ts
@@ -30,6 +30,17 @@ const mainPage = `<!doctype html><script>
3030 await waitFor('child-1')
3131 const bounds = await window.craft.window._call('getBounds', {}, 'settings')
3232 if (!(bounds.width > 0 && bounds.height > 0)) throw new Error('child bounds not routed to creator')
33 await window.craft.window._call('setSize', { width: 640, height: 480 }, 'settings')
34 let resized = false
35 for (let attempt = 0; attempt < 40; attempt++) {
36 const size = await window.craft.window._call('getSize', {}, 'settings')
37 if (Math.abs(size.width - 640) <= 40 && Math.abs(size.height - 480) <= 40) {
38 resized = true
39 break
40 }
41 await new Promise(resolve => setTimeout(resolve, 100))
42 }
43 if (!resized) throw new Error('live child resize did not change its size')
3344 const title = await window.craft.window._call('getTitle', {}, 'settings')
3445 if (title !== 'Child 1') throw new Error('child title not routed to creator: ' + title)
3546