Skip to content

Commit

Permalink
Add image binary listening feature
Browse files Browse the repository at this point in the history
  • Loading branch information
HuakunShen committed Jun 29, 2024
1 parent 9b72e65 commit 5edf65e
Show file tree
Hide file tree
Showing 13 changed files with 163 additions and 48 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "tauri-plugin-clipboard"
version = "2.0.4"
version = "2.1.0"
license = "MIT"
description = "A clipboard plugin for Tauri that supports text, files and image, as well as clipboard update listening."
description = "A clipboard plugin for Tauri that supports text, html, rtf, files and image, as well as clipboard update listening."
authors = [ "Huakun" ]
edition = "2021"
rust-version = "1.70"
Expand Down
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const COMMANDS: &[&str] = &[
"has_html",
"has_rtf",
"has_files",
"available_types",
"read_text",
"read_files",
"read_files_uris",
Expand Down
9 changes: 4 additions & 5 deletions examples/demo/src/lib/components/listener.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import {
onClipboardUpdate,
onImageUpdate,
onImageBinaryUpdate,
onTextUpdate,
onHTMLUpdate,
onRTFUpdate,
Expand Down Expand Up @@ -62,7 +63,9 @@
unlistenRTF = await onRTFUpdate((newRTF) => {
rtf = newRTF;
});
unlistenClipboard = await startListening();
unlistenClipboard = await startListening({
imageBinary: true
});
onClipboardUpdate(async () => {
clear();
Expand All @@ -73,10 +76,6 @@
has.hasFiles = await hasFiles();
console.log('plugin:clipboard://clipboard-monitor/update event received');
});
// setInterval(async () => {
// console.log("Running:", await isMonitorRunning());
// }, 1000);
});
listenToMonitorStatusUpdate((running) => {
Expand Down
1 change: 1 addition & 0 deletions examples/demo/src/lib/components/read-and-write.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import { onMount } from 'svelte';
import clipboard from 'tauri-plugin-clipboard-api';
let text = '';
let rtf = '';
Expand Down
129 changes: 94 additions & 35 deletions guest-js/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ export const HTML_CHANGED = "plugin:clipboard://html-changed";
export const RTF_CHANGED = "plugin:clipboard://rtf-changed";
export const FILES_CHANGED = "plugin:clipboard://files-changed";
export const IMAGE_CHANGED = "plugin:clipboard://image-changed";
export const IMAGE_BINARY_CHANGED = "plugin:clipboard://image-changed-binary";
export const IS_MONITOR_RUNNING_COMMAND = "plugin:clipboard|is_monitor_running";
export const HAS_TEXT_COMMAND = "plugin:clipboard|has_text";
export const HAS_IMAGE_COMMAND = "plugin:clipboard|has_image";
export const HAS_HTML_COMMAND = "plugin:clipboard|has_html";
export const HAS_RTF_COMMAND = "plugin:clipboard|has_rtf";
export const HAS_FILES_COMMAND = "plugin:clipboard|has_files";
export const AVAILABLE_TYPES_COMMAND = "plugin:clipboard|available_types";
export const WRITE_TEXT_COMMAND = "plugin:clipboard|write_text";
export const WRITE_HTML_COMMAND = "plugin:clipboard|write_html";
export const WRITE_HTML_AND_TEXT_COMMAND =
Expand All @@ -38,6 +40,9 @@ export const CLIPBOARD_MONITOR_STATUS_UPDATE_EVENT =
export const MONITOR_UPDATE_EVENT =
"plugin:clipboard://clipboard-monitor/update";
export const ClipboardChangedPayloadSchema = z.object({ value: z.string() });
export const ClipboardBinaryChangedPayloadSchema = z.object({
value: z.number().array(),
});
export const ClipboardChangedFilesPayloadSchema = z.object({
value: z.string().array(),
});
Expand Down Expand Up @@ -153,6 +158,14 @@ export function readImageBinary(format: "int_array" | "Uint8Array" | "Blob") {
});
}

export function convertIntArrToUint8Array(intArr: number[]): Uint8Array {
return new Uint8Array(intArr);
}

export function convertUint8ArrayToBlob(uintArr: Uint8Array): Blob {
return new Blob([uintArr]);
}

/**
* Here is the transformation flow,
* read clipboard image as Array<number> (int_array) -> int_array -> Uint8Array -> Blob -> ObjectURL
Expand Down Expand Up @@ -235,53 +248,90 @@ export function startBruteForceImageMonitor(delay: number = 1000) {
};
}

type UpdatedTypes = {
export type UpdatedTypes = {
text?: boolean;
html?: boolean;
rtf?: boolean;
image?: boolean;
imageBinary?: boolean; // get image in binary format
files?: boolean;
};

export type AvailableTypes = {
text: boolean;
html: boolean;
rtf: boolean;
image: boolean;
files: boolean;
};

export function getAvailableTypes(): Promise<AvailableTypes> {
return invoke<AvailableTypes>(AVAILABLE_TYPES_COMMAND);
}

/**
* Listen to "plugin:clipboard://clipboard-monitor/update" from Tauri core.
* The corresponding clipboard type event will be emitted when there is clipboard update.
* @param listenTypes types of clipboard data to listen to
* @returns unlisten function
*/
export function listenToClipboard(): Promise<UnlistenFn> {
export function listenToClipboard(
listenTypes: UpdatedTypes = {
text: true,
html: true,
rtf: true,
image: true,
imageBinary: false,
files: true,
}
): Promise<UnlistenFn> {
return listen(MONITOR_UPDATE_EVENT, async (e) => {
if (e.payload === "clipboard update") {
const hasData = await Promise.all([
hasFiles(),
hasImage(),
hasHTML(),
hasRTF(),
hasText(),
]);
const flags: UpdatedTypes = {
files: await hasFiles(),
image: await hasImage(),
html: await hasHTML(),
rtf: await hasRTF(),
text: await hasText(),
files: hasData[0],
image: hasData[1],
imageBinary: hasData[1],
html: hasData[2],
rtf: hasData[3],
text: hasData[4],
};
await emit(SOMETHING_CHANGED, flags);
if (flags.files) {
if (listenTypes.files && flags.files) {
const files = await readFiles();
if (files && files.length > 0) {
await emit(FILES_CHANGED, { value: files });
}
flags.files = true;
// flags.files = true;
return; // ! this return is necessary, copying files also update clipboard text, but we don't want text update to be triggered
}
if (flags.image) {
if (listenTypes.image && flags.image) {
const img = await readImageBase64();
if (img) await emit(IMAGE_CHANGED, { value: img });
flags.image = true;
// flags.image = true;
}
if (listenTypes.imageBinary && flags.imageBinary) {
const img = await readImageBinary("int_array");
if (img) await emit(IMAGE_BINARY_CHANGED, { value: img });
// flags.imageBinary = true;
}
if (flags.html) {
if (listenTypes.html && flags.html) {
await emit(HTML_CHANGED, { value: await readHtml() });
flags.html = true;
// flags.html = true;
}
if (flags.rtf) {
if (listenTypes.rtf && flags.rtf) {
await emit(RTF_CHANGED, { value: await readRtf() });
flags.rtf = true;
// flags.rtf = true;
}
if (flags.text) {
if (listenTypes.text && flags.text) {
await emit(TEXT_CHANGED, { value: await readText() });
flags.text = true;
// flags.text = true;
}
// when clear() is called, this error is thrown, let ignore it
// if (!success) {
Expand Down Expand Up @@ -320,52 +370,52 @@ export async function onTextUpdate(
* @param cb
* @returns
*/
export async function onSomethingUpdate(
cb: (updatedTypes: UpdatedTypes) => void
) {
return await listen(SOMETHING_CHANGED, (event) => {
export function onSomethingUpdate(cb: (updatedTypes: UpdatedTypes) => void) {
return listen(SOMETHING_CHANGED, (event) => {
cb(event.payload as UpdatedTypes);
});
}

export async function onHTMLUpdate(
cb: (text: string) => void
): Promise<UnlistenFn> {
return await listen(HTML_CHANGED, (event) => {
export function onHTMLUpdate(cb: (text: string) => void): Promise<UnlistenFn> {
return listen(HTML_CHANGED, (event) => {
const text = ClipboardChangedPayloadSchema.parse(event.payload).value;
cb(text);
});
}

export async function onRTFUpdate(
cb: (text: string) => void
): Promise<UnlistenFn> {
return await listen(RTF_CHANGED, (event) => {
export function onRTFUpdate(cb: (text: string) => void): Promise<UnlistenFn> {
return listen(RTF_CHANGED, (event) => {
const text = ClipboardChangedPayloadSchema.parse(event.payload).value;
cb(text);
});
}

export async function onFilesUpdate(
export function onFilesUpdate(
cb: (files: string[]) => void
): Promise<UnlistenFn> {
return await listen(FILES_CHANGED, (event) => {
return listen(FILES_CHANGED, (event) => {
const files = ClipboardChangedFilesPayloadSchema.parse(event.payload).value;
cb(files);
});
}

export async function onImageUpdate(
export function onImageUpdate(
cb: (base64ImageStr: string) => void
): Promise<UnlistenFn> {
return await listen(IMAGE_CHANGED, (event) => {
return listen(IMAGE_CHANGED, (event) => {
const base64ImageStr = ClipboardChangedPayloadSchema.parse(
event.payload
).value;
cb(base64ImageStr);
});
}

export function onImageBinaryUpdate(cb: (image: number[]) => void) {
return listen(IMAGE_BINARY_CHANGED, (event) => {
cb(ClipboardBinaryChangedPayloadSchema.parse(event.payload).value);
});
}

/**
* Used to check the status of clipboard monitor
* @returns Whether the monitor is running
Expand Down Expand Up @@ -408,9 +458,18 @@ export async function listenToMonitorStatusUpdate(
});
}

export function startListening(): Promise<() => Promise<void>> {
export function startListening(
listenTypes: UpdatedTypes = {
text: true,
html: true,
rtf: true,
image: true,
imageBinary: false,
files: true,
}
): Promise<() => Promise<void>> {
return startMonitor()
.then(() => listenToClipboard())
.then(() => listenToClipboard(listenTypes))
.then((unlistenClipboard) => {
// return an unlisten function that stop listening to clipboard update and stop the monitor
return async () => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tauri-plugin-clipboard-api",
"version": "2.0.4",
"version": "2.1.0",
"author": "Huakun",
"description": "",
"type": "module",
Expand Down
13 changes: 13 additions & 0 deletions permissions/autogenerated/commands/available_types.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!

"$schema" = "../../schemas/schema.json"

[[permission]]
identifier = "allow-available-types"
description = "Enables the available_types command without any pre-configured scope."
commands.allow = ["available_types"]

[[permission]]
identifier = "deny-available-types"
description = "Denies the available_types command without any pre-configured scope."
commands.deny = ["available_types"]
2 changes: 2 additions & 0 deletions permissions/autogenerated/reference.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
| Permission | Description |
|------|-----|
|`allow-available-types`|Enables the available_types command without any pre-configured scope.|
|`deny-available-types`|Denies the available_types command without any pre-configured scope.|
|`allow-clear`|Enables the clear command without any pre-configured scope.|
|`deny-clear`|Denies the clear command without any pre-configured scope.|
|`allow-execute`|Enables the execute command without any pre-configured scope.|
Expand Down
1 change: 1 addition & 0 deletions permissions/read-all.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ commands.allow = [
"has_html",
"has_rtf",
"has_files",
"available_types",
"read_text",
"read_files",
"read_files_uris",
Expand Down
14 changes: 14 additions & 0 deletions permissions/schemas/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,20 @@
"PermissionKind": {
"type": "string",
"oneOf": [
{
"description": "allow-available-types -> Enables the available_types command without any pre-configured scope.",
"type": "string",
"enum": [
"allow-available-types"
]
},
{
"description": "deny-available-types -> Denies the available_types command without any pre-configured scope.",
"type": "string",
"enum": [
"deny-available-types"
]
},
{
"description": "allow-clear -> Enables the clear command without any pre-configured scope.",
"type": "string",
Expand Down
10 changes: 7 additions & 3 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ pub fn has_files<R: Runtime>(
clipboard.has_files()
}

#[command]
pub fn available_types(
clipboard: State<'_, Clipboard>,
) -> Result<crate::desktop::AvailableTypes, String> {
clipboard.available_types()
}

#[command]
pub fn read_text<R: Runtime>(
_app: AppHandle<R>,
Expand Down Expand Up @@ -101,7 +108,6 @@ pub fn write_files_uris<R: Runtime>(
clipboard.write_files_uris(files_uris)
}


#[command]
pub fn write_files<R: Runtime>(
_app: AppHandle<R>,
Expand Down Expand Up @@ -134,8 +140,6 @@ pub fn write_files<R: Runtime>(
write_files_uris(_app, _window, clipboard, files_uris)
}



#[command]
pub fn write_text<R: Runtime>(
_app: AppHandle<R>,
Expand Down
Loading

0 comments on commit 5edf65e

Please sign in to comment.