@@ -29,6 +29,14 @@ const UI = {
2929 connected : false ,
3030 desktopName : "" ,
3131
32+ // Modifier key configuration
33+ _modifierKeys : {
34+ shift : { keysym : KeyTable . XK_Shift_L , code : "ShiftLeft" , buttonId : 'noVNC_toggle_shift_button' } ,
35+ ctrl : { keysym : KeyTable . XK_Control_L , code : "ControlLeft" , buttonId : 'noVNC_toggle_ctrl_button' } ,
36+ alt : { keysym : KeyTable . XK_Alt_L , code : "AltLeft" , buttonId : 'noVNC_toggle_alt_button' } ,
37+ windows : { keysym : KeyTable . XK_Super_L , code : "MetaLeft" , buttonId : 'noVNC_toggle_windows_button' }
38+ } ,
39+
3240 statusTimeout : null ,
3341 hideKeyboardTimeout : null ,
3442 idleControlbarTimeout : null ,
@@ -125,6 +133,11 @@ const UI = {
125133 document . getElementById ( "noVNC_status" )
126134 . addEventListener ( 'click' , UI . hideStatus ) ;
127135
136+ // Handle tab/window close to release modifier keys
137+ // This is critical for VMware VMs using websocket reverse proxy
138+ window . addEventListener ( 'beforeunload' , UI . handleBeforeUnload ) ;
139+ window . addEventListener ( 'pagehide' , UI . handlePageHide ) ;
140+
128141 // Bootstrap fallback input handler
129142 UI . keyboardinputReset ( ) ;
130143
@@ -1740,6 +1753,39 @@ const UI = {
17401753 UI . idleControlbar ( ) ;
17411754 } ,
17421755
1756+ _sendKeyUp ( keysym , code ) {
1757+ if ( ! UI . rfb ) return ;
1758+ UI . rfb . sendKey ( keysym , code , false ) ;
1759+ } ,
1760+
1761+ // Release a single modifier key if it's pressed
1762+ _releaseModifierKey ( keyName ) {
1763+ const keyConfig = UI . _modifierKeys [ keyName ] ;
1764+ if ( ! keyConfig ) return false ;
1765+
1766+ const btn = document . getElementById ( keyConfig . buttonId ) ;
1767+ if ( ! btn || ! btn . classList . contains ( "noVNC_selected" ) ) {
1768+ return false ;
1769+ }
1770+
1771+ UI . _sendKeyUp ( keyConfig . keysym , keyConfig . code ) ;
1772+ btn . classList . remove ( "noVNC_selected" ) ;
1773+ return true ;
1774+ } ,
1775+
1776+ // Release all currently pressed modifier keys
1777+ _releaseAllModifierKeys ( ) {
1778+ let keysReleased = false ;
1779+
1780+ // Release all modifier keys
1781+ for ( const keyName in UI . _modifierKeys ) {
1782+ if ( UI . _releaseModifierKey ( keyName ) ) {
1783+ keysReleased = true ;
1784+ }
1785+ }
1786+ return keysReleased ;
1787+ } ,
1788+
17431789 // Move focus to the screen in order to be able to use the
17441790 // keyboard right after these extra keys.
17451791 // The exception is when a virtual keyboard is used, because
@@ -1836,6 +1882,20 @@ const UI = {
18361882 selectbox . options . add ( optn ) ;
18371883 } ,
18381884
1885+ // Handle tab/window close events
1886+ // These fire when the user closes the tab, which doesn't call disconnect()
1887+ handleBeforeUnload ( event ) {
1888+ // Release modifier keys before tab closes
1889+ // This is critical for VMware VMs using websocket reverse proxy
1890+ UI . _releaseAllModifierKeys ( ) ;
1891+ } ,
1892+
1893+ handlePageHide ( event ) {
1894+ // Also handle pagehide as a fallback (fires in more browsers)
1895+ // Release modifier keys before page is hidden
1896+ UI . _releaseAllModifierKeys ( ) ;
1897+ } ,
1898+
18391899/* ------^-------
18401900 * /MISC
18411901 * ==============
0 commit comments