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.
- package/CHANGELOG.md +135 -115
- package/README.md +16 -8
- package/package.json +13 -4
- package/src/clipboard.ts +99 -89
- package/src/config.ts +5 -2
- package/src/debug-logger.ts +4 -2
- package/src/image-preview.ts +25 -32
- package/src/image-size.ts +62 -63
- package/src/image-transcode.ts +206 -0
- package/src/index.ts +23 -27
- package/src/inline-user-preview.ts +512 -523
- package/src/keybindings.ts +52 -50
- package/src/powershell.ts +82 -56
- package/src/preview-logging.ts +28 -0
- package/src/providers/command-runner.ts +86 -68
- package/src/providers/mac-osascript-pngf.ts +17 -56
- package/src/providers/mac-osascript-publicpng.ts +16 -55
- package/src/providers/mac-pngpaste.ts +16 -55
- package/src/providers/native-module.ts +84 -84
- package/src/providers/powershell-forms.ts +87 -95
- package/src/providers/provider-helpers.ts +196 -0
- package/src/providers/registry.ts +88 -88
- package/src/providers/types.ts +24 -24
- package/src/providers/wl-paste.ts +21 -56
- package/src/providers/xclip.ts +21 -57
- package/src/recent-images.ts +55 -83
- package/src/shell-environment.ts +75 -75
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { buildNamespaceWrappedCommand, defaultCommandExists, type CommandExists } from "../shell-environment.js";
|
|
2
|
+
import {
|
|
3
|
+
defaultCommandRunner,
|
|
4
|
+
MAX_BUFFER_BYTES,
|
|
5
|
+
READ_TIMEOUT_MS,
|
|
6
|
+
type CommandResult,
|
|
7
|
+
type CommandRunner,
|
|
8
|
+
} from "./command-runner.js";
|
|
9
|
+
import type {
|
|
10
|
+
ClipboardImageProvider,
|
|
11
|
+
ClipboardProviderContext,
|
|
12
|
+
ClipboardReadResult,
|
|
13
|
+
ProviderCapabilities,
|
|
14
|
+
} from "./types.js";
|
|
15
|
+
|
|
16
|
+
// Re-export the symbols command-based providers reference in their read
|
|
17
|
+
// overrides and option types. Routing every concrete provider's import
|
|
18
|
+
// through this one module — the shared `CommandRunnerProviderOptions`
|
|
19
|
+
// interface, the `LIST_TYPES_TIMEOUT_MS` constant, and the result types —
|
|
20
|
+
// keeps their import blocks short and avoids duplicated multi-line import
|
|
21
|
+
// boilerplate across providers.
|
|
22
|
+
export type { CommandExists } from "../shell-environment.js";
|
|
23
|
+
export type { CommandResult, CommandRunner } from "./command-runner.js";
|
|
24
|
+
export { LIST_TYPES_TIMEOUT_MS } from "./command-runner.js";
|
|
25
|
+
export type { ClipboardProviderContext, ClipboardReadResult } from "./types.js";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Minimal read-result shape shared by command-based and PowerShell-based
|
|
29
|
+
* clipboard providers. Both `CommandResult` (Buffer stdout) and
|
|
30
|
+
* `PowerShellCommandResult` (string stdout) satisfy this structural contract.
|
|
31
|
+
*/
|
|
32
|
+
interface ProviderReadResult {
|
|
33
|
+
missingCommand: boolean;
|
|
34
|
+
ok: boolean;
|
|
35
|
+
stdout: { length: number };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Result returned when the provider's command is not installed. */
|
|
39
|
+
export function providerUnavailable(): { available: false; image: null } {
|
|
40
|
+
return { available: false, image: null };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Result returned when the clipboard is available but holds no image. */
|
|
44
|
+
export function providerEmptyImage(): { available: true; image: null } {
|
|
45
|
+
return { available: true, image: null };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Result returned when a provider successfully read an image. */
|
|
49
|
+
export function providerImageResult(bytes: Uint8Array, mimeType: string): ClipboardReadResult {
|
|
50
|
+
return { available: true, image: { bytes, mimeType } };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Shared `isAvailable` implementation for providers that delegate to an
|
|
55
|
+
* external command whose presence is verified via `commandExists`.
|
|
56
|
+
*/
|
|
57
|
+
export function createCommandAvailabilityChecker(
|
|
58
|
+
commandName: string,
|
|
59
|
+
commandExists: CommandExists = defaultCommandExists,
|
|
60
|
+
): (context: ClipboardProviderContext) => boolean {
|
|
61
|
+
return (context: ClipboardProviderContext): boolean => {
|
|
62
|
+
try {
|
|
63
|
+
return commandExists(commandName, context);
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Maps a command read result to the standard unavailable/empty sentinel.
|
|
72
|
+
* Returns `null` when the caller should continue processing the result.
|
|
73
|
+
*/
|
|
74
|
+
export function mapProviderReadFallback(
|
|
75
|
+
result: ProviderReadResult,
|
|
76
|
+
requireNonEmptyStdout = true,
|
|
77
|
+
): { available: false; image: null } | { available: true; image: null } | null {
|
|
78
|
+
if (result.missingCommand) {
|
|
79
|
+
return providerUnavailable();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const isEmpty = !result.ok || (requireNonEmptyStdout && result.stdout.length === 0);
|
|
83
|
+
return isEmpty ? providerEmptyImage() : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Parses a command's stdout — a newline-separated list of MIME types as
|
|
88
|
+
* emitted by `wl-paste --list-types` / `xclip -t TARGETS -o` — into a
|
|
89
|
+
* trimmed, non-empty list. Shared by the Linux command-based providers so
|
|
90
|
+
* the parse chain is not duplicated per provider.
|
|
91
|
+
*/
|
|
92
|
+
export function parseMimeTypeList(stdout: Buffer): string[] {
|
|
93
|
+
return stdout
|
|
94
|
+
.toString("utf8")
|
|
95
|
+
.split(/\r?\n/)
|
|
96
|
+
.map((mimeType) => mimeType.trim())
|
|
97
|
+
.filter((mimeType) => mimeType.length > 0);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Options shared by namespace-wrapped (macOS) command providers. */
|
|
101
|
+
export interface NamespacedCommandProviderOptions {
|
|
102
|
+
priority?: number;
|
|
103
|
+
commandRunner?: CommandRunner;
|
|
104
|
+
commandExists?: CommandExists;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Options shared by plain command-runner (Linux) providers. */
|
|
108
|
+
export interface CommandRunnerProviderOptions {
|
|
109
|
+
priority?: number;
|
|
110
|
+
commandRunner?: CommandRunner;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Base for macOS providers that read the clipboard via a namespace-wrapped
|
|
115
|
+
* command (osascript/pngpaste). Encapsulates the commandRunner/commandExists
|
|
116
|
+
* wiring, the command-availability check, and the shared read preamble
|
|
117
|
+
* (namespace wrap -> run -> fallback), so concrete providers only describe
|
|
118
|
+
* their command args and how to turn stdout into an image.
|
|
119
|
+
*/
|
|
120
|
+
export abstract class NamespacedCommandProvider implements ClipboardImageProvider {
|
|
121
|
+
readonly capabilities: ProviderCapabilities;
|
|
122
|
+
private readonly commandRunner: CommandRunner;
|
|
123
|
+
private readonly commandExists: CommandExists;
|
|
124
|
+
private readonly isAvailableFn: (context: ClipboardProviderContext) => boolean;
|
|
125
|
+
|
|
126
|
+
constructor(
|
|
127
|
+
capabilities: ProviderCapabilities,
|
|
128
|
+
private readonly commandName: string,
|
|
129
|
+
options: NamespacedCommandProviderOptions = {},
|
|
130
|
+
) {
|
|
131
|
+
this.capabilities = capabilities;
|
|
132
|
+
this.commandRunner = options.commandRunner ?? defaultCommandRunner;
|
|
133
|
+
this.commandExists = options.commandExists ?? defaultCommandExists;
|
|
134
|
+
this.isAvailableFn = createCommandAvailabilityChecker(this.commandName, this.commandExists);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
isAvailable(context: ClipboardProviderContext): boolean {
|
|
138
|
+
return this.isAvailableFn(context);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
read(context: ClipboardProviderContext): ClipboardReadResult {
|
|
142
|
+
const wrapped = buildNamespaceWrappedCommand(this.commandName, this.buildArgs(), context, this.commandExists);
|
|
143
|
+
const result = this.commandRunner(wrapped.command, wrapped.args, {
|
|
144
|
+
environment: context.environment,
|
|
145
|
+
maxBuffer: MAX_BUFFER_BYTES,
|
|
146
|
+
timeout: READ_TIMEOUT_MS,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const fallback = mapProviderReadFallback(result);
|
|
150
|
+
if (fallback) {
|
|
151
|
+
return fallback;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return this.readFromResult(result);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
protected abstract buildArgs(): readonly string[];
|
|
158
|
+
protected abstract readFromResult(result: CommandResult): ClipboardReadResult;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Base for Linux providers that read the clipboard via a fixed command (no
|
|
163
|
+
* namespace wrapping). Provides the commandRunner field, an always-available
|
|
164
|
+
* `isAvailable`, and a shared `runCommand` helper so concrete providers only
|
|
165
|
+
* implement `read`.
|
|
166
|
+
*/
|
|
167
|
+
export abstract class CommandRunnerProvider implements ClipboardImageProvider {
|
|
168
|
+
readonly capabilities: ProviderCapabilities;
|
|
169
|
+
protected readonly commandRunner: CommandRunner;
|
|
170
|
+
|
|
171
|
+
constructor(capabilities: ProviderCapabilities, options: CommandRunnerProviderOptions = {}) {
|
|
172
|
+
this.capabilities = capabilities;
|
|
173
|
+
this.commandRunner = options.commandRunner ?? defaultCommandRunner;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
isAvailable(_context: ClipboardProviderContext): boolean {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
protected runCommand(
|
|
181
|
+
command: string,
|
|
182
|
+
args: readonly string[],
|
|
183
|
+
context: ClipboardProviderContext,
|
|
184
|
+
timeout: number = READ_TIMEOUT_MS,
|
|
185
|
+
): CommandResult {
|
|
186
|
+
return this.commandRunner(command, args, {
|
|
187
|
+
environment: context.environment,
|
|
188
|
+
maxBuffer: MAX_BUFFER_BYTES,
|
|
189
|
+
timeout,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
abstract read(context: ClipboardProviderContext): ClipboardReadResult;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export type { ClipboardImageProvider };
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ClipboardImageProvider,
|
|
3
|
-
ClipboardProviderContext,
|
|
4
|
-
ClipboardReadResult,
|
|
5
|
-
} from "./types.js";
|
|
6
|
-
import type { ClipboardImage } from "../types.js";
|
|
7
|
-
|
|
8
|
-
export interface ClipboardProviderAttempt {
|
|
9
|
-
providerId: string;
|
|
10
|
-
available: boolean;
|
|
11
|
-
imageFound: boolean;
|
|
12
|
-
skipped: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ClipboardProviderRegistryReadResult {
|
|
16
|
-
image: ClipboardImage | null;
|
|
17
|
-
attempts: ClipboardProviderAttempt[];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function supportsPlatform(provider: ClipboardImageProvider, platform: NodeJS.Platform): boolean {
|
|
21
|
-
const { platforms } = provider.capabilities;
|
|
22
|
-
return platforms === "*" || platforms.includes(platform);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function byPriority(left: ClipboardImageProvider, right: ClipboardImageProvider): number {
|
|
26
|
-
return left.capabilities.priority - right.capabilities.priority;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class ClipboardProviderRegistry {
|
|
30
|
-
private readonly providers: ClipboardImageProvider[] = [];
|
|
31
|
-
|
|
32
|
-
constructor(providers: readonly ClipboardImageProvider[] = []) {
|
|
33
|
-
for (const provider of providers) {
|
|
34
|
-
this.register(provider);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
register(provider: ClipboardImageProvider): this {
|
|
39
|
-
this.providers.push(provider);
|
|
40
|
-
return this;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
getProviders(platform: NodeJS.Platform): ClipboardImageProvider[] {
|
|
44
|
-
return this.providers.filter((provider) => supportsPlatform(provider, platform)).sort(byPriority);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async getEligible(context: ClipboardProviderContext): Promise<ClipboardImageProvider[]> {
|
|
48
|
-
const eligible: ClipboardImageProvider[] = [];
|
|
49
|
-
for (const provider of this.getProviders(context.platform)) {
|
|
50
|
-
if (await provider.isAvailable(context)) {
|
|
51
|
-
eligible.push(provider);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return eligible;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async read(context: ClipboardProviderContext): Promise<ClipboardProviderRegistryReadResult> {
|
|
59
|
-
const attempts: ClipboardProviderAttempt[] = [];
|
|
60
|
-
|
|
61
|
-
for (const provider of this.getProviders(context.platform)) {
|
|
62
|
-
const isAvailable = await provider.isAvailable(context);
|
|
63
|
-
if (!isAvailable) {
|
|
64
|
-
attempts.push({
|
|
65
|
-
providerId: provider.capabilities.id,
|
|
66
|
-
available: false,
|
|
67
|
-
imageFound: false,
|
|
68
|
-
skipped: true,
|
|
69
|
-
});
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const result: ClipboardReadResult = await provider.read(context);
|
|
74
|
-
attempts.push({
|
|
75
|
-
providerId: provider.capabilities.id,
|
|
76
|
-
available: result.available,
|
|
77
|
-
imageFound: result.image !== null,
|
|
78
|
-
skipped: false,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
if (result.image) {
|
|
82
|
-
return { image: result.image, attempts };
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return { image: null, attempts };
|
|
87
|
-
}
|
|
88
|
-
}
|
|
1
|
+
import type {
|
|
2
|
+
ClipboardImageProvider,
|
|
3
|
+
ClipboardProviderContext,
|
|
4
|
+
ClipboardReadResult,
|
|
5
|
+
} from "./types.js";
|
|
6
|
+
import type { ClipboardImage } from "../types.js";
|
|
7
|
+
|
|
8
|
+
export interface ClipboardProviderAttempt {
|
|
9
|
+
providerId: string;
|
|
10
|
+
available: boolean;
|
|
11
|
+
imageFound: boolean;
|
|
12
|
+
skipped: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ClipboardProviderRegistryReadResult {
|
|
16
|
+
image: ClipboardImage | null;
|
|
17
|
+
attempts: ClipboardProviderAttempt[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function supportsPlatform(provider: ClipboardImageProvider, platform: NodeJS.Platform): boolean {
|
|
21
|
+
const { platforms } = provider.capabilities;
|
|
22
|
+
return platforms === "*" || platforms.includes(platform);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function byPriority(left: ClipboardImageProvider, right: ClipboardImageProvider): number {
|
|
26
|
+
return left.capabilities.priority - right.capabilities.priority;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class ClipboardProviderRegistry {
|
|
30
|
+
private readonly providers: ClipboardImageProvider[] = [];
|
|
31
|
+
|
|
32
|
+
constructor(providers: readonly ClipboardImageProvider[] = []) {
|
|
33
|
+
for (const provider of providers) {
|
|
34
|
+
this.register(provider);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
register(provider: ClipboardImageProvider): this {
|
|
39
|
+
this.providers.push(provider);
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getProviders(platform: NodeJS.Platform): ClipboardImageProvider[] {
|
|
44
|
+
return this.providers.filter((provider) => supportsPlatform(provider, platform)).sort(byPriority);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async getEligible(context: ClipboardProviderContext): Promise<ClipboardImageProvider[]> {
|
|
48
|
+
const eligible: ClipboardImageProvider[] = [];
|
|
49
|
+
for (const provider of this.getProviders(context.platform)) {
|
|
50
|
+
if (await provider.isAvailable(context)) {
|
|
51
|
+
eligible.push(provider);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return eligible;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async read(context: ClipboardProviderContext): Promise<ClipboardProviderRegistryReadResult> {
|
|
59
|
+
const attempts: ClipboardProviderAttempt[] = [];
|
|
60
|
+
|
|
61
|
+
for (const provider of this.getProviders(context.platform)) {
|
|
62
|
+
const isAvailable = await provider.isAvailable(context);
|
|
63
|
+
if (!isAvailable) {
|
|
64
|
+
attempts.push({
|
|
65
|
+
providerId: provider.capabilities.id,
|
|
66
|
+
available: false,
|
|
67
|
+
imageFound: false,
|
|
68
|
+
skipped: true,
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const result: ClipboardReadResult = await provider.read(context);
|
|
74
|
+
attempts.push({
|
|
75
|
+
providerId: provider.capabilities.id,
|
|
76
|
+
available: result.available,
|
|
77
|
+
imageFound: result.image !== null,
|
|
78
|
+
skipped: false,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (result.image) {
|
|
82
|
+
return { image: result.image, attempts };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { image: null, attempts };
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/providers/types.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import type { ClipboardImage } from "../types.js";
|
|
2
|
-
|
|
3
|
-
export interface ClipboardReadResult {
|
|
4
|
-
available: boolean;
|
|
5
|
-
image: ClipboardImage | null;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface ClipboardProviderContext {
|
|
9
|
-
platform: NodeJS.Platform;
|
|
10
|
-
environment: NodeJS.ProcessEnv;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface ProviderCapabilities {
|
|
14
|
-
readonly id: string;
|
|
15
|
-
readonly name: string;
|
|
16
|
-
readonly platforms: readonly NodeJS.Platform[] | "*";
|
|
17
|
-
readonly priority: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface ClipboardImageProvider {
|
|
21
|
-
readonly capabilities: ProviderCapabilities;
|
|
22
|
-
isAvailable(context: ClipboardProviderContext): boolean | Promise<boolean>;
|
|
23
|
-
read(context: ClipboardProviderContext): ClipboardReadResult | Promise<ClipboardReadResult>;
|
|
24
|
-
}
|
|
1
|
+
import type { ClipboardImage } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export interface ClipboardReadResult {
|
|
4
|
+
available: boolean;
|
|
5
|
+
image: ClipboardImage | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ClipboardProviderContext {
|
|
9
|
+
platform: NodeJS.Platform;
|
|
10
|
+
environment: NodeJS.ProcessEnv;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ProviderCapabilities {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly platforms: readonly NodeJS.Platform[] | "*";
|
|
17
|
+
readonly priority: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ClipboardImageProvider {
|
|
21
|
+
readonly capabilities: ProviderCapabilities;
|
|
22
|
+
isAvailable(context: ClipboardProviderContext): boolean | Promise<boolean>;
|
|
23
|
+
read(context: ClipboardProviderContext): ClipboardReadResult | Promise<ClipboardReadResult>;
|
|
24
|
+
}
|
|
@@ -1,81 +1,46 @@
|
|
|
1
|
+
import { CommandRunnerProvider, providerEmptyImage, providerImageResult, providerUnavailable, LIST_TYPES_TIMEOUT_MS, parseMimeTypeList, type CommandRunnerProviderOptions, type ClipboardProviderContext, type ClipboardReadResult } from "./provider-helpers.js";
|
|
1
2
|
import { normalizeMimeType, selectPreferredImageMimeType } from "../image-mime.js";
|
|
2
|
-
import {
|
|
3
|
-
defaultCommandRunner,
|
|
4
|
-
LIST_TYPES_TIMEOUT_MS,
|
|
5
|
-
MAX_BUFFER_BYTES,
|
|
6
|
-
READ_TIMEOUT_MS,
|
|
7
|
-
type CommandRunner,
|
|
8
|
-
} from "./command-runner.js";
|
|
9
|
-
import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
|
|
10
3
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
id: "wl-paste",
|
|
23
|
-
name: "wl-paste",
|
|
24
|
-
platforms: ["linux"],
|
|
25
|
-
priority: options.priority ?? 10,
|
|
26
|
-
};
|
|
27
|
-
this.commandRunner = options.commandRunner ?? defaultCommandRunner;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
isAvailable(_context: ClipboardProviderContext): boolean {
|
|
31
|
-
return true;
|
|
4
|
+
export class WlPasteProvider extends CommandRunnerProvider {
|
|
5
|
+
constructor(options: CommandRunnerProviderOptions = {}) {
|
|
6
|
+
super(
|
|
7
|
+
{
|
|
8
|
+
id: "wl-paste",
|
|
9
|
+
name: "wl-paste",
|
|
10
|
+
platforms: ["linux"],
|
|
11
|
+
priority: options.priority ?? 10,
|
|
12
|
+
},
|
|
13
|
+
options,
|
|
14
|
+
);
|
|
32
15
|
}
|
|
33
16
|
|
|
34
17
|
read(context: ClipboardProviderContext): ClipboardReadResult {
|
|
35
|
-
const listTypes = this.
|
|
36
|
-
environment: context.environment,
|
|
37
|
-
maxBuffer: MAX_BUFFER_BYTES,
|
|
38
|
-
timeout: LIST_TYPES_TIMEOUT_MS,
|
|
39
|
-
});
|
|
18
|
+
const listTypes = this.runCommand("wl-paste", ["--list-types"], context, LIST_TYPES_TIMEOUT_MS);
|
|
40
19
|
if (listTypes.missingCommand) {
|
|
41
|
-
return
|
|
20
|
+
return providerUnavailable();
|
|
42
21
|
}
|
|
43
22
|
|
|
44
23
|
if (!listTypes.ok) {
|
|
45
|
-
return
|
|
24
|
+
return providerEmptyImage();
|
|
46
25
|
}
|
|
47
26
|
|
|
48
|
-
const mimeTypes = listTypes.stdout
|
|
49
|
-
.toString("utf8")
|
|
50
|
-
.split(/\r?\n/)
|
|
51
|
-
.map((mimeType) => mimeType.trim())
|
|
52
|
-
.filter((mimeType) => mimeType.length > 0);
|
|
27
|
+
const mimeTypes = parseMimeTypeList(listTypes.stdout);
|
|
53
28
|
|
|
54
29
|
const selectedMimeType = selectPreferredImageMimeType(mimeTypes);
|
|
55
30
|
if (!selectedMimeType) {
|
|
56
|
-
return
|
|
31
|
+
return providerEmptyImage();
|
|
57
32
|
}
|
|
58
33
|
|
|
59
|
-
const imageData = this.
|
|
34
|
+
const imageData = this.runCommand(
|
|
60
35
|
"wl-paste",
|
|
61
36
|
["--type", selectedMimeType, "--no-newline"],
|
|
62
|
-
|
|
63
|
-
environment: context.environment,
|
|
64
|
-
maxBuffer: MAX_BUFFER_BYTES,
|
|
65
|
-
timeout: READ_TIMEOUT_MS,
|
|
66
|
-
},
|
|
37
|
+
context,
|
|
67
38
|
);
|
|
68
39
|
|
|
69
40
|
if (!imageData.ok || imageData.stdout.length === 0) {
|
|
70
|
-
return
|
|
41
|
+
return providerEmptyImage();
|
|
71
42
|
}
|
|
72
43
|
|
|
73
|
-
return
|
|
74
|
-
available: true,
|
|
75
|
-
image: {
|
|
76
|
-
bytes: new Uint8Array(imageData.stdout),
|
|
77
|
-
mimeType: normalizeMimeType(selectedMimeType),
|
|
78
|
-
},
|
|
79
|
-
};
|
|
44
|
+
return providerImageResult(new Uint8Array(imageData.stdout), normalizeMimeType(selectedMimeType));
|
|
80
45
|
}
|
|
81
46
|
}
|
package/src/providers/xclip.ts
CHANGED
|
@@ -1,58 +1,32 @@
|
|
|
1
|
+
import { CommandRunnerProvider, providerEmptyImage, providerImageResult, providerUnavailable, LIST_TYPES_TIMEOUT_MS, parseMimeTypeList, type CommandRunnerProviderOptions, type ClipboardProviderContext, type ClipboardReadResult } from "./provider-helpers.js";
|
|
1
2
|
import { normalizeMimeType, selectPreferredImageMimeType, SUPPORTED_IMAGE_MIME_TYPES } from "../image-mime.js";
|
|
2
|
-
import {
|
|
3
|
-
defaultCommandRunner,
|
|
4
|
-
LIST_TYPES_TIMEOUT_MS,
|
|
5
|
-
MAX_BUFFER_BYTES,
|
|
6
|
-
READ_TIMEOUT_MS,
|
|
7
|
-
type CommandRunner,
|
|
8
|
-
} from "./command-runner.js";
|
|
9
|
-
import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
|
|
10
3
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
id: "xclip",
|
|
23
|
-
name: "xclip",
|
|
24
|
-
platforms: ["linux"],
|
|
25
|
-
priority: options.priority ?? 20,
|
|
26
|
-
};
|
|
27
|
-
this.commandRunner = options.commandRunner ?? defaultCommandRunner;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
isAvailable(_context: ClipboardProviderContext): boolean {
|
|
31
|
-
return true;
|
|
4
|
+
export class XclipProvider extends CommandRunnerProvider {
|
|
5
|
+
constructor(options: CommandRunnerProviderOptions = {}) {
|
|
6
|
+
super(
|
|
7
|
+
{
|
|
8
|
+
id: "xclip",
|
|
9
|
+
name: "xclip",
|
|
10
|
+
platforms: ["linux"],
|
|
11
|
+
priority: options.priority ?? 20,
|
|
12
|
+
},
|
|
13
|
+
options,
|
|
14
|
+
);
|
|
32
15
|
}
|
|
33
16
|
|
|
34
17
|
read(context: ClipboardProviderContext): ClipboardReadResult {
|
|
35
|
-
const targets = this.
|
|
18
|
+
const targets = this.runCommand(
|
|
36
19
|
"xclip",
|
|
37
20
|
["-selection", "clipboard", "-t", "TARGETS", "-o"],
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
maxBuffer: MAX_BUFFER_BYTES,
|
|
41
|
-
timeout: LIST_TYPES_TIMEOUT_MS,
|
|
42
|
-
},
|
|
21
|
+
context,
|
|
22
|
+
LIST_TYPES_TIMEOUT_MS,
|
|
43
23
|
);
|
|
44
24
|
|
|
45
25
|
if (targets.missingCommand) {
|
|
46
|
-
return
|
|
26
|
+
return providerUnavailable();
|
|
47
27
|
}
|
|
48
28
|
|
|
49
|
-
const advertisedMimeTypes = targets.ok
|
|
50
|
-
? targets.stdout
|
|
51
|
-
.toString("utf8")
|
|
52
|
-
.split(/\r?\n/)
|
|
53
|
-
.map((mimeType) => mimeType.trim())
|
|
54
|
-
.filter((mimeType) => mimeType.length > 0)
|
|
55
|
-
: [];
|
|
29
|
+
const advertisedMimeTypes = targets.ok ? parseMimeTypeList(targets.stdout) : [];
|
|
56
30
|
|
|
57
31
|
const preferredMimeType =
|
|
58
32
|
advertisedMimeTypes.length > 0 ? selectPreferredImageMimeType(advertisedMimeTypes) : null;
|
|
@@ -61,27 +35,17 @@ export class XclipProvider implements ClipboardImageProvider {
|
|
|
61
35
|
: [...SUPPORTED_IMAGE_MIME_TYPES];
|
|
62
36
|
|
|
63
37
|
for (const mimeType of mimeTypesToTry) {
|
|
64
|
-
const imageData = this.
|
|
38
|
+
const imageData = this.runCommand(
|
|
65
39
|
"xclip",
|
|
66
40
|
["-selection", "clipboard", "-t", mimeType, "-o"],
|
|
67
|
-
|
|
68
|
-
environment: context.environment,
|
|
69
|
-
maxBuffer: MAX_BUFFER_BYTES,
|
|
70
|
-
timeout: READ_TIMEOUT_MS,
|
|
71
|
-
},
|
|
41
|
+
context,
|
|
72
42
|
);
|
|
73
43
|
|
|
74
44
|
if (imageData.ok && imageData.stdout.length > 0) {
|
|
75
|
-
return
|
|
76
|
-
available: true,
|
|
77
|
-
image: {
|
|
78
|
-
bytes: new Uint8Array(imageData.stdout),
|
|
79
|
-
mimeType: normalizeMimeType(mimeType),
|
|
80
|
-
},
|
|
81
|
-
};
|
|
45
|
+
return providerImageResult(new Uint8Array(imageData.stdout), normalizeMimeType(mimeType));
|
|
82
46
|
}
|
|
83
47
|
}
|
|
84
48
|
|
|
85
|
-
return
|
|
49
|
+
return providerEmptyImage();
|
|
86
50
|
}
|
|
87
51
|
}
|