PC ve Sunucu Arasında Otomatik Clipboard(Kopya/Yapıştır.) Senkronizasyonu

:clipboard: Akıllı Clipboard Senkronizasyonu

:dart: Kısa Açıklama

noVNC web tabanlı RDP/VNC bağlantılarında bilgisayarınız ile sunucu arasında otomatik clipboard (kopyala-yapıştır) senkronizasyonu sağlar. Performans odaklı, akıllı timing koruması ile işlemlerinizi kesmeden arka planda sessizce çalışır.

:sparkles: Özellikler

  • :white_check_mark: Çift yönlü otomatik senkronizasyon (PC ↔ Sunucu)
  • :zap: Akıllı timing koruması (yapıştırma sırasında veri kaybı yok)
  • :circus_tent: UI’ı bloklamadan çalışır (requestIdleCallback)
  • :lock: Focus bazlı çalışma (gereksiz kaynak kullanımı yok)
  • :brain: Duplicate kontrolü (aynı veri tekrar kopyalanmaz)

:rocket: Kullanım Yöntemleri

Yöntem 1: Console Kullanımı (Hızlı Test)

  1. noVNC sayfasını açın
  2. F12 ile Developer Console’u açın
  3. Aşağıdaki kodu console’a yapıştırıp Enter’a basın:
(async function() {
    'use strict';
    
    if (window.clipboardSync) {
        window.clipboardSync.stop();
    }
    
    class SmartClipboardSync {
        constructor() {
            this.lastClipboardText = '';
            this.lastNoVNCText = '';
            this.isRunning = false;
            this.cachedInput = null;
            this.clipboardPermissionGranted = false;
            this.syncHistory = new Set();
            this.maxHistorySize = 10;
            this.lastPasteTime = 0;
            this.pasteProtectionMs = 2000;
            this.isPasting = false;
            this.lastClipboardRead = 0;
            this.clipboardReadThrottle = 1000;
            this.isPageVisible = true;
            this.hasFocus = false;
        }
        
        async start() {
            if (this.isRunning) return false;
            if (!document.hasFocus()) await this.waitForFocus();
            const hasPermission = await this.checkClipboardPermission();
            if (!hasPermission) return false;
            this.isRunning = true;
            await this.initializeNoVNCWatcher();
            this.setupVisibilityTracking();
            this.setupFocusTracking();
            this.startSmartSync();
            return true;
        }
        
        setupVisibilityTracking() {
            document.addEventListener('visibilitychange', () => {
                this.isPageVisible = !document.hidden;
            });
        }
        
        setupFocusTracking() {
            window.addEventListener('focus', () => { this.hasFocus = true; });
            window.addEventListener('blur', () => { this.hasFocus = false; });
            this.hasFocus = document.hasFocus();
        }
        
        startSmartSync() {
            window.addEventListener('focus', () => { this.checkClipboardOnFocus(); });
            document.addEventListener('visibilitychange', () => {
                if (!document.hidden) setTimeout(() => this.checkClipboardOnFocus(), 100);
            });
        }
        
        async checkClipboardOnFocus() {
            if (!this.isRunning || !this.clipboardPermissionGranted) return;
            if (!this.isPageVisible || !this.hasFocus) return;
            const now = Date.now();
            if (now - this.lastPasteTime < this.pasteProtectionMs) return;
            if (now - this.lastClipboardRead < this.clipboardReadThrottle) return;
            this.lastClipboardRead = now;
            try {
                const clipboardText = await navigator.clipboard.readText();
                if (clipboardText && clipboardText !== this.lastClipboardText && !this.syncHistory.has(clipboardText)) {
                    this.lastClipboardText = clipboardText;
                    this.addToHistory(clipboardText);
                    await this.pasteToNoVNC(clipboardText);
                }
            } catch (error) {}
        }
        
        waitForFocus() {
            return new Promise((resolve) => {
                if (document.hasFocus()) { resolve(); return; }
                const focusHandler = () => {
                    window.removeEventListener('focus', focusHandler);
                    document.removeEventListener('click', focusHandler);
                    resolve();
                };
                window.addEventListener('focus', focusHandler);
                document.addEventListener('click', focusHandler);
            });
        }
        
        stop() {
            if (!this.isRunning) return;
            if (this.cachedInput && this.originalValueSetter) {
                try {
                    Object.defineProperty(this.cachedInput, 'value', {
                        set: this.originalValueSetter,
                        get: Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').get,
                        configurable: true
                    });
                } catch (e) {}
            }
            this.cachedInput = null;
            this.isRunning = false;
            this.clipboardPermissionGranted = false;
        }
        
        async checkClipboardPermission() {
            try {
                if (!document.hasFocus()) return false;
                await navigator.clipboard.readText();
                this.clipboardPermissionGranted = true;
                return true;
            } catch (error) { return false; }
        }
        
        async noVNCToClipboard(newValue) {
            try {
                const now = Date.now();
                if (now - this.lastPasteTime < this.pasteProtectionMs) return;
                if (!newValue || newValue === this.lastNoVNCText || newValue === this.lastClipboardText || this.syncHistory.has(newValue)) return;
                this.lastNoVNCText = newValue;
                this.addToHistory(newValue);
                await navigator.clipboard.writeText(newValue);
                this.lastClipboardText = newValue;
            } catch (error) {}
        }
        
        async initializeNoVNCWatcher() {
            return new Promise((resolve) => {
                let attempts = 0;
                const maxAttempts = 30;
                const checkInput = () => {
                    const input = document.getElementById("noVNC_clipboard_text");
                    if (input) {
                        this.cachedInput = input;
                        this.lastNoVNCText = input.value || '';
                        this.interceptValueSetter(input);
                        resolve(true);
                    } else {
                        attempts++;
                        if (attempts < maxAttempts) setTimeout(checkInput, 1000);
                        else resolve(false);
                    }
                };
                checkInput();
            });
        }
        
        interceptValueSetter(input) {
            const descriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
            this.originalValueSetter = descriptor.set;
            const self = this;
            Object.defineProperty(input, 'value', {
                get() { return descriptor.get.call(this); },
                set(newValue) {
                    descriptor.set.call(this, newValue);
                    if (newValue && newValue.length > 0) {
                        if (window.requestIdleCallback) {
                            window.requestIdleCallback(() => { self.noVNCToClipboard(newValue); }, { timeout: 500 });
                        } else {
                            setTimeout(() => { self.noVNCToClipboard(newValue); }, 50);
                        }
                    }
                },
                configurable: true
            });
        }
        
        async pasteToNoVNC(text) {
            const input = this.getNoVNCInput();
            if (!input) return false;
            try {
                this.isPasting = true;
                this.lastPasteTime = Date.now();
                const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
                nativeInputValueSetter.call(input, text);
                this.lastNoVNCText = text;
                requestAnimationFrame(() => {
                    ['input', 'change'].forEach(eventType => {
                        const event = new Event(eventType, { bubbles: true, cancelable: true });
                        input.dispatchEvent(event);
                    });
                    this.isPasting = false;
                });
                return true;
            } catch (error) {
                this.isPasting = false;
                return false;
            }
        }
        
        getNoVNCInput() {
            if (this.cachedInput && document.contains(this.cachedInput)) return this.cachedInput;
            this.cachedInput = document.getElementById("noVNC_clipboard_text");
            return this.cachedInput;
        }
        
        addToHistory(text) {
            this.syncHistory.add(text);
            if (this.syncHistory.size > this.maxHistorySize) {
                const firstItem = this.syncHistory.values().next().value;
                this.syncHistory.delete(firstItem);
            }
        }
        
        getStatus() {
            return {
                isRunning: this.isRunning,
                hasFocus: this.hasFocus,
                isVisible: this.isPageVisible,
                hasPermission: this.clipboardPermissionGranted,
                lastClipboard: this.lastClipboardText?.substring(0, 50) || 'yok',
                lastNoVNC: this.lastNoVNCText?.substring(0, 50) || 'yok',
                historySize: this.syncHistory.size,
                pasteProtection: this.pasteProtectionMs + 'ms',
                method: 'Smart Sync'
            };
        }
    }
    
    window.clipboardSync = new SmartClipboardSync();
    
    window.stopSync = function() {
        if (window.clipboardSync) window.clipboardSync.stop();
    };
    
    window.syncStatus = function() {
        if (window.clipboardSync) {
            const status = window.clipboardSync.getStatus();
            console.table(status);
            return status;
        }
        return null;
    };
    
    window.restartSync = async function() {
        if (window.clipboardSync) window.clipboardSync.stop();
        window.clipboardSync = new SmartClipboardSync();
        await window.clipboardSync.start();
    };
    
    await window.clipboardSync.start();
})();
  1. Sayfaya tıklayın ve clipboard iznini verin
  2. :white_check_mark: Artık otomatik senkronizasyon çalışıyor!

Yardımcı Komutlar:

syncStatus()      // Durum bilgisi göster
stopSync()        // Durdur
restartSync()     // Yeniden başlat

Yöntem 2: Siteye Gömme (Kalıcı Kullanım)

Host sahipseniz ve scripti otomatik çalıştırmak istiyorsanız:

  1. noVNC sayfanızın HTML dosyasını açın
  2. </body> etiketinden hemen önce aşağıdaki kodu ekleyin:
<script>
(async function() {
    'use strict';
    if (window.clipboardSync) window.clipboardSync.stop();
    class SmartClipboardSync {
        constructor() {
            this.lastClipboardText = '';
            this.lastNoVNCText = '';
            this.isRunning = false;
            this.cachedInput = null;
            this.clipboardPermissionGranted = false;
            this.syncHistory = new Set();
            this.maxHistorySize = 10;
            this.lastPasteTime = 0;
            this.pasteProtectionMs = 2000;
            this.isPasting = false;
            this.lastClipboardRead = 0;
            this.clipboardReadThrottle = 1000;
            this.isPageVisible = true;
            this.hasFocus = false;
        }
        async start() {
            if (this.isRunning) return false;
            if (!document.hasFocus()) await this.waitForFocus();
            const hasPermission = await this.checkClipboardPermission();
            if (!hasPermission) return false;
            this.isRunning = true;
            await this.initializeNoVNCWatcher();
            this.setupVisibilityTracking();
            this.setupFocusTracking();
            this.startSmartSync();
            return true;
        }
        setupVisibilityTracking() {
            document.addEventListener('visibilitychange', () => {
                this.isPageVisible = !document.hidden;
            });
        }
        setupFocusTracking() {
            window.addEventListener('focus', () => { this.hasFocus = true; });
            window.addEventListener('blur', () => { this.hasFocus = false; });
            this.hasFocus = document.hasFocus();
        }
        startSmartSync() {
            window.addEventListener('focus', () => { this.checkClipboardOnFocus(); });
            document.addEventListener('visibilitychange', () => {
                if (!document.hidden) setTimeout(() => this.checkClipboardOnFocus(), 100);
            });
        }
        async checkClipboardOnFocus() {
            if (!this.isRunning || !this.clipboardPermissionGranted) return;
            if (!this.isPageVisible || !this.hasFocus) return;
            const now = Date.now();
            if (now - this.lastPasteTime < this.pasteProtectionMs) return;
            if (now - this.lastClipboardRead < this.clipboardReadThrottle) return;
            this.lastClipboardRead = now;
            try {
                const clipboardText = await navigator.clipboard.readText();
                if (clipboardText && clipboardText !== this.lastClipboardText && !this.syncHistory.has(clipboardText)) {
                    this.lastClipboardText = clipboardText;
                    this.addToHistory(clipboardText);
                    await this.pasteToNoVNC(clipboardText);
                }
            } catch (error) {}
        }
        waitForFocus() {
            return new Promise((resolve) => {
                if (document.hasFocus()) { resolve(); return; }
                const focusHandler = () => {
                    window.removeEventListener('focus', focusHandler);
                    document.removeEventListener('click', focusHandler);
                    resolve();
                };
                window.addEventListener('focus', focusHandler);
                document.addEventListener('click', focusHandler);
            });
        }
        stop() {
            if (!this.isRunning) return;
            if (this.cachedInput && this.originalValueSetter) {
                try {
                    Object.defineProperty(this.cachedInput, 'value', {
                        set: this.originalValueSetter,
                        get: Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').get,
                        configurable: true
                    });
                } catch (e) {}
            }
            this.cachedInput = null;
            this.isRunning = false;
            this.clipboardPermissionGranted = false;
        }
        async checkClipboardPermission() {
            try {
                if (!document.hasFocus()) return false;
                await navigator.clipboard.readText();
                this.clipboardPermissionGranted = true;
                return true;
            } catch (error) { return false; }
        }
        async noVNCToClipboard(newValue) {
            try {
                const now = Date.now();
                if (now - this.lastPasteTime < this.pasteProtectionMs) return;
                if (!newValue || newValue === this.lastNoVNCText || newValue === this.lastClipboardText || this.syncHistory.has(newValue)) return;
                this.lastNoVNCText = newValue;
                this.addToHistory(newValue);
                await navigator.clipboard.writeText(newValue);
                this.lastClipboardText = newValue;
            } catch (error) {}
        }
        async initializeNoVNCWatcher() {
            return new Promise((resolve) => {
                let attempts = 0;
                const maxAttempts = 30;
                const checkInput = () => {
                    const input = document.getElementById("noVNC_clipboard_text");
                    if (input) {
                        this.cachedInput = input;
                        this.lastNoVNCText = input.value || '';
                        this.interceptValueSetter(input);
                        resolve(true);
                    } else {
                        attempts++;
                        if (attempts < maxAttempts) setTimeout(checkInput, 1000);
                        else resolve(false);
                    }
                };
                checkInput();
            });
        }
        interceptValueSetter(input) {
            const descriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
            this.originalValueSetter = descriptor.set;
            const self = this;
            Object.defineProperty(input, 'value', {
                get() { return descriptor.get.call(this); },
                set(newValue) {
                    descriptor.set.call(this, newValue);
                    if (newValue && newValue.length > 0) {
                        if (window.requestIdleCallback) {
                            window.requestIdleCallback(() => { self.noVNCToClipboard(newValue); }, { timeout: 500 });
                        } else {
                            setTimeout(() => { self.noVNCToClipboard(newValue); }, 50);
                        }
                    }
                },
                configurable: true
            });
        }
        async pasteToNoVNC(text) {
            const input = this.getNoVNCInput();
            if (!input) return false;
            try {
                this.isPasting = true;
                this.lastPasteTime = Date.now();
                const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
                nativeInputValueSetter.call(input, text);
                this.lastNoVNCText = text;
                requestAnimationFrame(() => {
                    ['input', 'change'].forEach(eventType => {
                        const event = new Event(eventType, { bubbles: true, cancelable: true });
                        input.dispatchEvent(event);
                    });
                    this.isPasting = false;
                });
                return true;
            } catch (error) {
                this.isPasting = false;
                return false;
            }
        }
        getNoVNCInput() {
            if (this.cachedInput && document.contains(this.cachedInput)) return this.cachedInput;
            this.cachedInput = document.getElementById("noVNC_clipboard_text");
            return this.cachedInput;
        }
        addToHistory(text) {
            this.syncHistory.add(text);
            if (this.syncHistory.size > this.maxHistorySize) {
                const firstItem = this.syncHistory.values().next().value;
                this.syncHistory.delete(firstItem);
            }
        }
        getStatus() {
            return {
                isRunning: this.isRunning,
                hasFocus: this.hasFocus,
                isVisible: this.isPageVisible,
                hasPermission: this.clipboardPermissionGranted,
                lastClipboard: this.lastClipboardText?.substring(0, 50) || 'yok',
                lastNoVNC: this.lastNoVNCText?.substring(0, 50) || 'yok',
                historySize: this.syncHistory.size,
                pasteProtection: this.pasteProtectionMs + 'ms'
            };
        }
    }
    window.clipboardSync = new SmartClipboardSync();
    window.stopSync = function() { if (window.clipboardSync) window.clipboardSync.stop(); };
    window.syncStatus = function() { if (window.clipboardSync) { console.table(window.clipboardSync.getStatus()); return window.clipboardSync.getStatus(); } return null; };
    window.restartSync = async function() { if (window.clipboardSync) window.clipboardSync.stop(); window.clipboardSync = new SmartClipboardSync(); await window.clipboardSync.start(); };
    await window.clipboardSync.start();
})();
</script>
  1. Dosyayı kaydedin
  2. :white_check_mark: Her sayfa yüklendiğinde otomatik çalışır!

:pushpin: Notlar

  • Tarayıcı Uyumluluğu: Chrome, Edge, Firefox, Opera (modern sürümler)
  • İzin Gereksinimi: İlk kullanımda clipboard izni verilmeli
  • Güvenlik: Sadece aktif sekme ve odaklanmış durumdayken çalışır
  • Performans: Minimal kaynak kullanımı, UI’ı bloklamaz

:warning: Sorun Giderme

“Clipboard izni verilmedi” hatası:

  • Sayfaya tıklayın
  • Adres çubuğundaki kilit ikonuna tıklayın
  • Clipboard izinlerini kontrol edin

Çalışmıyor:

restartSync()  // Console'da çalıştırın

:speech_balloon: Geri Bildirim

Sorun veya öneri için forumda yorum bırakabilirsiniz.


Geliştirici: Topluluk katkısı
Lisans: Açık kaynak - Özgürce kullanılabilir
Dosya Eki

[upl-text-preview uuid=608df9e0-2457-40ee-b53e-22c3f4a3ffc2 url=https://www.bilgiveteknoloji.com/forum/public/assets/files/2025-10-22/1761129076-325795-clipboard-sync-smartjs.txt has_snippet=false]clipboard-sync-smartjs.txt[/upl-text-preview]

Desteğiniz için teşekkür ederiz :slightly_smiling_face: