@mariozechner/pi-coding-agent 0.57.1 → 0.58.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 +64 -0
- package/README.md +10 -6
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +7 -6
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +9 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +57 -9
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/wrapper.d.ts +4 -11
- package/dist/core/extensions/wrapper.d.ts.map +1 -1
- package/dist/core/extensions/wrapper.js +4 -78
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +26 -9
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/package-manager.d.ts +4 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +60 -27
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +43 -8
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/session-manager.d.ts +1 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +3 -2
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +29 -8
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +8 -17
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +6 -3
- package/dist/core/tools/find.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +12 -3
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +4 -4
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +94 -11
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +16 -15
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/exif-orientation.d.ts +5 -0
- package/dist/utils/exif-orientation.d.ts.map +1 -0
- package/dist/utils/exif-orientation.js +158 -0
- package/dist/utils/exif-orientation.js.map +1 -0
- package/dist/utils/image-convert.d.ts.map +1 -1
- package/dist/utils/image-convert.js +5 -1
- package/dist/utils/image-convert.js.map +1 -1
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +6 -1
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +5 -4
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/custom-provider.md +6 -2
- package/docs/extensions.md +16 -5
- package/docs/models.md +13 -1
- package/docs/packages.md +9 -0
- package/docs/settings.md +9 -0
- package/docs/terminal-setup.md +10 -2
- package/examples/extensions/antigravity-image-gen.ts +5 -4
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +2 -2
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/preset.ts +2 -3
- package/examples/extensions/sandbox/index.ts +2 -3
- package/examples/extensions/tool-override.ts +2 -3
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard-image.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"clipboard-image.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AASF,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE9E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAazE;AAmND,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA2CjC","sourcesContent":["import { spawnSync } from \"child_process\";\nimport { randomUUID } from \"crypto\";\nimport { readFileSync, unlinkSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { clipboard } from \"./clipboard-native.js\";\nimport { loadPhoton } from \"./photon.js\";\n\nexport type ClipboardImage = {\n\tbytes: Uint8Array;\n\tmimeType: string;\n};\n\nconst SUPPORTED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_READ_TIMEOUT_MS = 3000;\nconst DEFAULT_POWERSHELL_TIMEOUT_MS = 5000;\nconst DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;\n\nexport function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tswitch (baseMimeType(mimeType)) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) {\n\t\t\treturn match.raw;\n\t\t}\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction isSupportedImageMimeType(mimeType: string): boolean {\n\tconst base = baseMimeType(mimeType);\n\treturn SUPPORTED_IMAGE_MIME_TYPES.some((t) => t === base);\n}\n\n/**\n * Convert unsupported image formats to PNG using Photon.\n * Returns null if conversion is unavailable or fails.\n */\nasync function convertToPng(bytes: Uint8Array): Promise<Uint8Array | null> {\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst image = photon.PhotonImage.new_from_byteslice(bytes);\n\t\ttry {\n\t\t\treturn image.get_bytes();\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions?: { timeoutMs?: number; maxBufferBytes?: number; env?: NodeJS.ProcessEnv },\n): { stdout: Buffer; ok: boolean } {\n\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;\n\tconst maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;\n\n\tconst result = spawnSync(command, args, {\n\t\ttimeout: timeoutMs,\n\t\tmaxBuffer: maxBufferBytes,\n\t\tenv: options?.env,\n\t});\n\n\tif (result.error) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tif (result.status !== 0) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tconst stdout = Buffer.isBuffer(result.stdout)\n\t\t? result.stdout\n\t\t: Buffer.from(result.stdout ?? \"\", typeof result.stdout === \"string\" ? \"utf-8\" : undefined);\n\n\treturn { ok: true, stdout };\n}\n\nfunction readClipboardImageViaWlPaste(): ClipboardImage | null {\n\tconst list = runCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (!list.ok) {\n\t\treturn null;\n\t}\n\n\tconst types = list.stdout\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) {\n\t\treturn null;\n\t}\n\n\tconst data = runCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (!data.ok || data.stdout.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn { bytes: data.stdout, mimeType: baseMimeType(selectedType) };\n}\n\nfunction isWSL(env: NodeJS.ProcessEnv = process.env): boolean {\n\tif (env.WSL_DISTRO_NAME || env.WSLENV) {\n\t\treturn true;\n\t}\n\n\ttry {\n\t\tconst release = readFileSync(\"/proc/version\", \"utf-8\");\n\t\treturn /microsoft|wsl/i.test(release);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * On WSL, the Linux clipboard (Wayland/X11) does not receive image data from\n * Windows screenshots (Win+Shift+S). PowerShell can access the Windows clipboard\n * directly, so we use it as a fallback.\n */\nfunction readClipboardImageViaPowerShell(): ClipboardImage | null {\n\tconst tmpFile = join(tmpdir(), `pi-wsl-clip-${randomUUID()}.png`);\n\n\ttry {\n\t\tconst winPathResult = runCommand(\"wslpath\", [\"-w\", tmpFile], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\t\tif (!winPathResult.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst winPath = winPathResult.stdout.toString(\"utf-8\").trim();\n\t\tif (!winPath) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst psScript = [\n\t\t\t\"Add-Type -AssemblyName System.Windows.Forms\",\n\t\t\t\"Add-Type -AssemblyName System.Drawing\",\n\t\t\t\"$path = $env:PI_WSL_CLIPBOARD_IMAGE_PATH\",\n\t\t\t\"$img = [System.Windows.Forms.Clipboard]::GetImage()\",\n\t\t\t\"if ($img) { $img.Save($path, [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'ok' } else { Write-Output 'empty' }\",\n\t\t].join(\"; \");\n\n\t\tconst result = runCommand(\"powershell.exe\", [\"-NoProfile\", \"-Command\", psScript], {\n\t\t\ttimeoutMs: DEFAULT_POWERSHELL_TIMEOUT_MS,\n\t\t\tenv: { ...process.env, PI_WSL_CLIPBOARD_IMAGE_PATH: winPath },\n\t\t});\n\t\tif (!result.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst output = result.stdout.toString(\"utf-8\").trim();\n\t\tif (output !== \"ok\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { bytes: new Uint8Array(bytes), mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors.\n\t\t}\n\t}\n}\n\nfunction readClipboardImageViaXclip(): ClipboardImage | null {\n\tconst targets = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets.ok) {\n\t\tcandidateTypes = targets.stdout\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;\n\tconst tryTypes = preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : [...SUPPORTED_IMAGE_MIME_TYPES];\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data.ok && data.stdout.length > 0) {\n\t\t\treturn { bytes: data.stdout, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\nasync function readClipboardImageViaNativeClipboard(): Promise<ClipboardImage | null> {\n\tif (!clipboard || !clipboard.hasImage()) {\n\t\treturn null;\n\t}\n\n\tconst imageData = await clipboard.getImageBinary();\n\tif (!imageData || imageData.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);\n\treturn { bytes, mimeType: \"image/png\" };\n}\n\nexport async function readClipboardImage(options?: {\n\tenv?: NodeJS.ProcessEnv;\n\tplatform?: NodeJS.Platform;\n}): Promise<ClipboardImage | null> {\n\tconst env = options?.env ?? process.env;\n\tconst platform = options?.platform ?? process.platform;\n\n\tif (env.TERMUX_VERSION) {\n\t\treturn null;\n\t}\n\n\tlet image: ClipboardImage | null = null;\n\n\tif (platform === \"linux\") {\n\t\tconst wsl = isWSL(env);\n\t\tconst wayland = isWaylandSession(env);\n\n\t\tif (wayland || wsl) {\n\t\t\timage = readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();\n\t\t}\n\n\t\tif (!image && wsl) {\n\t\t\timage = readClipboardImageViaPowerShell();\n\t\t}\n\n\t\tif (!image && !wayland) {\n\t\t\timage = await readClipboardImageViaNativeClipboard();\n\t\t}\n\t} else {\n\t\timage = await readClipboardImageViaNativeClipboard();\n\t}\n\n\tif (!image) {\n\t\treturn null;\n\t}\n\n\t// Convert unsupported formats (e.g., BMP from WSLg) to PNG\n\tif (!isSupportedImageMimeType(image.mimeType)) {\n\t\tconst pngBytes = await convertToPng(image.bytes);\n\t\tif (!pngBytes) {\n\t\t\treturn null;\n\t\t}\n\t\treturn { bytes: pngBytes, mimeType: \"image/png\" };\n\t}\n\n\treturn image;\n}\n"]}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { spawnSync } from "child_process";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { readFileSync, unlinkSync } from "fs";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { join } from "path";
|
|
2
6
|
import { clipboard } from "./clipboard-native.js";
|
|
3
7
|
import { loadPhoton } from "./photon.js";
|
|
4
8
|
const SUPPORTED_IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
|
5
9
|
const DEFAULT_LIST_TIMEOUT_MS = 1000;
|
|
6
10
|
const DEFAULT_READ_TIMEOUT_MS = 3000;
|
|
11
|
+
const DEFAULT_POWERSHELL_TIMEOUT_MS = 5000;
|
|
7
12
|
const DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
8
13
|
export function isWaylandSession(env = process.env) {
|
|
9
14
|
return Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === "wayland";
|
|
@@ -71,6 +76,7 @@ function runCommand(command, args, options) {
|
|
|
71
76
|
const result = spawnSync(command, args, {
|
|
72
77
|
timeout: timeoutMs,
|
|
73
78
|
maxBuffer: maxBufferBytes,
|
|
79
|
+
env: options?.env,
|
|
74
80
|
});
|
|
75
81
|
if (result.error) {
|
|
76
82
|
return { ok: false, stdout: Buffer.alloc(0) };
|
|
@@ -103,6 +109,70 @@ function readClipboardImageViaWlPaste() {
|
|
|
103
109
|
}
|
|
104
110
|
return { bytes: data.stdout, mimeType: baseMimeType(selectedType) };
|
|
105
111
|
}
|
|
112
|
+
function isWSL(env = process.env) {
|
|
113
|
+
if (env.WSL_DISTRO_NAME || env.WSLENV) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const release = readFileSync("/proc/version", "utf-8");
|
|
118
|
+
return /microsoft|wsl/i.test(release);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* On WSL, the Linux clipboard (Wayland/X11) does not receive image data from
|
|
126
|
+
* Windows screenshots (Win+Shift+S). PowerShell can access the Windows clipboard
|
|
127
|
+
* directly, so we use it as a fallback.
|
|
128
|
+
*/
|
|
129
|
+
function readClipboardImageViaPowerShell() {
|
|
130
|
+
const tmpFile = join(tmpdir(), `pi-wsl-clip-${randomUUID()}.png`);
|
|
131
|
+
try {
|
|
132
|
+
const winPathResult = runCommand("wslpath", ["-w", tmpFile], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });
|
|
133
|
+
if (!winPathResult.ok) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const winPath = winPathResult.stdout.toString("utf-8").trim();
|
|
137
|
+
if (!winPath) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const psScript = [
|
|
141
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
142
|
+
"Add-Type -AssemblyName System.Drawing",
|
|
143
|
+
"$path = $env:PI_WSL_CLIPBOARD_IMAGE_PATH",
|
|
144
|
+
"$img = [System.Windows.Forms.Clipboard]::GetImage()",
|
|
145
|
+
"if ($img) { $img.Save($path, [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'ok' } else { Write-Output 'empty' }",
|
|
146
|
+
].join("; ");
|
|
147
|
+
const result = runCommand("powershell.exe", ["-NoProfile", "-Command", psScript], {
|
|
148
|
+
timeoutMs: DEFAULT_POWERSHELL_TIMEOUT_MS,
|
|
149
|
+
env: { ...process.env, PI_WSL_CLIPBOARD_IMAGE_PATH: winPath },
|
|
150
|
+
});
|
|
151
|
+
if (!result.ok) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
const output = result.stdout.toString("utf-8").trim();
|
|
155
|
+
if (output !== "ok") {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
const bytes = readFileSync(tmpFile);
|
|
159
|
+
if (bytes.length === 0) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
return { bytes: new Uint8Array(bytes), mimeType: "image/png" };
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
try {
|
|
169
|
+
unlinkSync(tmpFile);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// Ignore cleanup errors.
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
106
176
|
function readClipboardImageViaXclip() {
|
|
107
177
|
const targets = runCommand("xclip", ["-selection", "clipboard", "-t", "TARGETS", "-o"], {
|
|
108
178
|
timeoutMs: DEFAULT_LIST_TIMEOUT_MS,
|
|
@@ -125,6 +195,17 @@ function readClipboardImageViaXclip() {
|
|
|
125
195
|
}
|
|
126
196
|
return null;
|
|
127
197
|
}
|
|
198
|
+
async function readClipboardImageViaNativeClipboard() {
|
|
199
|
+
if (!clipboard || !clipboard.hasImage()) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
const imageData = await clipboard.getImageBinary();
|
|
203
|
+
if (!imageData || imageData.length === 0) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);
|
|
207
|
+
return { bytes, mimeType: "image/png" };
|
|
208
|
+
}
|
|
128
209
|
export async function readClipboardImage(options) {
|
|
129
210
|
const env = options?.env ?? process.env;
|
|
130
211
|
const platform = options?.platform ?? process.platform;
|
|
@@ -132,19 +213,21 @@ export async function readClipboardImage(options) {
|
|
|
132
213
|
return null;
|
|
133
214
|
}
|
|
134
215
|
let image = null;
|
|
135
|
-
if (platform === "linux"
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return null;
|
|
216
|
+
if (platform === "linux") {
|
|
217
|
+
const wsl = isWSL(env);
|
|
218
|
+
const wayland = isWaylandSession(env);
|
|
219
|
+
if (wayland || wsl) {
|
|
220
|
+
image = readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();
|
|
141
221
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
222
|
+
if (!image && wsl) {
|
|
223
|
+
image = readClipboardImageViaPowerShell();
|
|
224
|
+
}
|
|
225
|
+
if (!image && !wayland) {
|
|
226
|
+
image = await readClipboardImageViaNativeClipboard();
|
|
145
227
|
}
|
|
146
|
-
|
|
147
|
-
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
image = await readClipboardImageViaNativeClipboard();
|
|
148
231
|
}
|
|
149
232
|
if (!image) {
|
|
150
233
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard-image.js","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from \"child_process\";\n\nimport { clipboard } from \"./clipboard-native.js\";\nimport { loadPhoton } from \"./photon.js\";\n\nexport type ClipboardImage = {\n\tbytes: Uint8Array;\n\tmimeType: string;\n};\n\nconst SUPPORTED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_READ_TIMEOUT_MS = 3000;\nconst DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;\n\nexport function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tswitch (baseMimeType(mimeType)) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) {\n\t\t\treturn match.raw;\n\t\t}\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction isSupportedImageMimeType(mimeType: string): boolean {\n\tconst base = baseMimeType(mimeType);\n\treturn SUPPORTED_IMAGE_MIME_TYPES.some((t) => t === base);\n}\n\n/**\n * Convert unsupported image formats to PNG using Photon.\n * Returns null if conversion is unavailable or fails.\n */\nasync function convertToPng(bytes: Uint8Array): Promise<Uint8Array | null> {\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst image = photon.PhotonImage.new_from_byteslice(bytes);\n\t\ttry {\n\t\t\treturn image.get_bytes();\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions?: { timeoutMs?: number; maxBufferBytes?: number },\n): { stdout: Buffer; ok: boolean } {\n\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;\n\tconst maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;\n\n\tconst result = spawnSync(command, args, {\n\t\ttimeout: timeoutMs,\n\t\tmaxBuffer: maxBufferBytes,\n\t});\n\n\tif (result.error) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tif (result.status !== 0) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tconst stdout = Buffer.isBuffer(result.stdout)\n\t\t? result.stdout\n\t\t: Buffer.from(result.stdout ?? \"\", typeof result.stdout === \"string\" ? \"utf-8\" : undefined);\n\n\treturn { ok: true, stdout };\n}\n\nfunction readClipboardImageViaWlPaste(): ClipboardImage | null {\n\tconst list = runCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (!list.ok) {\n\t\treturn null;\n\t}\n\n\tconst types = list.stdout\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) {\n\t\treturn null;\n\t}\n\n\tconst data = runCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (!data.ok || data.stdout.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn { bytes: data.stdout, mimeType: baseMimeType(selectedType) };\n}\n\nfunction readClipboardImageViaXclip(): ClipboardImage | null {\n\tconst targets = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets.ok) {\n\t\tcandidateTypes = targets.stdout\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;\n\tconst tryTypes = preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : [...SUPPORTED_IMAGE_MIME_TYPES];\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data.ok && data.stdout.length > 0) {\n\t\t\treturn { bytes: data.stdout, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\nexport async function readClipboardImage(options?: {\n\tenv?: NodeJS.ProcessEnv;\n\tplatform?: NodeJS.Platform;\n}): Promise<ClipboardImage | null> {\n\tconst env = options?.env ?? process.env;\n\tconst platform = options?.platform ?? process.platform;\n\n\tif (env.TERMUX_VERSION) {\n\t\treturn null;\n\t}\n\n\tlet image: ClipboardImage | null = null;\n\n\tif (platform === \"linux\" && isWaylandSession(env)) {\n\t\timage = readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();\n\t} else {\n\t\tif (!clipboard || !clipboard.hasImage()) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst imageData = await clipboard.getImageBinary();\n\t\tif (!imageData || imageData.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);\n\t\timage = { bytes, mimeType: \"image/png\" };\n\t}\n\n\tif (!image) {\n\t\treturn null;\n\t}\n\n\t// Convert unsupported formats (e.g., BMP from WSLg) to PNG\n\tif (!isSupportedImageMimeType(image.mimeType)) {\n\t\tconst pngBytes = await convertToPng(image.bytes);\n\t\tif (!pngBytes) {\n\t\t\treturn null;\n\t\t}\n\t\treturn { bytes: pngBytes, mimeType: \"image/png\" };\n\t}\n\n\treturn image;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"clipboard-image.js","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from \"child_process\";\nimport { randomUUID } from \"crypto\";\nimport { readFileSync, unlinkSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { clipboard } from \"./clipboard-native.js\";\nimport { loadPhoton } from \"./photon.js\";\n\nexport type ClipboardImage = {\n\tbytes: Uint8Array;\n\tmimeType: string;\n};\n\nconst SUPPORTED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_READ_TIMEOUT_MS = 3000;\nconst DEFAULT_POWERSHELL_TIMEOUT_MS = 5000;\nconst DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;\n\nexport function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tswitch (baseMimeType(mimeType)) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) {\n\t\t\treturn match.raw;\n\t\t}\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction isSupportedImageMimeType(mimeType: string): boolean {\n\tconst base = baseMimeType(mimeType);\n\treturn SUPPORTED_IMAGE_MIME_TYPES.some((t) => t === base);\n}\n\n/**\n * Convert unsupported image formats to PNG using Photon.\n * Returns null if conversion is unavailable or fails.\n */\nasync function convertToPng(bytes: Uint8Array): Promise<Uint8Array | null> {\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst image = photon.PhotonImage.new_from_byteslice(bytes);\n\t\ttry {\n\t\t\treturn image.get_bytes();\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions?: { timeoutMs?: number; maxBufferBytes?: number; env?: NodeJS.ProcessEnv },\n): { stdout: Buffer; ok: boolean } {\n\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;\n\tconst maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;\n\n\tconst result = spawnSync(command, args, {\n\t\ttimeout: timeoutMs,\n\t\tmaxBuffer: maxBufferBytes,\n\t\tenv: options?.env,\n\t});\n\n\tif (result.error) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tif (result.status !== 0) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tconst stdout = Buffer.isBuffer(result.stdout)\n\t\t? result.stdout\n\t\t: Buffer.from(result.stdout ?? \"\", typeof result.stdout === \"string\" ? \"utf-8\" : undefined);\n\n\treturn { ok: true, stdout };\n}\n\nfunction readClipboardImageViaWlPaste(): ClipboardImage | null {\n\tconst list = runCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (!list.ok) {\n\t\treturn null;\n\t}\n\n\tconst types = list.stdout\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) {\n\t\treturn null;\n\t}\n\n\tconst data = runCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (!data.ok || data.stdout.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn { bytes: data.stdout, mimeType: baseMimeType(selectedType) };\n}\n\nfunction isWSL(env: NodeJS.ProcessEnv = process.env): boolean {\n\tif (env.WSL_DISTRO_NAME || env.WSLENV) {\n\t\treturn true;\n\t}\n\n\ttry {\n\t\tconst release = readFileSync(\"/proc/version\", \"utf-8\");\n\t\treturn /microsoft|wsl/i.test(release);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * On WSL, the Linux clipboard (Wayland/X11) does not receive image data from\n * Windows screenshots (Win+Shift+S). PowerShell can access the Windows clipboard\n * directly, so we use it as a fallback.\n */\nfunction readClipboardImageViaPowerShell(): ClipboardImage | null {\n\tconst tmpFile = join(tmpdir(), `pi-wsl-clip-${randomUUID()}.png`);\n\n\ttry {\n\t\tconst winPathResult = runCommand(\"wslpath\", [\"-w\", tmpFile], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\t\tif (!winPathResult.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst winPath = winPathResult.stdout.toString(\"utf-8\").trim();\n\t\tif (!winPath) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst psScript = [\n\t\t\t\"Add-Type -AssemblyName System.Windows.Forms\",\n\t\t\t\"Add-Type -AssemblyName System.Drawing\",\n\t\t\t\"$path = $env:PI_WSL_CLIPBOARD_IMAGE_PATH\",\n\t\t\t\"$img = [System.Windows.Forms.Clipboard]::GetImage()\",\n\t\t\t\"if ($img) { $img.Save($path, [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'ok' } else { Write-Output 'empty' }\",\n\t\t].join(\"; \");\n\n\t\tconst result = runCommand(\"powershell.exe\", [\"-NoProfile\", \"-Command\", psScript], {\n\t\t\ttimeoutMs: DEFAULT_POWERSHELL_TIMEOUT_MS,\n\t\t\tenv: { ...process.env, PI_WSL_CLIPBOARD_IMAGE_PATH: winPath },\n\t\t});\n\t\tif (!result.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst output = result.stdout.toString(\"utf-8\").trim();\n\t\tif (output !== \"ok\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { bytes: new Uint8Array(bytes), mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors.\n\t\t}\n\t}\n}\n\nfunction readClipboardImageViaXclip(): ClipboardImage | null {\n\tconst targets = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets.ok) {\n\t\tcandidateTypes = targets.stdout\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;\n\tconst tryTypes = preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : [...SUPPORTED_IMAGE_MIME_TYPES];\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data.ok && data.stdout.length > 0) {\n\t\t\treturn { bytes: data.stdout, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\nasync function readClipboardImageViaNativeClipboard(): Promise<ClipboardImage | null> {\n\tif (!clipboard || !clipboard.hasImage()) {\n\t\treturn null;\n\t}\n\n\tconst imageData = await clipboard.getImageBinary();\n\tif (!imageData || imageData.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);\n\treturn { bytes, mimeType: \"image/png\" };\n}\n\nexport async function readClipboardImage(options?: {\n\tenv?: NodeJS.ProcessEnv;\n\tplatform?: NodeJS.Platform;\n}): Promise<ClipboardImage | null> {\n\tconst env = options?.env ?? process.env;\n\tconst platform = options?.platform ?? process.platform;\n\n\tif (env.TERMUX_VERSION) {\n\t\treturn null;\n\t}\n\n\tlet image: ClipboardImage | null = null;\n\n\tif (platform === \"linux\") {\n\t\tconst wsl = isWSL(env);\n\t\tconst wayland = isWaylandSession(env);\n\n\t\tif (wayland || wsl) {\n\t\t\timage = readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();\n\t\t}\n\n\t\tif (!image && wsl) {\n\t\t\timage = readClipboardImageViaPowerShell();\n\t\t}\n\n\t\tif (!image && !wayland) {\n\t\t\timage = await readClipboardImageViaNativeClipboard();\n\t\t}\n\t} else {\n\t\timage = await readClipboardImageViaNativeClipboard();\n\t}\n\n\tif (!image) {\n\t\treturn null;\n\t}\n\n\t// Convert unsupported formats (e.g., BMP from WSLg) to PNG\n\tif (!isSupportedImageMimeType(image.mimeType)) {\n\t\tconst pngBytes = await convertToPng(image.bytes);\n\t\tif (!pngBytes) {\n\t\t\treturn null;\n\t\t}\n\t\treturn { bytes: pngBytes, mimeType: \"image/png\" };\n\t}\n\n\treturn image;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAkBA,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAoDlD","sourcesContent":["import { execSync, spawn } from \"child_process\";\nimport { platform } from \"os\";\nimport { isWaylandSession } from \"./clipboard-image.js\";\n\ntype NativeClipboardExecOptions = {\n\tinput: string;\n\ttimeout: number;\n\tstdio: [\"pipe\", \"ignore\", \"ignore\"];\n};\n\nfunction copyToX11Clipboard(options: NativeClipboardExecOptions): void {\n\ttry {\n\t\texecSync(\"xclip -selection clipboard\", options);\n\t} catch {\n\t\texecSync(\"xsel --clipboard --input\", options);\n\t}\n}\n\nexport function copyToClipboard(text: string): void {\n\t// Always emit OSC 52 - works over SSH/mosh, harmless locally\n\tconst encoded = Buffer.from(text).toString(\"base64\");\n\tprocess.stdout.write(`\\x1b]52;c;${encoded}\\x07`);\n\n\t// Also try native tools (best effort for local sessions)\n\tconst p = platform();\n\tconst options: NativeClipboardExecOptions = { input: text, timeout: 5000, stdio: [\"pipe\", \"ignore\", \"ignore\"] };\n\n\ttry {\n\t\tif (p === \"darwin\") {\n\t\t\texecSync(\"pbcopy\", options);\n\t\t} else if (p === \"win32\") {\n\t\t\texecSync(\"clip\", options);\n\t\t} else {\n\t\t\t// Linux. Try Termux, Wayland, or X11 clipboard tools.\n\t\t\tif (process.env.TERMUX_VERSION) {\n\t\t\t\ttry {\n\t\t\t\t\texecSync(\"termux-clipboard-set\", options);\n\t\t\t\t\treturn;\n\t\t\t\t} catch {\n\t\t\t\t\t// Fall back to Wayland or X11 tools.\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY);\n\t\t\tconst hasX11Display = Boolean(process.env.DISPLAY);\n\t\t\tconst isWayland = isWaylandSession();\n\t\t\tif (isWayland && hasWaylandDisplay) {\n\t\t\t\ttry {\n\t\t\t\t\t// Verify wl-copy exists (spawn errors are async and won't be caught)\n\t\t\t\t\texecSync(\"which wl-copy\", { stdio: \"ignore\" });\n\t\t\t\t\t// wl-copy with execSync hangs due to fork behavior; use spawn instead\n\t\t\t\t\tconst proc = spawn(\"wl-copy\", [], { stdio: [\"pipe\", \"ignore\", \"ignore\"] });\n\t\t\t\t\tproc.stdin.on(\"error\", () => {\n\t\t\t\t\t\t// Ignore EPIPE errors if wl-copy exits early\n\t\t\t\t\t});\n\t\t\t\t\tproc.stdin.write(text);\n\t\t\t\t\tproc.stdin.end();\n\t\t\t\t\tproc.unref();\n\t\t\t\t} catch {\n\t\t\t\t\tif (hasX11Display) {\n\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (hasX11Display) {\n\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore - OSC 52 already emitted as fallback\n\t}\n}\n"]}
|
package/dist/utils/clipboard.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { execSync, spawn } from "child_process";
|
|
2
2
|
import { platform } from "os";
|
|
3
3
|
import { isWaylandSession } from "./clipboard-image.js";
|
|
4
|
+
function copyToX11Clipboard(options) {
|
|
5
|
+
try {
|
|
6
|
+
execSync("xclip -selection clipboard", options);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
execSync("xsel --clipboard --input", options);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
4
12
|
export function copyToClipboard(text) {
|
|
5
13
|
// Always emit OSC 52 - works over SSH/mosh, harmless locally
|
|
6
14
|
const encoded = Buffer.from(text).toString("base64");
|
|
7
15
|
process.stdout.write(`\x1b]52;c;${encoded}\x07`);
|
|
8
16
|
// Also try native tools (best effort for local sessions)
|
|
9
17
|
const p = platform();
|
|
10
|
-
const options = { input: text, timeout: 5000 };
|
|
18
|
+
const options = { input: text, timeout: 5000, stdio: ["pipe", "ignore", "ignore"] };
|
|
11
19
|
try {
|
|
12
20
|
if (p === "darwin") {
|
|
13
21
|
execSync("pbcopy", options);
|
|
@@ -26,8 +34,10 @@ export function copyToClipboard(text) {
|
|
|
26
34
|
// Fall back to Wayland or X11 tools.
|
|
27
35
|
}
|
|
28
36
|
}
|
|
37
|
+
const hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY);
|
|
38
|
+
const hasX11Display = Boolean(process.env.DISPLAY);
|
|
29
39
|
const isWayland = isWaylandSession();
|
|
30
|
-
if (isWayland) {
|
|
40
|
+
if (isWayland && hasWaylandDisplay) {
|
|
31
41
|
try {
|
|
32
42
|
// Verify wl-copy exists (spawn errors are async and won't be caught)
|
|
33
43
|
execSync("which wl-copy", { stdio: "ignore" });
|
|
@@ -41,22 +51,13 @@ export function copyToClipboard(text) {
|
|
|
41
51
|
proc.unref();
|
|
42
52
|
}
|
|
43
53
|
catch {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
execSync("xclip -selection clipboard", options);
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
execSync("xsel --clipboard --input", options);
|
|
54
|
+
if (hasX11Display) {
|
|
55
|
+
copyToX11Clipboard(options);
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
|
-
else {
|
|
54
|
-
|
|
55
|
-
execSync("xclip -selection clipboard", options);
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
execSync("xsel --clipboard --input", options);
|
|
59
|
-
}
|
|
59
|
+
else if (hasX11Display) {
|
|
60
|
+
copyToX11Clipboard(options);
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAQxD,SAAS,kBAAkB,CAAC,OAAmC,EAAQ;IACtE,IAAI,CAAC;QACJ,QAAQ,CAAC,4BAA4B,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACR,QAAQ,CAAC,0BAA0B,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;AAAA,CACD;AAED,MAAM,UAAU,eAAe,CAAC,IAAY,EAAQ;IACnD,6DAA6D;IAC7D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,OAAO,MAAM,CAAC,CAAC;IAEjD,yDAAyD;IACzD,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC;IACrB,MAAM,OAAO,GAA+B,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;IAEhH,IAAI,CAAC;QACJ,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;YACpB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC7B,CAAC;aAAM,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC;YAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC3B,CAAC;aAAM,CAAC;YACP,sDAAsD;YACtD,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;gBAChC,IAAI,CAAC;oBACJ,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;oBAC1C,OAAO;gBACR,CAAC;gBAAC,MAAM,CAAC;oBACR,qCAAqC;gBACtC,CAAC;YACF,CAAC;YAED,MAAM,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YAC/D,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACnD,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;YACrC,IAAI,SAAS,IAAI,iBAAiB,EAAE,CAAC;gBACpC,IAAI,CAAC;oBACJ,qEAAqE;oBACrE,QAAQ,CAAC,eAAe,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;oBAC/C,sEAAsE;oBACtE,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;oBAC3E,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC;wBAC5B,6CAA6C;oBADhB,CAE7B,CAAC,CAAC;oBACH,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBACvB,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;oBACjB,IAAI,CAAC,KAAK,EAAE,CAAC;gBACd,CAAC;gBAAC,MAAM,CAAC;oBACR,IAAI,aAAa,EAAE,CAAC;wBACnB,kBAAkB,CAAC,OAAO,CAAC,CAAC;oBAC7B,CAAC;gBACF,CAAC;YACF,CAAC;iBAAM,IAAI,aAAa,EAAE,CAAC;gBAC1B,kBAAkB,CAAC,OAAO,CAAC,CAAC;YAC7B,CAAC;QACF,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,8CAA8C;IAC/C,CAAC;AAAA,CACD","sourcesContent":["import { execSync, spawn } from \"child_process\";\nimport { platform } from \"os\";\nimport { isWaylandSession } from \"./clipboard-image.js\";\n\ntype NativeClipboardExecOptions = {\n\tinput: string;\n\ttimeout: number;\n\tstdio: [\"pipe\", \"ignore\", \"ignore\"];\n};\n\nfunction copyToX11Clipboard(options: NativeClipboardExecOptions): void {\n\ttry {\n\t\texecSync(\"xclip -selection clipboard\", options);\n\t} catch {\n\t\texecSync(\"xsel --clipboard --input\", options);\n\t}\n}\n\nexport function copyToClipboard(text: string): void {\n\t// Always emit OSC 52 - works over SSH/mosh, harmless locally\n\tconst encoded = Buffer.from(text).toString(\"base64\");\n\tprocess.stdout.write(`\\x1b]52;c;${encoded}\\x07`);\n\n\t// Also try native tools (best effort for local sessions)\n\tconst p = platform();\n\tconst options: NativeClipboardExecOptions = { input: text, timeout: 5000, stdio: [\"pipe\", \"ignore\", \"ignore\"] };\n\n\ttry {\n\t\tif (p === \"darwin\") {\n\t\t\texecSync(\"pbcopy\", options);\n\t\t} else if (p === \"win32\") {\n\t\t\texecSync(\"clip\", options);\n\t\t} else {\n\t\t\t// Linux. Try Termux, Wayland, or X11 clipboard tools.\n\t\t\tif (process.env.TERMUX_VERSION) {\n\t\t\t\ttry {\n\t\t\t\t\texecSync(\"termux-clipboard-set\", options);\n\t\t\t\t\treturn;\n\t\t\t\t} catch {\n\t\t\t\t\t// Fall back to Wayland or X11 tools.\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY);\n\t\t\tconst hasX11Display = Boolean(process.env.DISPLAY);\n\t\t\tconst isWayland = isWaylandSession();\n\t\t\tif (isWayland && hasWaylandDisplay) {\n\t\t\t\ttry {\n\t\t\t\t\t// Verify wl-copy exists (spawn errors are async and won't be caught)\n\t\t\t\t\texecSync(\"which wl-copy\", { stdio: \"ignore\" });\n\t\t\t\t\t// wl-copy with execSync hangs due to fork behavior; use spawn instead\n\t\t\t\t\tconst proc = spawn(\"wl-copy\", [], { stdio: [\"pipe\", \"ignore\", \"ignore\"] });\n\t\t\t\t\tproc.stdin.on(\"error\", () => {\n\t\t\t\t\t\t// Ignore EPIPE errors if wl-copy exits early\n\t\t\t\t\t});\n\t\t\t\t\tproc.stdin.write(text);\n\t\t\t\t\tproc.stdin.end();\n\t\t\t\t\tproc.unref();\n\t\t\t\t} catch {\n\t\t\t\t\tif (hasX11Display) {\n\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (hasX11Display) {\n\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore - OSC 52 already emitted as fallback\n\t}\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PhotonImageType } from "./photon.js";
|
|
2
|
+
type Photon = typeof import("@silvia-odwyer/photon-node");
|
|
3
|
+
export declare function applyExifOrientation(photon: Photon, image: PhotonImageType, originalBytes: Uint8Array): PhotonImageType;
|
|
4
|
+
export {};
|
|
5
|
+
//# sourceMappingURL=exif-orientation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exif-orientation.d.ts","sourceRoot":"","sources":["../../src/utils/exif-orientation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,KAAK,MAAM,GAAG,cAAc,4BAA4B,CAAC,CAAC;AAgJ1D,wBAAgB,oBAAoB,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,EACtB,aAAa,EAAE,UAAU,GACvB,eAAe,CAgCjB","sourcesContent":["import type { PhotonImageType } from \"./photon.js\";\n\ntype Photon = typeof import(\"@silvia-odwyer/photon-node\");\n\nfunction readOrientationFromTiff(bytes: Uint8Array, tiffStart: number): number {\n\tif (tiffStart + 8 > bytes.length) return 1;\n\n\tconst byteOrder = (bytes[tiffStart] << 8) | bytes[tiffStart + 1];\n\tconst le = byteOrder === 0x4949;\n\n\tconst read16 = (pos: number): number => {\n\t\tif (le) return bytes[pos] | (bytes[pos + 1] << 8);\n\t\treturn (bytes[pos] << 8) | bytes[pos + 1];\n\t};\n\n\tconst read32 = (pos: number): number => {\n\t\tif (le) return bytes[pos] | (bytes[pos + 1] << 8) | (bytes[pos + 2] << 16) | (bytes[pos + 3] << 24);\n\t\treturn ((bytes[pos] << 24) | (bytes[pos + 1] << 16) | (bytes[pos + 2] << 8) | bytes[pos + 3]) >>> 0;\n\t};\n\n\tconst ifdOffset = read32(tiffStart + 4);\n\tconst ifdStart = tiffStart + ifdOffset;\n\tif (ifdStart + 2 > bytes.length) return 1;\n\n\tconst entryCount = read16(ifdStart);\n\tfor (let i = 0; i < entryCount; i++) {\n\t\tconst entryPos = ifdStart + 2 + i * 12;\n\t\tif (entryPos + 12 > bytes.length) return 1;\n\n\t\tif (read16(entryPos) === 0x0112) {\n\t\t\tconst value = read16(entryPos + 8);\n\t\t\treturn value >= 1 && value <= 8 ? value : 1;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\nfunction findJpegTiffOffset(bytes: Uint8Array): number {\n\tlet offset = 2;\n\twhile (offset < bytes.length - 1) {\n\t\tif (bytes[offset] !== 0xff) return -1;\n\t\tconst marker = bytes[offset + 1];\n\t\tif (marker === 0xff) {\n\t\t\toffset++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (marker === 0xe1) {\n\t\t\tif (offset + 4 >= bytes.length) return -1;\n\t\t\tconst segmentStart = offset + 4;\n\t\t\tif (segmentStart + 6 > bytes.length) return -1;\n\t\t\tif (!hasExifHeader(bytes, segmentStart)) return -1;\n\t\t\treturn segmentStart + 6;\n\t\t}\n\n\t\tif (offset + 4 > bytes.length) return -1;\n\t\tconst length = (bytes[offset + 2] << 8) | bytes[offset + 3];\n\t\toffset += 2 + length;\n\t}\n\n\treturn -1;\n}\n\nfunction findWebpTiffOffset(bytes: Uint8Array): number {\n\tlet offset = 12;\n\twhile (offset + 8 <= bytes.length) {\n\t\tconst chunkId = String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);\n\t\tconst chunkSize =\n\t\t\tbytes[offset + 4] | (bytes[offset + 5] << 8) | (bytes[offset + 6] << 16) | (bytes[offset + 7] << 24);\n\t\tconst dataStart = offset + 8;\n\n\t\tif (chunkId === \"EXIF\") {\n\t\t\tif (dataStart + chunkSize > bytes.length) return -1;\n\t\t\t// Some WebP files have \"Exif\\0\\0\" prefix before the TIFF header\n\t\t\tconst tiffStart = chunkSize >= 6 && hasExifHeader(bytes, dataStart) ? dataStart + 6 : dataStart;\n\t\t\treturn tiffStart;\n\t\t}\n\n\t\t// RIFF chunks are padded to even size\n\t\toffset = dataStart + chunkSize + (chunkSize % 2);\n\t}\n\n\treturn -1;\n}\n\nfunction hasExifHeader(bytes: Uint8Array, offset: number): boolean {\n\treturn (\n\t\tbytes[offset] === 0x45 &&\n\t\tbytes[offset + 1] === 0x78 &&\n\t\tbytes[offset + 2] === 0x69 &&\n\t\tbytes[offset + 3] === 0x66 &&\n\t\tbytes[offset + 4] === 0x00 &&\n\t\tbytes[offset + 5] === 0x00\n\t);\n}\n\nfunction getExifOrientation(bytes: Uint8Array): number {\n\tlet tiffOffset = -1;\n\n\t// JPEG: starts with FF D8\n\tif (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xd8) {\n\t\ttiffOffset = findJpegTiffOffset(bytes);\n\t}\n\t// WebP: starts with RIFF....WEBP\n\telse if (\n\t\tbytes.length >= 12 &&\n\t\tbytes[0] === 0x52 &&\n\t\tbytes[1] === 0x49 &&\n\t\tbytes[2] === 0x46 &&\n\t\tbytes[3] === 0x46 &&\n\t\tbytes[8] === 0x57 &&\n\t\tbytes[9] === 0x45 &&\n\t\tbytes[10] === 0x42 &&\n\t\tbytes[11] === 0x50\n\t) {\n\t\ttiffOffset = findWebpTiffOffset(bytes);\n\t}\n\n\tif (tiffOffset === -1) return 1;\n\treturn readOrientationFromTiff(bytes, tiffOffset);\n}\n\ntype DstIndexFn = (x: number, y: number, w: number, h: number) => number;\n\nfunction rotate90(photon: Photon, image: PhotonImageType, dstIndex: DstIndexFn): PhotonImageType {\n\tconst w = image.get_width();\n\tconst h = image.get_height();\n\tconst src = image.get_raw_pixels();\n\tconst dst = new Uint8Array(src.length);\n\n\tfor (let y = 0; y < h; y++) {\n\t\tfor (let x = 0; x < w; x++) {\n\t\t\tconst srcIdx = (y * w + x) * 4;\n\t\t\tconst dstIdx = dstIndex(x, y, w, h) * 4;\n\t\t\tdst[dstIdx] = src[srcIdx];\n\t\t\tdst[dstIdx + 1] = src[srcIdx + 1];\n\t\t\tdst[dstIdx + 2] = src[srcIdx + 2];\n\t\t\tdst[dstIdx + 3] = src[srcIdx + 3];\n\t\t}\n\t}\n\n\treturn new photon.PhotonImage(dst, h, w);\n}\n\n// Flip orientations mutate in-place. Rotations return a new image (caller must free the old one if different).\nexport function applyExifOrientation(\n\tphoton: Photon,\n\timage: PhotonImageType,\n\toriginalBytes: Uint8Array,\n): PhotonImageType {\n\tconst orientation = getExifOrientation(originalBytes);\n\tif (orientation === 1) return image;\n\n\tswitch (orientation) {\n\t\tcase 2:\n\t\t\tphoton.fliph(image);\n\t\t\treturn image;\n\t\tcase 3:\n\t\t\tphoton.fliph(image);\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 4:\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 5: {\n\t\t\tconst rotated = rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 6:\n\t\t\treturn rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));\n\t\tcase 7: {\n\t\t\tconst rotated = rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 8:\n\t\t\treturn rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);\n\t\tdefault:\n\t\t\treturn image;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
function readOrientationFromTiff(bytes, tiffStart) {
|
|
2
|
+
if (tiffStart + 8 > bytes.length)
|
|
3
|
+
return 1;
|
|
4
|
+
const byteOrder = (bytes[tiffStart] << 8) | bytes[tiffStart + 1];
|
|
5
|
+
const le = byteOrder === 0x4949;
|
|
6
|
+
const read16 = (pos) => {
|
|
7
|
+
if (le)
|
|
8
|
+
return bytes[pos] | (bytes[pos + 1] << 8);
|
|
9
|
+
return (bytes[pos] << 8) | bytes[pos + 1];
|
|
10
|
+
};
|
|
11
|
+
const read32 = (pos) => {
|
|
12
|
+
if (le)
|
|
13
|
+
return bytes[pos] | (bytes[pos + 1] << 8) | (bytes[pos + 2] << 16) | (bytes[pos + 3] << 24);
|
|
14
|
+
return ((bytes[pos] << 24) | (bytes[pos + 1] << 16) | (bytes[pos + 2] << 8) | bytes[pos + 3]) >>> 0;
|
|
15
|
+
};
|
|
16
|
+
const ifdOffset = read32(tiffStart + 4);
|
|
17
|
+
const ifdStart = tiffStart + ifdOffset;
|
|
18
|
+
if (ifdStart + 2 > bytes.length)
|
|
19
|
+
return 1;
|
|
20
|
+
const entryCount = read16(ifdStart);
|
|
21
|
+
for (let i = 0; i < entryCount; i++) {
|
|
22
|
+
const entryPos = ifdStart + 2 + i * 12;
|
|
23
|
+
if (entryPos + 12 > bytes.length)
|
|
24
|
+
return 1;
|
|
25
|
+
if (read16(entryPos) === 0x0112) {
|
|
26
|
+
const value = read16(entryPos + 8);
|
|
27
|
+
return value >= 1 && value <= 8 ? value : 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
function findJpegTiffOffset(bytes) {
|
|
33
|
+
let offset = 2;
|
|
34
|
+
while (offset < bytes.length - 1) {
|
|
35
|
+
if (bytes[offset] !== 0xff)
|
|
36
|
+
return -1;
|
|
37
|
+
const marker = bytes[offset + 1];
|
|
38
|
+
if (marker === 0xff) {
|
|
39
|
+
offset++;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (marker === 0xe1) {
|
|
43
|
+
if (offset + 4 >= bytes.length)
|
|
44
|
+
return -1;
|
|
45
|
+
const segmentStart = offset + 4;
|
|
46
|
+
if (segmentStart + 6 > bytes.length)
|
|
47
|
+
return -1;
|
|
48
|
+
if (!hasExifHeader(bytes, segmentStart))
|
|
49
|
+
return -1;
|
|
50
|
+
return segmentStart + 6;
|
|
51
|
+
}
|
|
52
|
+
if (offset + 4 > bytes.length)
|
|
53
|
+
return -1;
|
|
54
|
+
const length = (bytes[offset + 2] << 8) | bytes[offset + 3];
|
|
55
|
+
offset += 2 + length;
|
|
56
|
+
}
|
|
57
|
+
return -1;
|
|
58
|
+
}
|
|
59
|
+
function findWebpTiffOffset(bytes) {
|
|
60
|
+
let offset = 12;
|
|
61
|
+
while (offset + 8 <= bytes.length) {
|
|
62
|
+
const chunkId = String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
|
|
63
|
+
const chunkSize = bytes[offset + 4] | (bytes[offset + 5] << 8) | (bytes[offset + 6] << 16) | (bytes[offset + 7] << 24);
|
|
64
|
+
const dataStart = offset + 8;
|
|
65
|
+
if (chunkId === "EXIF") {
|
|
66
|
+
if (dataStart + chunkSize > bytes.length)
|
|
67
|
+
return -1;
|
|
68
|
+
// Some WebP files have "Exif\0\0" prefix before the TIFF header
|
|
69
|
+
const tiffStart = chunkSize >= 6 && hasExifHeader(bytes, dataStart) ? dataStart + 6 : dataStart;
|
|
70
|
+
return tiffStart;
|
|
71
|
+
}
|
|
72
|
+
// RIFF chunks are padded to even size
|
|
73
|
+
offset = dataStart + chunkSize + (chunkSize % 2);
|
|
74
|
+
}
|
|
75
|
+
return -1;
|
|
76
|
+
}
|
|
77
|
+
function hasExifHeader(bytes, offset) {
|
|
78
|
+
return (bytes[offset] === 0x45 &&
|
|
79
|
+
bytes[offset + 1] === 0x78 &&
|
|
80
|
+
bytes[offset + 2] === 0x69 &&
|
|
81
|
+
bytes[offset + 3] === 0x66 &&
|
|
82
|
+
bytes[offset + 4] === 0x00 &&
|
|
83
|
+
bytes[offset + 5] === 0x00);
|
|
84
|
+
}
|
|
85
|
+
function getExifOrientation(bytes) {
|
|
86
|
+
let tiffOffset = -1;
|
|
87
|
+
// JPEG: starts with FF D8
|
|
88
|
+
if (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xd8) {
|
|
89
|
+
tiffOffset = findJpegTiffOffset(bytes);
|
|
90
|
+
}
|
|
91
|
+
// WebP: starts with RIFF....WEBP
|
|
92
|
+
else if (bytes.length >= 12 &&
|
|
93
|
+
bytes[0] === 0x52 &&
|
|
94
|
+
bytes[1] === 0x49 &&
|
|
95
|
+
bytes[2] === 0x46 &&
|
|
96
|
+
bytes[3] === 0x46 &&
|
|
97
|
+
bytes[8] === 0x57 &&
|
|
98
|
+
bytes[9] === 0x45 &&
|
|
99
|
+
bytes[10] === 0x42 &&
|
|
100
|
+
bytes[11] === 0x50) {
|
|
101
|
+
tiffOffset = findWebpTiffOffset(bytes);
|
|
102
|
+
}
|
|
103
|
+
if (tiffOffset === -1)
|
|
104
|
+
return 1;
|
|
105
|
+
return readOrientationFromTiff(bytes, tiffOffset);
|
|
106
|
+
}
|
|
107
|
+
function rotate90(photon, image, dstIndex) {
|
|
108
|
+
const w = image.get_width();
|
|
109
|
+
const h = image.get_height();
|
|
110
|
+
const src = image.get_raw_pixels();
|
|
111
|
+
const dst = new Uint8Array(src.length);
|
|
112
|
+
for (let y = 0; y < h; y++) {
|
|
113
|
+
for (let x = 0; x < w; x++) {
|
|
114
|
+
const srcIdx = (y * w + x) * 4;
|
|
115
|
+
const dstIdx = dstIndex(x, y, w, h) * 4;
|
|
116
|
+
dst[dstIdx] = src[srcIdx];
|
|
117
|
+
dst[dstIdx + 1] = src[srcIdx + 1];
|
|
118
|
+
dst[dstIdx + 2] = src[srcIdx + 2];
|
|
119
|
+
dst[dstIdx + 3] = src[srcIdx + 3];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return new photon.PhotonImage(dst, h, w);
|
|
123
|
+
}
|
|
124
|
+
// Flip orientations mutate in-place. Rotations return a new image (caller must free the old one if different).
|
|
125
|
+
export function applyExifOrientation(photon, image, originalBytes) {
|
|
126
|
+
const orientation = getExifOrientation(originalBytes);
|
|
127
|
+
if (orientation === 1)
|
|
128
|
+
return image;
|
|
129
|
+
switch (orientation) {
|
|
130
|
+
case 2:
|
|
131
|
+
photon.fliph(image);
|
|
132
|
+
return image;
|
|
133
|
+
case 3:
|
|
134
|
+
photon.fliph(image);
|
|
135
|
+
photon.flipv(image);
|
|
136
|
+
return image;
|
|
137
|
+
case 4:
|
|
138
|
+
photon.flipv(image);
|
|
139
|
+
return image;
|
|
140
|
+
case 5: {
|
|
141
|
+
const rotated = rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));
|
|
142
|
+
photon.fliph(rotated);
|
|
143
|
+
return rotated;
|
|
144
|
+
}
|
|
145
|
+
case 6:
|
|
146
|
+
return rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));
|
|
147
|
+
case 7: {
|
|
148
|
+
const rotated = rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);
|
|
149
|
+
photon.fliph(rotated);
|
|
150
|
+
return rotated;
|
|
151
|
+
}
|
|
152
|
+
case 8:
|
|
153
|
+
return rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);
|
|
154
|
+
default:
|
|
155
|
+
return image;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=exif-orientation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exif-orientation.js","sourceRoot":"","sources":["../../src/utils/exif-orientation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PhotonImageType } from \"./photon.js\";\n\ntype Photon = typeof import(\"@silvia-odwyer/photon-node\");\n\nfunction readOrientationFromTiff(bytes: Uint8Array, tiffStart: number): number {\n\tif (tiffStart + 8 > bytes.length) return 1;\n\n\tconst byteOrder = (bytes[tiffStart] << 8) | bytes[tiffStart + 1];\n\tconst le = byteOrder === 0x4949;\n\n\tconst read16 = (pos: number): number => {\n\t\tif (le) return bytes[pos] | (bytes[pos + 1] << 8);\n\t\treturn (bytes[pos] << 8) | bytes[pos + 1];\n\t};\n\n\tconst read32 = (pos: number): number => {\n\t\tif (le) return bytes[pos] | (bytes[pos + 1] << 8) | (bytes[pos + 2] << 16) | (bytes[pos + 3] << 24);\n\t\treturn ((bytes[pos] << 24) | (bytes[pos + 1] << 16) | (bytes[pos + 2] << 8) | bytes[pos + 3]) >>> 0;\n\t};\n\n\tconst ifdOffset = read32(tiffStart + 4);\n\tconst ifdStart = tiffStart + ifdOffset;\n\tif (ifdStart + 2 > bytes.length) return 1;\n\n\tconst entryCount = read16(ifdStart);\n\tfor (let i = 0; i < entryCount; i++) {\n\t\tconst entryPos = ifdStart + 2 + i * 12;\n\t\tif (entryPos + 12 > bytes.length) return 1;\n\n\t\tif (read16(entryPos) === 0x0112) {\n\t\t\tconst value = read16(entryPos + 8);\n\t\t\treturn value >= 1 && value <= 8 ? value : 1;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\nfunction findJpegTiffOffset(bytes: Uint8Array): number {\n\tlet offset = 2;\n\twhile (offset < bytes.length - 1) {\n\t\tif (bytes[offset] !== 0xff) return -1;\n\t\tconst marker = bytes[offset + 1];\n\t\tif (marker === 0xff) {\n\t\t\toffset++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (marker === 0xe1) {\n\t\t\tif (offset + 4 >= bytes.length) return -1;\n\t\t\tconst segmentStart = offset + 4;\n\t\t\tif (segmentStart + 6 > bytes.length) return -1;\n\t\t\tif (!hasExifHeader(bytes, segmentStart)) return -1;\n\t\t\treturn segmentStart + 6;\n\t\t}\n\n\t\tif (offset + 4 > bytes.length) return -1;\n\t\tconst length = (bytes[offset + 2] << 8) | bytes[offset + 3];\n\t\toffset += 2 + length;\n\t}\n\n\treturn -1;\n}\n\nfunction findWebpTiffOffset(bytes: Uint8Array): number {\n\tlet offset = 12;\n\twhile (offset + 8 <= bytes.length) {\n\t\tconst chunkId = String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);\n\t\tconst chunkSize =\n\t\t\tbytes[offset + 4] | (bytes[offset + 5] << 8) | (bytes[offset + 6] << 16) | (bytes[offset + 7] << 24);\n\t\tconst dataStart = offset + 8;\n\n\t\tif (chunkId === \"EXIF\") {\n\t\t\tif (dataStart + chunkSize > bytes.length) return -1;\n\t\t\t// Some WebP files have \"Exif\\0\\0\" prefix before the TIFF header\n\t\t\tconst tiffStart = chunkSize >= 6 && hasExifHeader(bytes, dataStart) ? dataStart + 6 : dataStart;\n\t\t\treturn tiffStart;\n\t\t}\n\n\t\t// RIFF chunks are padded to even size\n\t\toffset = dataStart + chunkSize + (chunkSize % 2);\n\t}\n\n\treturn -1;\n}\n\nfunction hasExifHeader(bytes: Uint8Array, offset: number): boolean {\n\treturn (\n\t\tbytes[offset] === 0x45 &&\n\t\tbytes[offset + 1] === 0x78 &&\n\t\tbytes[offset + 2] === 0x69 &&\n\t\tbytes[offset + 3] === 0x66 &&\n\t\tbytes[offset + 4] === 0x00 &&\n\t\tbytes[offset + 5] === 0x00\n\t);\n}\n\nfunction getExifOrientation(bytes: Uint8Array): number {\n\tlet tiffOffset = -1;\n\n\t// JPEG: starts with FF D8\n\tif (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xd8) {\n\t\ttiffOffset = findJpegTiffOffset(bytes);\n\t}\n\t// WebP: starts with RIFF....WEBP\n\telse if (\n\t\tbytes.length >= 12 &&\n\t\tbytes[0] === 0x52 &&\n\t\tbytes[1] === 0x49 &&\n\t\tbytes[2] === 0x46 &&\n\t\tbytes[3] === 0x46 &&\n\t\tbytes[8] === 0x57 &&\n\t\tbytes[9] === 0x45 &&\n\t\tbytes[10] === 0x42 &&\n\t\tbytes[11] === 0x50\n\t) {\n\t\ttiffOffset = findWebpTiffOffset(bytes);\n\t}\n\n\tif (tiffOffset === -1) return 1;\n\treturn readOrientationFromTiff(bytes, tiffOffset);\n}\n\ntype DstIndexFn = (x: number, y: number, w: number, h: number) => number;\n\nfunction rotate90(photon: Photon, image: PhotonImageType, dstIndex: DstIndexFn): PhotonImageType {\n\tconst w = image.get_width();\n\tconst h = image.get_height();\n\tconst src = image.get_raw_pixels();\n\tconst dst = new Uint8Array(src.length);\n\n\tfor (let y = 0; y < h; y++) {\n\t\tfor (let x = 0; x < w; x++) {\n\t\t\tconst srcIdx = (y * w + x) * 4;\n\t\t\tconst dstIdx = dstIndex(x, y, w, h) * 4;\n\t\t\tdst[dstIdx] = src[srcIdx];\n\t\t\tdst[dstIdx + 1] = src[srcIdx + 1];\n\t\t\tdst[dstIdx + 2] = src[srcIdx + 2];\n\t\t\tdst[dstIdx + 3] = src[srcIdx + 3];\n\t\t}\n\t}\n\n\treturn new photon.PhotonImage(dst, h, w);\n}\n\n// Flip orientations mutate in-place. Rotations return a new image (caller must free the old one if different).\nexport function applyExifOrientation(\n\tphoton: Photon,\n\timage: PhotonImageType,\n\toriginalBytes: Uint8Array,\n): PhotonImageType {\n\tconst orientation = getExifOrientation(originalBytes);\n\tif (orientation === 1) return image;\n\n\tswitch (orientation) {\n\t\tcase 2:\n\t\t\tphoton.fliph(image);\n\t\t\treturn image;\n\t\tcase 3:\n\t\t\tphoton.fliph(image);\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 4:\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 5: {\n\t\t\tconst rotated = rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 6:\n\t\t\treturn rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));\n\t\tcase 7: {\n\t\t\tconst rotated = rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 8:\n\t\t\treturn rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);\n\t\tdefault:\n\t\t\treturn image;\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-convert.d.ts","sourceRoot":"","sources":["../../src/utils/image-convert.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"image-convert.d.ts","sourceRoot":"","sources":["../../src/utils/image-convert.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAsB,YAAY,CACjC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA8BpD","sourcesContent":["import { applyExifOrientation } from \"./exif-orientation.js\";\nimport { loadPhoton } from \"./photon.js\";\n\n/**\n * Convert image to PNG format for terminal display.\n * Kitty graphics protocol requires PNG format (f=100).\n */\nexport async function convertToPng(\n\tbase64Data: string,\n\tmimeType: string,\n): Promise<{ data: string; mimeType: string } | null> {\n\t// Already PNG, no conversion needed\n\tif (mimeType === \"image/png\") {\n\t\treturn { data: base64Data, mimeType };\n\t}\n\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\t// Photon not available, can't convert\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst bytes = new Uint8Array(Buffer.from(base64Data, \"base64\"));\n\t\tconst rawImage = photon.PhotonImage.new_from_byteslice(bytes);\n\t\tconst image = applyExifOrientation(photon, rawImage, bytes);\n\t\tif (image !== rawImage) rawImage.free();\n\t\ttry {\n\t\t\tconst pngBuffer = image.get_bytes();\n\t\t\treturn {\n\t\t\t\tdata: Buffer.from(pngBuffer).toString(\"base64\"),\n\t\t\t\tmimeType: \"image/png\",\n\t\t\t};\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\t// Conversion failed\n\t\treturn null;\n\t}\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { applyExifOrientation } from "./exif-orientation.js";
|
|
1
2
|
import { loadPhoton } from "./photon.js";
|
|
2
3
|
/**
|
|
3
4
|
* Convert image to PNG format for terminal display.
|
|
@@ -15,7 +16,10 @@ export async function convertToPng(base64Data, mimeType) {
|
|
|
15
16
|
}
|
|
16
17
|
try {
|
|
17
18
|
const bytes = new Uint8Array(Buffer.from(base64Data, "base64"));
|
|
18
|
-
const
|
|
19
|
+
const rawImage = photon.PhotonImage.new_from_byteslice(bytes);
|
|
20
|
+
const image = applyExifOrientation(photon, rawImage, bytes);
|
|
21
|
+
if (image !== rawImage)
|
|
22
|
+
rawImage.free();
|
|
19
23
|
try {
|
|
20
24
|
const pngBuffer = image.get_bytes();
|
|
21
25
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-convert.js","sourceRoot":"","sources":["../../src/utils/image-convert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,UAAkB,EAClB,QAAgB,EACqC;IACrD,oCAAoC;IACpC,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvC,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,sCAAsC;QACtC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;QAChE,MAAM,
|
|
1
|
+
{"version":3,"file":"image-convert.js","sourceRoot":"","sources":["../../src/utils/image-convert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,UAAkB,EAClB,QAAgB,EACqC;IACrD,oCAAoC;IACpC,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvC,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,sCAAsC;QACtC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,oBAAoB,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC5D,IAAI,KAAK,KAAK,QAAQ;YAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC;YACJ,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YACpC,OAAO;gBACN,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAC/C,QAAQ,EAAE,WAAW;aACrB,CAAC;QACH,CAAC;gBAAS,CAAC;YACV,KAAK,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,oBAAoB;QACpB,OAAO,IAAI,CAAC;IACb,CAAC;AAAA,CACD","sourcesContent":["import { applyExifOrientation } from \"./exif-orientation.js\";\nimport { loadPhoton } from \"./photon.js\";\n\n/**\n * Convert image to PNG format for terminal display.\n * Kitty graphics protocol requires PNG format (f=100).\n */\nexport async function convertToPng(\n\tbase64Data: string,\n\tmimeType: string,\n): Promise<{ data: string; mimeType: string } | null> {\n\t// Already PNG, no conversion needed\n\tif (mimeType === \"image/png\") {\n\t\treturn { data: base64Data, mimeType };\n\t}\n\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\t// Photon not available, can't convert\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst bytes = new Uint8Array(Buffer.from(base64Data, \"base64\"));\n\t\tconst rawImage = photon.PhotonImage.new_from_byteslice(bytes);\n\t\tconst image = applyExifOrientation(photon, rawImage, bytes);\n\t\tif (image !== rawImage) rawImage.free();\n\t\ttry {\n\t\t\tconst pngBuffer = image.get_bytes();\n\t\t\treturn {\n\t\t\t\tdata: Buffer.from(pngBuffer).toString(\"base64\"),\n\t\t\t\tmimeType: \"image/png\",\n\t\t\t};\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\t// Conversion failed\n\t\treturn null;\n\t}\n}\n"]}
|