pi-image-tools 1.3.0 → 1.4.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.
@@ -1,11 +1,4 @@
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";
1
+ import { NamespacedCommandProvider, providerImageResult, type ClipboardReadResult, type CommandExists, type CommandResult, type CommandRunner } from "./provider-helpers.js";
9
2
 
10
3
  const PUBLIC_PNG_SCRIPT = `ObjC.import('AppKit');
11
4
  const pasteboard = $.NSPasteboard.generalPasteboard;
@@ -21,57 +14,25 @@ export interface OsascriptPublicPngProviderOptions {
21
14
  commandExists?: CommandExists;
22
15
  }
23
16
 
24
- export class OsascriptPublicPngProvider implements ClipboardImageProvider {
25
- readonly capabilities;
26
- private readonly commandRunner: CommandRunner;
27
- private readonly commandExists: CommandExists;
28
-
17
+ export class OsascriptPublicPngProvider extends NamespacedCommandProvider {
29
18
  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(
19
+ super(
20
+ {
21
+ id: "mac-osascript-public-png",
22
+ name: "osascript public.png",
23
+ platforms: ["darwin"],
24
+ priority: options.priority ?? 20,
25
+ },
50
26
  "osascript",
51
- ["-l", "JavaScript", "-e", PUBLIC_PNG_SCRIPT],
52
- context,
53
- this.commandExists,
27
+ options,
54
28
  );
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
- }
29
+ }
64
30
 
65
- if (!result.ok || result.stdout.length === 0) {
66
- return { available: true, image: null };
67
- }
31
+ protected buildArgs(): readonly string[] {
32
+ return ["-l", "JavaScript", "-e", PUBLIC_PNG_SCRIPT];
33
+ }
68
34
 
69
- return {
70
- available: true,
71
- image: {
72
- bytes: new Uint8Array(result.stdout),
73
- mimeType: "image/png",
74
- },
75
- };
35
+ protected readFromResult(result: CommandResult): ClipboardReadResult {
36
+ return providerImageResult(new Uint8Array(result.stdout), "image/png");
76
37
  }
77
38
  }
@@ -1,11 +1,4 @@
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";
1
+ import { NamespacedCommandProvider, providerImageResult, type ClipboardReadResult, type CommandExists, type CommandResult, type CommandRunner } from "./provider-helpers.js";
9
2
 
10
3
  export interface PngpasteProviderOptions {
11
4
  priority?: number;
@@ -13,57 +6,25 @@ export interface PngpasteProviderOptions {
13
6
  commandExists?: CommandExists;
14
7
  }
15
8
 
16
- export class PngpasteProvider implements ClipboardImageProvider {
17
- readonly capabilities;
18
- private readonly commandRunner: CommandRunner;
19
- private readonly commandExists: CommandExists;
20
-
9
+ export class PngpasteProvider extends NamespacedCommandProvider {
21
10
  constructor(options: PngpasteProviderOptions = {}) {
22
- this.capabilities = {
23
- id: "mac-pngpaste",
24
- name: "pngpaste",
25
- platforms: ["darwin"],
26
- priority: options.priority ?? 10,
27
- };
28
- this.commandRunner = options.commandRunner ?? defaultCommandRunner;
29
- this.commandExists = options.commandExists ?? defaultCommandExists;
30
- }
31
-
32
- isAvailable(context: ClipboardProviderContext): boolean {
33
- try {
34
- return this.commandExists("pngpaste", context);
35
- } catch {
36
- return false;
37
- }
38
- }
39
-
40
- read(context: ClipboardProviderContext): ClipboardReadResult {
41
- const wrapped = buildNamespaceWrappedCommand(
11
+ super(
12
+ {
13
+ id: "mac-pngpaste",
14
+ name: "pngpaste",
15
+ platforms: ["darwin"],
16
+ priority: options.priority ?? 10,
17
+ },
42
18
  "pngpaste",
43
- ["-"],
44
- context,
45
- this.commandExists,
19
+ options,
46
20
  );
47
- const result = this.commandRunner(wrapped.command, wrapped.args, {
48
- environment: context.environment,
49
- maxBuffer: MAX_BUFFER_BYTES,
50
- timeout: READ_TIMEOUT_MS,
51
- });
52
-
53
- if (result.missingCommand) {
54
- return { available: false, image: null };
55
- }
21
+ }
56
22
 
57
- if (!result.ok || result.stdout.length === 0) {
58
- return { available: true, image: null };
59
- }
23
+ protected buildArgs(): readonly string[] {
24
+ return ["-"];
25
+ }
60
26
 
61
- return {
62
- available: true,
63
- image: {
64
- bytes: new Uint8Array(result.stdout),
65
- mimeType: "image/png",
66
- },
67
- };
27
+ protected readFromResult(result: CommandResult): ClipboardReadResult {
28
+ return providerImageResult(new Uint8Array(result.stdout), "image/png");
68
29
  }
69
30
  }
@@ -1,84 +1,84 @@
1
- import { createRequire } from "node:module";
2
-
3
- import { hasGraphicalSession } from "../shell-environment.js";
4
- import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
5
- import type { ClipboardModule } from "../types.js";
6
-
7
- const require = createRequire(import.meta.url);
8
- let cachedClipboardModule: ClipboardModule | null | undefined;
9
-
10
- export type ClipboardModuleLoader = (
11
- platform: NodeJS.Platform,
12
- environment: NodeJS.ProcessEnv,
13
- ) => ClipboardModule | null;
14
-
15
- export function loadClipboardModule(
16
- platform: NodeJS.Platform = process.platform,
17
- environment: NodeJS.ProcessEnv = process.env,
18
- ): ClipboardModule | null {
19
- if (cachedClipboardModule !== undefined) {
20
- return cachedClipboardModule;
21
- }
22
-
23
- if (environment.TERMUX_VERSION || !hasGraphicalSession(platform, environment)) {
24
- cachedClipboardModule = null;
25
- return cachedClipboardModule;
26
- }
27
-
28
- try {
29
- cachedClipboardModule = require("@mariozechner/clipboard") as ClipboardModule;
30
- } catch {
31
- cachedClipboardModule = null;
32
- }
33
-
34
- return cachedClipboardModule;
35
- }
36
-
37
- export interface NativeModuleProviderOptions {
38
- priority?: number;
39
- moduleLoader?: ClipboardModuleLoader;
40
- }
41
-
42
- export class NativeModuleProvider implements ClipboardImageProvider {
43
- readonly capabilities;
44
- private readonly moduleLoader: ClipboardModuleLoader;
45
-
46
- constructor(options: NativeModuleProviderOptions = {}) {
47
- this.capabilities = {
48
- id: "native-module",
49
- name: "@mariozechner/clipboard",
50
- platforms: "*" as const,
51
- priority: options.priority ?? 100,
52
- };
53
- this.moduleLoader = options.moduleLoader ?? loadClipboardModule;
54
- }
55
-
56
- isAvailable(context: ClipboardProviderContext): boolean {
57
- return this.moduleLoader(context.platform, context.environment) !== null;
58
- }
59
-
60
- async read(context: ClipboardProviderContext): Promise<ClipboardReadResult> {
61
- const clipboard = this.moduleLoader(context.platform, context.environment);
62
- if (!clipboard) {
63
- return { available: false, image: null };
64
- }
65
-
66
- if (!clipboard.hasImage()) {
67
- return { available: true, image: null };
68
- }
69
-
70
- const imageData = await clipboard.getImageBinary();
71
- if (!imageData || imageData.length === 0) {
72
- return { available: true, image: null };
73
- }
74
-
75
- const bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);
76
- return {
77
- available: true,
78
- image: {
79
- bytes,
80
- mimeType: "image/png",
81
- },
82
- };
83
- }
84
- }
1
+ import { createRequire } from "node:module";
2
+
3
+ import { hasGraphicalSession } from "../shell-environment.js";
4
+ import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
5
+ import type { ClipboardModule } from "../types.js";
6
+
7
+ const require = createRequire(import.meta.url);
8
+ let cachedClipboardModule: ClipboardModule | null | undefined;
9
+
10
+ export type ClipboardModuleLoader = (
11
+ platform: NodeJS.Platform,
12
+ environment: NodeJS.ProcessEnv,
13
+ ) => ClipboardModule | null;
14
+
15
+ export function loadClipboardModule(
16
+ platform: NodeJS.Platform = process.platform,
17
+ environment: NodeJS.ProcessEnv = process.env,
18
+ ): ClipboardModule | null {
19
+ if (cachedClipboardModule !== undefined) {
20
+ return cachedClipboardModule;
21
+ }
22
+
23
+ if (environment.TERMUX_VERSION || !hasGraphicalSession(platform, environment)) {
24
+ cachedClipboardModule = null;
25
+ return cachedClipboardModule;
26
+ }
27
+
28
+ try {
29
+ cachedClipboardModule = require("@mariozechner/clipboard") as ClipboardModule;
30
+ } catch {
31
+ cachedClipboardModule = null;
32
+ }
33
+
34
+ return cachedClipboardModule;
35
+ }
36
+
37
+ export interface NativeModuleProviderOptions {
38
+ priority?: number;
39
+ moduleLoader?: ClipboardModuleLoader;
40
+ }
41
+
42
+ export class NativeModuleProvider implements ClipboardImageProvider {
43
+ readonly capabilities;
44
+ private readonly moduleLoader: ClipboardModuleLoader;
45
+
46
+ constructor(options: NativeModuleProviderOptions = {}) {
47
+ this.capabilities = {
48
+ id: "native-module",
49
+ name: "@mariozechner/clipboard",
50
+ platforms: "*" as const,
51
+ priority: options.priority ?? 100,
52
+ };
53
+ this.moduleLoader = options.moduleLoader ?? loadClipboardModule;
54
+ }
55
+
56
+ isAvailable(context: ClipboardProviderContext): boolean {
57
+ return this.moduleLoader(context.platform, context.environment) !== null;
58
+ }
59
+
60
+ async read(context: ClipboardProviderContext): Promise<ClipboardReadResult> {
61
+ const clipboard = this.moduleLoader(context.platform, context.environment);
62
+ if (!clipboard) {
63
+ return { available: false, image: null };
64
+ }
65
+
66
+ if (!clipboard.hasImage()) {
67
+ return { available: true, image: null };
68
+ }
69
+
70
+ const imageData = await clipboard.getImageBinary();
71
+ if (!imageData || imageData.length === 0) {
72
+ return { available: true, image: null };
73
+ }
74
+
75
+ const bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);
76
+ return {
77
+ available: true,
78
+ image: {
79
+ bytes,
80
+ mimeType: "image/png",
81
+ },
82
+ };
83
+ }
84
+ }
@@ -1,95 +1,87 @@
1
- import { runPowerShellCommand, type PowerShellCommandResult, type RunPowerShellCommandOptions } from "../powershell.js";
2
- import { MAX_BUFFER_BYTES, READ_TIMEOUT_MS } from "./command-runner.js";
3
- import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
4
-
5
- export type PowerShellRunner = (
6
- script: string,
7
- options: RunPowerShellCommandOptions,
8
- ) => PowerShellCommandResult;
9
-
10
- export interface PowerShellFormsProviderOptions {
11
- priority?: number;
12
- powerShellRunner?: PowerShellRunner;
13
- }
14
-
15
- const READ_IMAGE_SCRIPT = `
16
- $ErrorActionPreference = 'Stop'
17
- Add-Type -AssemblyName System.Windows.Forms
18
- Add-Type -AssemblyName System.Drawing
19
-
20
- if (-not [System.Windows.Forms.Clipboard]::ContainsImage()) {
21
- return
22
- }
23
-
24
- $image = [System.Windows.Forms.Clipboard]::GetImage()
25
- if ($null -eq $image) {
26
- return
27
- }
28
-
29
- $stream = New-Object System.IO.MemoryStream
30
- try {
31
- $image.Save($stream, [System.Drawing.Imaging.ImageFormat]::Png)
32
- [System.Convert]::ToBase64String($stream.ToArray())
33
- } finally {
34
- $stream.Dispose()
35
- $image.Dispose()
36
- }
37
- `;
38
-
39
- export class PowerShellFormsProvider implements ClipboardImageProvider {
40
- readonly capabilities;
41
- private readonly powerShellRunner: PowerShellRunner;
42
-
43
- constructor(options: PowerShellFormsProviderOptions = {}) {
44
- this.capabilities = {
45
- id: "powershell-forms",
46
- name: "Windows PowerShell Forms clipboard",
47
- platforms: ["win32"],
48
- priority: options.priority ?? 20,
49
- };
50
- this.powerShellRunner = options.powerShellRunner ?? runPowerShellCommand;
51
- }
52
-
53
- isAvailable(_context: ClipboardProviderContext): boolean {
54
- return true;
55
- }
56
-
57
- read(_context: ClipboardProviderContext): ClipboardReadResult {
58
- const result = this.powerShellRunner(READ_IMAGE_SCRIPT, {
59
- encoded: true,
60
- maxBuffer: MAX_BUFFER_BYTES,
61
- sta: true,
62
- timeout: READ_TIMEOUT_MS,
63
- });
64
-
65
- if (result.missingCommand) {
66
- return { available: false, image: null };
67
- }
68
-
69
- if (!result.ok) {
70
- return { available: true, image: null };
71
- }
72
-
73
- const base64 = result.stdout.trim();
74
- if (!base64) {
75
- return { available: true, image: null };
76
- }
77
-
78
- try {
79
- const bytes = Buffer.from(base64, "base64");
80
- if (bytes.length === 0) {
81
- return { available: true, image: null };
82
- }
83
-
84
- return {
85
- available: true,
86
- image: {
87
- bytes: new Uint8Array(bytes),
88
- mimeType: "image/png",
89
- },
90
- };
91
- } catch {
92
- return { available: true, image: null };
93
- }
94
- }
95
- }
1
+ import { runPowerShellCommand, type PowerShellCommandResult, type RunPowerShellCommandOptions } from "../powershell.js";
2
+ import { MAX_BUFFER_BYTES, READ_TIMEOUT_MS } from "./command-runner.js";
3
+ import { mapProviderReadFallback, providerEmptyImage, providerImageResult } from "./provider-helpers.js";
4
+ import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
5
+
6
+ export type PowerShellRunner = (
7
+ script: string,
8
+ options: RunPowerShellCommandOptions,
9
+ ) => PowerShellCommandResult;
10
+
11
+ export interface PowerShellFormsProviderOptions {
12
+ priority?: number;
13
+ powerShellRunner?: PowerShellRunner;
14
+ }
15
+
16
+ const READ_IMAGE_SCRIPT = `
17
+ $ErrorActionPreference = 'Stop'
18
+ Add-Type -AssemblyName System.Windows.Forms
19
+ Add-Type -AssemblyName System.Drawing
20
+
21
+ if (-not [System.Windows.Forms.Clipboard]::ContainsImage()) {
22
+ return
23
+ }
24
+
25
+ $image = [System.Windows.Forms.Clipboard]::GetImage()
26
+ if ($null -eq $image) {
27
+ return
28
+ }
29
+
30
+ $stream = New-Object System.IO.MemoryStream
31
+ try {
32
+ $image.Save($stream, [System.Drawing.Imaging.ImageFormat]::Png)
33
+ [System.Convert]::ToBase64String($stream.ToArray())
34
+ } finally {
35
+ $stream.Dispose()
36
+ $image.Dispose()
37
+ }
38
+ `;
39
+
40
+ export class PowerShellFormsProvider implements ClipboardImageProvider {
41
+ readonly capabilities;
42
+ private readonly powerShellRunner: PowerShellRunner;
43
+
44
+ constructor(options: PowerShellFormsProviderOptions = {}) {
45
+ this.capabilities = {
46
+ id: "powershell-forms",
47
+ name: "Windows PowerShell Forms clipboard",
48
+ platforms: ["win32"],
49
+ priority: options.priority ?? 20,
50
+ };
51
+ this.powerShellRunner = options.powerShellRunner ?? runPowerShellCommand;
52
+ }
53
+
54
+ isAvailable(_context: ClipboardProviderContext): boolean {
55
+ return true;
56
+ }
57
+
58
+ read(_context: ClipboardProviderContext): ClipboardReadResult {
59
+ const result = this.powerShellRunner(READ_IMAGE_SCRIPT, {
60
+ encoded: true,
61
+ maxBuffer: MAX_BUFFER_BYTES,
62
+ sta: true,
63
+ timeout: READ_TIMEOUT_MS,
64
+ });
65
+
66
+ const fallback = mapProviderReadFallback(result, false);
67
+ if (fallback) {
68
+ return fallback;
69
+ }
70
+
71
+ const base64 = result.stdout.trim();
72
+ if (!base64) {
73
+ return providerEmptyImage();
74
+ }
75
+
76
+ try {
77
+ const bytes = Buffer.from(base64, "base64");
78
+ if (bytes.length === 0) {
79
+ return providerEmptyImage();
80
+ }
81
+
82
+ return providerImageResult(new Uint8Array(bytes), "image/png");
83
+ } catch {
84
+ return providerEmptyImage();
85
+ }
86
+ }
87
+ }