「コピー」ボタンで ID や URL をクリップボードに入れる、「貼り付け」ボタンで読み取った文字列を入力欄に入れる、パスワードをコピーして一定時間後に消す、といったテキストのやり取りは Clipboard Manager プラグインで行います。JS からは writeText() / readText() / clear()、Rust からは ClipboardExt の同名のメソッドを使います。つまずきやすいのは権限の設定と、読み取れないときに例外になる点です。画像は クリップボードに画像をコピーする で扱います。
前提条件
npm run tauri add clipboard-manager
npm パッケージと Rust クレートが追加され、lib.rs にプラグインの登録が入ります。
注意したいのは、clipboard-manager:default が何も許可しない点です。クリップボードには他のアプリでコピーしたパスワードなども入るため、使う操作を 1 つずつ許可します。core:default にも含まれません。
| 関数 | 権限 |
|---|---|
writeText() | clipboard-manager:allow-write-text |
readText() | clipboard-manager:allow-read-text |
writeHtml() | clipboard-manager:allow-write-html |
clear() | clipboard-manager:allow-clear |
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"clipboard-manager:allow-write-text",
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-html",
"clipboard-manager:allow-clear"
]
}
使わないものは外します。特に読み取りを許可すると、そのウィンドウで動くスクリプトはユーザーの操作が無くてもクリップボードを読めるようになります。コピーしかしないアプリなら allow-write-text だけで足ります。
1. フロントエンドから実装する (TypeScript)
コピーと貼り付け
readText() は、クリップボードが空のときや画像しか入っていないときに null や空文字列を返すのではなく、例外を投げます。「読めなければ何もしない」処理は try / catch で書き、権限の設定漏れまで握りつぶさないよう、エラーの文面で分けます。
import { writeText, readText } from '@tauri-apps/plugin-clipboard-manager';
// data-copy 属性の文字列をコピーするボタン
document.querySelectorAll<HTMLButtonElement>('button[data-copy]').forEach((btn) => {
btn.addEventListener('click', async () => {
await writeText(btn.dataset.copy ?? ''); // clipboard-manager:allow-write-text
btn.textContent = 'コピーしました';
});
});
// 貼り付け。テキストが無ければ null を返す
export async function pasteText(): Promise<string | null> {
try {
return await readText(); // clipboard-manager:allow-read-text
} catch (e) {
if (String(e).includes('not allowed')) throw e; // 権限の設定漏れは表に出す
return null; // 空、または画像などテキスト以外
}
}
書式付きでコピーする・消去する
writeHtml(html, altText) は、HTML と、書式を扱えない貼り付け先のためのプレーンテキストを同時に書き込みます。HTML を受け付けるワープロやメールには太字などが残り、テキストエディタには altText が入ります。altText を省略するとプレーンテキスト側が空になる場合があるので、必ず渡します。clear() はテキストだけでなく画像も含めてクリップボードを空にします。
import { writeHtml, clear } from '@tauri-apps/plugin-clipboard-manager';
export async function copyOrder(id: string) {
// 書式付き(HTML)と、プレーンテキストの代わりを一緒に書き込む
await writeHtml(`<b>注文番号</b>: ${id}`, `注文番号: ${id}`); // allow-write-html
}
export async function wipeClipboard() {
await clear(); // allow-clear。画像も消える
}
2. バックエンドから実装する (Rust)
tauri_plugin_clipboard_manager::ClipboardExt を use すると、AppHandle やウィンドウで clipboard() が使えます。capability の権限は JS からの呼び出しにだけ効くので、Rust で処理すれば WebView 側に allow-read-text を渡さずに済みます。クリップボードの中身を読むのは Rust のコマンドだけにして、必要な結果だけを JS に返す設計にできます。
読み取り(read_text())はメインスレッドで呼ばないようにします。Linux では、WebView からコピーした内容を読むときなどにアプリ全体が固まることがあります。async の付かないコマンドはメインスレッドで実行されるので、読み取るコマンドは async fn にし、時間をおいて行う処理は別スレッドに回します。
use std::time::Duration;
use tauri_plugin_clipboard_manager::ClipboardExt;
#[tauri::command]
fn copy_text(app: tauri::AppHandle, text: String) -> Result<(), String> {
app.clipboard().write_text(text).map_err(|e| e.to_string())
}
// 読み取りは async にしてメインスレッドを避ける
#[tauri::command]
async fn paste_text(app: tauri::AppHandle) -> Result<String, String> {
app.clipboard().read_text().map_err(|e| e.to_string())
}
/// パスワードなどをコピーし、30 秒後にまだ同じ内容なら消す
#[tauri::command]
fn copy_secret(app: tauri::AppHandle, secret: String) -> Result<(), String> {
app.clipboard().write_text(secret.as_str()).map_err(|e| e.to_string())?;
std::thread::spawn(move || {
std::thread::sleep(Duration::from_secs(30));
// その間にユーザーが別の物をコピーしていたら消さない
if app.clipboard().read_text().ok().as_deref() == Some(secret.as_str()) {
let _ = app.clipboard().clear();
}
});
Ok(())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_clipboard_manager::init()) // Rust だけで使う場合も必要
.invoke_handler(tauri::generate_handler![copy_text, paste_text, copy_secret])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';
await invoke('copy_secret', { secret: 'p@ssw0rd' }); // JS 側に clipboard の権限は不要
const text = await invoke<string>('paste_text');
console.log(text);
write_text() は &str と String のどちらも受け取ります。トレイやメニュー、グローバルショートカットのハンドラーでも AppHandle があれば同じように呼べます。ただしトレイとメニューのハンドラーはメインスレッドで動くので、そこで読み取るときは std::thread::spawn などで別スレッドに移します。
動作確認
npm run tauri dev で起動し、コピーボタンを押してからメモ帳などに貼り付けると、同じ文字列が入ります。copyOrder('12345') の後にワープロへ貼ると「注文番号」が太字になり、テキストエディタには altText の文字列が入ります。copy_secret を呼んで 30 秒待つとクリップボードが空になり、pasteText() は null を返します。途中で別の文字列をコピーした場合は消えません。
DevTools のコンソールで読み書きを確かめる例です。
import { writeText, readText } from '@tauri-apps/plugin-clipboard-manager';
await writeText('Hello, Tauri!');
console.log(await readText()); // Hello, Tauri!
よくあるエラーと対処法
- 「clipboard-manager.read_text not allowed. Permissions associated with this command: clipboard-manager:allow-read-text」: 権限の追加漏れです。
clipboard-manager:defaultだけでは何も許可されません。リリースビルドでは「Command plugin:clipboard-manager|read_text not allowed by ACL」になります。追加したらtauri devを再起動します。 - 「The clipboard contents were not available in the requested format or the clipboard is empty.」:
readText()/read_text()で、クリップボードが空か、画像などテキスト以外しか入っていません。空の貼り付けとして扱います。 - 「The native clipboard is not accessible due to being held by another party.」: 別のアプリがクリップボードを使用中です。少し待ってから再試行します。
- Rust で「state() called before manage() for」で始まるパニック:
.plugin(tauri_plugin_clipboard_manager::init())の登録漏れです。JS から使わない場合も登録が必要です。 - Linux で貼り付けた瞬間にアプリが固まる: 同期コマンドやメインスレッドで
read_text()を呼んでいます。async fnのコマンドか別スレッドに移します。
OS ごとの違いと注意点
- Android / iOS: 扱えるのはプレーンテキストだけです。
writeHtml()は「Unsupported on this platform」で失敗します。 - Android:
clear()が本当に消去するのは Android 9(API レベル 28)以上で、それより古い端末では空文字列を書き込んで代わりにします。 writeText()のlabelオプション: モバイル向けの指定で、デスクトップでは使われません。- Linux: 前述のとおり、Rust から読み取るときはメインスレッドを避けます。
- 共通: クリップボードは OS 全体で 1 つなので、書き込むとユーザーが直前にコピーしていた内容は上書きされます。自動でコピーするのは、ボタンを押したときなどユーザーの操作をきっかけにした場合に限るのが無難です。
