mirror of
https://github.com/gustavosett/Windows-11-Clipboard-History-For-Linux
synced 2026-04-25 17:15:35 +02:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf133c63a1 | ||
|
|
ae7bdfe86a | ||
|
|
034f5c3a99 | ||
|
|
86f9717d6d | ||
|
|
e37eee7c89 | ||
|
|
d30a71d5db | ||
|
|
0d15003b2f |
@@ -1,6 +1,6 @@
|
||||
# Maintainer: Gustavo Sett <gustaavoribeeiro@hotmail.com>
|
||||
pkgname=win11-clipboard-history-bin
|
||||
pkgver=0.6.4
|
||||
pkgver=0.6.5
|
||||
pkgrel=1
|
||||
pkgdesc="Windows 11-style Clipboard History Manager for Linux"
|
||||
arch=('x86_64' 'aarch64')
|
||||
|
||||
38
package-lock.json
generated
38
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "win11-clipboard-history",
|
||||
"version": "0.6.4",
|
||||
"version": "0.6.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "win11-clipboard-history",
|
||||
"version": "0.6.4",
|
||||
"version": "0.6.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.10.1",
|
||||
@@ -14,8 +14,8 @@
|
||||
"@tauri-apps/plugin-shell": "^2.3.5",
|
||||
"clsx": "^2.1.0",
|
||||
"emojilib": "^4.0.2",
|
||||
"fuse.js": "^7.0.0",
|
||||
"lucide-react": "^0.576.0",
|
||||
"fuse.js": "^7.3.0",
|
||||
"lucide-react": "^1.7.0",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-window": "^2.2.7"
|
||||
@@ -39,7 +39,7 @@
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.56.0",
|
||||
"vite": "^7.3.1"
|
||||
"vite": "^7.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
@@ -3061,9 +3061,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
|
||||
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -3097,12 +3097,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fuse.js": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
|
||||
"integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==",
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.3.0.tgz",
|
||||
"integrity": "sha512-plz8RVjfcDedTGfVngWH1jmJvBvAwi1v2jecfDerbEnMcmOYUEEwKFTHbNoCiYyzaK2Ws8lABkTCcRSqCY1q4w==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/krisk"
|
||||
}
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
@@ -3633,9 +3637,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.576.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.576.0.tgz",
|
||||
"integrity": "sha512-koNxU14BXrxUfZQ9cUaP0ES1uyPZKYDjk31FQZB6dQ/x+tXk979sVAn9ppZ/pVeJJyOxVM8j1E+8QEuSc02Vug==",
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.7.0.tgz",
|
||||
"integrity": "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
@@ -4221,9 +4225,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
|
||||
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
8
package.json
generated
8
package.json
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "win11-clipboard-history",
|
||||
"private": true,
|
||||
"version": "0.6.4",
|
||||
"version": "0.6.5",
|
||||
"type": "module",
|
||||
"description": "A Windows 11-style Clipboard History Manager for Linux built with Tauri",
|
||||
"author": "gustavosett",
|
||||
@@ -34,8 +34,8 @@
|
||||
"@tauri-apps/plugin-shell": "^2.3.5",
|
||||
"clsx": "^2.1.0",
|
||||
"emojilib": "^4.0.2",
|
||||
"fuse.js": "^7.0.0",
|
||||
"lucide-react": "^0.576.0",
|
||||
"fuse.js": "^7.3.0",
|
||||
"lucide-react": "^1.7.0",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-window": "^2.2.7"
|
||||
@@ -59,6 +59,6 @@
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.56.0",
|
||||
"vite": "^7.3.1"
|
||||
"vite": "^7.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "win11-clipboard-history-lib"
|
||||
version = "0.6.4"
|
||||
version = "0.6.5"
|
||||
description = "A Windows 11-style Clipboard History Manager for Linux"
|
||||
authors = ["gustavosett"]
|
||||
license = "MIT"
|
||||
|
||||
@@ -20,6 +20,7 @@ pub const DEFAULT_MAX_HISTORY_SIZE: usize = 50;
|
||||
const PREVIEW_TEXT_MAX_LEN: usize = 100;
|
||||
const GIF_CACHE_MARKER: &str = "win11-clipboard-history/gifs/";
|
||||
const FILE_URI_PREFIX: &str = "file://";
|
||||
const WL_COPY_SETTLE_TIME: u64 = 150;
|
||||
|
||||
// --- Helper Functions ---
|
||||
|
||||
@@ -618,7 +619,7 @@ impl ClipboardManager {
|
||||
self.last_pasted_text = Some(text.clone());
|
||||
self.last_pasted_image_hash = None;
|
||||
}
|
||||
ClipboardContent::RichText { plain, .. } => {
|
||||
ClipboardContent::RichText { plain, html: _ } => {
|
||||
self.last_pasted_text = Some(plain.clone());
|
||||
self.last_pasted_image_hash = None;
|
||||
}
|
||||
@@ -643,23 +644,20 @@ impl ClipboardManager {
|
||||
self.mark_as_pasted(item);
|
||||
|
||||
// 2. Write content to OS clipboard
|
||||
let mut clipboard = get_system_clipboard()?;
|
||||
|
||||
match &item.content {
|
||||
ClipboardContent::Text(text) => {
|
||||
clipboard.set_text(text).map_err(|e| e.to_string())?;
|
||||
self.set_text_robust(text)?;
|
||||
}
|
||||
ClipboardContent::RichText { plain, html } => {
|
||||
// Set HTML with plain text as fallback - this preserves formatting
|
||||
clipboard
|
||||
.set_html(html, Some(plain))
|
||||
.map_err(|e| e.to_string())?;
|
||||
self.set_html_robust(html, plain)?;
|
||||
}
|
||||
ClipboardContent::Image {
|
||||
base64,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
let mut clipboard = get_system_clipboard()?;
|
||||
self.write_image_to_clipboard(&mut clipboard, base64, *width, *height)?;
|
||||
}
|
||||
}
|
||||
@@ -708,4 +706,112 @@ impl ClipboardManager {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Robustly set text to clipboard using xclip/wl-copy on Linux if available,
|
||||
/// falling back to arboard. This fixes issues on distros like Kali Linux.
|
||||
pub fn set_text_robust(&self, text: &str) -> Result<(), String> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if crate::session::is_wayland() {
|
||||
if let Ok(()) = self.set_clipboard_external(
|
||||
"wl-copy",
|
||||
&["--type", "text/plain;charset=utf-8"],
|
||||
text,
|
||||
) {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
if let Ok(()) = self.set_clipboard_external(
|
||||
"xclip",
|
||||
&["-selection", "clipboard", "-t", "UTF8_STRING"],
|
||||
text,
|
||||
) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to arboard
|
||||
let mut clipboard = get_system_clipboard()?;
|
||||
clipboard.set_text(text).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Robustly set HTML to clipboard using xclip/wl-copy on Linux if available,
|
||||
/// falling back to arboard.
|
||||
pub fn set_html_robust(&self, html: &str, plain: &str) -> Result<(), String> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if crate::session::is_wayland() {
|
||||
if let Ok(()) =
|
||||
self.set_clipboard_external("wl-copy", &["--type", "text/html"], html)
|
||||
{
|
||||
let _ = self.set_text_robust(plain);
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
if let Ok(()) = self.set_clipboard_external(
|
||||
"xclip",
|
||||
&["-selection", "clipboard", "-t", "text/html"],
|
||||
html,
|
||||
) {
|
||||
let _ = self.set_text_robust(plain);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to arboard (which handles multiple MIME types correctly)
|
||||
let mut clipboard = get_system_clipboard()?;
|
||||
clipboard
|
||||
.set_html(html, Some(plain))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn set_clipboard_external(&self, cmd: &str, args: &[&str], data: &str) -> Result<(), String> {
|
||||
use std::io::{Read, Write};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
let mut child = Command::new(cmd)
|
||||
.args(args)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to spawn {}: {}", cmd, e))?;
|
||||
|
||||
if let Some(mut stdin) = child.stdin.take() {
|
||||
stdin
|
||||
.write_all(data.as_bytes())
|
||||
.map_err(|e| format!("Pipe write error: {}", e))?;
|
||||
}
|
||||
|
||||
// Wait briefly to see if it crashed
|
||||
thread::sleep(Duration::from_millis(WL_COPY_SETTLE_TIME));
|
||||
|
||||
match child.try_wait() {
|
||||
Ok(Some(status)) if !status.success() => {
|
||||
let mut stderr = String::new();
|
||||
if let Some(mut stderr_pipe) = child.stderr.take() {
|
||||
let _ = stderr_pipe.read_to_string(&mut stderr);
|
||||
}
|
||||
Err(format!(
|
||||
"{} exited with status {}. Stderr: {}",
|
||||
cmd,
|
||||
status,
|
||||
stderr.trim()
|
||||
))
|
||||
}
|
||||
Ok(_) => {
|
||||
// If it's still running or exited successfully, we assume it worked.
|
||||
// For xclip/wl-copy, they often background themselves or stay alive to serve content.
|
||||
if cmd == "xclip" {
|
||||
thread::spawn(move || {
|
||||
let _ = child.wait();
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(format!("Process status check failed: {}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,12 +206,7 @@ async fn paste_text(
|
||||
{
|
||||
let mut manager = state.clipboard_manager.lock();
|
||||
manager.mark_text_as_pasted(&text);
|
||||
|
||||
use arboard::Clipboard;
|
||||
Clipboard::new()
|
||||
.map_err(|e| e.to_string())?
|
||||
.set_text(&text)
|
||||
.map_err(|e| e.to_string())?;
|
||||
manager.set_text_robust(&text)?;
|
||||
}
|
||||
|
||||
// 3. Simulate Paste
|
||||
|
||||
2
src-tauri/tauri.conf.json
generated
2
src-tauri/tauri.conf.json
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "win11-clipboard-history",
|
||||
"version": "0.6.4",
|
||||
"version": "0.6.5",
|
||||
"identifier": "dev.gustavosett.clipboard-history",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
||||
Reference in New Issue
Block a user