Files
crossdesk-web-client/control.min.js
yexuejc 3d80f627a8 build(dist): 更新HTML引用为压缩版本的JavaScript文件
- 将control.js替换为control.min.js
- 将web_client.js替换为web_client.min.js
- 减少页面加载时的脚本文件大小
- 优化生产环境下的资源加载性能
2026-03-04 21:56:42 +08:00

2 lines
33 KiB
JavaScript

(function(){const m={mouse:0,keyboard:1,audio_capture:2,host_infomation:3,display_id:4},n={move:0,left_down:1,left_up:2,right_down:3,right_up:4,middle_down:5,middle_up:6,wheel_vertical:7,wheel_horizontal:8},r=l=>Math.max(0,Math.min(1,l)),v=l=>{if(!l||!l.tagName)return!1;const t=l.tagName.toLowerCase();if(t==="textarea")return!0;if(t!=="input")return!1;const e=(l.getAttribute("type")||"text").toLowerCase();return!["checkbox","radio","button","submit","reset"].includes(e)};class T{constructor(){this.dataChannel=null,this.elements={video:document.getElementById("video"),mediaContainer:document.getElementById("media"),videoContainer:document.getElementById("video-container"),virtualMouse:document.getElementById("virtual-mouse"),virtualMouseHeader:document.getElementById("virtual-mouse-header"),virtualLeftBtn:document.getElementById("virtual-left-btn"),virtualRightBtn:document.getElementById("virtual-right-btn"),virtualScrollUp:document.getElementById("virtual-scroll-up"),virtualScrollDown:document.getElementById("virtual-scroll-down"),virtualTouchpad:null,virtualDragHandle:document.getElementById("virtual-mouse-drag-handle"),mobileModeSelector:document.getElementById("mobile-mode-selector"),mouseControlMode:document.getElementById("mouse-control-mode"),virtualKeyboard:document.getElementById("virtual-keyboard"),keyboardHeader:document.getElementById("keyboard-header"),keyboardToggleMouse:document.getElementById("keyboard-toggle-mouse"),keyboardToggle:document.getElementById("keyboard-toggle"),keyboardClose:document.getElementById("keyboard-close"),virtualMouseMinimize:document.getElementById("virtual-mouse-minimize"),virtualMouseRestore:document.getElementById("virtual-mouse-restore")},this.virtualKeyTimers=new Map,this.virtualScrollTimers=new Map,this.state={pointerLocked:!1,normalizedPos:{x:.5,y:.5},lastPointerPos:null,lastWheelAt:0,touchpadStart:null,draggingVirtualMouse:!1,dragOffset:{x:0,y:0},draggingVirtualKeyboard:!1,keyboardDragOffset:{x:0,y:0},draggingPanel:!1,pointerLockToastTimer:null,videoRect:null,gestureActive:!1,gestureButton:null,gestureStart:null,isMobile:!1,mobileControlMode:"absolute",touchActive:!1,touchStartPos:null,touchLastPos:null,pinchZoomActive:!1,initialPinchDistance:0,initialScale:1,currentScale:1,lastDoubleTapTime:0,initialPinchCenter:null,initialTranslateX:0,initialTranslateY:0,currentTranslateX:0,currentTranslateY:0,virtualMouseMinimized:!1},this.onPointerLockChange=this.onPointerLockChange.bind(this),this.onPointerLockError=this.onPointerLockError.bind(this),this.onPointerDown=this.onPointerDown.bind(this),this.onPointerMove=this.onPointerMove.bind(this),this.onPointerUp=this.onPointerUp.bind(this),this.onPointerCancel=this.onPointerCancel.bind(this),this.onWheel=this.onWheel.bind(this),this.onTouchStartFallback=this.onTouchStartFallback.bind(this),this.onTouchMoveFallback=this.onTouchMoveFallback.bind(this),this.onTouchEndFallback=this.onTouchEndFallback.bind(this),this.onVirtualLeftStart=this.onVirtualLeftStart.bind(this),this.onVirtualRightStart=this.onVirtualRightStart.bind(this),this.onVirtualButtonMove=this.onVirtualButtonMove.bind(this),this.onVirtualButtonEnd=this.onVirtualButtonEnd.bind(this),this.onDragHandleTouchStart=this.onDragHandleTouchStart.bind(this),this.onDragHandleTouchMove=this.onDragHandleTouchMove.bind(this),this.onDragHandleTouchEnd=this.onDragHandleTouchEnd.bind(this),this.onDragHandleClick=this.onDragHandleClick.bind(this),this.onKeyboardDragHandleTouchStart=this.onKeyboardDragHandleTouchStart.bind(this),this.onKeyboardDragHandleTouchMove=this.onKeyboardDragHandleTouchMove.bind(this),this.onKeyboardDragHandleTouchEnd=this.onKeyboardDragHandleTouchEnd.bind(this),this.onPinchStart=this.onPinchStart.bind(this),this.onPinchMove=this.onPinchMove.bind(this),this.onPinchEnd=this.onPinchEnd.bind(this),this.init()}init(){const{video:t}=this.elements;if(!t){console.warn("CrossDeskControl: video element not found");return}t.style.pointerEvents="auto",t.tabIndex=0,this.bindPointerLockEvents(),this.bindPointerListeners(),this.bindKeyboardListeners(),this.setupVirtualMouse(),this.setupVirtualKeyboard()}setDataChannel(t){this.dataChannel=t}isChannelOpen(){return this.dataChannel&&this.dataChannel.readyState==="open"}send(t){if(!this.isChannelOpen())return!1;try{const e=JSON.stringify(t);return this.dataChannel.send(e),!0}catch(e){return console.error("CrossDeskControl: failed to send action",e),!1}}sendMouseAction({x:t,y:e,flag:s,scroll:i=0}){if(this.isDraggingAnyElement())return;const o=typeof s=="string"?n[s]??n.move:s|0,a={type:m.mouse,mouse:{x:r(t),y:r(e),s:i|0,flag:o}};this.send(a)}sendKeyboardAction(t,e){const s={type:m.keyboard,keyboard:{key_value:t|0,flag:e?0:1}};this.send(s)}sendAudioCapture(t){const e={type:m.audio_capture,audio_capture:!!t};this.send(e)}sendDisplayId(t){const e=typeof t=="number"&&Number.isFinite(t)?t:parseInt(t,10);if(isNaN(e)||!Number.isFinite(e)){console.warn("sendDisplayId: Invalid display_id:",t);return}const s={type:m.display_id,display_id:e|0};this.send(s)}sendRawMessage(t){if(!this.isChannelOpen())return!1;try{return this.dataChannel.send(t),!0}catch(e){return console.error("CrossDeskControl: failed to send raw message",e),!1}}bindPointerLockEvents(){document.addEventListener("pointerlockchange",this.onPointerLockChange),document.addEventListener("pointerlockerror",this.onPointerLockError),document.addEventListener("keydown",t=>{t.ctrlKey&&t.key==="Escape"&&document.exitPointerLock?.()})}onPointerLockChange(){this.state.pointerLocked=document.pointerLockElement===this.elements.video,this.state.pointerLocked?this.state.videoRect=this.elements.video?.getBoundingClientRect()??null:(this.state.videoRect=null,this.showPointerLockToast("\u5DF2\u9000\u51FA\u9F20\u6807\u9501\u5B9A\uFF0C\u6309 Esc \u6216\u70B9\u51FB\u89C6\u9891\u91CD\u65B0\u9501\u5B9A\uFF08\u91CA\u653E\u53EF\u6309 Ctrl+Esc\uFF09",3e3))}onPointerLockError(){this.showPointerLockToast("\u9F20\u6807\u9501\u5B9A\u5931\u8D25",2500)}bindPointerListeners(){const{video:t}=this.elements;if(t){try{t.style.touchAction="none"}catch{}t.addEventListener("pointerdown",this.onPointerDown,{passive:!1}),document.addEventListener("pointermove",this.onPointerMove,{passive:!1}),document.addEventListener("pointerup",this.onPointerUp,{passive:!1}),document.addEventListener("pointercancel",this.onPointerCancel),t.addEventListener("wheel",this.onWheel,{passive:!1}),window.PointerEvent||(t.addEventListener("touchstart",this.onTouchStartFallback,{passive:!1}),document.addEventListener("touchmove",this.onTouchMoveFallback,{passive:!1}),document.addEventListener("touchend",this.onTouchEndFallback,{passive:!1}),document.addEventListener("touchcancel",this.onTouchEndFallback,{passive:!1}))}}onPointerDown(t){const e=typeof t.button=="number"?t.button:0;if(e<0)return;const s=t.target;if(!(s&&(s.closest("#panel-collapsed-bar")||s.closest("#connected-panel")))&&!this.isInsidePanel(t.clientX,t.clientY)&&!this.state.draggingPanel){if(this.state.isMobile&&t.pointerType==="touch"&&!this.state.pinchZoomActive){if(t.preventDefault?.(),this.ensureVideoRect(),this.state.videoRect&&this.isInsideVideo(t.clientX,t.clientY)&&(this.state.mobileControlMode==="absolute"?(this.updateNormalizedFromClient(t.clientX,t.clientY),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move})):(this.state.touchActive=!0,this.state.touchStartPos={x:t.clientX,y:t.clientY},this.state.touchLastPos={x:t.clientX,y:t.clientY})),this.elements.video&&t.pointerId!==void 0&&t.pointerId!==null)try{this.elements.video.setPointerCapture(t.pointerId)}catch{}return}if(t.preventDefault?.(),this.state.lastPointerPos={x:t.clientX,y:t.clientY},this.ensureVideoRect(),this.state.videoRect&&this.isInsideVideo(t.clientX,t.clientY)&&this.requestPointerLock(),this.elements.video&&t.pointerId!==void 0&&t.pointerId!==null)try{this.elements.video.setPointerCapture(t.pointerId)}catch{}this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:this.buttonToFlag(e,!0)})}}onPointerMove(t){const e=t.target;if(e&&(e.closest("#panel-collapsed-bar")||e.closest("#connected-panel"))||this.isInsidePanel(t.clientX,t.clientY)||this.state.draggingPanel||this.state.pinchZoomActive)return;if(this.state.isMobile&&t.pointerType==="touch"&&this.state.touchActive&&this.state.mobileControlMode==="relative"){if(t.preventDefault?.(),this.ensureVideoRect(),!this.state.videoRect||!this.state.touchLastPos)return;const h=t.clientX-this.state.touchLastPos.x,u=t.clientY-this.state.touchLastPos.y,d=h/this.state.videoRect.width,c=u/this.state.videoRect.height;this.state.normalizedPos.x=r(this.state.normalizedPos.x+d),this.state.normalizedPos.y=r(this.state.normalizedPos.y+c),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move}),this.state.touchLastPos={x:t.clientX,y:t.clientY};return}if(this.state.isMobile&&t.pointerType==="touch"&&this.state.mobileControlMode==="absolute"){if(t.preventDefault?.(),this.ensureVideoRect(),!this.state.videoRect||!this.isInsideVideo(t.clientX,t.clientY))return;this.updateNormalizedFromClient(t.clientX,t.clientY),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move});return}if(!this.state.pointerLocked&&!this.state.lastPointerPos)return;const s=this.state.pointerLocked?t.movementX:t.clientX-(this.state.lastPointerPos?.x??t.clientX),i=this.state.pointerLocked?t.movementY:t.clientY-(this.state.lastPointerPos?.y??t.clientY);if(this.state.pointerLocked||(this.state.lastPointerPos={x:t.clientX,y:t.clientY}),this.ensureVideoRect(),!this.state.videoRect)return;if(this.state.pointerLocked){this.state.normalizedPos.x=r(this.state.normalizedPos.x+s/this.state.videoRect.width),this.state.normalizedPos.y=r(this.state.normalizedPos.y+i/this.state.videoRect.height),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move});return}if(!this.isInsideVideo(t.clientX,t.clientY))return;const o=(t.clientX-this.state.videoRect.left)/this.state.videoRect.width,a=(t.clientY-this.state.videoRect.top)/this.state.videoRect.height;this.state.normalizedPos={x:r(o),y:r(a)},this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move})}onPointerUp(t){if(this.isInsidePanel(t.clientX,t.clientY)){this.elements.video?.releasePointerCapture?.(t.pointerId??0);return}if(this.state.isMobile&&t.pointerType==="touch"){this.elements.video?.releasePointerCapture?.(t.pointerId??0),this.state.touchActive=!1,this.state.touchStartPos=null,this.state.touchLastPos=null;return}const e=typeof t.button=="number"?t.button:0;this.elements.video?.releasePointerCapture?.(t.pointerId??0),this.state.lastPointerPos=null,this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:this.buttonToFlag(e,!1)})}onPointerCancel(){this.state.lastPointerPos=null,this.state.isMobile&&(this.state.touchActive=!1,this.state.touchStartPos=null,this.state.touchLastPos=null)}onWheel(t){const e=Date.now();if(e-this.state.lastWheelAt<50||(this.state.lastWheelAt=e,this.isInsidePanel(t.clientX,t.clientY))||(this.ensureVideoRect(),!this.state.videoRect))return;let s=this.state.normalizedPos;if(!this.state.pointerLocked){if(!this.isInsideVideo(t.clientX,t.clientY))return;s={x:(t.clientX-this.state.videoRect.left)/this.state.videoRect.width,y:(t.clientY-this.state.videoRect.top)/this.state.videoRect.height}}this.sendMouseAction({x:s.x,y:s.y,flag:t.deltaY===0?n.wheel_horizontal:n.wheel_vertical,scroll:t.deltaY||t.deltaX}),t.preventDefault()}onTouchStartFallback(t){if(!t.touches?.length)return;const e=t.target;if(e&&(e.closest("#panel-collapsed-bar")||e.closest("#connected-panel")))return;const s=t.touches[0];this.isInsidePanel(s.clientX,s.clientY)||this.state.pinchZoomActive||this.state.draggingPanel||t.touches.length===2||(t.preventDefault(),this.ensureVideoRect(),this.state.videoRect&&this.isInsideVideo(s.clientX,s.clientY)&&(this.state.mobileControlMode==="absolute"?(this.updateNormalizedFromClient(s.clientX,s.clientY),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move})):(this.state.touchActive=!0,this.state.touchStartPos={x:s.clientX,y:s.clientY},this.state.touchLastPos={x:s.clientX,y:s.clientY})))}onTouchMoveFallback(t){if(!t.touches?.length)return;const e=t.target;if(e&&(e.closest("#panel-collapsed-bar")||e.closest("#connected-panel")))return;const s=t.touches[0];if(!this.isInsidePanel(s.clientX,s.clientY)&&!(this.state.pinchZoomActive||this.state.draggingPanel||t.touches.length===2)&&(t.preventDefault(),this.ensureVideoRect(),!!this.state.videoRect)){if(this.state.mobileControlMode==="absolute")this.isInsideVideo(s.clientX,s.clientY)&&(this.updateNormalizedFromClient(s.clientX,s.clientY),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move}));else if(this.state.touchActive&&this.state.touchLastPos){const i=s.clientX-this.state.touchLastPos.x,o=s.clientY-this.state.touchLastPos.y,a=i/this.state.videoRect.width,h=o/this.state.videoRect.height;this.state.normalizedPos.x=r(this.state.normalizedPos.x+a),this.state.normalizedPos.y=r(this.state.normalizedPos.y+h),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move}),this.state.touchLastPos={x:s.clientX,y:s.clientY}}}}onTouchEndFallback(t){this.state.touchActive=!1,this.state.touchStartPos=null,this.state.touchLastPos=null}buttonToFlag(t,e){const s={0:{down:n.left_down,up:n.left_up},1:{down:n.middle_down,up:n.middle_up},2:{down:n.right_down,up:n.right_up}},i=s[t]||s[0];return e?i.down:i.up}requestPointerLock(){try{this.elements.video?.requestPointerLock?.()}catch(t){console.warn("CrossDeskControl: requestPointerLock failed",t)}}ensureVideoRect(){const{video:t}=this.elements;t&&(this.state.videoRect=t.getBoundingClientRect())}isInsideVideo(t,e){const s=this.state.videoRect;return s?t>=s.left&&t<=s.right&&e>=s.top&&e<=s.bottom:!1}isInsidePanel(t,e){const s=document.getElementById("connected-panel");if(!s)return!1;const i=s.getBoundingClientRect();return t>=i.left&&t<=i.right&&e>=i.top&&e<=i.bottom}updateNormalizedFromClient(t,e){this.state.videoRect&&(this.state.normalizedPos={x:r((t-this.state.videoRect.left)/this.state.videoRect.width),y:r((e-this.state.videoRect.top)/this.state.videoRect.height)})}bindKeyboardListeners(){document.addEventListener("keydown",t=>{this.isChannelOpen()&&(t.repeat||v(t.target)||this.sendKeyboardAction(t.keyCode??0,!0))}),document.addEventListener("keyup",t=>{this.isChannelOpen()&&(v(t.target)||this.sendKeyboardAction(t.keyCode??0,!1))})}setupVirtualMouse(){const t=window.matchMedia("(hover: hover) and (pointer: fine)").matches;if(this.state.isMobile=!t,t){this.elements.virtualMouse&&(this.elements.virtualMouse.style.pointerEvents="none"),this.elements.mobileModeSelector&&(this.elements.mobileModeSelector.style.display="none"),this.elements.virtualMouseMinimize&&(this.elements.virtualMouseMinimize.style.display="none");return}if(this.elements.virtualMouseMinimize&&(this.elements.virtualMouseMinimize.style.display="flex"),this.elements.mobileModeSelector&&(this.elements.mobileModeSelector.style.display="flex"),this.elements.mouseControlMode&&(this.elements.mouseControlMode.addEventListener("change",e=>{this.state.mobileControlMode=e.target.value}),this.state.mobileControlMode=this.elements.mouseControlMode.value),this.elements.virtualLeftBtn?.addEventListener("touchstart",this.onVirtualLeftStart,{passive:!1}),this.elements.virtualRightBtn?.addEventListener("touchstart",this.onVirtualRightStart,{passive:!1}),document.addEventListener("touchmove",this.onVirtualButtonMove,{passive:!1}),document.addEventListener("touchend",this.onVirtualButtonEnd,{passive:!1}),document.addEventListener("touchcancel",this.onVirtualButtonEnd,{passive:!1}),this.elements.virtualScrollUp){const e=i=>{i.preventDefault(),this.handleVirtualScrollPress(this.elements.virtualScrollUp,"up",!0)},s=i=>{i.preventDefault(),this.handleVirtualScrollPress(this.elements.virtualScrollUp,"up",!1)};this.elements.virtualScrollUp.addEventListener("mousedown",e,{passive:!1}),this.elements.virtualScrollUp.addEventListener("mouseup",s,{passive:!1}),this.elements.virtualScrollUp.addEventListener("mouseleave",s,{passive:!1}),this.elements.virtualScrollUp.addEventListener("touchstart",e,{passive:!1}),this.elements.virtualScrollUp.addEventListener("touchend",s,{passive:!1}),this.elements.virtualScrollUp.addEventListener("touchcancel",s,{passive:!1})}if(this.elements.virtualScrollDown){const e=i=>{i.preventDefault(),this.handleVirtualScrollPress(this.elements.virtualScrollDown,"down",!0)},s=i=>{i.preventDefault(),this.handleVirtualScrollPress(this.elements.virtualScrollDown,"down",!1)};this.elements.virtualScrollDown.addEventListener("mousedown",e,{passive:!1}),this.elements.virtualScrollDown.addEventListener("mouseup",s,{passive:!1}),this.elements.virtualScrollDown.addEventListener("mouseleave",s,{passive:!1}),this.elements.virtualScrollDown.addEventListener("touchstart",e,{passive:!1}),this.elements.virtualScrollDown.addEventListener("touchend",s,{passive:!1}),this.elements.virtualScrollDown.addEventListener("touchcancel",s,{passive:!1})}this.bindVirtualMouseDragging(),this.bindVirtualKeyboardDragging(),this.elements.virtualMouseMinimize&&this.elements.virtualMouseMinimize.addEventListener("click",e=>{e.stopPropagation(),this.minimizeVirtualMouse()}),this.elements.virtualMouseRestore&&this.elements.virtualMouseRestore.addEventListener("click",e=>{e.stopPropagation(),this.restoreVirtualMouse()}),this.state.isMobile&&this.elements.video&&(this.elements.video.addEventListener("touchstart",this.onPinchStart,{passive:!1}),document.addEventListener("touchmove",this.onPinchMove,{passive:!1}),document.addEventListener("touchend",this.onPinchEnd,{passive:!1}),document.addEventListener("touchcancel",this.onPinchEnd,{passive:!1}))}setupVirtualKeyboard(){window.matchMedia("(hover: hover) and (pointer: fine)").matches&&this.elements.virtualKeyboard&&(this.elements.virtualKeyboard.style.display="none"),this.elements.keyboardToggleMouse&&(this.elements.keyboardToggleMouse.style.display="block",this.elements.keyboardToggleMouse.addEventListener("click",()=>{this.toggleVirtualKeyboard()})),this.elements.keyboardToggle&&this.elements.keyboardToggle.addEventListener("click",()=>{this.toggleVirtualKeyboard()}),this.elements.keyboardClose&&this.elements.keyboardClose.addEventListener("click",()=>{this.hideVirtualKeyboard()}),document.querySelectorAll(".keyboard-key").forEach(s=>{const i=a=>{a.preventDefault(),this.handleVirtualKeyPress(s,!0)},o=a=>{a.preventDefault(),this.handleVirtualKeyPress(s,!1),setTimeout(()=>{document.activeElement===s&&s.blur(),s.style.backgroundColor="",s.style.transform="",s.style.boxShadow=""},0)};s.addEventListener("mousedown",i),s.addEventListener("mouseup",o),s.addEventListener("mouseleave",o),s.addEventListener("touchstart",i,{passive:!1}),s.addEventListener("touchend",o,{passive:!1}),s.addEventListener("touchcancel",o,{passive:!1}),s._keyboardKeyRef=s})}toggleVirtualKeyboard(){if(!this.elements.virtualKeyboard)return;this.elements.virtualKeyboard.style.display!=="none"?this.hideVirtualKeyboard():this.showVirtualKeyboard()}showVirtualKeyboard(){this.elements.virtualKeyboard&&(this.elements.virtualKeyboard.style.display="block")}hideVirtualKeyboard(){this.elements.virtualKeyboard&&(this.elements.virtualKeyboard.style.display="none")}handleVirtualKeyPress(t,e){if(!this.isChannelOpen())return;const s=parseInt(t.getAttribute("data-keycode"),10);if(!isNaN(s))if(e){this.stopVirtualKeyRepeat(t),this.sendKeyboardAction(s,!0),t.style.backgroundColor="rgba(180, 180, 180, 0.95)",t.style.transform="scale(0.92)",t.style.boxShadow="0 1px 2px rgba(0, 0, 0, 0.3)";const i={longPressTimer:null,repeatTimer:null};i.longPressTimer=setTimeout(()=>{i.repeatTimer=setInterval(()=>{this.isChannelOpen()&&(this.sendKeyboardAction(s,!0),setTimeout(()=>{this.sendKeyboardAction(s,!1)},30))},100)},300),this.virtualKeyTimers.set(t,i)}else this.stopVirtualKeyRepeat(t),this.sendKeyboardAction(s,!1),setTimeout(()=>{document.activeElement===t&&t.blur(),t.style.backgroundColor="",t.style.transform="",t.style.boxShadow="",t.style.outline=""},0)}stopVirtualKeyRepeat(t){const e=this.virtualKeyTimers.get(t);e&&(e.longPressTimer&&clearTimeout(e.longPressTimer),e.repeatTimer&&clearInterval(e.repeatTimer),this.virtualKeyTimers.delete(t))}emitVirtualWheel(t="up"){const e=t==="up"?-1:1;this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.wheel_vertical,scroll:e})}handleVirtualScrollPress(t,e,s){if(this.isChannelOpen())if(s){this.stopVirtualScrollRepeat(t),this.emitVirtualWheel(e),t.style.backgroundColor="rgba(180, 180, 180, 0.95)",t.style.transform="scale(0.92)",t.style.boxShadow="0 1px 2px rgba(0, 0, 0, 0.3)";const i={longPressTimer:null,repeatTimer:null};i.longPressTimer=setTimeout(()=>{i.repeatTimer=setInterval(()=>{this.isChannelOpen()&&this.emitVirtualWheel(e)},100)},300),this.virtualScrollTimers.set(t,i)}else this.stopVirtualScrollRepeat(t),setTimeout(()=>{document.activeElement===t&&t.blur(),t.style.backgroundColor="",t.style.transform="",t.style.boxShadow="",t.style.outline=""},0)}stopVirtualScrollRepeat(t){const e=this.virtualScrollTimers.get(t);e&&(e.longPressTimer&&clearTimeout(e.longPressTimer),e.repeatTimer&&clearInterval(e.repeatTimer),this.virtualScrollTimers.delete(t))}onVirtualLeftStart(t){const e=t.touches?.[0];e&&(t.preventDefault(),this.ensureVideoRect(),this.state.gestureActive=!0,this.state.gestureButton={down:n.left_down,up:n.left_up},this.state.gestureStart={x:e.clientX,y:e.clientY,normalizedX:this.state.normalizedPos.x,normalizedY:this.state.normalizedPos.y},this.elements.virtualLeftBtn&&(this.elements.virtualLeftBtn.style.backgroundColor="var(--primary-color)",this.elements.virtualLeftBtn.style.color="#fff"),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.left_down}))}onVirtualRightStart(t){const e=t.touches?.[0];e&&(t.preventDefault(),this.ensureVideoRect(),this.state.gestureActive=!0,this.state.gestureButton={down:n.right_down,up:n.right_up},this.state.gestureStart={x:e.clientX,y:e.clientY,normalizedX:this.state.normalizedPos.x,normalizedY:this.state.normalizedPos.y},this.elements.virtualRightBtn&&(this.elements.virtualRightBtn.style.backgroundColor="var(--primary-color)",this.elements.virtualRightBtn.style.color="#fff"),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.right_down}))}onVirtualButtonMove(t){if(!this.state.gestureActive||!this.state.gestureStart)return;const e=t.touches?.[0];if(!e||(t.preventDefault(),this.ensureVideoRect(),!this.state.videoRect))return;const s=2,i=e.clientX-this.state.gestureStart.x,o=e.clientY-this.state.gestureStart.y,a=this.state.gestureStart.normalizedX+i/this.state.videoRect.width*s,h=this.state.gestureStart.normalizedY+o/this.state.videoRect.height*s;this.state.normalizedPos={x:r(a),y:r(h)},this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move})}onVirtualButtonEnd(t){if(!this.state.gestureActive)return;t.preventDefault?.();const e=this.state.gestureButton?.up??n.left_up;this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:e}),e===n.left_up&&this.elements.virtualLeftBtn?(this.elements.virtualLeftBtn.style.backgroundColor="",this.elements.virtualLeftBtn.style.color="",document.activeElement===this.elements.virtualLeftBtn&&this.elements.virtualLeftBtn.blur()):e===n.right_up&&this.elements.virtualRightBtn&&(this.elements.virtualRightBtn.style.backgroundColor="",this.elements.virtualRightBtn.style.color="",document.activeElement===this.elements.virtualRightBtn&&this.elements.virtualRightBtn.blur()),this.state.gestureActive=!1,this.state.gestureButton=null,this.state.gestureStart=null}bindVirtualMouseDragging(){const{virtualMouse:t,virtualMouseHeader:e,videoContainer:s}=this.elements;if(!t||!e||!s)return;e.addEventListener("touchstart",this.onDragHandleTouchStart,{passive:!1}),document.addEventListener("touchmove",this.onDragHandleTouchMove,{passive:!1}),document.addEventListener("touchend",this.onDragHandleTouchEnd,{passive:!1}),document.addEventListener("touchcancel",this.onDragHandleTouchEnd,{passive:!1});const i=document.getElementById("keyboard-toggle-mouse");i&&(i.addEventListener("touchstart",o=>{o.stopPropagation()},{passive:!0}),i.addEventListener("click",o=>{o.stopPropagation()})),this.elements.virtualMouseMinimize&&(this.elements.virtualMouseMinimize.addEventListener("touchstart",o=>{o.stopPropagation()},{passive:!0}),this.elements.virtualMouseMinimize.addEventListener("click",o=>{o.stopPropagation()}))}bindVirtualKeyboardDragging(){const{virtualKeyboard:t,keyboardHeader:e,keyboardClose:s,videoContainer:i}=this.elements;!t||!e||!i||(e.addEventListener("touchstart",this.onKeyboardDragHandleTouchStart,{passive:!1}),document.addEventListener("touchmove",this.onKeyboardDragHandleTouchMove,{passive:!1}),document.addEventListener("touchend",this.onKeyboardDragHandleTouchEnd,{passive:!1}),document.addEventListener("touchcancel",this.onKeyboardDragHandleTouchEnd,{passive:!1}),s&&(s.addEventListener("touchstart",o=>{o.stopPropagation()},{passive:!0}),s.addEventListener("click",o=>{o.stopPropagation()})))}onDragHandleTouchStart(t){const e=t.touches?.[0];if(!e||!this.elements.virtualMouse)return;const s=t.target;if(s&&(s.id==="keyboard-toggle-mouse"||s.closest("#keyboard-toggle-mouse")||s.id==="virtual-mouse-minimize"||s.closest("#virtual-mouse-minimize")))return;t.preventDefault();const i=this.elements.virtualMouse.getBoundingClientRect();this.state.draggingVirtualMouse=!0,this.elements.virtualMouse.classList.add("dragging"),this.state.dragOffset={x:e.clientX-i.left,y:e.clientY-i.top}}onDragHandleTouchMove(t){if(!this.state.draggingVirtualMouse)return;const e=t.touches?.[0];if(!e||!this.elements.videoContainer||!this.elements.virtualMouse)return;t.preventDefault();const s=this.elements.videoContainer.getBoundingClientRect();let i=e.clientX-this.state.dragOffset.x-s.left,o=e.clientY-this.state.dragOffset.y-s.top;const a=Math.max(0,s.width-this.elements.virtualMouse.offsetWidth),h=Math.max(0,s.height-this.elements.virtualMouse.offsetHeight);i=Math.max(0,Math.min(i,a)),o=Math.max(0,Math.min(o,h)),this.elements.virtualMouse.style.left=`${i}px`,this.elements.virtualMouse.style.top=`${o}px`,this.elements.virtualMouse.style.bottom="auto",this.elements.virtualMouse.style.transform="none"}onDragHandleTouchEnd(){this.state.draggingVirtualMouse=!1,this.elements.virtualMouse&&this.elements.virtualMouse.classList.remove("dragging")}onDragHandleClick(t){t.stopPropagation(),this.elements.virtualMouse?.classList.toggle("minimized")}onKeyboardDragHandleTouchStart(t){const e=t.touches?.[0];if(!e||!this.elements.virtualKeyboard)return;const s=t.target;if(s&&(s.id==="keyboard-close"||s.closest("#keyboard-close")))return;t.preventDefault();const i=this.elements.virtualKeyboard.getBoundingClientRect();this.state.draggingVirtualKeyboard=!0,this.elements.virtualKeyboard.classList.add("dragging"),this.state.keyboardDragOffset={x:e.clientX-i.left,y:e.clientY-i.top}}onKeyboardDragHandleTouchMove(t){if(!this.state.draggingVirtualKeyboard)return;const e=t.touches?.[0];if(!e||!this.elements.videoContainer||!this.elements.virtualKeyboard)return;t.preventDefault();const s=this.elements.videoContainer.getBoundingClientRect();let i=e.clientX-this.state.keyboardDragOffset.x-s.left,o=e.clientY-this.state.keyboardDragOffset.y-s.top;const a=Math.max(0,s.width-this.elements.virtualKeyboard.offsetWidth),h=Math.max(0,s.height-this.elements.virtualKeyboard.offsetHeight);i=Math.max(0,Math.min(i,a)),o=Math.max(0,Math.min(o,h)),this.elements.virtualKeyboard.style.left=`${i}px`,this.elements.virtualKeyboard.style.top=`${o}px`,this.elements.virtualKeyboard.style.bottom="auto",this.elements.virtualKeyboard.style.transform="none"}onKeyboardDragHandleTouchEnd(){this.state.draggingVirtualKeyboard=!1,this.elements.virtualKeyboard&&this.elements.virtualKeyboard.classList.remove("dragging")}showPointerLockToast(t,e=2500){let s=document.getElementById("pointerlock-toast");s||(s=document.createElement("div"),s.id="pointerlock-toast",Object.assign(s.style,{position:"fixed",left:"50%",bottom:"24px",transform:"translateX(-50%)",background:"rgba(0,0,0,0.75)",color:"#fff",padding:"8px 12px",borderRadius:"6px",fontSize:"13px",zIndex:"9999",pointerEvents:"none",opacity:"1",transition:"opacity 0.2s"}),document.body.appendChild(s)),s.textContent=t,s.style.opacity="1",this.state.pointerLockToastTimer&&clearTimeout(this.state.pointerLockToastTimer),this.state.pointerLockToastTimer=setTimeout(()=>{s.style.opacity="0",this.state.pointerLockToastTimer=null},e)}handleExternalMouseEvent(t){if(!(!t||!t.type)&&!this.isDraggingAnyElement())switch(t.type){case"mousedown":this.onPointerDown(t);break;case"mouseup":this.onPointerUp(t);break;case"mousemove":this.onPointerMove(t);break;case"wheel":this.onWheel(t);break;default:break}}isDraggingAnyElement(){return this.state.draggingVirtualMouse||this.state.draggingVirtualKeyboard||this.state.draggingPanel}setDraggingPanel(t){this.state.draggingPanel=t}getTouchDistance(t,e){const s=e.clientX-t.clientX,i=e.clientY-t.clientY;return Math.sqrt(s*s+i*i)}getTouchCenter(t,e){return{x:(t.clientX+e.clientX)/2,y:(t.clientY+e.clientY)/2}}onPinchStart(t){if(!(t.target!==this.elements.video&&!this.elements.video?.contains(t.target))){if(t.touches.length===2){t.preventDefault(),t.stopPropagation(),this.state.pinchZoomActive=!0;const e=t.touches[0],s=t.touches[1];this.state.initialPinchDistance=this.getTouchDistance(e,s),this.state.initialScale=this.state.currentScale,this.state.initialPinchCenter=this.getTouchCenter(e,s),this.state.initialTranslateX=this.state.currentTranslateX,this.state.initialTranslateY=this.state.currentTranslateY,this.state.touchActive=!1,this.state.touchStartPos=null,this.state.touchLastPos=null}else if(t.touches.length===1&&!this.state.pinchZoomActive){const e=Date.now();e-this.state.lastDoubleTapTime<300?(t.preventDefault(),this.resetZoom(),this.state.lastDoubleTapTime=0):this.state.lastDoubleTapTime=e}}}onPinchMove(t){if(t.touches.length===2){if(!this.state.pinchZoomActive){this.state.pinchZoomActive=!0;const d=t.touches[0],c=t.touches[1];this.state.initialPinchDistance=this.getTouchDistance(d,c),this.state.initialScale=this.state.currentScale,this.state.initialPinchCenter=this.getTouchCenter(d,c),this.state.initialTranslateX=this.state.currentTranslateX,this.state.initialTranslateY=this.state.currentTranslateY}t.preventDefault(),t.stopPropagation();const e=t.touches[0],s=t.touches[1],i=this.getTouchDistance(e,s),o=this.getTouchCenter(e,s),a=i/this.state.initialPinchDistance,h=this.state.initialScale*a,u=Math.max(1,Math.min(3,h));if(this.state.currentScale=u,this.state.initialPinchCenter){const d=o.x-this.state.initialPinchCenter.x,c=o.y-this.state.initialPinchCenter.y;let g=this.state.initialTranslateX+d,p=this.state.initialTranslateY+c;if(this.elements.video&&this.elements.videoContainer&&(this.ensureVideoRect(),this.state.videoRect)){const y=this.state.videoRect.width,b=this.state.videoRect.height,L=y*u,x=b*u,P=Math.max(0,(L-y)/2),M=Math.max(0,(x-b)/2);g=Math.max(-P,Math.min(P,g)),p=Math.max(-M,Math.min(M,p))}this.state.currentTranslateX=g,this.state.currentTranslateY=p}this.elements.video&&(this.elements.video.style.transform=`scale(${u}) translate(${this.state.currentTranslateX}px, ${this.state.currentTranslateY}px)`,this.elements.video.style.transformOrigin="center center")}else this.state.pinchZoomActive&&t.touches.length<2&&(this.state.pinchZoomActive=!1,this.state.initialPinchDistance=0,this.state.initialPinchCenter=null)}onPinchEnd(t){this.state.pinchZoomActive&&t.touches.length<2&&(this.state.pinchZoomActive=!1,this.state.initialPinchDistance=0,this.state.initialPinchCenter=null)}minimizeVirtualMouse(){if(!(!this.elements.virtualMouse||this.state.virtualMouseMinimized)&&(this.state.virtualMouseMinimized=!0,this.elements.virtualMouse.classList.add("minimized-to-statusbar"),this.elements.virtualMouse.style.display="none",this.elements.virtualMouseRestore)){this.elements.virtualMouseRestore.style.display="inline-flex";const t=document.querySelector(".connection-status-group");if(t&&this.elements.virtualMouseRestore.parentElement){const e=t.getBoundingClientRect(),s=this.elements.virtualMouseRestore.parentElement.getBoundingClientRect(),i=e.left-s.left-48;this.elements.virtualMouseRestore.style.left=`${i}px`,this.elements.virtualMouseRestore.style.right="auto",this.elements.virtualMouseRestore.style.top="0",this.elements.virtualMouseRestore.style.transform="none"}}}restoreVirtualMouse(){!this.elements.virtualMouse||!this.state.virtualMouseMinimized||(this.state.virtualMouseMinimized=!1,this.elements.virtualMouse.classList.remove("minimized-to-statusbar"),this.elements.virtualMouse.style.display="flex",this.elements.virtualMouseRestore&&(this.elements.virtualMouseRestore.style.display="none"))}resetZoom(){this.state.currentScale=1,this.state.initialScale=1,this.state.currentTranslateX=0,this.state.currentTranslateY=0,this.state.initialTranslateX=0,this.state.initialTranslateY=0,this.elements.video&&(this.elements.video.style.transform="scale(1) translate(0, 0)",this.elements.video.style.transformOrigin="center center")}}const f=new T;window.CrossDeskControl=f,window.sendRemoteActionAt=(l,t,e,s)=>f.sendMouseAction({x:l,y:t,flag:e,scroll:s}),window.sendMouseEvent=l=>f.handleExternalMouseEvent(l)})();