@oh-my-pi/pi-natives 17.4.2 → 18.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/native/desktop-adapter.d.ts +28 -0
- package/native/desktop-adapter.js +341 -0
- package/native/desktop.js +4 -1
- package/native/index.d.ts +105 -1
- package/native/index.js +10 -2
- package/native/loader-state.js +22 -0
- package/package.json +8 -6
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface AdaptedDesktopCapabilities {
|
|
2
|
+
readonly [key: string]: unknown;
|
|
3
|
+
readonly ax: boolean;
|
|
4
|
+
readonly backgroundWindowInput: boolean;
|
|
5
|
+
readonly deliveryModes: readonly string[];
|
|
6
|
+
readonly axPermission: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface AdaptedDesktopSession {
|
|
10
|
+
readonly capabilities: AdaptedDesktopCapabilities;
|
|
11
|
+
listWindows(): Promise<Array<Record<string, unknown>>>;
|
|
12
|
+
capture(target: string, caps?: unknown): Promise<Record<string, unknown>>;
|
|
13
|
+
click(
|
|
14
|
+
target: string,
|
|
15
|
+
x: number,
|
|
16
|
+
y: number,
|
|
17
|
+
options?: { button?: string; count?: number; modifiers?: string[]; deliveryMode?: string },
|
|
18
|
+
): Promise<void>;
|
|
19
|
+
typeText(target: string, text: string, options?: { deliveryMode?: string }): Promise<void>;
|
|
20
|
+
keyChord(target: string, keys: string[], options?: { deliveryMode?: string }): Promise<void>;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface AdaptedDesktopSessionConstructor {
|
|
25
|
+
new (options: Record<string, unknown>): AdaptedDesktopSession;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function adaptDesktopSession(NativeDesktopSession: unknown): AdaptedDesktopSessionConstructor;
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
const LEGACY_ERROR_CODES = {
|
|
2
|
+
DESKTOP_INVALID_OPTIONS: "InvalidTarget",
|
|
3
|
+
DESKTOP_INVALID_ACTION: "InvalidTarget",
|
|
4
|
+
DESKTOP_BACKEND_UNAVAILABLE: null,
|
|
5
|
+
DESKTOP_PERMISSION_DENIED: "PermissionDenied",
|
|
6
|
+
DESKTOP_CAPTURE_FAILED: "CaptureFailed",
|
|
7
|
+
DESKTOP_INPUT_FAILED: "InputFailed",
|
|
8
|
+
DESKTOP_DEADLINE_EXCEEDED: "Timeout",
|
|
9
|
+
DESKTOP_LAYOUT_CHANGED: "InvalidCoordinateFrame",
|
|
10
|
+
DESKTOP_COORDINATE_OUT_OF_BOUNDS: "InvalidCoordinateFrame",
|
|
11
|
+
DESKTOP_SESSION_CLOSED: "Closed",
|
|
12
|
+
DESKTOP_WORKER_FAILED: "Internal",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const ADAPTED_SESSION_CLASSES = new WeakMap();
|
|
16
|
+
|
|
17
|
+
function desktopError(code, message) {
|
|
18
|
+
return new Error(`${code}: ${message}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeError(error, fallbackCode) {
|
|
22
|
+
if (!(error instanceof Error)) return desktopError(fallbackCode, String(error));
|
|
23
|
+
if (/^[A-Z][A-Za-z]+: /.test(error.message)) return error;
|
|
24
|
+
|
|
25
|
+
const match = /^(DESKTOP_[A-Z_]+):\s*(.*)$/.exec(error.message);
|
|
26
|
+
if (match === null) return desktopError(fallbackCode, error.message);
|
|
27
|
+
const code = LEGACY_ERROR_CODES[match[1]] ?? fallbackCode;
|
|
28
|
+
return desktopError(code, match[2]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeCapabilities(capabilities) {
|
|
32
|
+
return {
|
|
33
|
+
...capabilities,
|
|
34
|
+
ax: false,
|
|
35
|
+
backgroundWindowInput: false,
|
|
36
|
+
deliveryModes: ["foreground"],
|
|
37
|
+
axPermission: "unavailable",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function legacyPoint(point) {
|
|
42
|
+
return { x: Math.round(point.x), y: Math.round(point.y) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function captureCapsKey(caps) {
|
|
46
|
+
return `${caps?.maxWidth ?? ""}:${caps?.maxHeight ?? ""}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function legacyButton(button) {
|
|
50
|
+
return button === "middle" ? "wheel" : button;
|
|
51
|
+
}
|
|
52
|
+
function sourceDimensions(capture) {
|
|
53
|
+
if (capture.sourceWidth !== undefined && capture.sourceHeight !== undefined) {
|
|
54
|
+
return { sourceWidth: capture.sourceWidth, sourceHeight: capture.sourceHeight };
|
|
55
|
+
}
|
|
56
|
+
const displays = Array.isArray(capture.displays) ? capture.displays : [];
|
|
57
|
+
if (displays.length === 0) {
|
|
58
|
+
return { sourceWidth: capture.width, sourceHeight: capture.height };
|
|
59
|
+
}
|
|
60
|
+
const minX = Math.min(...displays.map(display => display.x));
|
|
61
|
+
const minY = Math.min(...displays.map(display => display.y));
|
|
62
|
+
const maxX = Math.max(...displays.map(display => display.x + display.width));
|
|
63
|
+
const maxY = Math.max(...displays.map(display => display.y + display.height));
|
|
64
|
+
const nativeScale = Math.max(1, ...displays.map(display => display.scale ?? 1));
|
|
65
|
+
return {
|
|
66
|
+
sourceWidth: Math.max(1, Math.round((maxX - minX) * nativeScale)),
|
|
67
|
+
sourceHeight: Math.max(1, Math.round((maxY - minY) * nativeScale)),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function frameSignature(capture) {
|
|
72
|
+
return JSON.stringify({
|
|
73
|
+
target: capture.target,
|
|
74
|
+
width: capture.width,
|
|
75
|
+
height: capture.height,
|
|
76
|
+
displays: capture.displays?.map(display => ({
|
|
77
|
+
id: display.id,
|
|
78
|
+
x: display.x,
|
|
79
|
+
y: display.y,
|
|
80
|
+
width: display.width,
|
|
81
|
+
height: display.height,
|
|
82
|
+
scale: display.scale,
|
|
83
|
+
pixelX: display.pixelX,
|
|
84
|
+
pixelY: display.pixelY,
|
|
85
|
+
pixelWidth: display.pixelWidth,
|
|
86
|
+
pixelHeight: display.pixelHeight,
|
|
87
|
+
})),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Adapt the pre-parity desktop addon ABI used by pull-request CI artifacts to
|
|
93
|
+
* the current session contract. Released addons exposed capture/execute/close;
|
|
94
|
+
* current addons already expose the complete API and pass through unchanged.
|
|
95
|
+
*/
|
|
96
|
+
export function adaptDesktopSession(NativeDesktopSession) {
|
|
97
|
+
if (typeof NativeDesktopSession?.prototype?.click === "function") return NativeDesktopSession;
|
|
98
|
+
const cached = ADAPTED_SESSION_CLASSES.get(NativeDesktopSession);
|
|
99
|
+
if (cached) return cached;
|
|
100
|
+
|
|
101
|
+
class DesktopSession {
|
|
102
|
+
#native;
|
|
103
|
+
#nativeDesktopSession;
|
|
104
|
+
#options;
|
|
105
|
+
#sessions;
|
|
106
|
+
#closed = false;
|
|
107
|
+
#capturedTargets = new Map();
|
|
108
|
+
|
|
109
|
+
constructor(options) {
|
|
110
|
+
try {
|
|
111
|
+
this.#nativeDesktopSession = NativeDesktopSession;
|
|
112
|
+
this.#options = options;
|
|
113
|
+
this.#native = new NativeDesktopSession(options);
|
|
114
|
+
this.#sessions = new Map([[captureCapsKey(), this.#native]]);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
throw normalizeError(error, "Internal");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get capabilities() {
|
|
121
|
+
return normalizeCapabilities(this.#native.capabilities);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#ensureOpen() {
|
|
125
|
+
if (this.#closed) throw desktopError("Closed", "desktop session is closed");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#nativeForCapturedTarget(target) {
|
|
129
|
+
const capture = this.#capturedTargets.get(target);
|
|
130
|
+
if (capture) return capture.native;
|
|
131
|
+
throw desktopError(
|
|
132
|
+
"InvalidCoordinateFrame",
|
|
133
|
+
`no capture of '${target}' yet — take a screenshot of this target first`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#sessionForCapture(caps) {
|
|
138
|
+
const key = captureCapsKey(caps);
|
|
139
|
+
const existing = this.#sessions.get(key);
|
|
140
|
+
if (existing) return existing;
|
|
141
|
+
try {
|
|
142
|
+
const native = new this.#nativeDesktopSession({
|
|
143
|
+
...this.#options,
|
|
144
|
+
maxWidth: caps?.maxWidth,
|
|
145
|
+
maxHeight: caps?.maxHeight,
|
|
146
|
+
});
|
|
147
|
+
this.#sessions.set(key, native);
|
|
148
|
+
return native;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw normalizeError(error, "CaptureFailed");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#ensureForeground(target, options) {
|
|
155
|
+
if (options?.deliveryMode !== "foreground" && (target !== "desktop" || options?.deliveryMode !== undefined)) {
|
|
156
|
+
throw desktopError(
|
|
157
|
+
"BackgroundUnavailable",
|
|
158
|
+
"the installed native addon supports foreground input only",
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async #execute(actions, target, native = this.#native, fallbackCode = "InputFailed") {
|
|
164
|
+
try {
|
|
165
|
+
const capture = await native.execute(Array.isArray(actions) ? actions : [actions], target);
|
|
166
|
+
const previous = this.#capturedTargets.get(target);
|
|
167
|
+
if (capture && previous?.native === native && frameSignature(capture) !== previous.signature) {
|
|
168
|
+
this.#capturedTargets.delete(target);
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
throw normalizeError(error, fallbackCode);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async listDisplays() {
|
|
176
|
+
this.#ensureOpen();
|
|
177
|
+
throw desktopError("CaptureFailed", "the installed native addon does not expose display enumeration");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async listWindows() {
|
|
181
|
+
this.#ensureOpen();
|
|
182
|
+
if (typeof this.#native.listWindows !== "function") {
|
|
183
|
+
throw desktopError("CaptureFailed", "the installed native addon does not expose window enumeration");
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
return await this.#native.listWindows();
|
|
187
|
+
} catch (error) {
|
|
188
|
+
throw normalizeError(error, "CaptureFailed");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async capture(target, caps) {
|
|
193
|
+
this.#ensureOpen();
|
|
194
|
+
if (target !== "desktop" && !/^\d+$/.test(target)) {
|
|
195
|
+
throw desktopError("InvalidTarget", `invalid window target '${target}'`);
|
|
196
|
+
}
|
|
197
|
+
if (target !== "desktop" && typeof this.#native.listWindows !== "function") {
|
|
198
|
+
throw desktopError("CaptureFailed", "the installed native addon does not support window capture");
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const native = this.#sessionForCapture(caps);
|
|
202
|
+
const capture = await native.capture(target);
|
|
203
|
+
const adapted = { ...capture, ...sourceDimensions(capture), target };
|
|
204
|
+
this.#capturedTargets.set(target, { native, signature: frameSignature(adapted) });
|
|
205
|
+
return adapted;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
throw normalizeError(error, "CaptureFailed");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async click(target, x, y, options) {
|
|
212
|
+
this.#ensureOpen();
|
|
213
|
+
this.#ensureForeground(target, options);
|
|
214
|
+
const native = this.#nativeForCapturedTarget(target);
|
|
215
|
+
const count = Math.max(1, options?.count ?? 1);
|
|
216
|
+
const button = legacyButton(options?.button ?? "left");
|
|
217
|
+
const point = { x: Math.round(x), y: Math.round(y), keys: options?.modifiers ?? [] };
|
|
218
|
+
const actions =
|
|
219
|
+
count === 2 && button === "left"
|
|
220
|
+
? [{ type: "double_click", ...point }]
|
|
221
|
+
: Array.from({ length: count }, () => ({ type: "click", ...point, button }));
|
|
222
|
+
await this.#execute(actions, target, native);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async moveMouse(target, x, y, options) {
|
|
226
|
+
this.#ensureOpen();
|
|
227
|
+
this.#ensureForeground(target, options);
|
|
228
|
+
await this.#execute(
|
|
229
|
+
{ type: "move", x: Math.round(x), y: Math.round(y), keys: options?.modifiers ?? [] },
|
|
230
|
+
target,
|
|
231
|
+
this.#nativeForCapturedTarget(target),
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async drag(target, path, options) {
|
|
236
|
+
this.#ensureOpen();
|
|
237
|
+
this.#ensureForeground(target, options);
|
|
238
|
+
await this.#execute(
|
|
239
|
+
{ type: "drag", path: path.map(legacyPoint), keys: options?.modifiers ?? [] },
|
|
240
|
+
target,
|
|
241
|
+
this.#nativeForCapturedTarget(target),
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async scroll(target, x, y, dx, dy, options) {
|
|
246
|
+
this.#ensureOpen();
|
|
247
|
+
this.#ensureForeground(target, options);
|
|
248
|
+
await this.#execute(
|
|
249
|
+
{
|
|
250
|
+
type: "scroll",
|
|
251
|
+
x: Math.round(x),
|
|
252
|
+
y: Math.round(y),
|
|
253
|
+
scroll_x: Math.round(dx),
|
|
254
|
+
scroll_y: Math.round(dy),
|
|
255
|
+
keys: options?.modifiers ?? [],
|
|
256
|
+
},
|
|
257
|
+
target,
|
|
258
|
+
this.#nativeForCapturedTarget(target),
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async typeText(target, text, options) {
|
|
263
|
+
this.#ensureOpen();
|
|
264
|
+
this.#ensureForeground(target, options);
|
|
265
|
+
await this.#execute({ type: "type", text }, target, this.#capturedTargets.get(target)?.native ?? this.#native);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async keyChord(target, keys, options) {
|
|
269
|
+
this.#ensureOpen();
|
|
270
|
+
this.#ensureForeground(target, options);
|
|
271
|
+
await this.#execute({ type: "keypress", keys }, target, this.#capturedTargets.get(target)?.native ?? this.#native);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async raiseWindow() {
|
|
275
|
+
this.#ensureOpen();
|
|
276
|
+
throw desktopError("BackgroundUnavailable", "the installed native addon does not support window control");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async axSnapshot() {
|
|
280
|
+
this.#ensureOpen();
|
|
281
|
+
throw desktopError("AxUnsupported", "accessibility is unavailable in the installed native addon");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async axQuery() {
|
|
285
|
+
return this.axSnapshot();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async axElementAt() {
|
|
289
|
+
return this.axSnapshot();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async axFocused() {
|
|
293
|
+
return this.axSnapshot();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async axNode() {
|
|
297
|
+
return this.axSnapshot();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async axAttributes() {
|
|
301
|
+
return this.axSnapshot();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async axChildren() {
|
|
305
|
+
return this.axSnapshot();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async axParent() {
|
|
309
|
+
return this.axSnapshot();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async axPerform() {
|
|
313
|
+
return this.axSnapshot();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async axSetValue() {
|
|
317
|
+
return this.axSnapshot();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async axFocus() {
|
|
321
|
+
return this.axSnapshot();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async axClick() {
|
|
325
|
+
return this.axSnapshot();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async close() {
|
|
329
|
+
if (this.#closed) return;
|
|
330
|
+
this.#closed = true;
|
|
331
|
+
try {
|
|
332
|
+
await Promise.all([...this.#sessions.values()].map(native => native.close()));
|
|
333
|
+
} catch (error) {
|
|
334
|
+
throw normalizeError(error, "Internal");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
ADAPTED_SESSION_CLASSES.set(NativeDesktopSession, DesktopSession);
|
|
340
|
+
return DesktopSession;
|
|
341
|
+
}
|
package/native/desktop.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { adaptDesktopSession } from "./desktop-adapter.js";
|
|
1
2
|
import { loadNative } from "./loader-state.js";
|
|
2
3
|
|
|
4
|
+
let DesktopSession;
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* Construct a desktop session without loading the native addon until the
|
|
5
8
|
* computer worker receives its initialization message.
|
|
6
9
|
*/
|
|
7
10
|
export function createDesktopSession(options) {
|
|
8
|
-
|
|
11
|
+
DesktopSession ??= adaptDesktopSession(loadNative().DesktopSession);
|
|
9
12
|
return new DesktopSession(options);
|
|
10
13
|
}
|
package/native/index.d.ts
CHANGED
|
@@ -77,6 +77,24 @@ export declare class FileLock {
|
|
|
77
77
|
release(): void
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Stateful incremental syntax highlighter for streamed code.
|
|
82
|
+
*
|
|
83
|
+
* Carries syntect parser state across [`HighlightStream::push`] calls so
|
|
84
|
+
* chunked highlighting of a growing buffer is byte-identical to highlighting
|
|
85
|
+
* the concatenated text in one call. Feed newline-terminated complete lines;
|
|
86
|
+
* only the final push may omit the trailing newline. An unresolved language
|
|
87
|
+
* echoes input unchanged.
|
|
88
|
+
*/
|
|
89
|
+
export declare class HighlightStream {
|
|
90
|
+
/** Create a stream for `lang`; an unknown language yields a passthrough. */
|
|
91
|
+
constructor(lang: string | undefined | null, colors: HighlightColors)
|
|
92
|
+
/** Whether the language resolved to a grammar; `false` means passthrough. */
|
|
93
|
+
get supported(): boolean
|
|
94
|
+
/** Highlight the next chunk and advance parser state. */
|
|
95
|
+
push(chunk: string): string
|
|
96
|
+
}
|
|
97
|
+
|
|
80
98
|
/** WebRTC peer that accepts 16 kHz mono PCM and renders remote Opus audio. */
|
|
81
99
|
export declare class LiveWebRtcPeer {
|
|
82
100
|
/**
|
|
@@ -234,6 +252,48 @@ export declare class Shell {
|
|
|
234
252
|
liveBackgroundJobCount(): Promise<number>
|
|
235
253
|
}
|
|
236
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Dedicated writer thread for one terminal fd.
|
|
257
|
+
*
|
|
258
|
+
* Constructed by the TUI's `ProcessTerminal` around stdout. The fd is
|
|
259
|
+
* `dup(2)`'d at construction and closed on drop, so later manipulation of the
|
|
260
|
+
* original descriptor does not affect the pump.
|
|
261
|
+
*/
|
|
262
|
+
export declare class TtyWriter {
|
|
263
|
+
/**
|
|
264
|
+
* Start a pump thread for `fd` (typically 1). Fails on non-Unix hosts and
|
|
265
|
+
* when the descriptor cannot be duplicated.
|
|
266
|
+
*/
|
|
267
|
+
constructor(fd: number)
|
|
268
|
+
/**
|
|
269
|
+
* Enqueue terminal output; never blocks. Returns the total bytes now
|
|
270
|
+
* pending (including this chunk).
|
|
271
|
+
*
|
|
272
|
+
* Reads the JS string as UTF-16 through the thread's scratch arena and
|
|
273
|
+
* transcodes it with `xutf` straight into the shared back buffer, so a
|
|
274
|
+
* warm writer costs no per-call heap allocation.
|
|
275
|
+
*/
|
|
276
|
+
write(data: string): number
|
|
277
|
+
/** Bytes accepted but not yet written to the terminal. */
|
|
278
|
+
pending(): number
|
|
279
|
+
/** True once a write failed (dead PTY); queued output has been dropped. */
|
|
280
|
+
get dead(): boolean
|
|
281
|
+
/**
|
|
282
|
+
* Block the calling thread until the queue drains, the writer dies, or
|
|
283
|
+
* `timeout_ms` elapses. Returns true when fully drained. Exit paths only.
|
|
284
|
+
*/
|
|
285
|
+
flushSync(timeoutMs: number): boolean
|
|
286
|
+
/**
|
|
287
|
+
* Flush (bounded by `flush_timeout_ms`), stop the pump thread, and join it.
|
|
288
|
+
*
|
|
289
|
+
* A pump stuck in a blocked `write(2)` (stalled-but-alive PTY consumer)
|
|
290
|
+
* cannot be joined without freezing the caller: when the bounded flush
|
|
291
|
+
* times out the thread is detached instead and its dup'd fd is leaked —
|
|
292
|
+
* closing it under a blocked write would race kernel fd reuse.
|
|
293
|
+
*/
|
|
294
|
+
stop(flushTimeoutMs: number): void
|
|
295
|
+
}
|
|
296
|
+
|
|
237
297
|
/**
|
|
238
298
|
* Install the bounded Tokio runtime napi-rs adopts for async exports and the
|
|
239
299
|
* bounded Rayon global pool used by native parallel iterators.
|
|
@@ -279,7 +339,7 @@ export declare function __ompInstallTokioRuntime(): void
|
|
|
279
339
|
* `packages/natives/native/index.js` (which derives the name from
|
|
280
340
|
* `package.json#version`).
|
|
281
341
|
*/
|
|
282
|
-
export declare function
|
|
342
|
+
export declare function __piNativesV18_0_1(): void
|
|
283
343
|
|
|
284
344
|
/**
|
|
285
345
|
* Apply ast-grep rewrite rules to matching files; honors `dryRun` and returns
|
|
@@ -1399,6 +1459,31 @@ export declare enum MacOSAppearance {
|
|
|
1399
1459
|
Light = 'light'
|
|
1400
1460
|
}
|
|
1401
1461
|
|
|
1462
|
+
/**
|
|
1463
|
+
* Return the autocorrection macOS chooses for one completed-word range.
|
|
1464
|
+
*
|
|
1465
|
+
* Returns `null` when no confident correction exists or the service is
|
|
1466
|
+
* unavailable.
|
|
1467
|
+
* On macOS, the lookup runs on the dedicated spelling thread.
|
|
1468
|
+
*/
|
|
1469
|
+
export declare function macOSAutocorrectWord(text: string, start: number, length: number): Promise<string | null>
|
|
1470
|
+
|
|
1471
|
+
/**
|
|
1472
|
+
* Find every misspelled word using the active macOS dictionaries.
|
|
1473
|
+
*
|
|
1474
|
+
* Returns an empty list when Apple's spelling service is unavailable.
|
|
1475
|
+
* On macOS, the check runs on the dedicated spelling thread.
|
|
1476
|
+
*/
|
|
1477
|
+
export declare function macOSCheckSpelling(text: string): Promise<Array<SpellingRange>>
|
|
1478
|
+
|
|
1479
|
+
/**
|
|
1480
|
+
* Return macOS dictionary completions for one partial-word range.
|
|
1481
|
+
*
|
|
1482
|
+
* Returns an empty list when Apple's spelling service is unavailable.
|
|
1483
|
+
* On macOS, the lookup runs on the dedicated spelling thread.
|
|
1484
|
+
*/
|
|
1485
|
+
export declare function macOSCompleteWord(text: string, start: number, length: number): Promise<Array<string>>
|
|
1486
|
+
|
|
1402
1487
|
/**
|
|
1403
1488
|
* Options for starting a macOS power assertion.
|
|
1404
1489
|
*
|
|
@@ -1423,6 +1508,17 @@ export interface MacOSPowerAssertionOptions {
|
|
|
1423
1508
|
display?: boolean
|
|
1424
1509
|
}
|
|
1425
1510
|
|
|
1511
|
+
/** Whether the host can use Apple's native spelling service. */
|
|
1512
|
+
export declare function macOSSpellCheckerAvailable(): boolean
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* Return macOS replacement guesses for one misspelled-word range.
|
|
1516
|
+
*
|
|
1517
|
+
* Returns an empty list when Apple's spelling service is unavailable.
|
|
1518
|
+
* On macOS, the lookup runs on the dedicated spelling thread.
|
|
1519
|
+
*/
|
|
1520
|
+
export declare function macOSSpellingGuesses(text: string, start: number, length: number): Promise<Array<string>>
|
|
1521
|
+
|
|
1426
1522
|
/** A single match in the content. */
|
|
1427
1523
|
export interface Match {
|
|
1428
1524
|
/** 1-indexed line number. */
|
|
@@ -1952,6 +2048,14 @@ export interface SnapcompactRenderOptions {
|
|
|
1952
2048
|
*/
|
|
1953
2049
|
export declare function snapcompactSupportedChars(font: string, chars: string): string
|
|
1954
2050
|
|
|
2051
|
+
/** A misspelled span measured in JavaScript/UTF-16 code units. */
|
|
2052
|
+
export interface SpellingRange {
|
|
2053
|
+
/** Inclusive UTF-16 start offset. */
|
|
2054
|
+
start: number
|
|
2055
|
+
/** UTF-16 length of the misspelled span. */
|
|
2056
|
+
length: number
|
|
2057
|
+
}
|
|
2058
|
+
|
|
1955
2059
|
/**
|
|
1956
2060
|
* Unified-diff hunks with jsdiff
|
|
1957
2061
|
* `structuredPatch(_, _, oldText, newText, _, _, { context }).hunks`
|
package/native/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadNative } from "./loader-state.js";
|
|
2
|
+
import { adaptDesktopSession } from "./desktop-adapter.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Native addon entrypoint.
|
|
@@ -18,18 +19,20 @@ const nativeBindings = loadNative();
|
|
|
18
19
|
// classes
|
|
19
20
|
export const AudioCapture = nativeBindings.AudioCapture;
|
|
20
21
|
export const AudioPlayback = nativeBindings.AudioPlayback;
|
|
21
|
-
export const DesktopSession = nativeBindings.DesktopSession;
|
|
22
|
+
export const DesktopSession = adaptDesktopSession(nativeBindings.DesktopSession);
|
|
22
23
|
export const FileLock = nativeBindings.FileLock;
|
|
24
|
+
export const HighlightStream = nativeBindings.HighlightStream;
|
|
23
25
|
export const LiveWebRtcPeer = nativeBindings.LiveWebRtcPeer;
|
|
24
26
|
export const MacAppearanceObserver = nativeBindings.MacAppearanceObserver;
|
|
25
27
|
export const MacOSPowerAssertion = nativeBindings.MacOSPowerAssertion;
|
|
26
28
|
export const Process = nativeBindings.Process;
|
|
27
29
|
export const PtySession = nativeBindings.PtySession;
|
|
28
30
|
export const Shell = nativeBindings.Shell;
|
|
31
|
+
export const TtyWriter = nativeBindings.TtyWriter;
|
|
29
32
|
|
|
30
33
|
// functions
|
|
31
34
|
export const __ompInstallTokioRuntime = nativeBindings.__ompInstallTokioRuntime;
|
|
32
|
-
export const
|
|
35
|
+
export const __piNativesV18_0_1 = nativeBindings.__piNativesV18_0_1;
|
|
33
36
|
export const astEdit = nativeBindings.astEdit;
|
|
34
37
|
export const astGrep = nativeBindings.astGrep;
|
|
35
38
|
export const astMatch = nativeBindings.astMatch;
|
|
@@ -63,6 +66,11 @@ export const isoResolve = nativeBindings.isoResolve;
|
|
|
63
66
|
export const isoStart = nativeBindings.isoStart;
|
|
64
67
|
export const isoStop = nativeBindings.isoStop;
|
|
65
68
|
export const listWorkspace = nativeBindings.listWorkspace;
|
|
69
|
+
export const macOSAutocorrectWord = nativeBindings.macOSAutocorrectWord;
|
|
70
|
+
export const macOSCheckSpelling = nativeBindings.macOSCheckSpelling;
|
|
71
|
+
export const macOSCompleteWord = nativeBindings.macOSCompleteWord;
|
|
72
|
+
export const macOSSpellCheckerAvailable = nativeBindings.macOSSpellCheckerAvailable;
|
|
73
|
+
export const macOSSpellingGuesses = nativeBindings.macOSSpellingGuesses;
|
|
66
74
|
export const matchesKey = nativeBindings.matchesKey;
|
|
67
75
|
export const matchesKittySequence = nativeBindings.matchesKittySequence;
|
|
68
76
|
export const matchesLegacySequence = nativeBindings.matchesLegacySequence;
|
package/native/loader-state.js
CHANGED
|
@@ -648,6 +648,27 @@ function maybeStageNodeModulesAddon(ctx, errors) {
|
|
|
648
648
|
return stagedPath;
|
|
649
649
|
}
|
|
650
650
|
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Before version sentinels were exported, published native addons still shared
|
|
654
|
+
* this stable core ABI. Let those on-disk addons bridge a package-version bump
|
|
655
|
+
* when they expose the signature; keep every versioned addon and a current
|
|
656
|
+
* on-disk file paired with resident old exports on the strict path below.
|
|
657
|
+
*/
|
|
658
|
+
function isCompatiblePreSentinelNativeAddon(bindings, diskHasExpectedSentinel) {
|
|
659
|
+
if (diskHasExpectedSentinel) return false;
|
|
660
|
+
if (Object.keys(bindings).some(key => /^__piNativesV[A-Za-z0-9_]+$/.test(key))) return false;
|
|
661
|
+
return (
|
|
662
|
+
typeof bindings.countTokens === "function" &&
|
|
663
|
+
typeof bindings.executeShell === "function" &&
|
|
664
|
+
typeof bindings.visibleWidth === "function" &&
|
|
665
|
+
typeof bindings.DesktopSession === "function" &&
|
|
666
|
+
typeof bindings.DesktopSession.prototype?.capture === "function" &&
|
|
667
|
+
typeof bindings.DesktopSession.prototype?.execute === "function" &&
|
|
668
|
+
typeof bindings.DesktopSession.prototype?.close === "function"
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
651
672
|
export function validateLoadedBindings(ctx, bindings, candidate) {
|
|
652
673
|
// In workspace dev (running out of `packages/natives/native/` rather than a
|
|
653
674
|
// `node_modules` install or a compiled bundle) the local `.node` only gains
|
|
@@ -680,6 +701,7 @@ export function validateLoadedBindings(ctx, bindings, candidate) {
|
|
|
680
701
|
// The successful require above normally guarantees readability. If the
|
|
681
702
|
// file disappears concurrently, retain the safe reinstall diagnosis.
|
|
682
703
|
}
|
|
704
|
+
if (isCompatiblePreSentinelNativeAddon(bindings, diskHasExpectedSentinel)) return;
|
|
683
705
|
if (residentSentinel && diskHasExpectedSentinel) {
|
|
684
706
|
const residentVersion = residentSentinel.slice("__piNativesV".length).replace(/_/g, ".");
|
|
685
707
|
throw new Error(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-natives",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "18.0.1",
|
|
4
4
|
"description": "Native Rust bindings for PDF conversion, audio, WebRTC, grep, clipboard, image processing, syntax highlighting, PTY, and shell operations via N-API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
@@ -68,6 +68,8 @@
|
|
|
68
68
|
"native/clipboard.d.ts",
|
|
69
69
|
"native/desktop.js",
|
|
70
70
|
"native/desktop.d.ts",
|
|
71
|
+
"native/desktop-adapter.js",
|
|
72
|
+
"native/desktop-adapter.d.ts",
|
|
71
73
|
"native/loader-state.js",
|
|
72
74
|
"native/loader-state.d.ts",
|
|
73
75
|
"native/embedded-addon.js",
|
|
@@ -90,10 +92,10 @@
|
|
|
90
92
|
}
|
|
91
93
|
},
|
|
92
94
|
"optionalDependencies": {
|
|
93
|
-
"@oh-my-pi/pi-natives-linux-x64": "
|
|
94
|
-
"@oh-my-pi/pi-natives-linux-arm64": "
|
|
95
|
-
"@oh-my-pi/pi-natives-darwin-x64": "
|
|
96
|
-
"@oh-my-pi/pi-natives-darwin-arm64": "
|
|
97
|
-
"@oh-my-pi/pi-natives-win32-x64": "
|
|
95
|
+
"@oh-my-pi/pi-natives-linux-x64": "18.0.1",
|
|
96
|
+
"@oh-my-pi/pi-natives-linux-arm64": "18.0.1",
|
|
97
|
+
"@oh-my-pi/pi-natives-darwin-x64": "18.0.1",
|
|
98
|
+
"@oh-my-pi/pi-natives-darwin-arm64": "18.0.1",
|
|
99
|
+
"@oh-my-pi/pi-natives-win32-x64": "18.0.1"
|
|
98
100
|
}
|
|
99
101
|
}
|