@quickgui/native 0.0.1
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 +49 -0
- package/binding.d.ts +746 -0
- package/binding.js +834 -0
- package/dialog.ts +176 -0
- package/host.ts +58 -0
- package/index.ts +1603 -0
- package/integrations.ts +258 -0
- package/native-tree.ts +377 -0
- package/package.json +63 -0
- package/protocol.ts +287 -0
- package/quickgui-native.darwin-arm64.node +0 -0
- package/quickgui-native.darwin-x64.node +0 -0
- package/scripts/build.ts +74 -0
- package/single-instance.ts +31 -0
- package/system.ts +1653 -0
- package/tray.ts +399 -0
package/protocol.ts
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
|
|
3
|
+
export const PROTOCOL_VERSION = 15;
|
|
4
|
+
export const ROOT_NODE_ID = 0;
|
|
5
|
+
export const NO_ANCHOR = 0xffff_ffff;
|
|
6
|
+
|
|
7
|
+
export const enum NativeNodeTag {
|
|
8
|
+
View = 1,
|
|
9
|
+
Button = 2,
|
|
10
|
+
Text = 3,
|
|
11
|
+
Sentinel = 4,
|
|
12
|
+
Input = 5,
|
|
13
|
+
Markdown = 6,
|
|
14
|
+
VirtualList = 7,
|
|
15
|
+
Terminal = 8,
|
|
16
|
+
Svg = 9,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const enum PropertyCode {
|
|
20
|
+
Display = 1,
|
|
21
|
+
FlexDirection = 2,
|
|
22
|
+
FlexWrap = 3,
|
|
23
|
+
FlexGrow = 4,
|
|
24
|
+
FlexShrink = 5,
|
|
25
|
+
FlexBasis = 6,
|
|
26
|
+
AlignItems = 7,
|
|
27
|
+
AlignSelf = 8,
|
|
28
|
+
JustifyContent = 9,
|
|
29
|
+
AlignContent = 10,
|
|
30
|
+
Gap = 11,
|
|
31
|
+
ColumnGap = 12,
|
|
32
|
+
RowGap = 13,
|
|
33
|
+
Width = 14,
|
|
34
|
+
Height = 15,
|
|
35
|
+
MinWidth = 16,
|
|
36
|
+
MinHeight = 17,
|
|
37
|
+
MaxWidth = 18,
|
|
38
|
+
MaxHeight = 19,
|
|
39
|
+
Padding = 20,
|
|
40
|
+
PaddingTop = 21,
|
|
41
|
+
PaddingRight = 22,
|
|
42
|
+
PaddingBottom = 23,
|
|
43
|
+
PaddingLeft = 24,
|
|
44
|
+
Margin = 25,
|
|
45
|
+
MarginTop = 26,
|
|
46
|
+
MarginRight = 27,
|
|
47
|
+
MarginBottom = 28,
|
|
48
|
+
MarginLeft = 29,
|
|
49
|
+
BackgroundColor = 30,
|
|
50
|
+
Color = 31,
|
|
51
|
+
Opacity = 32,
|
|
52
|
+
BorderWidth = 33,
|
|
53
|
+
BorderColor = 34,
|
|
54
|
+
BorderRadius = 35,
|
|
55
|
+
FontSize = 36,
|
|
56
|
+
FontWeight = 37,
|
|
57
|
+
LineHeight = 38,
|
|
58
|
+
TextAlign = 39,
|
|
59
|
+
WhiteSpace = 40,
|
|
60
|
+
TextOverflow = 41,
|
|
61
|
+
LineClamp = 42,
|
|
62
|
+
Overflow = 43,
|
|
63
|
+
OverflowX = 44,
|
|
64
|
+
OverflowY = 45,
|
|
65
|
+
Cursor = 46,
|
|
66
|
+
AppRegion = 47,
|
|
67
|
+
Disabled = 48,
|
|
68
|
+
AccessibilityLabel = 49,
|
|
69
|
+
Role = 50,
|
|
70
|
+
TabIndex = 51,
|
|
71
|
+
Position = 52,
|
|
72
|
+
Top = 53,
|
|
73
|
+
Right = 54,
|
|
74
|
+
Bottom = 55,
|
|
75
|
+
Left = 56,
|
|
76
|
+
UserSelect = 57,
|
|
77
|
+
ClickListener = 58,
|
|
78
|
+
HoverListener = 59,
|
|
79
|
+
Visibility = 60,
|
|
80
|
+
AspectRatio = 61,
|
|
81
|
+
Value = 62,
|
|
82
|
+
Placeholder = 63,
|
|
83
|
+
Multiline = 64,
|
|
84
|
+
InputListener = 65,
|
|
85
|
+
SubmitListener = 66,
|
|
86
|
+
Streaming = 67,
|
|
87
|
+
MarkdownCodeBackground = 68,
|
|
88
|
+
MarkdownBorderColor = 69,
|
|
89
|
+
MarkdownMutedColor = 70,
|
|
90
|
+
MarkdownLinkColor = 71,
|
|
91
|
+
MarkdownCodeTextColor = 72,
|
|
92
|
+
MarkdownBlockGap = 73,
|
|
93
|
+
MarkdownCodeFontSize = 74,
|
|
94
|
+
ScrollToEndRevision = 75,
|
|
95
|
+
Password = 76,
|
|
96
|
+
EstimatedItemHeight = 77,
|
|
97
|
+
Overscan = 78,
|
|
98
|
+
ListAlignment = 79,
|
|
99
|
+
FollowMode = 80,
|
|
100
|
+
AnchorTarget = 81,
|
|
101
|
+
AnchorPlacement = 82,
|
|
102
|
+
AnchorGap = 83,
|
|
103
|
+
ViewportMargin = 84,
|
|
104
|
+
DismissOnEscape = 85,
|
|
105
|
+
DismissOnPointerOutside = 86,
|
|
106
|
+
DismissListener = 87,
|
|
107
|
+
TerminalProgram = 88,
|
|
108
|
+
TerminalArguments = 89,
|
|
109
|
+
TerminalWorkingDirectory = 90,
|
|
110
|
+
TerminalEnvironment = 91,
|
|
111
|
+
TerminalScrollback = 92,
|
|
112
|
+
TerminalStatusListener = 93,
|
|
113
|
+
HoverBackgroundColor = 94,
|
|
114
|
+
HoverColor = 95,
|
|
115
|
+
ActiveBackgroundColor = 96,
|
|
116
|
+
ActiveColor = 97,
|
|
117
|
+
Transition = 98,
|
|
118
|
+
PointerListener = 99,
|
|
119
|
+
FocusOnPointer = 100,
|
|
120
|
+
FontFamily = 101,
|
|
121
|
+
TerminalPalette = 102,
|
|
122
|
+
TerminalCursorColor = 103,
|
|
123
|
+
HitSlop = 104,
|
|
124
|
+
HitSlopTop = 105,
|
|
125
|
+
HitSlopRight = 106,
|
|
126
|
+
HitSlopBottom = 107,
|
|
127
|
+
HitSlopLeft = 108,
|
|
128
|
+
Overlay = 109,
|
|
129
|
+
FocusTrap = 110,
|
|
130
|
+
RestorePreviousFocus = 111,
|
|
131
|
+
AutoFocus = 112,
|
|
132
|
+
AccessibilityModal = 113,
|
|
133
|
+
TerminalPaddingColor = 114,
|
|
134
|
+
TerminalFontThicken = 115,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export type NativePropertyValue = boolean | number | string | null;
|
|
138
|
+
|
|
139
|
+
const encoder = new TextEncoder();
|
|
140
|
+
|
|
141
|
+
export class MutationBatch {
|
|
142
|
+
#bytes = new Uint8Array(512);
|
|
143
|
+
#view = new DataView(this.#bytes.buffer);
|
|
144
|
+
#length = 10;
|
|
145
|
+
#count = 0;
|
|
146
|
+
|
|
147
|
+
get empty(): boolean {
|
|
148
|
+
return this.#count === 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
get mutationCount(): number {
|
|
152
|
+
return this.#count;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
createElement(id: number, tag: NativeNodeTag): void {
|
|
156
|
+
this.#op(1);
|
|
157
|
+
this.#u32(id);
|
|
158
|
+
this.#u8(tag);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
createText(id: number, value: string): void {
|
|
162
|
+
this.#op(2);
|
|
163
|
+
this.#u32(id);
|
|
164
|
+
this.#string(value);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
createSentinel(id: number): void {
|
|
168
|
+
this.#op(3);
|
|
169
|
+
this.#u32(id);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
setProperty(
|
|
173
|
+
id: number,
|
|
174
|
+
property: PropertyCode,
|
|
175
|
+
value: NativePropertyValue,
|
|
176
|
+
color = false,
|
|
177
|
+
): void {
|
|
178
|
+
this.#op(4);
|
|
179
|
+
this.#u32(id);
|
|
180
|
+
this.#u16(property);
|
|
181
|
+
if (value === null) {
|
|
182
|
+
this.#u8(0);
|
|
183
|
+
} else if (typeof value === "boolean") {
|
|
184
|
+
this.#u8(1);
|
|
185
|
+
this.#u8(value ? 1 : 0);
|
|
186
|
+
} else if (typeof value === "number") {
|
|
187
|
+
if (!Number.isFinite(value)) {
|
|
188
|
+
throw new TypeError(`QuickGUI property ${property} must be finite`);
|
|
189
|
+
}
|
|
190
|
+
if (color) {
|
|
191
|
+
this.#u8(3);
|
|
192
|
+
this.#u32(value);
|
|
193
|
+
} else {
|
|
194
|
+
this.#u8(2);
|
|
195
|
+
this.#f32(value);
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
this.#u8(4);
|
|
199
|
+
this.#string(value);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
replaceText(id: number, value: string): void {
|
|
204
|
+
this.#op(5);
|
|
205
|
+
this.#u32(id);
|
|
206
|
+
this.#string(value);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
insert(parent: number, child: number, before?: number): void {
|
|
210
|
+
this.#op(6);
|
|
211
|
+
this.#u32(parent);
|
|
212
|
+
this.#u32(child);
|
|
213
|
+
this.#u32(before ?? NO_ANCHOR);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
remove(parent: number, child: number): void {
|
|
217
|
+
this.#op(7);
|
|
218
|
+
this.#u32(parent);
|
|
219
|
+
this.#u32(child);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
cleanup(parent: number, children: readonly number[]): void {
|
|
223
|
+
this.#op(8);
|
|
224
|
+
this.#u32(parent);
|
|
225
|
+
this.#u32(children.length);
|
|
226
|
+
for (const child of children) this.#u32(child);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
finish(): Buffer {
|
|
230
|
+
if (this.#count === 0) return Buffer.alloc(0);
|
|
231
|
+
this.#bytes.set([0x51, 0x47, 0x4d, 0x42], 0);
|
|
232
|
+
this.#view.setUint16(4, PROTOCOL_VERSION, true);
|
|
233
|
+
this.#view.setUint32(6, this.#count, true);
|
|
234
|
+
return Buffer.from(this.#bytes.buffer, 0, this.#length);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#op(opcode: number): void {
|
|
238
|
+
this.#count++;
|
|
239
|
+
this.#u8(opcode);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#ensure(additional: number): void {
|
|
243
|
+
const required = this.#length + additional;
|
|
244
|
+
if (required <= this.#bytes.length) return;
|
|
245
|
+
let capacity = this.#bytes.length;
|
|
246
|
+
while (capacity < required) capacity *= 2;
|
|
247
|
+
const next = new Uint8Array(capacity);
|
|
248
|
+
next.set(this.#bytes.subarray(0, this.#length));
|
|
249
|
+
this.#bytes = next;
|
|
250
|
+
this.#view = new DataView(next.buffer);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#u8(value: number): void {
|
|
254
|
+
this.#ensure(1);
|
|
255
|
+
this.#view.setUint8(this.#length, value);
|
|
256
|
+
this.#length += 1;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#u16(value: number): void {
|
|
260
|
+
this.#ensure(2);
|
|
261
|
+
this.#view.setUint16(this.#length, value, true);
|
|
262
|
+
this.#length += 2;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
#u32(value: number): void {
|
|
266
|
+
if (!Number.isInteger(value) || value < 0 || value > 0xffff_ffff) {
|
|
267
|
+
throw new RangeError(`QuickGUI protocol value ${value} is not a u32`);
|
|
268
|
+
}
|
|
269
|
+
this.#ensure(4);
|
|
270
|
+
this.#view.setUint32(this.#length, value, true);
|
|
271
|
+
this.#length += 4;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#f32(value: number): void {
|
|
275
|
+
this.#ensure(4);
|
|
276
|
+
this.#view.setFloat32(this.#length, value, true);
|
|
277
|
+
this.#length += 4;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
#string(value: string): void {
|
|
281
|
+
const bytes = encoder.encode(value);
|
|
282
|
+
this.#u32(bytes.length);
|
|
283
|
+
this.#ensure(bytes.length);
|
|
284
|
+
this.#bytes.set(bytes, this.#length);
|
|
285
|
+
this.#length += bytes.length;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
Binary file
|
|
Binary file
|
package/scripts/build.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
const packageRoot = resolve(import.meta.dir, "..");
|
|
6
|
+
const debug = process.argv.includes("--debug");
|
|
7
|
+
const forwardedArguments = process.argv.slice(2).filter((argument) => argument !== "--debug");
|
|
8
|
+
const environment = { ...process.env };
|
|
9
|
+
|
|
10
|
+
const zig = findGhosttyZig();
|
|
11
|
+
if (process.platform === "darwin") {
|
|
12
|
+
environment.QUICKGUI_ZIG = zig;
|
|
13
|
+
} else {
|
|
14
|
+
environment.PATH = `${dirname(zig)}:${environment.PATH ?? ""}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const arguments_ = [
|
|
18
|
+
"napi",
|
|
19
|
+
"build",
|
|
20
|
+
"--platform",
|
|
21
|
+
...(debug ? [] : ["--release"]),
|
|
22
|
+
"--esm",
|
|
23
|
+
"--js",
|
|
24
|
+
"binding.js",
|
|
25
|
+
"--dts",
|
|
26
|
+
"binding.d.ts",
|
|
27
|
+
...forwardedArguments,
|
|
28
|
+
];
|
|
29
|
+
const command =
|
|
30
|
+
process.platform === "darwin"
|
|
31
|
+
? [resolve(packageRoot, "..", "..", "scripts", "with-macos-ghostty-zig.sh"), ...arguments_]
|
|
32
|
+
: arguments_;
|
|
33
|
+
const build = Bun.spawn(command, {
|
|
34
|
+
cwd: packageRoot,
|
|
35
|
+
env: environment,
|
|
36
|
+
stdin: "inherit",
|
|
37
|
+
stdout: "inherit",
|
|
38
|
+
stderr: "inherit",
|
|
39
|
+
});
|
|
40
|
+
process.exit(await build.exited);
|
|
41
|
+
|
|
42
|
+
function findGhosttyZig(): string {
|
|
43
|
+
const candidates: string[] = [];
|
|
44
|
+
if (environment.QUICKGUI_ZIG) candidates.push(environment.QUICKGUI_ZIG);
|
|
45
|
+
|
|
46
|
+
const current = commandOutput(["sh", "-c", "command -v zig"]);
|
|
47
|
+
if (current) candidates.push(current);
|
|
48
|
+
|
|
49
|
+
const miseRoot = commandOutput(["mise", "where", "zig@0.15.2"], tmpdir());
|
|
50
|
+
if (miseRoot) {
|
|
51
|
+
candidates.push(join(miseRoot, "zig"));
|
|
52
|
+
candidates.push(join(miseRoot, "bin", "zig"));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const candidate of candidates) {
|
|
56
|
+
if (!existsSync(candidate)) continue;
|
|
57
|
+
const path = realpathSync(candidate);
|
|
58
|
+
if (commandOutput([path, "version"]) === "0.15.2") return path;
|
|
59
|
+
}
|
|
60
|
+
throw new Error(
|
|
61
|
+
"@quickgui/native requires Zig 0.15.2 to build libghostty-vt. " +
|
|
62
|
+
"Install it with `mise install zig@0.15.2` or set QUICKGUI_ZIG.",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function commandOutput(
|
|
67
|
+
command: string[],
|
|
68
|
+
cwd = packageRoot,
|
|
69
|
+
env: Record<string, string | undefined> = process.env,
|
|
70
|
+
): string | undefined {
|
|
71
|
+
const result = Bun.spawnSync(command, { cwd, env, stderr: "pipe", stdout: "pipe" });
|
|
72
|
+
if (result.exitCode !== 0) return undefined;
|
|
73
|
+
return result.stdout.toString().trim() || undefined;
|
|
74
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Protocol } from "./integrations.ts";
|
|
2
|
+
|
|
3
|
+
export interface SecondInstanceEvent {
|
|
4
|
+
argv: readonly string[];
|
|
5
|
+
cwd: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function urlsFromArguments(arguments_: readonly string[]): string[] {
|
|
9
|
+
const urls: string[] = [];
|
|
10
|
+
for (const argument of arguments_) {
|
|
11
|
+
try {
|
|
12
|
+
const url = new URL(argument);
|
|
13
|
+
if (url.protocol && url.protocol !== "http:" && url.protocol !== "https:") {
|
|
14
|
+
urls.push(url.href);
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
// Ordinary CLI flags and filesystem paths are not deep links.
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return urls;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const DeepLink = Object.freeze({
|
|
24
|
+
getLaunchUrls(): readonly string[] {
|
|
25
|
+
return urlsFromArguments(process.argv.slice(1));
|
|
26
|
+
},
|
|
27
|
+
supportsDynamicRegistration: Protocol.supportsDynamicRegistration,
|
|
28
|
+
register: Protocol.register,
|
|
29
|
+
unregister: Protocol.unregister,
|
|
30
|
+
isRegistered: Protocol.isRegistered,
|
|
31
|
+
});
|