forked from justrach/merjs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathspike.zig
More file actions
158 lines (147 loc) · 6.49 KB
/
spike.zig
File metadata and controls
158 lines (147 loc) · 6.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/// #50 Research Spike — Zig 0.15 -> Objective-C bridge for AppKit + WebKit
///
/// QUESTION: @cImport or extern fn declarations?
///
/// FINDING:
/// @cImport(@cInclude("AppKit/AppKit.h")) does NOT work — ObjC-specific syntax
/// (@interface, @protocol) breaks translate-c.
/// @cImport(@cInclude("objc/runtime.h")) works but gives messy types + variadic
/// objc_msgSend that can't be called directly.
///
/// WINNING PATTERN: skip @cImport entirely. Declare the three ObjC runtime
/// primitives as `extern fn`. Cast objc_msgSend per call site. Define all
/// AppKit/WebKit constants as Zig consts. Zero headers required.
///
/// COMPILE-TIME: extern fn declarations compile without errors.
/// LINK-TIME: requires -framework AppKit -framework WebKit -framework Foundation + lc
/// RUNTIME: objc_getClass("WKWebView") non-null once WebKit linked.
/// CGRect passes correctly on arm64 (no objc_msgSend_stret needed).
///
/// DOWNSTREAM contracts:
/// #55 frameworks: AppKit, WebKit, Foundation + libc
/// #52 wrapper shape: see send* helpers below
/// #53 threading: NSApp.run() MUST be main thread; HTTP server on std.Thread
const std = @import("std");
// ObjC runtime — extern, no @cImport needed
extern fn objc_getClass(name: [*:0]const u8) ?*anyopaque;
extern fn sel_registerName(name: [*:0]const u8) ?*anyopaque;
extern fn objc_msgSend() void; // variadic; cast per call-site
// Types — defined manually, no AppKit.h
const Id = ?*anyopaque;
const Sel = ?*anyopaque;
const CGFloat = f64;
const CGPoint = extern struct { x: CGFloat, y: CGFloat };
const CGSize = extern struct { width: CGFloat, height: CGFloat };
const CGRect = extern struct { origin: CGPoint, size: CGSize };
const NSUInteger = c_ulong;
const NSInteger = c_long;
const BOOL = i8; // signed char; YES=1 NO=0
// AppKit constants — no @cImport of AppKit.h needed
const NSWindowStyleMaskTitled: NSUInteger = 1;
const NSWindowStyleMaskClosable: NSUInteger = 2;
const NSWindowStyleMaskMiniaturizable: NSUInteger = 4;
const NSWindowStyleMaskResizable: NSUInteger = 8;
const NSBackingStoreBuffered: NSUInteger = 2;
const NSApplicationActivationPolicyRegular: NSInteger = 0;
const YES: BOOL = 1;
const NO: BOOL = 0;
fn cls(name: [*:0]const u8) Id {
return objc_getClass(name);
}
fn sel(name: [*:0]const u8) Sel {
return sel_registerName(name);
}
// Typed objc_msgSend casts — one per distinct signature
fn send(recv: Id, s: Sel) Id {
const F = *const fn (Id, Sel) callconv(.c) Id;
return @as(F, @ptrCast(&objc_msgSend))(recv, s);
}
fn sendv(recv: Id, s: Sel) void {
const F = *const fn (Id, Sel) callconv(.c) void;
@as(F, @ptrCast(&objc_msgSend))(recv, s);
}
fn send1(recv: Id, s: Sel, a: Id) Id {
const F = *const fn (Id, Sel, Id) callconv(.c) Id;
return @as(F, @ptrCast(&objc_msgSend))(recv, s, a);
}
fn send1v(recv: Id, s: Sel, a: Id) void {
const F = *const fn (Id, Sel, Id) callconv(.c) void;
@as(F, @ptrCast(&objc_msgSend))(recv, s, a);
}
fn sendStr(recv: Id, s: Sel, str: [*:0]const u8) Id {
const F = *const fn (Id, Sel, [*:0]const u8) callconv(.c) Id;
return @as(F, @ptrCast(&objc_msgSend))(recv, s, str);
}
fn sendIntv(recv: Id, s: Sel, a: NSInteger) void {
const F = *const fn (Id, Sel, NSInteger) callconv(.c) void;
@as(F, @ptrCast(&objc_msgSend))(recv, s, a);
}
fn sendBoolv(recv: Id, s: Sel, a: BOOL) void {
const F = *const fn (Id, Sel, BOOL) callconv(.c) void;
@as(F, @ptrCast(&objc_msgSend))(recv, s, a);
}
fn sendWindowInit(recv: Id, s: Sel, rect: CGRect, style: NSUInteger, backing: NSUInteger, defer_: BOOL) Id {
const F = *const fn (Id, Sel, CGRect, NSUInteger, NSUInteger, BOOL) callconv(.c) Id;
return @as(F, @ptrCast(&objc_msgSend))(recv, s, rect, style, backing, defer_);
}
fn sendWebViewInit(recv: Id, s: Sel, frame: CGRect, config: Id) Id {
const F = *const fn (Id, Sel, CGRect, Id) callconv(.c) Id;
return @as(F, @ptrCast(&objc_msgSend))(recv, s, frame, config);
}
pub fn main() void {
std.debug.print("=== merjs desktop spike — Zig 0.15 ObjC bridge ===\n", .{});
// [1/4] NSApplication
const app = send(cls("NSApplication"), sel("sharedApplication"));
if (app == null) @panic("NSApplication.sharedApplication returned nil");
sendIntv(app, sel("setActivationPolicy:"), NSApplicationActivationPolicyRegular);
std.debug.print("[1/4] NSApplication sharedApplication OK\n", .{});
// [2/4] NSWindow
const frame = CGRect{
.origin = .{ .x = 0, .y = 0 },
.size = .{ .width = 1200, .height = 800 },
};
const style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
const window = sendWindowInit(
send(cls("NSWindow"), sel("alloc")),
sel("initWithContentRect:styleMask:backing:defer:"),
frame,
style,
NSBackingStoreBuffered,
NO,
);
if (window == null) @panic("NSWindow init returned nil");
const title_str = sendStr(cls("NSString"), sel("stringWithUTF8String:"), "merjs Desktop");
send1v(window, sel("setTitle:"), title_str);
std.debug.print("[2/4] NSWindow initWithContentRect OK\n", .{});
// [3/4] WKWebView
if (cls("WKWebView") == null) @panic("WKWebView class not found — check -framework WebKit");
const wkconfig = send(
send(cls("WKWebViewConfiguration"), sel("alloc")),
sel("init"),
);
const webview = sendWebViewInit(
send(cls("WKWebView"), sel("alloc")),
sel("initWithFrame:configuration:"),
frame,
wkconfig,
);
if (webview == null) @panic("WKWebView init returned nil");
send1v(window, sel("setContentView:"), webview);
std.debug.print("[3/4] WKWebView initWithFrame OK\n", .{});
// [4/4] Load URL (data URI — spike is self-contained, no server needed)
const url_str = sendStr(
cls("NSString"),
sel("stringWithUTF8String:"),
"data:text/html,<h1 style='font-family:system-ui;padding:40px'>merjs desktop spike works!</h1>",
);
const url = send1(cls("NSURL"), sel("URLWithString:"), url_str);
const request = send1(cls("NSURLRequest"), sel("requestWithURL:"), url);
_ = send1(webview, sel("loadRequest:"), request);
std.debug.print("[4/4] WKWebView loadRequest OK\n", .{});
// Show window + run loop
send1v(window, sel("makeKeyAndOrderFront:"), null);
sendBoolv(app, sel("activateIgnoringOtherApps:"), YES);
std.debug.print("Running NSApp event loop — close the window to quit.\n", .{});
sendv(app, sel("run"));
}