Akıllı Clipboard Senkronizasyonu
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.
Özellikler
Çift yönlü otomatik senkronizasyon (PC ↔ Sunucu)
Akıllı timing koruması (yapıştırma sırasında veri kaybı yok)
UI’ı bloklamadan çalışır (requestIdleCallback)
Focus bazlı çalışma (gereksiz kaynak kullanımı yok)
Duplicate kontrolü (aynı veri tekrar kopyalanmaz)
Kullanım Yöntemleri
Yöntem 1: Console Kullanımı (Hızlı Test)
- noVNC sayfasını açın
- F12 ile Developer Console’u açın
- 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();
})();
- Sayfaya tıklayın ve clipboard iznini verin
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:
- noVNC sayfanızın HTML dosyasını açın
</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>
- Dosyayı kaydedin
Her sayfa yüklendiğinde otomatik çalışır!
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
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
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]