151 lines
5.9 KiB
HTML
151 lines
5.9 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="zh-CN">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>xc-remote</title>
|
||
|
|
<link rel="stylesheet" href="css/main.css">
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<canvas id="video-canvas"></canvas>
|
||
|
|
<button id="file-transfer-btn">📁 文件传输</button>
|
||
|
|
|
||
|
|
<script src="js/jsmpeg.min.js"></script>
|
||
|
|
<script src="js/utils.js"></script>
|
||
|
|
<script src="js/player.js"></script>
|
||
|
|
<script src="js/input.js"></script>
|
||
|
|
<script src="js/file-panel.js"></script>
|
||
|
|
<script src="js/app.js"></script>
|
||
|
|
<script>
|
||
|
|
(function() {
|
||
|
|
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||
|
|
const wsHost = window.location.hostname;
|
||
|
|
const wsPort = window.location.port;
|
||
|
|
const wsUrlBase = wsPort ? `${wsProtocol}//${wsHost}:${wsPort}/ws` : `${wsProtocol}//${wsHost}/ws`;
|
||
|
|
const password = getCookie('auth') || '';
|
||
|
|
const WS_URL = password ? `${wsUrlBase}?password=${encodeURIComponent(password)}` : wsUrlBase;
|
||
|
|
|
||
|
|
let clipboardWs = null;
|
||
|
|
let isClipboardSyncing = false;
|
||
|
|
|
||
|
|
function connectClipboardWs() {
|
||
|
|
if (clipboardWs && clipboardWs.readyState === WebSocket.OPEN) {
|
||
|
|
return clipboardWs;
|
||
|
|
}
|
||
|
|
|
||
|
|
clipboardWs = new WebSocket(WS_URL);
|
||
|
|
|
||
|
|
clipboardWs.onmessage = async (e) => {
|
||
|
|
if (e.data instanceof Blob) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const msg = JSON.parse(e.data);
|
||
|
|
|
||
|
|
if (msg.type === 'clipboardData') {
|
||
|
|
if (msg.contentType === 'text' && msg.data) {
|
||
|
|
try {
|
||
|
|
await navigator.clipboard.writeText(msg.data);
|
||
|
|
console.log('Clipboard synced from remote');
|
||
|
|
} catch (err) {
|
||
|
|
console.error('Failed to write local clipboard:', err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else if (msg.type === 'clipboardTooLarge') {
|
||
|
|
console.warn('Remote clipboard content too large:', msg.size);
|
||
|
|
}
|
||
|
|
} catch (err) {}
|
||
|
|
};
|
||
|
|
|
||
|
|
clipboardWs.onopen = () => console.log('Clipboard sync ready');
|
||
|
|
clipboardWs.onclose = () => { clipboardWs = null; };
|
||
|
|
clipboardWs.onerror = () => { clipboardWs = null; };
|
||
|
|
|
||
|
|
return clipboardWs;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function syncFromRemote() {
|
||
|
|
if (isClipboardSyncing) return;
|
||
|
|
isClipboardSyncing = true;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const ws = connectClipboardWs();
|
||
|
|
const sendRequest = () => {
|
||
|
|
ws.send(JSON.stringify({ type: 'clipboardGet' }));
|
||
|
|
};
|
||
|
|
|
||
|
|
if (ws.readyState === WebSocket.OPEN) {
|
||
|
|
sendRequest();
|
||
|
|
} else {
|
||
|
|
ws.onopen = sendRequest;
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
setTimeout(() => { isClipboardSyncing = false; }, 500);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function syncToRemote() {
|
||
|
|
if (isClipboardSyncing) return;
|
||
|
|
isClipboardSyncing = true;
|
||
|
|
|
||
|
|
try {
|
||
|
|
let text = '';
|
||
|
|
try {
|
||
|
|
text = await navigator.clipboard.readText();
|
||
|
|
} catch (err) {
|
||
|
|
console.warn('Cannot read local clipboard');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!text) return;
|
||
|
|
|
||
|
|
const ws = connectClipboardWs();
|
||
|
|
const sendRequest = () => {
|
||
|
|
ws.send(JSON.stringify({
|
||
|
|
type: 'clipboardSet',
|
||
|
|
contentType: 'text',
|
||
|
|
data: text
|
||
|
|
}));
|
||
|
|
console.log('Clipboard synced to remote');
|
||
|
|
};
|
||
|
|
|
||
|
|
if (ws.readyState === WebSocket.OPEN) {
|
||
|
|
sendRequest();
|
||
|
|
} else {
|
||
|
|
ws.onopen = sendRequest;
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
setTimeout(() => { isClipboardSyncing = false; }, 500);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
document.addEventListener('keydown', async (e) => {
|
||
|
|
if (e.ctrlKey && e.key === 'c' && !e.shiftKey && !e.altKey) {
|
||
|
|
if (document.activeElement.tagName === 'INPUT' ||
|
||
|
|
document.activeElement.tagName === 'TEXTAREA') {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
e.preventDefault();
|
||
|
|
await syncFromRemote();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (e.ctrlKey && e.key === 'v' && !e.shiftKey && !e.altKey) {
|
||
|
|
if (document.activeElement.tagName === 'INPUT' ||
|
||
|
|
document.activeElement.tagName === 'TEXTAREA') {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
e.preventDefault();
|
||
|
|
await syncToRemote();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
connectClipboardWs();
|
||
|
|
|
||
|
|
const filePanel = new FilePanel();
|
||
|
|
document.getElementById('file-transfer-btn').addEventListener('click', () => {
|
||
|
|
filePanel.toggle();
|
||
|
|
});
|
||
|
|
})();
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|