Compare commits

...

7 Commits

Author SHA1 Message Date
Gustavo Carvalho
bf133c63a1 chore: bump version to 0.6.5 2026-04-18 15:47:09 -03:00
freshCoder21313
ae7bdfe86a fix(clipboard): improve linux reliability (#229)
* fix(clipboard): improve linux reliability

- Implemented robust text and HTML setting using xclip and wl-copy
- Added fallback to arboard for better compatibility on Linux distros
- Updated package-lock.json with peer dependency metadata

* refactor(clipboard): remove unused variables

- Clean up unused variables to resolve compiler warnings
- Remove redundant system clipboard initialization in paste_item

* fix(clipboard): improve linux external tool handling

- Added target OS guards to prevent executing Linux-specific commands on other platforms
- Enhanced error reporting by capturing stderr when external tools like xclip or wl-copy fail
- Updated HTML clipboard logic to attempt setting both HTML and plain text formats sequentially"

* fix(linux): improve multi-format clipboard copy

- Ensure plain text is set robustly after HTML content on Wayland and X11
- Prevents potential race conditions or overwrites when using external tools

* fix(clipboard): explicitly set UTF-8 targets to prevent mojibake on browsers

Browsers strictly enforce X11/Wayland clipboard target MIME types and atoms.
When clipboard data is set with `text/plain`, browsers default to decoding
it as ISO-8859-1 or ASCII, which breaks special characters, icons, and emojis
such as kaomoji (e.g. they show as `ðð§(。â¥â¿â¥ï½¡)`).

This commit resolves the mojibake issue on Linux by:
- Explicitly appending `;charset=utf-8` to the target when using `wl-copy`.
- Explicitly using the `UTF8_STRING` atom instead of `text/plain` for `xclip` to conform with X11 conventions.
2026-04-18 15:45:47 -03:00
dependabot[bot]
034f5c3a99 chore(deps): bump vite from 7.3.1 to 7.3.2 (#222)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.3.1 to 7.3.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.2
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-08 11:30:28 -03:00
dependabot[bot]
86f9717d6d chore(deps): bump fuse.js in the production-dependencies group (#221)
Bumps the production-dependencies group with 1 update: [fuse.js](https://github.com/krisk/Fuse).


Updates `fuse.js` from 7.1.0 to 7.3.0
- [Release notes](https://github.com/krisk/Fuse/releases)
- [Changelog](https://github.com/krisk/Fuse/blob/main/CHANGELOG.md)
- [Commits](https://github.com/krisk/Fuse/compare/v7.1.0...v7.3.0)

---
updated-dependencies:
- dependency-name: fuse.js
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-08 11:30:10 -03:00
dependabot[bot]
e37eee7c89 chore(deps): bump lucide-react in the production-dependencies group (#219)
Bumps the production-dependencies group with 1 update: [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react).


Updates `lucide-react` from 0.577.0 to 1.7.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.7.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.7.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: production-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 12:38:46 -03:00
dependabot[bot]
d30a71d5db chore(deps): bump flatted from 3.3.3 to 3.4.2 (#216)
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.2.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.2)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-24 06:27:41 -03:00
dependabot[bot]
0d15003b2f chore(deps): bump lucide-react in the production-dependencies group (#211)
Bumps the production-dependencies group with 1 update: [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react).


Updates `lucide-react` from 0.576.0 to 0.577.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.577.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 0.577.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 06:34:03 -03:00
7 changed files with 142 additions and 37 deletions

View File

@@ -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
View File

@@ -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
View File

@@ -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"
}
}

View File

@@ -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"

View File

@@ -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)),
}
}
}

View File

@@ -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

View File

@@ -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",