pi-image-tools 1.2.1 → 1.3.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/CHANGELOG.md +15 -0
- package/README.md +4 -4
- package/package.json +69 -69
- package/src/clipboard.ts +89 -336
- package/src/config.ts +24 -15
- package/src/index.ts +115 -32
- package/src/inline-user-preview.ts +225 -69
- package/src/keybindings.ts +10 -11
- package/src/providers/command-runner.ts +68 -0
- package/src/providers/mac-osascript-pngf.ts +101 -0
- package/src/providers/mac-osascript-publicpng.ts +77 -0
- package/src/providers/mac-pngpaste.ts +69 -0
- package/src/providers/native-module.ts +84 -0
- package/src/providers/powershell-forms.ts +95 -0
- package/src/providers/registry.ts +88 -0
- package/src/providers/types.ts +24 -0
- package/src/providers/wl-paste.ts +81 -0
- package/src/providers/xclip.ts +87 -0
- package/src/shell-environment.ts +75 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.0] - 2026-06-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Added a provider registry for platform-specific clipboard readers.
|
|
7
|
+
- Added macOS `pngpaste` and `osascript` clipboard fallbacks.
|
|
8
|
+
- Added an inline preview queue and chat component for user image previews.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Deferred preview, clipboard, and recent-image module loading from the extension entrypoint.
|
|
12
|
+
- Made shortcut conflict defaults platform-aware.
|
|
13
|
+
- Updated Pi peer dependency ranges and the optional clipboard dependency for 0.78-compatible runtimes.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Improved shell and session handling for clipboard command providers.
|
|
17
|
+
|
|
3
18
|
## [1.2.1] - 2026-05-26
|
|
4
19
|
|
|
5
20
|
### Changed
|
package/README.md
CHANGED
|
@@ -25,11 +25,9 @@ Image attachment and preview extension for the **Pi coding agent**.
|
|
|
25
25
|
|----------|-----------------|---------------------|-------|
|
|
26
26
|
| Windows | Yes | Yes | Uses native clipboard module first, then PowerShell fallback |
|
|
27
27
|
| Linux | Yes | Yes | Requires a graphical session; uses `wl-paste` or `xclip`, then native module fallback |
|
|
28
|
-
| macOS | Yes
|
|
28
|
+
| macOS | Yes | Yes | Uses `pngpaste` first, then `osascript` and native module fallbacks |
|
|
29
29
|
| Termux / headless Linux | No | Limited | Clipboard image paste is disabled without a graphical session |
|
|
30
30
|
|
|
31
|
-
\* macOS clipboard image support relies on the optional native clipboard module.
|
|
32
|
-
|
|
33
31
|
## Installation
|
|
34
32
|
|
|
35
33
|
### Extension folder
|
|
@@ -273,7 +271,9 @@ Preview behavior:
|
|
|
273
271
|
- `xclip` in X11 sessions
|
|
274
272
|
- `@mariozechner/clipboard` fallback
|
|
275
273
|
- **macOS**
|
|
276
|
-
-
|
|
274
|
+
- `pngpaste`
|
|
275
|
+
- `osascript` PNG clipboard readers
|
|
276
|
+
- `@mariozechner/clipboard` fallback
|
|
277
277
|
|
|
278
278
|
If a platform-specific reader exists but no image is currently on the clipboard, the command returns a normal “No image found in clipboard” message. If no usable reader exists at all, the extension surfaces a setup-oriented error.
|
|
279
279
|
|
package/package.json
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pi-image-tools",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Image attachment and rendering extension for Pi TUI",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./index.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./index.ts"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"index.ts",
|
|
12
|
-
"src",
|
|
13
|
-
"config/config.example.json",
|
|
14
|
-
"README.md",
|
|
15
|
-
"CHANGELOG.md",
|
|
16
|
-
"LICENSE"
|
|
17
|
-
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"typecheck": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noEmit",
|
|
20
|
-
"build": "npm run typecheck",
|
|
21
|
-
"lint": "npm run typecheck",
|
|
22
|
-
"test": "node --test",
|
|
23
|
-
"check": "npm run build && npm run test"
|
|
24
|
-
},
|
|
25
|
-
"keywords": [
|
|
26
|
-
"pi-package",
|
|
27
|
-
"pi",
|
|
28
|
-
"pi-extension",
|
|
29
|
-
"pi-coding-agent",
|
|
30
|
-
"pi-tui",
|
|
31
|
-
"coding-agent",
|
|
32
|
-
"image",
|
|
33
|
-
"image-attachment",
|
|
34
|
-
"image-preview",
|
|
35
|
-
"clipboard",
|
|
36
|
-
"preview",
|
|
37
|
-
"windows",
|
|
38
|
-
"linux",
|
|
39
|
-
"cross-platform"
|
|
40
|
-
],
|
|
41
|
-
"author": "MasuRii",
|
|
42
|
-
"license": "MIT",
|
|
43
|
-
"repository": {
|
|
44
|
-
"type": "git",
|
|
45
|
-
"url": "git+https://github.com/MasuRii/pi-image-tools.git"
|
|
46
|
-
},
|
|
47
|
-
"homepage": "https://github.com/MasuRii/pi-image-tools#readme",
|
|
48
|
-
"bugs": {
|
|
49
|
-
"url": "https://github.com/MasuRii/pi-image-tools/issues"
|
|
50
|
-
},
|
|
51
|
-
"engines": {
|
|
52
|
-
"node": ">=20"
|
|
53
|
-
},
|
|
54
|
-
"publishConfig": {
|
|
55
|
-
"access": "public"
|
|
56
|
-
},
|
|
57
|
-
"pi": {
|
|
58
|
-
"extensions": [
|
|
59
|
-
"./index.ts"
|
|
60
|
-
]
|
|
61
|
-
},
|
|
62
|
-
"peerDependencies": {
|
|
63
|
-
"@earendil-works/pi-coding-agent": "^0.74.0 || ^0.75.0",
|
|
64
|
-
"@earendil-works/pi-tui": "^0.74.0 || ^0.75.0"
|
|
65
|
-
},
|
|
66
|
-
"optionalDependencies": {
|
|
67
|
-
"@mariozechner/clipboard": "^0.3.
|
|
68
|
-
}
|
|
69
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-image-tools",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Image attachment and rendering extension for Pi TUI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.ts"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.ts",
|
|
12
|
+
"src",
|
|
13
|
+
"config/config.example.json",
|
|
14
|
+
"README.md",
|
|
15
|
+
"CHANGELOG.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"typecheck": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noEmit",
|
|
20
|
+
"build": "npm run typecheck",
|
|
21
|
+
"lint": "npm run typecheck",
|
|
22
|
+
"test": "node --test test/*.test.js test/providers/*.test.js",
|
|
23
|
+
"check": "npm run build && npm run test"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"pi-package",
|
|
27
|
+
"pi",
|
|
28
|
+
"pi-extension",
|
|
29
|
+
"pi-coding-agent",
|
|
30
|
+
"pi-tui",
|
|
31
|
+
"coding-agent",
|
|
32
|
+
"image",
|
|
33
|
+
"image-attachment",
|
|
34
|
+
"image-preview",
|
|
35
|
+
"clipboard",
|
|
36
|
+
"preview",
|
|
37
|
+
"windows",
|
|
38
|
+
"linux",
|
|
39
|
+
"cross-platform"
|
|
40
|
+
],
|
|
41
|
+
"author": "MasuRii",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/MasuRii/pi-image-tools.git"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/MasuRii/pi-image-tools#readme",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/MasuRii/pi-image-tools/issues"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=20"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"pi": {
|
|
58
|
+
"extensions": [
|
|
59
|
+
"./index.ts"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@earendil-works/pi-coding-agent": "^0.74.0 || ^0.75.0 || ^0.77.0 || ^0.78.0",
|
|
64
|
+
"@earendil-works/pi-tui": "^0.74.0 || ^0.75.0 || ^0.77.0 || ^0.78.0"
|
|
65
|
+
},
|
|
66
|
+
"optionalDependencies": {
|
|
67
|
+
"@mariozechner/clipboard": "^0.3.9"
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/clipboard.ts
CHANGED
|
@@ -1,336 +1,89 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
platform
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const result = spawnSync(command, args, {
|
|
91
|
-
timeout,
|
|
92
|
-
maxBuffer: MAX_BUFFER_BYTES,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
if (result.error) {
|
|
96
|
-
return {
|
|
97
|
-
ok: false,
|
|
98
|
-
stdout: Buffer.alloc(0),
|
|
99
|
-
missingCommand: isErrnoException(result.error) && result.error.code === "ENOENT",
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const stdout = Buffer.isBuffer(result.stdout)
|
|
104
|
-
? result.stdout
|
|
105
|
-
: Buffer.from(result.stdout ?? "", typeof result.stdout === "string" ? "utf8" : undefined);
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
ok: result.status === 0,
|
|
109
|
-
stdout,
|
|
110
|
-
missingCommand: false,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function readClipboardImageViaPowerShell(): ClipboardReadResult {
|
|
115
|
-
const script = `
|
|
116
|
-
$ErrorActionPreference = 'Stop'
|
|
117
|
-
Add-Type -AssemblyName System.Windows.Forms
|
|
118
|
-
Add-Type -AssemblyName System.Drawing
|
|
119
|
-
|
|
120
|
-
if (-not [System.Windows.Forms.Clipboard]::ContainsImage()) {
|
|
121
|
-
return
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
$image = [System.Windows.Forms.Clipboard]::GetImage()
|
|
125
|
-
if ($null -eq $image) {
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
$stream = New-Object System.IO.MemoryStream
|
|
130
|
-
try {
|
|
131
|
-
$image.Save($stream, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
132
|
-
[System.Convert]::ToBase64String($stream.ToArray())
|
|
133
|
-
} finally {
|
|
134
|
-
$stream.Dispose()
|
|
135
|
-
$image.Dispose()
|
|
136
|
-
}
|
|
137
|
-
`;
|
|
138
|
-
|
|
139
|
-
const result = runPowerShellCommand(script, {
|
|
140
|
-
encoded: true,
|
|
141
|
-
sta: true,
|
|
142
|
-
timeout: READ_TIMEOUT_MS,
|
|
143
|
-
maxBuffer: MAX_BUFFER_BYTES,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
if (result.missingCommand) {
|
|
147
|
-
return { available: false, image: null };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (!result.ok) {
|
|
151
|
-
return { available: true, image: null };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const base64 = result.stdout.trim();
|
|
155
|
-
if (!base64) {
|
|
156
|
-
return { available: true, image: null };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
const bytes = Buffer.from(base64, "base64");
|
|
161
|
-
if (bytes.length === 0) {
|
|
162
|
-
return { available: true, image: null };
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
available: true,
|
|
167
|
-
image: {
|
|
168
|
-
bytes: new Uint8Array(bytes),
|
|
169
|
-
mimeType: "image/png",
|
|
170
|
-
},
|
|
171
|
-
};
|
|
172
|
-
} catch {
|
|
173
|
-
return { available: true, image: null };
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function readClipboardImageViaWlPaste(): ClipboardReadResult {
|
|
178
|
-
const listTypes = runCommand("wl-paste", ["--list-types"], LIST_TYPES_TIMEOUT_MS);
|
|
179
|
-
if (listTypes.missingCommand) {
|
|
180
|
-
return { available: false, image: null };
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (!listTypes.ok) {
|
|
184
|
-
return { available: true, image: null };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const mimeTypes = listTypes.stdout
|
|
188
|
-
.toString("utf8")
|
|
189
|
-
.split(/\r?\n/)
|
|
190
|
-
.map((mimeType) => mimeType.trim())
|
|
191
|
-
.filter((mimeType) => mimeType.length > 0);
|
|
192
|
-
|
|
193
|
-
const selectedMimeType = selectPreferredImageMimeType(mimeTypes);
|
|
194
|
-
if (!selectedMimeType) {
|
|
195
|
-
return { available: true, image: null };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const imageData = runCommand(
|
|
199
|
-
"wl-paste",
|
|
200
|
-
["--type", selectedMimeType, "--no-newline"],
|
|
201
|
-
READ_TIMEOUT_MS,
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
if (!imageData.ok || imageData.stdout.length === 0) {
|
|
205
|
-
return { available: true, image: null };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return {
|
|
209
|
-
available: true,
|
|
210
|
-
image: {
|
|
211
|
-
bytes: new Uint8Array(imageData.stdout),
|
|
212
|
-
mimeType: normalizeMimeType(selectedMimeType),
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function readClipboardImageViaXclip(): ClipboardReadResult {
|
|
218
|
-
const targets = runCommand(
|
|
219
|
-
"xclip",
|
|
220
|
-
["-selection", "clipboard", "-t", "TARGETS", "-o"],
|
|
221
|
-
LIST_TYPES_TIMEOUT_MS,
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
if (targets.missingCommand) {
|
|
225
|
-
return { available: false, image: null };
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const advertisedMimeTypes = targets.ok
|
|
229
|
-
? targets.stdout
|
|
230
|
-
.toString("utf8")
|
|
231
|
-
.split(/\r?\n/)
|
|
232
|
-
.map((mimeType) => mimeType.trim())
|
|
233
|
-
.filter((mimeType) => mimeType.length > 0)
|
|
234
|
-
: [];
|
|
235
|
-
|
|
236
|
-
const preferredMimeType =
|
|
237
|
-
advertisedMimeTypes.length > 0 ? selectPreferredImageMimeType(advertisedMimeTypes) : null;
|
|
238
|
-
const mimeTypesToTry = preferredMimeType
|
|
239
|
-
? [preferredMimeType, ...SUPPORTED_IMAGE_MIME_TYPES]
|
|
240
|
-
: [...SUPPORTED_IMAGE_MIME_TYPES];
|
|
241
|
-
|
|
242
|
-
for (const mimeType of mimeTypesToTry) {
|
|
243
|
-
const imageData = runCommand(
|
|
244
|
-
"xclip",
|
|
245
|
-
["-selection", "clipboard", "-t", mimeType, "-o"],
|
|
246
|
-
READ_TIMEOUT_MS,
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
if (imageData.ok && imageData.stdout.length > 0) {
|
|
250
|
-
return {
|
|
251
|
-
available: true,
|
|
252
|
-
image: {
|
|
253
|
-
bytes: new Uint8Array(imageData.stdout),
|
|
254
|
-
mimeType: normalizeMimeType(mimeType),
|
|
255
|
-
},
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return { available: true, image: null };
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function getUnavailableReaderMessage(platform: NodeJS.Platform): string {
|
|
264
|
-
switch (platform) {
|
|
265
|
-
case "linux":
|
|
266
|
-
return "No Linux clipboard image reader is available. Install wl-clipboard or xclip, or ensure @mariozechner/clipboard is installed.";
|
|
267
|
-
case "darwin":
|
|
268
|
-
return "No macOS clipboard image reader is available. Ensure @mariozechner/clipboard is installed.";
|
|
269
|
-
case "win32":
|
|
270
|
-
return "No Windows clipboard image reader is available. Ensure PowerShell is available or @mariozechner/clipboard is installed.";
|
|
271
|
-
default:
|
|
272
|
-
return `Clipboard image paste is not supported on platform: ${platform}`;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export async function readClipboardImage(options?: {
|
|
277
|
-
environment?: NodeJS.ProcessEnv;
|
|
278
|
-
platform?: NodeJS.Platform;
|
|
279
|
-
}): Promise<ClipboardImage | null> {
|
|
280
|
-
const environment = options?.environment ?? process.env;
|
|
281
|
-
const platform = options?.platform ?? process.platform;
|
|
282
|
-
|
|
283
|
-
if (environment.TERMUX_VERSION) {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (!hasGraphicalSession(platform, environment)) {
|
|
288
|
-
throw new Error("Clipboard image paste requires a graphical desktop session with DISPLAY or WAYLAND_DISPLAY.");
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const readerResults: ClipboardReadResult[] = [];
|
|
292
|
-
|
|
293
|
-
const recordResult = (result: ClipboardReadResult): ClipboardImage | null => {
|
|
294
|
-
readerResults.push(result);
|
|
295
|
-
return result.image;
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
if (platform === "win32") {
|
|
299
|
-
const nativeImage = recordResult(await readClipboardImageViaNativeModule(platform, environment));
|
|
300
|
-
if (nativeImage) {
|
|
301
|
-
return nativeImage;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const powerShellImage = recordResult(readClipboardImageViaPowerShell());
|
|
305
|
-
if (powerShellImage) {
|
|
306
|
-
return powerShellImage;
|
|
307
|
-
}
|
|
308
|
-
} else if (platform === "linux") {
|
|
309
|
-
const sessionReaders = isWaylandSession(environment)
|
|
310
|
-
? [readClipboardImageViaWlPaste, readClipboardImageViaXclip]
|
|
311
|
-
: [readClipboardImageViaXclip, readClipboardImageViaWlPaste];
|
|
312
|
-
|
|
313
|
-
for (const reader of sessionReaders) {
|
|
314
|
-
const image = recordResult(reader());
|
|
315
|
-
if (image) {
|
|
316
|
-
return image;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const nativeImage = recordResult(await readClipboardImageViaNativeModule(platform, environment));
|
|
321
|
-
if (nativeImage) {
|
|
322
|
-
return nativeImage;
|
|
323
|
-
}
|
|
324
|
-
} else {
|
|
325
|
-
const nativeImage = recordResult(await readClipboardImageViaNativeModule(platform, environment));
|
|
326
|
-
if (nativeImage) {
|
|
327
|
-
return nativeImage;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (readerResults.some((result) => result.available)) {
|
|
332
|
-
return null;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
throw new Error(getUnavailableReaderMessage(platform));
|
|
336
|
-
}
|
|
1
|
+
import { hasGraphicalSession, isWaylandSession } from "./shell-environment.js";
|
|
2
|
+
import { NativeModuleProvider } from "./providers/native-module.js";
|
|
3
|
+
import { OsascriptPngfProvider } from "./providers/mac-osascript-pngf.js";
|
|
4
|
+
import { OsascriptPublicPngProvider } from "./providers/mac-osascript-publicpng.js";
|
|
5
|
+
import { PngpasteProvider } from "./providers/mac-pngpaste.js";
|
|
6
|
+
import { PowerShellFormsProvider } from "./providers/powershell-forms.js";
|
|
7
|
+
import { ClipboardProviderRegistry } from "./providers/registry.js";
|
|
8
|
+
import { WlPasteProvider } from "./providers/wl-paste.js";
|
|
9
|
+
import { XclipProvider } from "./providers/xclip.js";
|
|
10
|
+
import type { ClipboardImage } from "./types.js";
|
|
11
|
+
|
|
12
|
+
export { hasGraphicalSession } from "./shell-environment.js";
|
|
13
|
+
|
|
14
|
+
export function buildDefaultClipboardProviderRegistry(
|
|
15
|
+
platform: NodeJS.Platform,
|
|
16
|
+
environment: NodeJS.ProcessEnv,
|
|
17
|
+
): ClipboardProviderRegistry {
|
|
18
|
+
const registry = new ClipboardProviderRegistry();
|
|
19
|
+
|
|
20
|
+
if (platform === "win32") {
|
|
21
|
+
registry
|
|
22
|
+
.register(new NativeModuleProvider({ priority: 10 }))
|
|
23
|
+
.register(new PowerShellFormsProvider({ priority: 20 }));
|
|
24
|
+
return registry;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (platform === "linux") {
|
|
28
|
+
const waylandFirst = isWaylandSession(environment);
|
|
29
|
+
registry
|
|
30
|
+
.register(new WlPasteProvider({ priority: waylandFirst ? 10 : 20 }))
|
|
31
|
+
.register(new XclipProvider({ priority: waylandFirst ? 20 : 10 }))
|
|
32
|
+
.register(new NativeModuleProvider({ priority: 30 }));
|
|
33
|
+
return registry;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (platform === "darwin") {
|
|
37
|
+
registry
|
|
38
|
+
.register(new PngpasteProvider({ priority: 10 }))
|
|
39
|
+
.register(new OsascriptPublicPngProvider({ priority: 20 }))
|
|
40
|
+
.register(new OsascriptPngfProvider({ priority: 30 }))
|
|
41
|
+
.register(new NativeModuleProvider({ priority: 40 }));
|
|
42
|
+
return registry;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
registry.register(new NativeModuleProvider({ priority: 100 }));
|
|
46
|
+
return registry;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getUnavailableReaderMessage(platform: NodeJS.Platform): string {
|
|
50
|
+
switch (platform) {
|
|
51
|
+
case "linux":
|
|
52
|
+
return "No Linux clipboard image reader is available. Install wl-clipboard or xclip, or ensure @mariozechner/clipboard is installed.";
|
|
53
|
+
case "darwin":
|
|
54
|
+
return "No macOS clipboard image reader is available. Install pngpaste, ensure osascript is available, or ensure @mariozechner/clipboard is installed.";
|
|
55
|
+
case "win32":
|
|
56
|
+
return "No Windows clipboard image reader is available. Ensure PowerShell is available or @mariozechner/clipboard is installed.";
|
|
57
|
+
default:
|
|
58
|
+
return `Clipboard image paste is not supported on platform: ${platform}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function readClipboardImage(options?: {
|
|
63
|
+
environment?: NodeJS.ProcessEnv;
|
|
64
|
+
platform?: NodeJS.Platform;
|
|
65
|
+
registry?: ClipboardProviderRegistry;
|
|
66
|
+
}): Promise<ClipboardImage | null> {
|
|
67
|
+
const environment = options?.environment ?? process.env;
|
|
68
|
+
const platform = options?.platform ?? process.platform;
|
|
69
|
+
|
|
70
|
+
if (environment.TERMUX_VERSION) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!hasGraphicalSession(platform, environment)) {
|
|
75
|
+
throw new Error("Clipboard image paste requires a graphical desktop session with DISPLAY or WAYLAND_DISPLAY.");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const registry = options?.registry ?? buildDefaultClipboardProviderRegistry(platform, environment);
|
|
79
|
+
const result = await registry.read({ platform, environment });
|
|
80
|
+
if (result.image) {
|
|
81
|
+
return result.image;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (result.attempts.some((attempt) => attempt.available)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
throw new Error(getUnavailableReaderMessage(platform));
|
|
89
|
+
}
|