pi-image-tools 1.3.0 → 1.3.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/CHANGELOG.md +8 -0
- package/README.md +6 -3
- package/package.json +3 -3
- package/src/clipboard.ts +99 -89
- package/src/config.ts +2 -2
- package/src/image-size.ts +62 -63
- package/src/image-transcode.ts +198 -0
- package/src/keybindings.ts +52 -50
- package/src/providers/command-runner.ts +68 -68
- package/src/providers/mac-osascript-pngf.ts +101 -101
- package/src/providers/mac-osascript-publicpng.ts +77 -77
- package/src/providers/mac-pngpaste.ts +69 -69
- package/src/providers/native-module.ts +84 -84
- package/src/providers/powershell-forms.ts +95 -95
- package/src/providers/registry.ts +88 -88
- package/src/providers/types.ts +24 -24
- package/src/providers/wl-paste.ts +81 -81
- package/src/providers/xclip.ts +87 -87
- package/src/recent-images.ts +1 -0
- package/src/shell-environment.ts +75 -75
package/src/keybindings.ts
CHANGED
|
@@ -72,49 +72,51 @@ const LEGACY_KEYBINDING_NAME_MIGRATIONS: Record<string, string> = {
|
|
|
72
72
|
deleteSessionNoninvasive: "app.session.deleteNoninvasive",
|
|
73
73
|
};
|
|
74
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
|
-
|
|
75
|
+
function getAppKeybindingDefaults(platform: NodeJS.Platform): KeybindingDefaults {
|
|
76
|
+
return {
|
|
77
|
+
"app.interrupt": "escape",
|
|
78
|
+
"app.clear": "ctrl+c",
|
|
79
|
+
"app.exit": "ctrl+d",
|
|
80
|
+
"app.suspend": platform === "win32" ? [] : "ctrl+z",
|
|
81
|
+
"app.thinking.cycle": "shift+tab",
|
|
82
|
+
"app.model.cycleForward": "ctrl+p",
|
|
83
|
+
"app.model.cycleBackward": "shift+ctrl+p",
|
|
84
|
+
"app.model.select": "ctrl+l",
|
|
85
|
+
"app.tools.expand": "ctrl+o",
|
|
86
|
+
"app.thinking.toggle": "ctrl+t",
|
|
87
|
+
"app.session.toggleNamedFilter": "ctrl+n",
|
|
88
|
+
"app.editor.external": "ctrl+g",
|
|
89
|
+
"app.message.followUp": "alt+enter",
|
|
90
|
+
"app.message.dequeue": "alt+up",
|
|
91
|
+
"app.clipboard.pasteImage": platform === "win32" ? "alt+v" : "ctrl+v",
|
|
92
|
+
"app.session.new": [],
|
|
93
|
+
"app.session.tree": [],
|
|
94
|
+
"app.session.fork": [],
|
|
95
|
+
"app.session.resume": [],
|
|
96
|
+
"app.tree.foldOrUp": ["ctrl+left", "alt+left"],
|
|
97
|
+
"app.tree.unfoldOrDown": ["ctrl+right", "alt+right"],
|
|
98
|
+
"app.tree.editLabel": "shift+l",
|
|
99
|
+
"app.tree.toggleLabelTimestamp": "shift+t",
|
|
100
|
+
"app.session.togglePath": "ctrl+p",
|
|
101
|
+
"app.session.toggleSort": "ctrl+s",
|
|
102
|
+
"app.session.rename": "ctrl+r",
|
|
103
|
+
"app.session.delete": "ctrl+d",
|
|
104
|
+
"app.session.deleteNoninvasive": "ctrl+backspace",
|
|
105
|
+
"app.models.save": "ctrl+s",
|
|
106
|
+
"app.models.enableAll": "ctrl+a",
|
|
107
|
+
"app.models.clearAll": "ctrl+x",
|
|
108
|
+
"app.models.toggleProvider": "ctrl+p",
|
|
109
|
+
"app.models.reorderUp": "alt+up",
|
|
110
|
+
"app.models.reorderDown": "alt+down",
|
|
111
|
+
"app.tree.filter.default": "ctrl+d",
|
|
112
|
+
"app.tree.filter.noTools": "ctrl+t",
|
|
113
|
+
"app.tree.filter.userOnly": "ctrl+u",
|
|
114
|
+
"app.tree.filter.labeledOnly": "ctrl+l",
|
|
115
|
+
"app.tree.filter.all": "ctrl+a",
|
|
116
|
+
"app.tree.filter.cycleForward": "ctrl+o",
|
|
117
|
+
"app.tree.filter.cycleBackward": "shift+ctrl+o",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
118
120
|
|
|
119
121
|
export interface RegisterImagePasteKeybindingsOptions {
|
|
120
122
|
config: ImageToolsConfig;
|
|
@@ -186,7 +188,7 @@ function normalizeUserKeybindings(rawConfig: Record<string, unknown> | undefined
|
|
|
186
188
|
return userKeybindings;
|
|
187
189
|
}
|
|
188
190
|
|
|
189
|
-
function getBuiltinKeybindingDefaults(): KeybindingDefaults {
|
|
191
|
+
function getBuiltinKeybindingDefaults(platform: NodeJS.Platform): KeybindingDefaults {
|
|
190
192
|
const tuiDefaults: KeybindingDefaults = {};
|
|
191
193
|
for (const [keybinding, definition] of Object.entries(TUI_KEYBINDINGS)) {
|
|
192
194
|
tuiDefaults[keybinding] = definition.defaultKeys;
|
|
@@ -194,12 +196,12 @@ function getBuiltinKeybindingDefaults(): KeybindingDefaults {
|
|
|
194
196
|
|
|
195
197
|
return {
|
|
196
198
|
...tuiDefaults,
|
|
197
|
-
...
|
|
199
|
+
...getAppKeybindingDefaults(platform),
|
|
198
200
|
};
|
|
199
201
|
}
|
|
200
202
|
|
|
201
|
-
function getConfiguredBuiltinShortcuts(): Set<string> {
|
|
202
|
-
const defaults = getBuiltinKeybindingDefaults();
|
|
203
|
+
function getConfiguredBuiltinShortcuts(platform: NodeJS.Platform): Set<string> {
|
|
204
|
+
const defaults = getBuiltinKeybindingDefaults(platform);
|
|
203
205
|
const userKeybindings = normalizeUserKeybindings(readKeybindingsConfig());
|
|
204
206
|
const builtinShortcuts = new Set<string>();
|
|
205
207
|
|
|
@@ -224,8 +226,8 @@ function getImagePasteShortcutCandidates(platform: NodeJS.Platform): KeyId[] {
|
|
|
224
226
|
return ["ctrl+v", "alt+v", "ctrl+alt+v"];
|
|
225
227
|
}
|
|
226
228
|
|
|
227
|
-
function removeBuiltinConflicts(shortcuts: readonly KeyId[]): KeyId[] {
|
|
228
|
-
const builtinShortcuts = getConfiguredBuiltinShortcuts();
|
|
229
|
+
function removeBuiltinConflicts(shortcuts: readonly KeyId[], platform: NodeJS.Platform): KeyId[] {
|
|
230
|
+
const builtinShortcuts = getConfiguredBuiltinShortcuts(platform);
|
|
229
231
|
|
|
230
232
|
return shortcuts.filter((shortcut) => !builtinShortcuts.has(normalizeShortcutKey(shortcut)));
|
|
231
233
|
}
|
|
@@ -238,7 +240,7 @@ export function getImagePasteShortcuts(
|
|
|
238
240
|
config.shortcuts.avoidBuiltinConflicts || config.shortcuts.suppressBuiltinConflictWarnings;
|
|
239
241
|
const candidates = config.shortcuts.pasteImage ?? getImagePasteShortcutCandidates(platform);
|
|
240
242
|
|
|
241
|
-
return shouldAvoidBuiltinConflicts ? removeBuiltinConflicts(candidates) : [...candidates];
|
|
243
|
+
return shouldAvoidBuiltinConflicts ? removeBuiltinConflicts(candidates, platform) : [...candidates];
|
|
242
244
|
}
|
|
243
245
|
|
|
244
246
|
export function registerImagePasteKeybindings(
|
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
|
|
3
|
-
import { isErrnoException } from "../errors.js";
|
|
4
|
-
|
|
5
|
-
export const LIST_TYPES_TIMEOUT_MS = 1000;
|
|
6
|
-
export const READ_TIMEOUT_MS = 5000;
|
|
7
|
-
export const MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
8
|
-
|
|
9
|
-
export interface CommandResult {
|
|
10
|
-
ok: boolean;
|
|
11
|
-
stdout: Buffer;
|
|
12
|
-
stderr: Buffer;
|
|
13
|
-
missingCommand: boolean;
|
|
14
|
-
status: number | null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface CommandRunOptions {
|
|
18
|
-
environment?: NodeJS.ProcessEnv;
|
|
19
|
-
maxBuffer?: number;
|
|
20
|
-
timeout: number;
|
|
21
|
-
windowsHide?: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type CommandRunner = (
|
|
25
|
-
command: string,
|
|
26
|
-
args: readonly string[],
|
|
27
|
-
options: CommandRunOptions,
|
|
28
|
-
) => CommandResult;
|
|
29
|
-
|
|
30
|
-
function toBuffer(value: unknown): Buffer {
|
|
31
|
-
if (Buffer.isBuffer(value)) {
|
|
32
|
-
return value;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (value instanceof Uint8Array) {
|
|
36
|
-
return Buffer.from(value);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return Buffer.from(value === undefined || value === null ? "" : String(value), "utf8");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export const defaultCommandRunner: CommandRunner = (command, args, options) => {
|
|
43
|
-
const result = spawnSync(command, [...args], {
|
|
44
|
-
env: options.environment,
|
|
45
|
-
maxBuffer: options.maxBuffer ?? MAX_BUFFER_BYTES,
|
|
46
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
47
|
-
timeout: options.timeout,
|
|
48
|
-
windowsHide: options.windowsHide,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
if (result.error) {
|
|
52
|
-
return {
|
|
53
|
-
ok: false,
|
|
54
|
-
stdout: toBuffer(result.stdout),
|
|
55
|
-
stderr: toBuffer(result.stderr),
|
|
56
|
-
missingCommand: isErrnoException(result.error) && result.error.code === "ENOENT",
|
|
57
|
-
status: result.status ?? null,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
ok: result.status === 0,
|
|
63
|
-
stdout: toBuffer(result.stdout),
|
|
64
|
-
stderr: toBuffer(result.stderr),
|
|
65
|
-
missingCommand: false,
|
|
66
|
-
status: result.status ?? null,
|
|
67
|
-
};
|
|
68
|
-
};
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
import { isErrnoException } from "../errors.js";
|
|
4
|
+
|
|
5
|
+
export const LIST_TYPES_TIMEOUT_MS = 1000;
|
|
6
|
+
export const READ_TIMEOUT_MS = 5000;
|
|
7
|
+
export const MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
8
|
+
|
|
9
|
+
export interface CommandResult {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
stdout: Buffer;
|
|
12
|
+
stderr: Buffer;
|
|
13
|
+
missingCommand: boolean;
|
|
14
|
+
status: number | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CommandRunOptions {
|
|
18
|
+
environment?: NodeJS.ProcessEnv;
|
|
19
|
+
maxBuffer?: number;
|
|
20
|
+
timeout: number;
|
|
21
|
+
windowsHide?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type CommandRunner = (
|
|
25
|
+
command: string,
|
|
26
|
+
args: readonly string[],
|
|
27
|
+
options: CommandRunOptions,
|
|
28
|
+
) => CommandResult;
|
|
29
|
+
|
|
30
|
+
function toBuffer(value: unknown): Buffer {
|
|
31
|
+
if (Buffer.isBuffer(value)) {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (value instanceof Uint8Array) {
|
|
36
|
+
return Buffer.from(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return Buffer.from(value === undefined || value === null ? "" : String(value), "utf8");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const defaultCommandRunner: CommandRunner = (command, args, options) => {
|
|
43
|
+
const result = spawnSync(command, [...args], {
|
|
44
|
+
env: options.environment,
|
|
45
|
+
maxBuffer: options.maxBuffer ?? MAX_BUFFER_BYTES,
|
|
46
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
47
|
+
timeout: options.timeout,
|
|
48
|
+
windowsHide: options.windowsHide,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (result.error) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
stdout: toBuffer(result.stdout),
|
|
55
|
+
stderr: toBuffer(result.stderr),
|
|
56
|
+
missingCommand: isErrnoException(result.error) && result.error.code === "ENOENT",
|
|
57
|
+
status: result.status ?? null,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
ok: result.status === 0,
|
|
63
|
+
stdout: toBuffer(result.stdout),
|
|
64
|
+
stderr: toBuffer(result.stderr),
|
|
65
|
+
missingCommand: false,
|
|
66
|
+
status: result.status ?? null,
|
|
67
|
+
};
|
|
68
|
+
};
|
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
import { buildNamespaceWrappedCommand, defaultCommandExists, type CommandExists } from "../shell-environment.js";
|
|
2
|
-
import {
|
|
3
|
-
defaultCommandRunner,
|
|
4
|
-
MAX_BUFFER_BYTES,
|
|
5
|
-
READ_TIMEOUT_MS,
|
|
6
|
-
type CommandRunner,
|
|
7
|
-
} from "./command-runner.js";
|
|
8
|
-
import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
|
|
9
|
-
|
|
10
|
-
const PNGF_SCRIPT = `try
|
|
11
|
-
set imageData to the clipboard as «class PNGf»
|
|
12
|
-
return imageData
|
|
13
|
-
on error
|
|
14
|
-
return ""
|
|
15
|
-
end try`;
|
|
16
|
-
|
|
17
|
-
export interface OsascriptPngfProviderOptions {
|
|
18
|
-
priority?: number;
|
|
19
|
-
commandRunner?: CommandRunner;
|
|
20
|
-
commandExists?: CommandExists;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function parseAppleScriptPngfData(stdout: Buffer): Uint8Array | null {
|
|
24
|
-
const text = stdout.toString("utf8").trim();
|
|
25
|
-
if (text.length === 0) {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const match = text.match(/«data\s+PNGf([0-9a-fA-F\s]+)»/i);
|
|
30
|
-
if (!match) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const hex = match[1]?.replace(/\s+/g, "") ?? "";
|
|
35
|
-
if (hex.length === 0 || hex.length % 2 !== 0) {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const bytes = Buffer.from(hex, "hex");
|
|
40
|
-
return bytes.length > 0 ? new Uint8Array(bytes) : null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class OsascriptPngfProvider implements ClipboardImageProvider {
|
|
44
|
-
readonly capabilities;
|
|
45
|
-
private readonly commandRunner: CommandRunner;
|
|
46
|
-
private readonly commandExists: CommandExists;
|
|
47
|
-
|
|
48
|
-
constructor(options: OsascriptPngfProviderOptions = {}) {
|
|
49
|
-
this.capabilities = {
|
|
50
|
-
id: "mac-osascript-pngf",
|
|
51
|
-
name: "osascript PNGf",
|
|
52
|
-
platforms: ["darwin"],
|
|
53
|
-
priority: options.priority ?? 30,
|
|
54
|
-
};
|
|
55
|
-
this.commandRunner = options.commandRunner ?? defaultCommandRunner;
|
|
56
|
-
this.commandExists = options.commandExists ?? defaultCommandExists;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
isAvailable(context: ClipboardProviderContext): boolean {
|
|
60
|
-
try {
|
|
61
|
-
return this.commandExists("osascript", context);
|
|
62
|
-
} catch {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
read(context: ClipboardProviderContext): ClipboardReadResult {
|
|
68
|
-
const wrapped = buildNamespaceWrappedCommand(
|
|
69
|
-
"osascript",
|
|
70
|
-
["-e", PNGF_SCRIPT],
|
|
71
|
-
context,
|
|
72
|
-
this.commandExists,
|
|
73
|
-
);
|
|
74
|
-
const result = this.commandRunner(wrapped.command, wrapped.args, {
|
|
75
|
-
environment: context.environment,
|
|
76
|
-
maxBuffer: MAX_BUFFER_BYTES,
|
|
77
|
-
timeout: READ_TIMEOUT_MS,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
if (result.missingCommand) {
|
|
81
|
-
return { available: false, image: null };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!result.ok || result.stdout.length === 0) {
|
|
85
|
-
return { available: true, image: null };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const bytes = parseAppleScriptPngfData(result.stdout);
|
|
89
|
-
if (!bytes) {
|
|
90
|
-
return { available: true, image: null };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
available: true,
|
|
95
|
-
image: {
|
|
96
|
-
bytes,
|
|
97
|
-
mimeType: "image/png",
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
}
|
|
1
|
+
import { buildNamespaceWrappedCommand, defaultCommandExists, type CommandExists } from "../shell-environment.js";
|
|
2
|
+
import {
|
|
3
|
+
defaultCommandRunner,
|
|
4
|
+
MAX_BUFFER_BYTES,
|
|
5
|
+
READ_TIMEOUT_MS,
|
|
6
|
+
type CommandRunner,
|
|
7
|
+
} from "./command-runner.js";
|
|
8
|
+
import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
|
|
9
|
+
|
|
10
|
+
const PNGF_SCRIPT = `try
|
|
11
|
+
set imageData to the clipboard as «class PNGf»
|
|
12
|
+
return imageData
|
|
13
|
+
on error
|
|
14
|
+
return ""
|
|
15
|
+
end try`;
|
|
16
|
+
|
|
17
|
+
export interface OsascriptPngfProviderOptions {
|
|
18
|
+
priority?: number;
|
|
19
|
+
commandRunner?: CommandRunner;
|
|
20
|
+
commandExists?: CommandExists;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parseAppleScriptPngfData(stdout: Buffer): Uint8Array | null {
|
|
24
|
+
const text = stdout.toString("utf8").trim();
|
|
25
|
+
if (text.length === 0) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const match = text.match(/«data\s+PNGf([0-9a-fA-F\s]+)»/i);
|
|
30
|
+
if (!match) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const hex = match[1]?.replace(/\s+/g, "") ?? "";
|
|
35
|
+
if (hex.length === 0 || hex.length % 2 !== 0) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const bytes = Buffer.from(hex, "hex");
|
|
40
|
+
return bytes.length > 0 ? new Uint8Array(bytes) : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class OsascriptPngfProvider implements ClipboardImageProvider {
|
|
44
|
+
readonly capabilities;
|
|
45
|
+
private readonly commandRunner: CommandRunner;
|
|
46
|
+
private readonly commandExists: CommandExists;
|
|
47
|
+
|
|
48
|
+
constructor(options: OsascriptPngfProviderOptions = {}) {
|
|
49
|
+
this.capabilities = {
|
|
50
|
+
id: "mac-osascript-pngf",
|
|
51
|
+
name: "osascript PNGf",
|
|
52
|
+
platforms: ["darwin"],
|
|
53
|
+
priority: options.priority ?? 30,
|
|
54
|
+
};
|
|
55
|
+
this.commandRunner = options.commandRunner ?? defaultCommandRunner;
|
|
56
|
+
this.commandExists = options.commandExists ?? defaultCommandExists;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
isAvailable(context: ClipboardProviderContext): boolean {
|
|
60
|
+
try {
|
|
61
|
+
return this.commandExists("osascript", context);
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
read(context: ClipboardProviderContext): ClipboardReadResult {
|
|
68
|
+
const wrapped = buildNamespaceWrappedCommand(
|
|
69
|
+
"osascript",
|
|
70
|
+
["-e", PNGF_SCRIPT],
|
|
71
|
+
context,
|
|
72
|
+
this.commandExists,
|
|
73
|
+
);
|
|
74
|
+
const result = this.commandRunner(wrapped.command, wrapped.args, {
|
|
75
|
+
environment: context.environment,
|
|
76
|
+
maxBuffer: MAX_BUFFER_BYTES,
|
|
77
|
+
timeout: READ_TIMEOUT_MS,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (result.missingCommand) {
|
|
81
|
+
return { available: false, image: null };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!result.ok || result.stdout.length === 0) {
|
|
85
|
+
return { available: true, image: null };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const bytes = parseAppleScriptPngfData(result.stdout);
|
|
89
|
+
if (!bytes) {
|
|
90
|
+
return { available: true, image: null };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
available: true,
|
|
95
|
+
image: {
|
|
96
|
+
bytes,
|
|
97
|
+
mimeType: "image/png",
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
import { buildNamespaceWrappedCommand, defaultCommandExists, type CommandExists } from "../shell-environment.js";
|
|
2
|
-
import {
|
|
3
|
-
defaultCommandRunner,
|
|
4
|
-
MAX_BUFFER_BYTES,
|
|
5
|
-
READ_TIMEOUT_MS,
|
|
6
|
-
type CommandRunner,
|
|
7
|
-
} from "./command-runner.js";
|
|
8
|
-
import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
|
|
9
|
-
|
|
10
|
-
const PUBLIC_PNG_SCRIPT = `ObjC.import('AppKit');
|
|
11
|
-
const pasteboard = $.NSPasteboard.generalPasteboard;
|
|
12
|
-
const data = pasteboard.dataForType('public.png');
|
|
13
|
-
if (!data) {
|
|
14
|
-
$.exit(2);
|
|
15
|
-
}
|
|
16
|
-
$.NSFileHandle.fileHandleWithStandardOutput.writeData(data);`;
|
|
17
|
-
|
|
18
|
-
export interface OsascriptPublicPngProviderOptions {
|
|
19
|
-
priority?: number;
|
|
20
|
-
commandRunner?: CommandRunner;
|
|
21
|
-
commandExists?: CommandExists;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class OsascriptPublicPngProvider implements ClipboardImageProvider {
|
|
25
|
-
readonly capabilities;
|
|
26
|
-
private readonly commandRunner: CommandRunner;
|
|
27
|
-
private readonly commandExists: CommandExists;
|
|
28
|
-
|
|
29
|
-
constructor(options: OsascriptPublicPngProviderOptions = {}) {
|
|
30
|
-
this.capabilities = {
|
|
31
|
-
id: "mac-osascript-public-png",
|
|
32
|
-
name: "osascript public.png",
|
|
33
|
-
platforms: ["darwin"],
|
|
34
|
-
priority: options.priority ?? 20,
|
|
35
|
-
};
|
|
36
|
-
this.commandRunner = options.commandRunner ?? defaultCommandRunner;
|
|
37
|
-
this.commandExists = options.commandExists ?? defaultCommandExists;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
isAvailable(context: ClipboardProviderContext): boolean {
|
|
41
|
-
try {
|
|
42
|
-
return this.commandExists("osascript", context);
|
|
43
|
-
} catch {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
read(context: ClipboardProviderContext): ClipboardReadResult {
|
|
49
|
-
const wrapped = buildNamespaceWrappedCommand(
|
|
50
|
-
"osascript",
|
|
51
|
-
["-l", "JavaScript", "-e", PUBLIC_PNG_SCRIPT],
|
|
52
|
-
context,
|
|
53
|
-
this.commandExists,
|
|
54
|
-
);
|
|
55
|
-
const result = this.commandRunner(wrapped.command, wrapped.args, {
|
|
56
|
-
environment: context.environment,
|
|
57
|
-
maxBuffer: MAX_BUFFER_BYTES,
|
|
58
|
-
timeout: READ_TIMEOUT_MS,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
if (result.missingCommand) {
|
|
62
|
-
return { available: false, image: null };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (!result.ok || result.stdout.length === 0) {
|
|
66
|
-
return { available: true, image: null };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
available: true,
|
|
71
|
-
image: {
|
|
72
|
-
bytes: new Uint8Array(result.stdout),
|
|
73
|
-
mimeType: "image/png",
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
1
|
+
import { buildNamespaceWrappedCommand, defaultCommandExists, type CommandExists } from "../shell-environment.js";
|
|
2
|
+
import {
|
|
3
|
+
defaultCommandRunner,
|
|
4
|
+
MAX_BUFFER_BYTES,
|
|
5
|
+
READ_TIMEOUT_MS,
|
|
6
|
+
type CommandRunner,
|
|
7
|
+
} from "./command-runner.js";
|
|
8
|
+
import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
|
|
9
|
+
|
|
10
|
+
const PUBLIC_PNG_SCRIPT = `ObjC.import('AppKit');
|
|
11
|
+
const pasteboard = $.NSPasteboard.generalPasteboard;
|
|
12
|
+
const data = pasteboard.dataForType('public.png');
|
|
13
|
+
if (!data) {
|
|
14
|
+
$.exit(2);
|
|
15
|
+
}
|
|
16
|
+
$.NSFileHandle.fileHandleWithStandardOutput.writeData(data);`;
|
|
17
|
+
|
|
18
|
+
export interface OsascriptPublicPngProviderOptions {
|
|
19
|
+
priority?: number;
|
|
20
|
+
commandRunner?: CommandRunner;
|
|
21
|
+
commandExists?: CommandExists;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class OsascriptPublicPngProvider implements ClipboardImageProvider {
|
|
25
|
+
readonly capabilities;
|
|
26
|
+
private readonly commandRunner: CommandRunner;
|
|
27
|
+
private readonly commandExists: CommandExists;
|
|
28
|
+
|
|
29
|
+
constructor(options: OsascriptPublicPngProviderOptions = {}) {
|
|
30
|
+
this.capabilities = {
|
|
31
|
+
id: "mac-osascript-public-png",
|
|
32
|
+
name: "osascript public.png",
|
|
33
|
+
platforms: ["darwin"],
|
|
34
|
+
priority: options.priority ?? 20,
|
|
35
|
+
};
|
|
36
|
+
this.commandRunner = options.commandRunner ?? defaultCommandRunner;
|
|
37
|
+
this.commandExists = options.commandExists ?? defaultCommandExists;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
isAvailable(context: ClipboardProviderContext): boolean {
|
|
41
|
+
try {
|
|
42
|
+
return this.commandExists("osascript", context);
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
read(context: ClipboardProviderContext): ClipboardReadResult {
|
|
49
|
+
const wrapped = buildNamespaceWrappedCommand(
|
|
50
|
+
"osascript",
|
|
51
|
+
["-l", "JavaScript", "-e", PUBLIC_PNG_SCRIPT],
|
|
52
|
+
context,
|
|
53
|
+
this.commandExists,
|
|
54
|
+
);
|
|
55
|
+
const result = this.commandRunner(wrapped.command, wrapped.args, {
|
|
56
|
+
environment: context.environment,
|
|
57
|
+
maxBuffer: MAX_BUFFER_BYTES,
|
|
58
|
+
timeout: READ_TIMEOUT_MS,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (result.missingCommand) {
|
|
62
|
+
return { available: false, image: null };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!result.ok || result.stdout.length === 0) {
|
|
66
|
+
return { available: true, image: null };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
available: true,
|
|
71
|
+
image: {
|
|
72
|
+
bytes: new Uint8Array(result.stdout),
|
|
73
|
+
mimeType: "image/png",
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|