콘텐츠로 이동

Zig 백엔드 SDK

@import("suji")로 접근. dylib으로 빌드되어 main suji 프로세스에 dlopen 됨.

const suji = @import("suji");
const std = @import("std");
pub const my_app = suji.app()
.named("my-plugin") // 선택 — ready/bye 로그 prefix 구분
.handle("ping", ping) // 1-arity
.handle("save", save) // 2-arity (InvokeEvent)
.on("window:all-closed", onAllClosed); // 이벤트 리스너
fn ping(req: suji.Request) suji.Response {
return req.ok(.{ .msg = "pong" });
}
fn save(req: suji.Request, event: suji.InvokeEvent) suji.Response {
const content = req.string("content") orelse "";
std.debug.print("save from window {d}\n", .{event.window.id});
return req.ok(.{ .ok = true });
}
fn onAllClosed(_: suji.Event) void {
if (!std.mem.eql(u8, suji.platform(), suji.PLATFORM_MACOS)) {
suji.quit();
}
}
comptime {
_ = suji.exportApp(my_app);
}
pub const Request = struct {
raw: []const u8, // 원본 JSON
arena: std.mem.Allocator, // 요청 스코프 arena
pub fn string(self, key: []const u8) ?[]const u8;
pub fn int(self, key: []const u8) ?i64;
pub fn float(self, key: []const u8) ?f64;
pub fn bool(self, key: []const u8) ?bool;
pub fn ok(self, value: anytype) Response;
pub fn err(self, message: []const u8) Response;
};
pub const Response = struct {
data: []const u8, // 직렬화된 JSON 바이트
};

req.ok(.{...}) / req.err(...)로만 생성. 직접 구성할 일 없음.

pub const InvokeEvent = struct {
window: Window,
pub const Window = struct {
id: u32, // wire `__window` 필드 — 호출한 창의 WM id
name: ?[]const u8, // wire `__window_name` 필드 — 창 이름 (익명 창은 null)
};
};

핸들러 시그니처 2가지 모두 허용:

fn h1(req: Request) Response { ... } // 기본
fn h2(req: Request, event: InvokeEvent) Response { ... } // 창 식별 필요 시

1-arity와 2-arity 모두 허용되며 호환성이 유지된다.

pub const Event = struct {
channel: []const u8,
data: []const u8,
};

on("window:all-closed", fn(Event) void) 같은 리스너 콜백이 받음. InvokeEvent와 다른 역할 — 이벤트 구독용 payload.

suji.platform() []const u8; // "macos" | "linux" | "windows"
suji.quit() void; // Electron app.quit() 대응
suji.PLATFORM_MACOS, PLATFORM_LINUX, PLATFORM_WINDOWS // 상수
fn callRust(req: suji.Request) suji.Response {
var resp_buf: [1024]u8 = undefined;
if (req.invoke("rust", "{\"cmd\":\"hash\",\"data\":\"abc\"}", &resp_buf)) |resp| {
// resp는 다른 백엔드의 JSON 응답
return req.ok(.{ .from_rust = resp });
}
return req.err("rust failed");
}
suji.send("theme-changed", "{\"color\":\"dark\"}");

모든 창의 __suji__.on("theme-changed", ...) 리스너에 JSON 파싱되어 전달.

Frontend @suji/apiwindows.*와 같은 cmd JSON을 코어로 보낸다. 응답은 ?[]const u8 JSON 문자열.

// 새 창 (단축)
_ = suji.windows.createSimple("Settings", "http://localhost:12300/settings");
// 옵션 풀 셋 — opts_json은 cmd 객체 안의 raw 필드 (camelCase)
_ = suji.windows.create("\"name\":\"hud\",\"frame\":false,\"transparent\":true,\"alwaysOnTop\":true");
// 페이지 조작
_ = suji.windows.loadURL(2, "https://example.com/");
_ = suji.windows.reload(2, true); // ignoreCache
_ = suji.windows.executeJavaScript(2, "document.title='Hi'"); // fire-and-forget
_ = suji.windows.setTitle(2, "New Title");
_ = suji.windows.setBounds(2, .{ .x = 100, .y = 100, .width = 1200, .height = 800 });
// 상태 조회
_ = suji.windows.getURL(2); // {ok, url} 또는 {ok:false, url:null}
_ = suji.windows.isLoading(2); // {ok, loading}
// 줌
_ = suji.windows.setZoomLevel(2, 1.5); // logarithmic
_ = suji.windows.setZoomFactor(2, 1.2); // linear (factor=pow(1.2, level))
_ = suji.windows.getZoomLevel(2);
_ = suji.windows.getZoomFactor(2);
// DevTools
_ = suji.windows.openDevTools(2);
_ = suji.windows.toggleDevTools(2);
_ = suji.windows.isDevToolsOpened(2);
// 편집/검색
_ = suji.windows.undo(2);
_ = suji.windows.copy(2);
_ = suji.windows.findInPage(2, "hello", .{ .forward = true, .match_case = false, .find_next = false });
_ = suji.windows.stopFindInPage(2, /* clear_selection */ true);
// PDF 인쇄 (콜백 async — 결과는 `window:pdf-print-finished` 이벤트)
_ = suji.windows.printToPDF(2, "/tmp/report.pdf");

title/url/code/text 등 문자열 필드는 SDK가 자동 escape (" \\ 이스케이프 + control char drop).

응답은 ?[]const u8 JSON 문자열.

_ = suji.fs.mkdir("/tmp/suji", true);
_ = suji.fs.writeFile("/tmp/suji/hello.txt", "hello\nworld");
_ = suji.fs.readFile("/tmp/suji/hello.txt");
_ = suji.fs.stat("/tmp/suji/hello.txt");
_ = suji.fs.readdir("/tmp/suji");

현재 텍스트 파일 중심 API이며 대용량/바이너리 스트리밍은 지원하지 않는다.