Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions __test__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
hasRtf,
hasText,
setBuffer,
setContents,
setHtml,
setText,
startWatch,
Expand Down Expand Up @@ -68,6 +69,30 @@ test.serial('setBuffer / getBuffer round-trips with custom format', async (t) =>
t.deepEqual(Array.from(result), data)
})

// ── Multiple formats ─────────────────────────────────────────────────

test.serial('setContents writes multiple clipboard representations at once', async (t) => {
const format = 'com.ava.test-set-contents'
const data = [0x48, 0x65, 0x6c, 0x6c, 0x6f]

await setContents({
text: 'hello',
html: '<p>hello</p>',
rtf: '{\\rtf1\\ansi hello}',
buffers: [{ format, buffer: data }],
})

t.is(await getText(), 'hello')
t.true(hasHtml())
t.true((await getHtml()).includes('hello'))
t.true(hasRtf())
t.deepEqual(Array.from(await getBuffer(format)), data)
})

test.serial('setContents rejects empty content', async (t) => {
await t.throwsAsync(() => setContents({}), { message: /contents is empty/ })
})

// ── Clear ────────────────────────────────────────────────────────────

test.serial('clear removes clipboard content', async (t) => {
Expand Down
15 changes: 15 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ export declare function availableFormats(): Array<string>

export declare function clear(): Promise<void>

export interface ClipboardBufferContent {
format: string
buffer: Array<number>
}

export interface ClipboardContents {
text?: string
html?: string
rtf?: string
buffers?: Array<ClipboardBufferContent>
}

export declare function getBuffer(format: string): Promise<Array<number>>

export declare function getFiles(): Promise<Array<string>>
Expand All @@ -35,6 +47,9 @@ export declare function hasText(): boolean

export declare function setBuffer(format: string, buffer: Array<number>): Promise<void>

/** Write multiple representations of the same clipboard content in a single operation. */
export declare function setContents(contents: ClipboardContents): Promise<void>

export declare function setFiles(files: Array<string>): Promise<void>

export declare function setHtml(html: string): Promise<void>
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,7 @@ module.exports.hasImage = nativeBinding.hasImage
module.exports.hasRtf = nativeBinding.hasRtf
module.exports.hasText = nativeBinding.hasText
module.exports.setBuffer = nativeBinding.setBuffer
module.exports.setContents = nativeBinding.setContents
module.exports.setFiles = nativeBinding.setFiles
module.exports.setHtml = nativeBinding.setHtml
module.exports.setImageBase64 = nativeBinding.setImageBase64
Expand Down
51 changes: 49 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#![deny(clippy::all)]
use base64::{engine::general_purpose, Engine as _};
use clipboard_rs::{
common::RustImage, Clipboard, ClipboardContext, ClipboardHandler, ClipboardWatcher,
ClipboardWatcherContext, ContentFormat, RustImageData, WatcherShutdown,
common::RustImage, Clipboard, ClipboardContent, ClipboardContext, ClipboardHandler,
ClipboardWatcher, ClipboardWatcherContext, ContentFormat, RustImageData, WatcherShutdown,
};
use napi::bindgen_prelude::*;
use napi::threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunctionCallMode};
Expand Down Expand Up @@ -139,6 +139,53 @@ pub async fn set_buffer(format: String, buffer: Vec<u8>) -> Result<()> {
ctx.set_buffer(&format, buffer).map_err(cb_err)
}

#[napi(object)]
pub struct ClipboardBufferContent {
pub format: String,
pub buffer: Vec<u8>,
}

#[napi(object)]
pub struct ClipboardContents {
pub text: Option<String>,
pub html: Option<String>,
pub rtf: Option<String>,
pub buffers: Option<Vec<ClipboardBufferContent>>,
}

/// Write multiple representations of the same clipboard content in a single operation.
#[napi]
pub async fn set_contents(contents: ClipboardContents) -> Result<()> {
let ctx = ClipboardContext::new().map_err(cb_err)?;
let mut clipboard_contents = Vec::new();

if let Some(text) = contents.text {
clipboard_contents.push(ClipboardContent::Text(text));
}
if let Some(html) = contents.html {
clipboard_contents.push(ClipboardContent::Html(html));
}
if let Some(rtf) = contents.rtf {
clipboard_contents.push(ClipboardContent::Rtf(rtf));
}
if let Some(buffers) = contents.buffers {
clipboard_contents.extend(
buffers
.into_iter()
.map(|entry| ClipboardContent::Other(entry.format, entry.buffer)),
);
}

if clipboard_contents.is_empty() {
return Err(Error::new(
Status::InvalidArg,
"contents is empty; use clear() to clear the clipboard",
));
}

ctx.set(clipboard_contents).map_err(cb_err)
}

#[napi]
pub async fn clear() -> Result<()> {
let ctx = ClipboardContext::new().map_err(cb_err)?;
Expand Down