Skip to content

Commit d9bb2a3

Browse files
kai3316claude
andcommitted
fix: Linux dialog deadlock + Chinese encoding + QR/notify fallbacks
- Use tk.Toplevel instead of CTkToplevel on Linux for transfer request, peer selection, and zip progress dialogs (same fix as macOS commit c324611) - Read clipboard via NSPasteboard ctypes bridge for guaranteed UTF-8 on macOS - Add _safe_decode() with CJK encoding fallback chain for history previews - Fix HTML preview: strip style/script blocks, comments, unescape entities - Fix QR code not rendering on Linux: convert qrcode image to RGB - Fix notifications on Linux: catch NotImplementedError, fall back to notify-send - Make root window transparent instead of withdrawn on Linux (fixes window restore) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 3cbc584 commit d9bb2a3

7 files changed

Lines changed: 243 additions & 102 deletions

File tree

internal/clipboard/clipboard_darwin.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,14 @@ def read(self) -> ClipboardContent:
292292
# -- text / html / rtf via pbpaste (no TCC issues) ---------------------
293293

294294
def _get_text(self) -> bytes:
295+
# Prefer ctypes NSPasteboard → public.utf8-plain-text (guaranteed UTF-8).
296+
# pbpaste -Prefer txt can return bytes in a legacy encoding (e.g. GBK)
297+
# for CJK text, producing garbled characters when decoded as UTF-8.
298+
data = _pb_data_for_type(b"public.utf8-plain-text")
299+
if data:
300+
return data
301+
302+
# Fallback: pbpaste
295303
try:
296304
result = subprocess.run(
297305
["pbpaste", "-Prefer", "txt"],

internal/clipboard/history.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,33 @@
3333
}
3434

3535

36+
def _safe_decode(data: bytes) -> str:
37+
"""Decode bytes to string, trying common encodings."""
38+
try:
39+
return data.decode("utf-8")
40+
except UnicodeDecodeError:
41+
pass
42+
for enc in ("gbk", "gb2312", "gb18030", "big5", "shift-jis", "euc-kr"):
43+
try:
44+
return data.decode(enc)
45+
except (UnicodeDecodeError, UnicodeEncodeError):
46+
continue
47+
return data.decode("utf-8", errors="replace")
48+
49+
3650
def _strip_html(text: str) -> str:
37-
"""Remove HTML tags and collapse whitespace."""
51+
"""Remove HTML tags, style/script blocks, comments, and unescape entities."""
52+
import html as _html
53+
# Remove <style> and <script> blocks (including their content)
54+
text = re.sub(r"<style[^>]*>.*?</style>", "", text, flags=re.DOTALL | re.IGNORECASE)
55+
text = re.sub(r"<script[^>]*>.*?</script>", "", text, flags=re.DOTALL | re.IGNORECASE)
56+
# Remove HTML comments
57+
text = re.sub(r"<!--.*?-->", "", text, flags=re.DOTALL)
58+
# Strip remaining tags
3859
plain = re.sub(r"<[^>]*>", "", text)
60+
# Unescape HTML entities
61+
plain = _html.unescape(plain)
62+
# Collapse whitespace
3963
plain = re.sub(r"\s+", " ", plain)
4064
return plain.strip()
4165

@@ -73,10 +97,10 @@ def _make_dedup_key(content: ClipboardContent) -> str:
7397
def _build_preview(types: dict[ContentType, bytes]) -> str:
7498
"""Build a human-readable preview from clipboard content."""
7599
if ContentType.TEXT in types:
76-
text = types[ContentType.TEXT].decode("utf-8", errors="replace")
100+
text = _safe_decode(types[ContentType.TEXT])
77101
return text[:200]
78102
if ContentType.HTML in types:
79-
html = types[ContentType.HTML].decode("utf-8", errors="replace")
103+
html = _safe_decode(types[ContentType.HTML])
80104
plain = _strip_html(html)
81105
return plain[:200] if plain else "[HTML]"
82106
if ContentType.IMAGE_EMF in types:

internal/platform/notify.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,25 @@ def show(self, title: str, message: str):
7272
if self._tray_icon:
7373
try:
7474
self._tray_icon.notify(message, title=title)
75+
except NotImplementedError:
76+
logger.debug("pystray notify not implemented for this backend")
77+
self._fallback_notify(title, message)
7578
except Exception:
7679
logger.debug("Desktop notification failed", exc_info=True)
7780

81+
@staticmethod
82+
def _fallback_notify(title: str, message: str):
83+
"""Fallback desktop notification via system command (Linux)."""
84+
import subprocess
85+
import sys
86+
if sys.platform == "linux":
87+
try:
88+
subprocess.run(
89+
["notify-send", title, message],
90+
capture_output=True, timeout=5,
91+
)
92+
except Exception:
93+
pass
94+
7895

7996
notification_mgr = NotificationManager()

internal/ui/dashboard.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2272,6 +2272,7 @@ def _refresh_web_card(self):
22722272
import qrcode
22732273
from PIL import Image
22742274
img = qrcode.make(url)
2275+
img = img.convert("RGB")
22752276
img = img.resize((100, 100), Image.LANCZOS)
22762277
ctk_img = ctk.CTkImage(light_image=img, dark_image=img, size=(100, 100))
22772278
self._web_qr_label.configure(image=ctk_img, text="")

internal/ui/settings_window.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ def _refresh_web_qr(self):
624624

625625
if token:
626626
img = _qrcode.make(url)
627+
img = img.convert("RGB")
627628
img = img.resize((200, 200), Image.LANCZOS)
628629
self._web_qr_image = ctk.CTkImage(
629630
light_image=img, dark_image=img, size=(200, 200),

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "clipsync"
7-
version = "1.0.1"
7+
version = "1.0.3"
88
description = "Cross-platform clipboard sharing over LAN"
99
readme = "README.md"
1010
license = { text = "MIT" }

0 commit comments

Comments
 (0)