@@ -80,7 +80,7 @@ def urlretrieve(url, filename, reporthook=None):
8080 "solaris" : "2.0.0" ,
8181 "omnios" : "2.0.3" ,
8282 "haiku" : "0.0.2" ,
83- "openindiana" : "2.0.2 "
83+ "openindiana" : "2.0.3 "
8484}
8585
8686VERSION_TOKEN_RE = re .compile (r"[0-9]+|[A-Za-z]+" )
@@ -441,6 +441,7 @@ def fatal(msg):
441441 </div>
442442 <div class="toolbar">
443443 <div class="toolbar-group">
444+ <button id="btn-sticky-shift" onclick="toggleSticky('ShiftLeft', 0xffe1, this)" title="Sticky Shift">Shift</button>
444445 <button id="btn-sticky-ctrl" onclick="toggleSticky('ControlLeft', 0xffe3, this)" title="Sticky Ctrl">Ctrl</button>
445446 <button id="btn-sticky-alt" onclick="toggleSticky('AltLeft', 0xffe9, this)" title="Sticky Alt">Alt</button>
446447 <button id="btn-sticky-meta" onclick="toggleSticky('MetaLeft', 0xffeb, this)" title="Sticky Meta">
@@ -660,8 +661,9 @@ def fatal(msg):
660661
661662 const setEncodings = new Uint8Array([
662663 2, 0,
663- 0, 1,
664- 0, 0, 0, 0
664+ 0, 2,
665+ 0, 0, 0, 0,
666+ 255, 255, 255, 33 // DesktopSize pseudo-encoding (-223)
665667 ]);
666668 ws.send(setEncodings);
667669
@@ -699,6 +701,11 @@ def fatal(msg):
699701 const h = view.getUint16(6);
700702 const enc = view.getInt32(8);
701703 offset += 12;
704+
705+ if (enc === -223) { // VM resolution changed
706+ location.reload();
707+ return;
708+ }
702709
703710 // Detect VM software cursor by looking for small updates near host mouse position
704711 if (isCheckingCursor && !cursorDetected) {
@@ -914,16 +921,17 @@ def fatal(msg):
914921document.addEventListener('keydown', e => sendKey(e, true));
915922document.addEventListener('keyup', e => sendKey(e, false));
916923
924+ // Track which keysym was sent for each physical code to ensure consistent keyup
925+ const pressedKeysyms = {};
926+
917927function sendKey(e, down) {
918928 if (!ws) return;
919929
920- // Captured keys that we handle via code-to-keysym mapping
921930 const code = e.code;
922931 const key = e.key;
923932
924933 // Support Ctrl+V (Windows/Linux) or Cmd+V (Mac) for pasting
925934 if ((e.ctrlKey || e.metaKey) && (key === 'v' || key === 'V' || code === 'KeyV')) {
926- // We let the 'paste' event handle this to avoid permission prompts where possible
927935 return;
928936 }
929937
@@ -942,47 +950,63 @@ def fatal(msg):
942950 }
943951
944952 const keyMap = {
945- // Special keys
946953 'Backspace': 0xff08, 'Tab': 0xff09, 'Enter': 0xff0d, 'Escape': 0xff1b, 'Delete': 0xffff,
947954 'Home': 0xff50, 'End': 0xff57, 'PageUp': 0xff55, 'PageDown': 0xff56,
948955 'ArrowLeft': 0xff51, 'ArrowUp': 0xff52, 'ArrowRight': 0xff53, 'ArrowDown': 0xff54, 'Insert': 0xff63,
949956 'F1': 0xffbe, 'F2': 0xffbf, 'F3': 0xffc0, 'F4': 0xffc1, 'F5': 0xffc2, 'F6': 0xffc3,
950957 'F7': 0xffc4, 'F8': 0xffc5, 'F9': 0xffc6, 'F10': 0xffc7, 'F11': 0xffc8, 'F12': 0xffc9,
951958 'ShiftLeft': 0xffe1, 'ShiftRight': 0xffe2, 'ControlLeft': 0xffe3, 'ControlRight': 0xffe4,
952959 'AltLeft': 0xffe9, 'AltRight': 0xffea, 'MetaLeft': 0xffeb, 'MetaRight': 0xffec, 'Space': 0x0020,
953- // Map Digit keys to their base ASCII (prevents Shift+1 sending '!')
954- 'Digit1': 0x31, 'Digit2': 0x32, 'Digit3': 0x33, 'Digit4': 0x34, 'Digit5': 0x35,
955- 'Digit6': 0x36, 'Digit7': 0x37, 'Digit8': 0x38, 'Digit9': 0x39, 'Digit0': 0x30,
956- // Map alphabet keys
957- 'KeyA': 0x61, 'KeyB': 0x62, 'KeyC': 0x63, 'KeyD': 0x64, 'KeyE': 0x65, 'KeyF': 0x66, 'KeyG': 0x67,
958- 'KeyH': 0x68, 'KeyI': 0x69, 'KeyJ': 0x6a, 'KeyK': 0x6b, 'KeyL': 0x6c, 'KeyM': 0x6d, 'KeyN': 0x6e,
959- 'KeyO': 0x6f, 'KeyP': 0x70, 'KeyQ': 0x71, 'KeyR': 0x72, 'KeyS': 0x73, 'KeyT': 0x74, 'KeyU': 0x75,
960- 'KeyV': 0x76, 'KeyW': 0x77, 'KeyX': 0x78, 'KeyY': 0x79, 'KeyZ': 0x7a,
961- // Punctuations (using e.code ensures we send the base keysym regardless of Shift)
962- 'Semicolon': 0x3b, 'Equal': 0x3d, 'Comma': 0x2c, 'Minus': 0x2d, 'Period': 0x2e, 'Slash': 0x2f,
963- 'Backquote': 0x60, 'BracketLeft': 0x5b, 'Backslash': 0x5c, 'BracketRight': 0x5d, 'Quote': 0x27
960+ 'Shift': 0xffe1, 'Control': 0xffe3, 'Alt': 0xffe9, 'Meta': 0xffeb
964961 };
965962
966963 let keysym = 0;
967- if (keyMap[code]) {
968- keysym = keyMap[code];
969- } else if (keyMap[key]) {
970- keysym = keyMap[key];
971- } else if (key.length === 1) {
972- keysym = key.charCodeAt(0);
964+ if (down) {
965+ // Prioritize specific control keys
966+ if (keyMap[code]) {
967+ keysym = keyMap[code];
968+ } else if (keyMap[key]) {
969+ keysym = keyMap[key];
970+ } else if (key.length === 1) {
971+ let char = key;
972+ const softShift = stickyStates['ShiftLeft'] || stickyStates['ShiftRight'];
973+ if (softShift && !e.shiftKey) {
974+ // If software Shift is on but physical is not, escalate letters to uppercase.
975+ // This is necessary because VNC servers often interpret keysyms literally.
976+ if (char >= 'a' && char <= 'z') char = char.toUpperCase();
977+ else if (char >= 'A' && char <= 'Z') char = char.toLowerCase(); // Caps lock inverse? No, stick to shift logic.
978+ }
979+
980+ keysym = char.charCodeAt(0);
981+ // If Ctrl or Alt is down, we want the base keysym (e.g. 'c' for Ctrl+C)
982+ if ((e.ctrlKey || e.altKey || e.metaKey) && keysym < 32) {
983+ if (keysym >= 1 && keysym <= 26) keysym += 96;
984+ }
985+ }
986+
987+ if (keysym) {
988+ pressedKeysyms[code] = keysym;
989+ }
973990 } else {
974- return;
991+ keysym = pressedKeysyms[code];
992+ delete pressedKeysyms[code];
993+
994+ // Fallback for keyup if keydown was missed
995+ if (!keysym) {
996+ if (keyMap[code]) keysym = keyMap[code];
997+ else if (keyMap[key]) keysym = keyMap[key];
998+ else if (key.length === 1) keysym = key.charCodeAt(0);
999+ }
9751000 }
9761001
1002+ if (!keysym) return;
1003+
9771004 e.preventDefault();
9781005
9791006 try {
9801007 ws.send(new Uint8Array([
9811008 4, down ? 1 : 0, 0, 0,
982- (keysym >> 24) & 0xff,
983- (keysym >> 16) & 0xff,
984- (keysym >> 8) & 0xff,
985- keysym & 0xff
1009+ (keysym >> 24) & 0xff, (keysym >> 16) & 0xff, (keysym >> 8) & 0xff, keysym & 0xff
9861010 ]));
9871011 } catch (err) {
9881012 console.error("Failed to send key:", err);
0 commit comments