@@ -2,10 +2,10 @@ import type { SessionShareOnlineUser } from "@jumpserver/connectors-core";
22import type { MaybeRefOrGetter } from "vue" ;
33import type { LionUploadCustomRequestOptions } from "@/lion/types/upload" ;
44import type { GuacamoleConnectionErrorDetails } from "@/lion/utils/status" ;
5- import { useDebounceFn } from "@vueuse/core" ;
5+ import { useDebounceFn , useEventListener } from "@vueuse/core" ;
66
77import * as Guacamole from "guacamole-common-js-jumpserver/dist/guacamole-common" ;
8- import { computed , nextTick , ref , shallowRef , toValue } from "vue" ;
8+ import { computed , ref , shallowRef , toValue , watch } from "vue" ;
99import { LUNA_MESSAGE_TYPE } from "@/lion/types/postmessage.type" ;
1010import { withLionUrl } from "@/lion/utils/base" ;
1111import { readClipboardText , writeClipboardBlob , writeClipboardText } from "@/lion/utils/clipboard" ;
@@ -206,6 +206,7 @@ export function useGuacamoleClient(
206206 const currentHeight = ref ( window . innerHeight ) ;
207207 const pixelDensity = 1 ;
208208 const sink = new Guacamole . InputSink ( ) ;
209+ sink . getElement ( ) . disabled = true ;
209210 const keyboard = new Guacamole . Keyboard ( ) ;
210211 const pressedKeys = ref < Set < number > > ( new Set ( ) ) ;
211212 const isRemoteApp = ref < boolean > ( false ) ;
@@ -442,38 +443,85 @@ export function useGuacamoleClient(
442443 }
443444 } , 300 ) ;
444445
445- const registerMouseAndKeyboardHandler = ( ) => {
446+ const registerMouseAndKeyboardHandler = ( isActive : ( ) => boolean = ( ) => true ) => {
446447 const client = guaClient . value as any ;
447448 if ( ! client || ! client . getDisplay ) {
448449 return console . warn ( "Guacamole client is not initialized or does not support mouse and keyboard events" ) ;
449450 }
450451 inputCleanup ?.( ) ;
451452 inputCleanup = null ;
452- const mouse = registerMouse ( client ) ;
453- const touchScreen = registerTouchScreen ( client ) ;
454-
455- registerKeyboard ( client ) ;
456453 const display = client . getDisplay ( ) ;
457454 const displayEl = display . getElement ( ) ;
458-
455+ const sinkEl = sink . getElement ( ) as HTMLTextAreaElement ;
456+ const inputEnabled = ( ) =>
457+ guaClient . value === client &&
458+ isActive ( ) &&
459+ currentUser . value . writable !== false &&
460+ [ 2 , 3 ] . includes ( connectStatus . value ) ;
461+ const isPageActive = ( ) => ! document . hidden && document . hasFocus ( ) ;
462+ const canInput = ( ) => inputEnabled ( ) && isPageActive ( ) ;
463+ const canUseSink = ( ) => canInput ( ) && ! sinkEl . disabled ;
464+ const mouse = registerMouse ( client , canUseSink , ( ) => focusInput ( true ) ) ;
465+ const touchScreen = registerTouchScreen ( client , canInput ) ;
466+ registerKeyboard ( client , canUseSink ) ;
467+
468+ let restoreOnFocus = false ;
469+ const releaseInput = ( ) => {
470+ keyboard . reset ( ) ;
471+ // ponytail: The legacy dependency lacks upstream Mouse.reset(); use that API when upgrading.
472+ for ( const source of [ mouse , touchScreen ] ) {
473+ const state = source ?. currentState ;
474+ if ( ! state || ! ( state . left || state . middle || state . right || state . up || state . down ) ) continue ;
475+ state . left = state . middle = state . right = state . up = state . down = false ;
476+ sendScaledMouseState ( client , state ) ;
477+ }
478+ } ;
479+ const suspendInput = ( ) => {
480+ releaseInput ( ) ;
481+ // Block Guacamole's pending/document-keydown refocusing while inactive.
482+ sinkEl . disabled = true ;
483+ } ;
484+ function focusInput ( force = false ) {
485+ if ( ! inputEnabled ( ) || ! isPageActive ( ) || ! displayEl . isConnected || ! displayEl . getClientRects ( ) . length ) return ;
486+ const focused = document . activeElement ;
487+ if ( ! force && focused !== sinkEl && focused !== document . body && focused !== null ) return ;
488+ restoreOnFocus = true ;
489+ sinkEl . disabled = false ;
490+ sink . focus ( ) ;
491+ }
492+ const handleFocusIn = ( event : FocusEvent ) => {
493+ if ( event . target === document . body ) return ;
494+ restoreOnFocus = event . target === sinkEl ;
495+ if ( ! restoreOnFocus ) suspendInput ( ) ;
496+ } ;
497+ const handlePageFocus = ( event : Event ) => {
498+ if ( event . type === "blur" || ! isPageActive ( ) ) suspendInput ( ) ;
499+ else if ( restoreOnFocus ) focusInput ( ) ;
500+ } ;
459501 const handleMouseEnter = ( ) => {
460- document . body . focus ( ) ;
461502 display . showCursor ( false ) ;
462- nextTick ( ( ) => {
463- sink . focus ( ) ;
464- } ) ;
465- } ;
466- const handleMouseLeave = ( ) => {
467- nextTick ( ( ) => {
468- keyboard . reset ( ) ;
469- } ) ;
503+ focusInput ( ) ;
470504 } ;
471- displayEl . addEventListener ( "mouseenter" , handleMouseEnter ) ;
472- displayEl . addEventListener ( "mouseleave" , handleMouseLeave ) ;
505+ const stopListeners = [
506+ useEventListener ( displayEl , "mouseenter" , handleMouseEnter ) ,
507+ useEventListener ( displayEl , "mouseleave" , releaseInput ) ,
508+ useEventListener ( sinkEl , "blur" , releaseInput ) ,
509+ useEventListener ( document , "focusin" , handleFocusIn ) ,
510+ useEventListener ( document , "visibilitychange" , handlePageFocus ) ,
511+ useEventListener ( window , [ "blur" , "focus" ] , handlePageFocus )
512+ ] ;
513+ const stopWatch = watch (
514+ inputEnabled ,
515+ ( enabled ) => {
516+ if ( enabled ) sinkEl . disabled = false ;
517+ else suspendInput ( ) ;
518+ } ,
519+ { immediate : true , flush : "sync" }
520+ ) ;
473521 inputCleanup = ( ) => {
474- displayEl . removeEventListener ( "mouseenter" , handleMouseEnter ) ;
475- displayEl . removeEventListener ( "mouseleave" , handleMouseLeave ) ;
476- keyboard . reset ( ) ;
522+ stopWatch ( ) ;
523+ stopListeners . forEach ( ( stop ) => stop ( ) ) ;
524+ suspendInput ( ) ;
477525 keyboard . onkeydown = null ;
478526 keyboard . onkeyup = null ;
479527 if ( mouse ) {
@@ -645,7 +693,7 @@ export function useGuacamoleClient(
645693 }
646694 return false ;
647695 } ;
648- function registerKeyboard ( client : any ) {
696+ function registerKeyboard ( client : any , canInput : ( ) => boolean = ( ) => true ) {
649697 if ( ! client || ! client . getDisplay ) {
650698 console . warn ( "Guacamole client is not initialized or does not support keyboard events" ) ;
651699 return ;
@@ -661,6 +709,7 @@ export function useGuacamoleClient(
661709 }
662710
663711 keyboard . onkeydown = ( keysym : any ) => {
712+ if ( ! canInput ( ) ) return true ;
664713 if ( isBlockedCombination ( keysym ) ) {
665714 console . warn ( "Keydown Blocked key combination detected:" , keysym ) ;
666715 return ;
@@ -670,17 +719,14 @@ export function useGuacamoleClient(
670719 lunaCommunicator . sendLuna ( LUNA_MESSAGE_TYPE . KEYBOARDEVENT , "" ) ;
671720 } ;
672721 keyboard . onkeyup = ( keysym : any ) => {
673- if ( keysym !== commandKeySym && isBlockedCombination ( keysym ) ) {
674- console . warn ( "Keyup Blocked key combination detected:" , keysym ) ;
675- return ;
676- }
677- pressedKeys . value . delete ( keysym ) ;
722+ // A key sent before a blocked combination still needs its matching release.
723+ if ( ! pressedKeys . value . delete ( keysym ) ) return ;
678724 client . sendKeyEvent ( 0 , keysym ) ;
679725 } ;
680726 display . getElement ( ) . appendChild ( sink . getElement ( ) ) ;
681727 }
682728
683- function registerTouchScreen ( client : any ) {
729+ function registerTouchScreen ( client : any , canInput : ( ) => boolean = ( ) => true ) {
684730 if ( ! client || ! client . getDisplay ) {
685731 console . warn ( "Guacamole client is not initialized or does not support screen events" ) ;
686732 return null ;
@@ -693,7 +739,7 @@ export function useGuacamoleClient(
693739 const touchScreen = new Guacamole . Mouse . Touchscreen ( display . getElement ( ) ) ;
694740 const handleEmulatedMouseDown = ( mouseState : any ) => {
695741 // Emulate mouse down event
696- if ( ! client || ! display ) {
742+ if ( ! client || ! display || ! canInput ( ) ) {
697743 return ;
698744 }
699745 lunaCommunicator . sendLuna ( LUNA_MESSAGE_TYPE . MOUSE_EVENT , "" ) ;
@@ -704,7 +750,7 @@ export function useGuacamoleClient(
704750
705751 const handleEmulatedMouseState = ( mouseState : any ) => {
706752 // Emulate mouse move/up event
707- if ( ! client || ! display ) {
753+ if ( ! client || ! display || ! canInput ( ) ) {
708754 return ;
709755 }
710756 lunaCommunicator . sendLuna ( LUNA_MESSAGE_TYPE . MOUSE_EVENT , "" ) ;
@@ -740,7 +786,7 @@ export function useGuacamoleClient(
740786 guaClient . value . sendKeyEvent ( released , keysym ) ;
741787 } ;
742788
743- function registerMouse ( client : any ) {
789+ function registerMouse ( client : any , canInput : ( ) => boolean = ( ) => true , beforeMouseDown ?: ( ) => void ) {
744790 if ( ! client || ! client . getDisplay ) {
745791 console . warn ( "Guacamole client is not initialized or does not support mouse events" ) ;
746792 return null ;
@@ -751,16 +797,16 @@ export function useGuacamoleClient(
751797 return null ;
752798 }
753799 const sendMouseState = ( mouseState : any ) => {
800+ if ( ! canInput ( ) ) return ;
754801 sendScaledMouseState ( client , mouseState ) ;
755802 } ;
756803 const mouse = new Guacamole . Mouse ( display . getElement ( ) ) ;
757- mouse . onmousedown =
758- mouse . onmouseup =
759- mouse . onmousemove =
760- ( mouseState : any ) => {
761- // Send mouse state, hide cursor if necessary
762- sendMouseState ( mouseState ) ;
763- } ;
804+ mouse . onmousedown = ( mouseState : any ) => {
805+ // Follow upstream guacClient: request focus before forwarding the mouse down.
806+ beforeMouseDown ?.( ) ;
807+ sendMouseState ( mouseState ) ;
808+ } ;
809+ mouse . onmouseup = mouse . onmousemove = sendMouseState ;
764810 mouse . onmouseout = ( _mouseState : any ) => {
765811 // Send mouse state, hide cursor if necessary
766812 display . showCursor ( false ) ;
0 commit comments