@nativedesktop/native 0.1.0
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/include/nd.h +157 -0
- package/include/nd_plugin.h +71 -0
- package/linux/nd_native_gtk.h +24 -0
- package/macos/NativeDesktopNative.swift +103 -0
- package/macos/module.modulemap +4 -0
- package/package.json +23 -0
package/include/nd.h
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/* include/nd.h — the ONLY public surface of libnd. Hand-written; kept in
|
|
2
|
+
lockstep with src/abi.zig by compile-time @sizeOf asserts. Canonical copy
|
|
3
|
+
lives in include/; packages/native/include/ carries a machine-synced copy
|
|
4
|
+
(scripts/sync-native-headers.sh, cmp-checked in CI) — edit include/ only. */
|
|
5
|
+
#ifndef ND_H
|
|
6
|
+
#define ND_H
|
|
7
|
+
#include <stdint.h>
|
|
8
|
+
#include <stdbool.h>
|
|
9
|
+
#include "nd_plugin.h"
|
|
10
|
+
|
|
11
|
+
typedef void* nd_widget; /* opaque backend handle (GtkWidget* / NSView*) */
|
|
12
|
+
typedef struct nd_context nd_context; /* opaque core instance */
|
|
13
|
+
|
|
14
|
+
/* Geometry in logical window-top-left space (matches getTree contract). */
|
|
15
|
+
typedef struct { int32_t x, y, w, h; } nd_rect;
|
|
16
|
+
|
|
17
|
+
/* The backend vtable: the embedder fills these; the core calls up through them.
|
|
18
|
+
`props_json` / `arg_json` are NUL-terminated UTF-8 JSON (M6a-D2). All calls
|
|
19
|
+
arrive on the embedder's UI thread (the core marshals). */
|
|
20
|
+
typedef struct nd_backend {
|
|
21
|
+
/* structural + prop ops (mirror the Zig seam) */
|
|
22
|
+
nd_widget (*create)(nd_context*, const char* kind, const char* props_json);
|
|
23
|
+
void (*apply_props)(nd_context*, nd_widget, const char* kind, const char* props_json);
|
|
24
|
+
void (*append_child)(nd_context*, nd_widget parent, const char* parent_kind,
|
|
25
|
+
nd_widget child, const char* attached_json);
|
|
26
|
+
void (*insert_before)(nd_context*, nd_widget parent, const char* parent_kind,
|
|
27
|
+
nd_widget child, nd_widget before /* nullable */,
|
|
28
|
+
const char* attached_json);
|
|
29
|
+
void (*remove_child)(nd_context*, nd_widget parent, const char* parent_kind, nd_widget child);
|
|
30
|
+
void (*set_text)(nd_context*, nd_widget, const char* text);
|
|
31
|
+
void (*set_visible)(nd_context*, nd_widget, bool visible);
|
|
32
|
+
void (*apply_style)(nd_context*, nd_widget, uint32_t node_id, const char* style_json);
|
|
33
|
+
void (*connect_events)(nd_context*, nd_widget, const char* kind, uint32_t node_id);
|
|
34
|
+
bool (*has_parent)(nd_context*, nd_widget);
|
|
35
|
+
void (*unparent)(nd_context*, nd_widget);
|
|
36
|
+
nd_widget (*get_window)(nd_context*);
|
|
37
|
+
|
|
38
|
+
/* embedder UI-thread marshal + host chrome (M6a Task 3): the core's
|
|
39
|
+
commit-apply/child-exit/overlay paint call up through these instead of
|
|
40
|
+
importing glib/gio directly. GTK fills marshal_async with
|
|
41
|
+
g_main_context_invoke_full; the Mac shell fills it with
|
|
42
|
+
dispatch_async_f. `show_overlay("")` (empty message) is the clear
|
|
43
|
+
sentinel — the core calls it that way from its dev-mode Restart/respawn
|
|
44
|
+
path instead of a dedicated clear-overlay vtable field. */
|
|
45
|
+
void (*marshal_async)(nd_context*, void (*fn)(void*), void* data);
|
|
46
|
+
void (*show_overlay)(nd_context*, const char* message);
|
|
47
|
+
|
|
48
|
+
/* automation backend half (M6a-D3) */
|
|
49
|
+
bool (*node_visible)(nd_context*, nd_widget);
|
|
50
|
+
bool (*node_bounds)(nd_context*, nd_widget, nd_rect* out); /* false = no bounds */
|
|
51
|
+
bool (*snapshot)(nd_context*, const char* png_path); /* in-process render */
|
|
52
|
+
/* action: "click"|"setValue"|"type"|"scroll"; arg_json carries params.
|
|
53
|
+
returns 0 ok, or a negative JSON-RPC-style code; err_json_out (nullable,
|
|
54
|
+
caller frees via nd_free) gets a data object on failure. */
|
|
55
|
+
int32_t (*semantic_action)(nd_context*, nd_widget, uint32_t node_id,
|
|
56
|
+
const char* action, const char* arg_json,
|
|
57
|
+
char** result_json_out, char** err_json_out);
|
|
58
|
+
|
|
59
|
+
/* app -> widget imperative command (M14, widgetCommand NDP frame): `command`
|
|
60
|
+
is one of the widget's schema-declared commands (widgets.json commands[]),
|
|
61
|
+
`arg_json` its JSON argument (or "null"). Arrives on the UI thread. */
|
|
62
|
+
void (*widget_command)(nd_context*, nd_widget, const char* kind,
|
|
63
|
+
const char* command, const char* arg_json);
|
|
64
|
+
|
|
65
|
+
/* multi-window reconstruction (per-window generalization of get_window):
|
|
66
|
+
given a Window node's stored handle, return the handle to REBIND to when a
|
|
67
|
+
crash/dev-Restart respawn re-creates that window, so the core reuses the
|
|
68
|
+
surviving OS window instead of opening a duplicate. GTK returns the handle
|
|
69
|
+
unchanged (its gtk.Window handle is stable); the AppKit shell resolves it
|
|
70
|
+
to the window's CURRENT content view (which differs once a SplitView took
|
|
71
|
+
over as contentViewController). */
|
|
72
|
+
nd_widget (*resolve_window)(nd_context*, nd_widget);
|
|
73
|
+
|
|
74
|
+
/* widget-preserving cross-window move (drag a tab between windows): relocate
|
|
75
|
+
an EXISTING live native widget from `old_parent` to `new_parent` WITHOUT
|
|
76
|
+
destroying it, so a <webview>'s loaded page / scroll / JS state survives —
|
|
77
|
+
a remove+create would reload it. Reuses each backend's ordinary remove +
|
|
78
|
+
append/insert per parent kind, so the target attaches correctly whatever
|
|
79
|
+
the parent (Box slot, Window, ...). `before` (nullable) positions the child;
|
|
80
|
+
NULL appends. `old_parent` may be NULL (the child is a detached pool node
|
|
81
|
+
not yet shown in any window). The handle stays alive across the unparent
|
|
82
|
+
(GTK g_object_ref brackets the move; the AppKit shell relies on the core's
|
|
83
|
+
create-time retain). Arrives on the UI thread. Appended (append-only
|
|
84
|
+
vtable) — bumps @sizeOf(NdBackend) to 21 words in src/abi.zig. */
|
|
85
|
+
void (*reparent_child)(nd_context*, nd_widget child,
|
|
86
|
+
nd_widget old_parent /* nullable */, const char* old_parent_kind,
|
|
87
|
+
nd_widget new_parent, const char* new_parent_kind,
|
|
88
|
+
nd_widget before /* nullable */, const char* attached_json);
|
|
89
|
+
|
|
90
|
+
/* app -> host system capability request (systemRequest NDP frame). `method`
|
|
91
|
+
is a dotted capability (e.g. "dialog.openFile"), `params_json` its JSON
|
|
92
|
+
argument. Fire-and-forget: the backend delivers the (possibly async)
|
|
93
|
+
result later via nd_system_response. Arrives on the UI thread. Appended
|
|
94
|
+
(append-only vtable) — bumps @sizeOf(NdBackend) to 22 words in src/abi.zig. */
|
|
95
|
+
void (*system_request)(nd_context*, uint32_t id, const char* method, const char* params_json);
|
|
96
|
+
|
|
97
|
+
/* drop the backend's per-node ownership reference when the core forgets a
|
|
98
|
+
node id (tree remove / generation GC / clearAppNodes). GTK: g_object_unref
|
|
99
|
+
of the create-time ref_sink; the Mac shell: Unmanaged.release of the
|
|
100
|
+
create-time retain. The widget object stays alive while native parents
|
|
101
|
+
still reference it. Arrives on the UI thread. Appended (append-only
|
|
102
|
+
vtable) — bumps @sizeOf(NdBackend) to 23 words in src/abi.zig. */
|
|
103
|
+
void (*release_node)(nd_context*, nd_widget);
|
|
104
|
+
} nd_backend;
|
|
105
|
+
|
|
106
|
+
/* lifecycle */
|
|
107
|
+
nd_context* nd_init(void); /* create core, spawn nothing yet */
|
|
108
|
+
void nd_register_backend(nd_context*, const nd_backend*); /* store the vtable */
|
|
109
|
+
/* Name the active widget backend ("gtk" | "appkit"). The core echoes it in the
|
|
110
|
+
NDP helloAck so the Bun child's Platform.backend can branch on the renderer
|
|
111
|
+
(which the OS alone can't reveal — GTK runs on macOS too). Call before
|
|
112
|
+
nd_start_runtime; absent = "unknown". */
|
|
113
|
+
void nd_set_backend_name(nd_context*, const char* name);
|
|
114
|
+
int32_t nd_start_runtime(nd_context*); /* open NDP socket, spawn bun child */
|
|
115
|
+
int32_t nd_start_automation(nd_context*); /* open automation socket + thread */
|
|
116
|
+
void nd_shutdown(nd_context*); /* stop runtime; destroy views/plugins */
|
|
117
|
+
/* Capability ACL (D12): install a per-window grants manifest (NUL-terminated
|
|
118
|
+
JSON; see docs). Absent = safe default (core UI ops granted, plugin ops
|
|
119
|
+
denied). Call before nd_start_runtime. */
|
|
120
|
+
void nd_set_acl(nd_context*, const char* grants_json);
|
|
121
|
+
/* Load a native nd_plugin_v1 shared library (opt-in). Returns 0 ok, negative
|
|
122
|
+
on ABI mismatch / missing entry / capability-denied. */
|
|
123
|
+
int32_t nd_load_plugin(nd_context*, const char* path);
|
|
124
|
+
/* Load every plugin listed in ND_PLUGIN_PATHS (colon-separated; legacy
|
|
125
|
+
single-path ND_PLUGIN_PATH as fallback), skipping empty segments. Prints
|
|
126
|
+
one "ND_PLUGIN_LOAD_FAILED path=... rc=..." diagnostic per failed path. */
|
|
127
|
+
void nd_load_plugins_from_env(nd_context*);
|
|
128
|
+
|
|
129
|
+
/* Host-side native-view bridge (plugin ABI v2): the generic <nativeView>
|
|
130
|
+
widget's create/apply/command/destroy route through these into the loaded
|
|
131
|
+
plugin's nd_view_impl registered under `view_kind`. The GTK backend calls the
|
|
132
|
+
Zig fns in src/plugin.zig directly; the AppKit/Swift shell calls these C
|
|
133
|
+
wrappers. create returns NULL when no module registered `view_kind` (the
|
|
134
|
+
backend then renders an empty placeholder). */
|
|
135
|
+
nd_widget nd_plugin_view_create(nd_context*, const char* view_kind, const char* props_json);
|
|
136
|
+
void nd_plugin_view_apply_props(nd_context*, const char* view_kind, nd_widget, const char* props_json);
|
|
137
|
+
void nd_plugin_view_connect(nd_context*, const char* view_kind, nd_widget, uint32_t node_id);
|
|
138
|
+
void nd_plugin_view_command(nd_context*, const char* view_kind, nd_widget, const char* command, const char* arg_json);
|
|
139
|
+
void nd_plugin_view_destroy(nd_context*, const char* view_kind, nd_widget);
|
|
140
|
+
|
|
141
|
+
void nd_free(void* p); /* free a core-allocated string */
|
|
142
|
+
|
|
143
|
+
/* embedder -> core: a native event happened (button clicked, text changed, …).
|
|
144
|
+
`payload_json` is a NUL-terminated JSON object or "{}". */
|
|
145
|
+
void nd_emit_event(nd_context*, uint32_t node_id, const char* name, const char* payload_json);
|
|
146
|
+
|
|
147
|
+
/* backend -> core: the (possibly async) result of an earlier system_request,
|
|
148
|
+
correlated by `id`. ok=true splices `json` verbatim into the systemResponse
|
|
149
|
+
`result`; ok=false carries `json` as the error message string. */
|
|
150
|
+
void nd_system_response(nd_context*, uint32_t id, bool ok, const char* json);
|
|
151
|
+
/* backend -> core: an app-level event not tied to a widget node (app
|
|
152
|
+
activation, OS open-url/open-file launch, notification click, file drop,
|
|
153
|
+
capability event stream). `channel` names the stream; `data_json` is its
|
|
154
|
+
NUL-terminated JSON payload. */
|
|
155
|
+
void nd_system_event(nd_context*, const char* channel, const char* data_json);
|
|
156
|
+
|
|
157
|
+
#endif /* ND_H */
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* include/nd_plugin.h — the nd_plugin_v1 native-plugin ABI (spec §9, D12).
|
|
2
|
+
A native plugin is a C-ABI shared library exporting nd_plugin_entry(), which
|
|
3
|
+
returns a pointer to a statically-lived nd_plugin_v1. Capability strings are
|
|
4
|
+
checked against the NDP-dispatch ACL; a command runs only if its plugin's
|
|
5
|
+
declared permission is granted for the window. Opt-in: nothing loads a
|
|
6
|
+
plugin unless the embedder calls nd_load_plugin (include/nd.h). Canonical
|
|
7
|
+
copy lives in include/; packages/native/include/ carries a machine-synced
|
|
8
|
+
copy (scripts/sync-native-headers.sh, cmp-checked in CI) — edit include/
|
|
9
|
+
only. */
|
|
10
|
+
#ifndef ND_PLUGIN_H
|
|
11
|
+
#define ND_PLUGIN_H
|
|
12
|
+
#include <stdint.h>
|
|
13
|
+
#include <stddef.h>
|
|
14
|
+
|
|
15
|
+
/* Append-only ABI history:
|
|
16
|
+
v2 adds register_view + nd_view_impl so a plugin can register its own native
|
|
17
|
+
widget. v3 appends connect + emit_event so that widget can emit events for a
|
|
18
|
+
retained node. v1/v2 plugins keep loading unchanged; consumers must only read
|
|
19
|
+
fields available in the plugin's declared abi_version. */
|
|
20
|
+
#define ND_PLUGIN_ABI_VERSION 3
|
|
21
|
+
|
|
22
|
+
typedef struct nd_plugin_registry nd_plugin_registry;
|
|
23
|
+
|
|
24
|
+
/* A plugin command handler: receives the arg JSON (NUL-terminated) and writes
|
|
25
|
+
a malloc'd result JSON to *result_out (freed by the core via nd_free).
|
|
26
|
+
Returns 0 ok, negative JSON-RPC-style code on error. */
|
|
27
|
+
typedef int32_t (*nd_command_fn)(const char* arg_json, char** result_out);
|
|
28
|
+
|
|
29
|
+
/* A native-view factory a plugin registers under a `view_kind` string. The
|
|
30
|
+
handle each fn returns/takes is the backend-native widget as an opaque
|
|
31
|
+
pointer (GtkWidget* on GTK, NSView* on AppKit) — the core never dereferences
|
|
32
|
+
it; it flows through append_child/apply_style/unparent by parent kind alone.
|
|
33
|
+
`props_json`/`arg_json` are NUL-terminated JSON. Native views are inherently
|
|
34
|
+
backend-specific: a module ships a GTK impl and/or an AppKit impl. */
|
|
35
|
+
typedef struct nd_view_impl {
|
|
36
|
+
void* (*create)(const char* props_json);
|
|
37
|
+
void (*apply_props)(void* view, const char* props_json);
|
|
38
|
+
void (*command)(void* view, const char* command, const char* arg_json);
|
|
39
|
+
void (*destroy)(void* view);
|
|
40
|
+
/* v3: called after creation once the core node identity is known. */
|
|
41
|
+
void (*connect)(void* view, uint32_t node_id);
|
|
42
|
+
} nd_view_impl;
|
|
43
|
+
|
|
44
|
+
/* Host callbacks a plugin uses from init(). register_command associates a
|
|
45
|
+
command name (reachable over NDP as {"type":"pluginCommand","name",...})
|
|
46
|
+
with a handler; the core namespaces it as plugin:<plugin-name>.<command>.
|
|
47
|
+
register_view (v2, appended) associates a `view_kind` string with a native-
|
|
48
|
+
view factory the generic <nativeView> widget routes to. emit_event is v3 and
|
|
49
|
+
becomes valid only after connect supplies a node id. The core copies the
|
|
50
|
+
nd_view_impl by value, so it may live on the plugin's stack in init(). */
|
|
51
|
+
struct nd_plugin_registry {
|
|
52
|
+
void* host; /* opaque core handle; pass back to the callbacks */
|
|
53
|
+
void (*register_command)(nd_plugin_registry*, const char* command, nd_command_fn);
|
|
54
|
+
void (*register_view)(nd_plugin_registry*, const char* view_kind, const nd_view_impl*);
|
|
55
|
+
/* v3: emit a generic event for a connected node through the core. */
|
|
56
|
+
void (*emit_event)(nd_plugin_registry*, uint32_t node_id, const char* name,
|
|
57
|
+
const char* payload_json);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
typedef struct nd_plugin_v1 {
|
|
61
|
+
uint32_t abi_version; /* 1, 2, or ND_PLUGIN_ABI_VERSION; old layouts remain valid */
|
|
62
|
+
const char* name; /* plugin identity, e.g. "hello" */
|
|
63
|
+
const char* const* capabilities; /* NULL-terminated permission strings, e.g. {"plugin:hello.greet", NULL} */
|
|
64
|
+
int32_t (*init)(nd_plugin_registry*); /* register commands/widgets; 0 ok */
|
|
65
|
+
void (*deinit)(void);
|
|
66
|
+
} nd_plugin_v1;
|
|
67
|
+
|
|
68
|
+
/* Every plugin shared library exports this symbol. */
|
|
69
|
+
typedef const nd_plugin_v1* (*nd_plugin_entry_fn)(void);
|
|
70
|
+
|
|
71
|
+
#endif /* ND_PLUGIN_H */
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#ifndef ND_NATIVE_GTK_H
|
|
2
|
+
#define ND_NATIVE_GTK_H
|
|
3
|
+
|
|
4
|
+
#include <gtk/gtk.h>
|
|
5
|
+
#include "../include/nd_plugin.h"
|
|
6
|
+
|
|
7
|
+
/* Small C convenience layer for app-owned GTK plugins. The app still exports
|
|
8
|
+
nd_plugin_entry and owns every widget/state allocation. */
|
|
9
|
+
typedef struct nd_gtk_view_state {
|
|
10
|
+
nd_plugin_registry* registry;
|
|
11
|
+
uint32_t node_id;
|
|
12
|
+
} nd_gtk_view_state;
|
|
13
|
+
|
|
14
|
+
static inline void nd_gtk_connect_state(nd_gtk_view_state* state, nd_plugin_registry* registry, uint32_t node_id) {
|
|
15
|
+
state->registry = registry;
|
|
16
|
+
state->node_id = node_id;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static inline void nd_gtk_emit(nd_gtk_view_state* state, const char* name, const char* payload_json) {
|
|
20
|
+
if (state && state->registry && state->registry->emit_event)
|
|
21
|
+
state->registry->emit_event(state->registry, state->node_id, name, payload_json ? payload_json : "{}");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#endif
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import SwiftUI
|
|
3
|
+
import CNdPlugin
|
|
4
|
+
|
|
5
|
+
/// Protocol implemented by app-owned AppKit views hosted by NativeDesktop.
|
|
6
|
+
public protocol NativeDesktopView: AnyObject {
|
|
7
|
+
func apply(propsJSON: String)
|
|
8
|
+
func connect(nodeID: UInt32, emit: @escaping (_ name: String, _ payloadJSON: String) -> Void)
|
|
9
|
+
func command(_ name: String, argJSON: String)
|
|
10
|
+
func destroy()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public extension NativeDesktopView {
|
|
14
|
+
func connect(nodeID: UInt32, emit: @escaping (String, String) -> Void) {}
|
|
15
|
+
func command(_ name: String, argJSON: String) {}
|
|
16
|
+
func destroy() {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// C-ABI glue for plugins written against NativeDesktopView. nd_view_impl
|
|
20
|
+
/// callbacks carry no context pointer, so registerView supports one view kind
|
|
21
|
+
/// per plugin; hand-roll an nd_view_impl to register more.
|
|
22
|
+
public enum NativeDesktopPlugin {
|
|
23
|
+
private static var registry: UnsafeMutablePointer<nd_plugin_registry>?
|
|
24
|
+
private static var factory: ((String) -> NSView & NativeDesktopView)?
|
|
25
|
+
|
|
26
|
+
/// Allocates an nd_plugin_v1 with the stable lifetime the ABI requires;
|
|
27
|
+
/// return the result from @_cdecl("nd_plugin_entry").
|
|
28
|
+
public static func descriptor(
|
|
29
|
+
name: String,
|
|
30
|
+
capabilities: [String] = [],
|
|
31
|
+
initialize: @escaping @convention(c) (UnsafeMutablePointer<nd_plugin_registry>?) -> Int32,
|
|
32
|
+
deinitialize: @escaping @convention(c) () -> Void = {}
|
|
33
|
+
) -> UnsafePointer<nd_plugin_v1> {
|
|
34
|
+
let caps = UnsafeMutablePointer<UnsafePointer<CChar>?>.allocate(capacity: capabilities.count + 1)
|
|
35
|
+
for (index, capability) in capabilities.enumerated() { caps[index] = UnsafePointer(strdup(capability)) }
|
|
36
|
+
caps[capabilities.count] = nil
|
|
37
|
+
let plugin = UnsafeMutablePointer<nd_plugin_v1>.allocate(capacity: 1)
|
|
38
|
+
plugin.initialize(to: nd_plugin_v1(abi_version: UInt32(ND_PLUGIN_ABI_VERSION), name: UnsafePointer(strdup(name)), capabilities: UnsafePointer(caps), init: initialize, deinit: deinitialize))
|
|
39
|
+
return UnsafePointer(plugin)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Call from the descriptor's initialize callback.
|
|
43
|
+
public static func registerView(
|
|
44
|
+
_ registry: UnsafeMutablePointer<nd_plugin_registry>,
|
|
45
|
+
kind: String,
|
|
46
|
+
factory: @escaping (String) -> NSView & NativeDesktopView
|
|
47
|
+
) {
|
|
48
|
+
self.registry = registry
|
|
49
|
+
self.factory = factory
|
|
50
|
+
var impl = nd_view_impl(
|
|
51
|
+
create: { props in
|
|
52
|
+
guard let view = NativeDesktopPlugin.factory?(NativeDesktopPlugin.string(props)) else { return nil }
|
|
53
|
+
return Unmanaged.passRetained(view as NSView).toOpaque()
|
|
54
|
+
},
|
|
55
|
+
apply_props: { raw, props in
|
|
56
|
+
NativeDesktopPlugin.view(raw)?.apply(propsJSON: NativeDesktopPlugin.string(props))
|
|
57
|
+
},
|
|
58
|
+
command: { raw, name, arg in
|
|
59
|
+
guard let name else { return }
|
|
60
|
+
NativeDesktopPlugin.view(raw)?.command(String(cString: name), argJSON: NativeDesktopPlugin.string(arg))
|
|
61
|
+
},
|
|
62
|
+
destroy: { raw in
|
|
63
|
+
guard let raw else { return }
|
|
64
|
+
NativeDesktopPlugin.view(raw)?.destroy()
|
|
65
|
+
Unmanaged<NSView>.fromOpaque(raw).release()
|
|
66
|
+
},
|
|
67
|
+
connect: { raw, nodeID in
|
|
68
|
+
NativeDesktopPlugin.view(raw)?.connect(nodeID: nodeID) { name, payload in
|
|
69
|
+
guard let registry = NativeDesktopPlugin.registry, let emit = registry.pointee.emit_event else { return }
|
|
70
|
+
name.withCString { n in payload.withCString { p in emit(registry, nodeID, n, p) } }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
registry.pointee.register_view(registry, kind, &impl)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private static func view(_ raw: UnsafeMutableRawPointer?) -> (NSView & NativeDesktopView)? {
|
|
78
|
+
raw.flatMap { Unmanaged<NSView>.fromOpaque($0).takeUnretainedValue() as? NSView & NativeDesktopView }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private static func string(_ pointer: UnsafePointer<CChar>?) -> String {
|
|
82
|
+
pointer.map { String(cString: $0) } ?? "{}"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// AppKit host for a SwiftUI component. Updating `rootView` preserves the
|
|
87
|
+
/// ordinary NSView identity expected by NativeDesktop's retained tree.
|
|
88
|
+
public final class NativeDesktopSwiftUIView<Content: View>: NSHostingView<Content>, NativeDesktopView {
|
|
89
|
+
private let updateRoot: (String) -> Content
|
|
90
|
+
|
|
91
|
+
public init(initialPropsJSON: String, content: @escaping (String) -> Content) {
|
|
92
|
+
self.updateRoot = content
|
|
93
|
+
super.init(rootView: content(initialPropsJSON))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@available(*, unavailable)
|
|
97
|
+
required init(rootView: Content) { fatalError("use init(initialPropsJSON:content:)") }
|
|
98
|
+
|
|
99
|
+
@available(*, unavailable)
|
|
100
|
+
required init?(coder: NSCoder) { fatalError("init(coder:) is unavailable") }
|
|
101
|
+
|
|
102
|
+
public func apply(propsJSON: String) { rootView = updateRoot(propsJSON) }
|
|
103
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nativedesktop/native",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/FormalSnake/NativeDesktop#readme",
|
|
7
|
+
"bugs": "https://github.com/FormalSnake/NativeDesktop/issues",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/FormalSnake/NativeDesktop.git",
|
|
11
|
+
"directory": "packages/native"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"files": ["include", "macos", "linux"],
|
|
17
|
+
"exports": {
|
|
18
|
+
"./include/nd.h": "./include/nd.h",
|
|
19
|
+
"./include/nd_plugin.h": "./include/nd_plugin.h",
|
|
20
|
+
"./macos": "./macos/NativeDesktopNative.swift",
|
|
21
|
+
"./linux": "./linux/nd_native_gtk.h"
|
|
22
|
+
}
|
|
23
|
+
}
|