Last active
November 4, 2025 08:21
-
-
Save hizkifw/98a0353b4e6f5638e30e949b900abc0f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Clipboard synchronization script for X11/Xwayland to Wayland | |
| # Monitors X11 clipboard and syncs to Wayland when it changes | |
| # Note: wl-clip-persist is required (https://github.com/Linus789/wl-clip-persist) | |
| # Prevent multiple instances | |
| # Use XDG_RUNTIME_DIR if available, fallback to /run/user/$UID, then /tmp | |
| if [ -n "$XDG_RUNTIME_DIR" ] && [ -d "$XDG_RUNTIME_DIR" ]; then | |
| LOCKFILE="$XDG_RUNTIME_DIR/clipboard-sync.lock" | |
| elif [ -d "/run/user/$UID" ]; then | |
| LOCKFILE="/run/user/$UID/clipboard-sync.lock" | |
| else | |
| LOCKFILE="/tmp/clipboard-sync-$UID.lock" | |
| fi | |
| exec 200>"$LOCKFILE" | |
| if ! flock -n 200; then | |
| echo "Another instance is already running. Exiting." | |
| exit 1 | |
| fi | |
| # Cleanup on exit | |
| trap "rm -f '$LOCKFILE'" EXIT | |
| # Colors for output | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| NC='\033[0m' # No Color | |
| # Function to log messages | |
| log() { | |
| echo -e "${GREEN}[$(date '+%H:%M:%S')]${NC} $1" | |
| } | |
| error() { | |
| echo -e "${RED}[$(date '+%H:%M:%S')] ERROR:${NC} $1" >&2 | |
| } | |
| warn() { | |
| echo -e "${YELLOW}[$(date '+%H:%M:%S')] WARN:${NC} $1" | |
| } | |
| debug() { | |
| if [ "$DEBUG" = "1" ]; then | |
| echo -e "${BLUE}[$(date '+%H:%M:%S')] DEBUG:${NC} $1" | |
| fi | |
| } | |
| # Check if required tools are available | |
| if ! command -v xclip &> /dev/null; then | |
| error "xclip is not installed. Please install it: sudo pacman -S xclip" | |
| exit 1 | |
| fi | |
| if ! command -v wl-paste &> /dev/null || ! command -v wl-copy &> /dev/null; then | |
| error "wl-clipboard is not installed. Please install it: sudo pacman -S wl-clipboard" | |
| exit 1 | |
| fi | |
| if ! command -v timeout &> /dev/null; then | |
| error "timeout command not found. Please install coreutils." | |
| exit 1 | |
| fi | |
| log "Starting clipboard synchronization (X11 -> Wayland)" | |
| log "Press Ctrl+C to stop" | |
| # Initialize clipboard states with hash to avoid storing large content | |
| last_x11_hash="" | |
| last_wayland_hash="" | |
| last_synced_hash="" | |
| # Sleep interval in seconds | |
| SLEEP_INTERVAL=0.3 | |
| TIMEOUT=0.2 | |
| # Function to get clipboard hash | |
| get_hash() { | |
| echo -n "$1" | md5sum | cut -d' ' -f1 | |
| } | |
| # Function to get hash from binary data | |
| get_binary_hash() { | |
| md5sum | cut -d' ' -f1 | |
| } | |
| # Function to get X11 clipboard MIME types | |
| get_x11_types() { | |
| timeout "$TIMEOUT" xclip -selection clipboard -t TARGETS -o 2>/dev/null | grep -v "^TARGETS$" | |
| } | |
| # Function to get Wayland clipboard MIME types | |
| get_wayland_types() { | |
| timeout "$TIMEOUT" wl-paste --list-types 2>/dev/null | |
| } | |
| # Function to select best MIME type for syncing | |
| select_mime_type() { | |
| local types="$1" | |
| # Priority order: images first, then text | |
| if echo "$types" | grep -q "image/png"; then | |
| echo "image/png" | |
| elif echo "$types" | grep -q "image/jpeg"; then | |
| echo "image/jpeg" | |
| elif echo "$types" | grep -q "image/jpg"; then | |
| echo "image/jpg" | |
| elif echo "$types" | grep -q "image/"; then | |
| echo "$types" | grep "^image/" | head -n1 | |
| elif echo "$types" | grep -q "text/plain"; then | |
| echo "text/plain" | |
| elif echo "$types" | grep -q "UTF8_STRING"; then | |
| echo "UTF8_STRING" | |
| elif echo "$types" | grep -q "STRING"; then | |
| echo "STRING" | |
| elif echo "$types" | grep -q "TEXT"; then | |
| echo "TEXT" | |
| else | |
| # Return first available type | |
| echo "$types" | head -n1 | |
| fi | |
| } | |
| while true; do | |
| # Get X11 clipboard MIME types | |
| x11_types=$(get_x11_types) | |
| x11_types_exit=$? | |
| if [ $x11_types_exit -eq 124 ]; then | |
| warn "xclip (TARGETS) timed out" | |
| sleep "$SLEEP_INTERVAL" | |
| continue | |
| fi | |
| # Get Wayland clipboard MIME types | |
| wayland_types=$(get_wayland_types) | |
| wayland_types_exit=$? | |
| # Determine X11 clipboard type and content | |
| if [ $x11_types_exit -eq 0 ] && [ -n "$x11_types" ]; then | |
| x11_mime=$(select_mime_type "$x11_types") | |
| if [[ "$x11_mime" == image/* ]]; then | |
| # Handle image content | |
| debug "X11 clipboard contains image: $x11_mime" | |
| x11_clipboard=$(timeout "$TIMEOUT" xclip -selection clipboard -t "$x11_mime" -o 2>/dev/null | base64 -w 0) | |
| x11_exit_code=$? | |
| x11_is_image=1 | |
| else | |
| # Handle text content | |
| debug "X11 clipboard contains text: $x11_mime" | |
| x11_clipboard=$(timeout "$TIMEOUT" xclip -selection clipboard -o 2>/dev/null) | |
| x11_exit_code=$? | |
| x11_is_image=0 | |
| fi | |
| else | |
| x11_clipboard="" | |
| x11_exit_code=1 | |
| x11_mime="" | |
| x11_is_image=0 | |
| fi | |
| if [ $x11_exit_code -eq 124 ]; then | |
| warn "xclip timed out" | |
| sleep "$SLEEP_INTERVAL" | |
| continue | |
| fi | |
| # Determine Wayland clipboard type and content | |
| if [ $wayland_types_exit -eq 0 ] && [ -n "$wayland_types" ]; then | |
| wayland_mime=$(select_mime_type "$wayland_types") | |
| if [[ "$wayland_mime" == image/* ]]; then | |
| # Handle image content | |
| debug "Wayland clipboard contains image: $wayland_mime" | |
| wayland_clipboard=$(timeout "$TIMEOUT" wl-paste -t "$wayland_mime" 2>/dev/null | base64 -w 0) | |
| wayland_exit_code=$? | |
| else | |
| # Handle text content | |
| wayland_clipboard=$(timeout "$TIMEOUT" wl-paste 2>/dev/null) | |
| wayland_exit_code=$? | |
| fi | |
| else | |
| wayland_clipboard="" | |
| wayland_exit_code=1 | |
| wayland_mime="" | |
| fi | |
| # Calculate X11 hash | |
| if [ -n "$x11_clipboard" ]; then | |
| x11_hash=$(get_hash "$x11_clipboard") | |
| else | |
| x11_hash="" | |
| fi | |
| # Handle wl-paste timeout - assume clipboard didn't change | |
| if [ $wayland_exit_code -eq 124 ]; then | |
| debug "wl-paste timed out, assuming Wayland clipboard unchanged" | |
| wayland_hash="$last_wayland_hash" | |
| else | |
| # Calculate wayland hash only if wl-paste succeeded | |
| if [ -n "$wayland_clipboard" ]; then | |
| wayland_hash=$(get_hash "$wayland_clipboard") | |
| else | |
| wayland_hash="" | |
| fi | |
| fi | |
| debug "X11: $x11_hash ($x11_mime), Wayland: $wayland_hash ($wayland_mime), Last synced: $last_synced_hash" | |
| # Check if Wayland clipboard changed externally (not from our sync) | |
| # Skip this check if wl-paste timed out | |
| if [ $wayland_exit_code -ne 124 ]; then | |
| if [ -n "$wayland_hash" ] && [ "$wayland_hash" != "$last_wayland_hash" ] && [ "$wayland_hash" != "$last_synced_hash" ]; then | |
| log "Wayland clipboard changed externally (clearing sync flag)" | |
| last_wayland_hash="$wayland_hash" | |
| # Clear the last synced hash so next X11 copy will trigger sync even if same content | |
| last_synced_hash="" | |
| fi | |
| fi | |
| # Check if X11 clipboard has changed | |
| if [ $x11_exit_code -eq 0 ] && [ -n "$x11_clipboard" ] && [ -n "$x11_hash" ]; then | |
| if [ "$x11_hash" != "$last_x11_hash" ]; then | |
| if [ $x11_is_image -eq 1 ]; then | |
| log "X11 clipboard changed (image: $x11_mime), syncing to Wayland..." | |
| else | |
| log "X11 clipboard changed (text), syncing to Wayland..." | |
| debug "Content preview: ${x11_clipboard:0:50}..." | |
| fi | |
| # Copy X11 clipboard to Wayland with timeout | |
| if [ $x11_is_image -eq 1 ]; then | |
| # Sync image content | |
| if echo -n "$x11_clipboard" | base64 -d | timeout "$TIMEOUT" wl-copy -t "$x11_mime" 2>/dev/null; then | |
| log "✓ Synced image to Wayland clipboard" | |
| last_x11_hash="$x11_hash" | |
| last_synced_hash="$x11_hash" | |
| last_wayland_hash="$x11_hash" | |
| else | |
| error "Failed to sync image to Wayland clipboard" | |
| fi | |
| else | |
| # Sync text content | |
| if echo -n "$x11_clipboard" | timeout "$TIMEOUT" wl-copy 2>/dev/null; then | |
| log "✓ Synced text to Wayland clipboard" | |
| last_x11_hash="$x11_hash" | |
| last_synced_hash="$x11_hash" | |
| last_wayland_hash="$x11_hash" | |
| else | |
| error "Failed to sync text to Wayland clipboard" | |
| fi | |
| fi | |
| elif [ "$x11_hash" = "$last_x11_hash" ] && [ "$x11_hash" != "$last_synced_hash" ]; then | |
| # Same X11 content as before, but it wasn't synced last time (because Wayland changed) | |
| # Re-sync it now | |
| if [ $x11_is_image -eq 1 ]; then | |
| log "Re-syncing same X11 image to Wayland..." | |
| if echo -n "$x11_clipboard" | base64 -d | timeout "$TIMEOUT" wl-copy -t "$x11_mime" 2>/dev/null; then | |
| log "✓ Re-synced image to Wayland clipboard" | |
| last_synced_hash="$x11_hash" | |
| last_wayland_hash="$x11_hash" | |
| else | |
| error "Failed to re-sync image to Wayland clipboard" | |
| fi | |
| else | |
| log "Re-syncing same X11 text to Wayland..." | |
| if echo -n "$x11_clipboard" | timeout "$TIMEOUT" wl-copy 2>/dev/null; then | |
| log "✓ Re-synced text to Wayland clipboard" | |
| last_synced_hash="$x11_hash" | |
| last_wayland_hash="$x11_hash" | |
| else | |
| error "Failed to re-sync text to Wayland clipboard" | |
| fi | |
| fi | |
| fi | |
| fi | |
| sleep "$SLEEP_INTERVAL" | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment