Skip to content

Commit 77e7528

Browse files
Paul Cwolfsoftwaresystemsltd
andcommitted
fix: VNC mobile keyboard support for iOS/Android
Add a Keyboard toolbar button that triggers the soft keyboard on mobile devices by focusing a hidden textarea, forwarding key events to noVNC. Co-Authored-By: CodeWolf <paul@wolf.uk.com> Co-Authored-By: Wolf Software Systems Ltd <paul@wolf.uk.com>
1 parent 0e8c595 commit 77e7528

2 files changed

Lines changed: 61 additions & 1 deletion

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "wolfstack"
3-
version = "16.3.1"
3+
version = "16.3.2"
44
edition = "2024"
55
authors = ["Wolf Software Systems Ltd"]
66
description = "Server management platform for the Wolf software suite"

web/vnc.html

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,12 @@ <h3 id="vm-name">VM Console</h3>
121121
<span id="status-badge" class="badge disconnected">Connecting...</span>
122122
<div class="spacer"></div>
123123
<button onclick="sendCtrlAltDel()" class="ctrl-alt-del" title="Send Ctrl+Alt+Del">Ctrl+Alt+Del</button>
124+
<button onclick="toggleKeyboard()" id="kb-btn" title="Show on-screen keyboard">Keyboard</button>
124125
<button onclick="toggleFullscreen()">Fullscreen</button>
125126
</div>
127+
<!-- Hidden textarea to trigger iOS/mobile soft keyboard -->
128+
<textarea id="mobile-kb-input" autocapitalize="off" autocomplete="off" autocorrect="off" spellcheck="false"
129+
style="position:fixed;top:-9999px;left:-9999px;width:1px;height:1px;opacity:0;font-size:16px;"></textarea>
126130
<div id="vnc-container" style="position: relative;">
127131
<div class="status-overlay" id="status-overlay">
128132
<div class="spinner"></div>
@@ -325,6 +329,62 @@ <h3 id="vm-name">VM Console</h3>
325329
document.exitFullscreen();
326330
}
327331
};
332+
333+
// Mobile soft keyboard support (iOS/Android)
334+
const kbInput = document.getElementById('mobile-kb-input');
335+
let kbOpen = false;
336+
337+
window.toggleKeyboard = function () {
338+
if (kbOpen) {
339+
kbInput.blur();
340+
kbOpen = false;
341+
document.getElementById('kb-btn').style.background = '';
342+
} else {
343+
kbInput.focus();
344+
kbOpen = true;
345+
document.getElementById('kb-btn').style.background = '#065f46';
346+
}
347+
};
348+
349+
kbInput.addEventListener('blur', () => {
350+
kbOpen = false;
351+
document.getElementById('kb-btn').style.background = '';
352+
});
353+
354+
// Forward key events from the hidden textarea to noVNC
355+
kbInput.addEventListener('keydown', (e) => {
356+
if (!rfb) return;
357+
// Let noVNC handle the key via its own canvas focus for special keys
358+
const key = e.key;
359+
if (key === 'Enter') rfb.sendKey(0xFF0D);
360+
else if (key === 'Backspace') rfb.sendKey(0xFF08);
361+
else if (key === 'Tab') rfb.sendKey(0xFF09);
362+
else if (key === 'Escape') rfb.sendKey(0xFF1B);
363+
else if (key === 'ArrowLeft') rfb.sendKey(0xFF51);
364+
else if (key === 'ArrowUp') rfb.sendKey(0xFF52);
365+
else if (key === 'ArrowRight') rfb.sendKey(0xFF53);
366+
else if (key === 'ArrowDown') rfb.sendKey(0xFF54);
367+
else if (key === 'Delete') rfb.sendKey(0xFFFF);
368+
else if (key === 'Home') rfb.sendKey(0xFF50);
369+
else if (key === 'End') rfb.sendKey(0xFF57);
370+
else return; // printable chars handled via input event
371+
e.preventDefault();
372+
});
373+
374+
// Capture printable characters via input event (works on iOS)
375+
kbInput.addEventListener('input', (e) => {
376+
if (!rfb) return;
377+
const data = e.data;
378+
if (data) {
379+
for (const ch of data) {
380+
const code = ch.codePointAt(0);
381+
rfb.sendKey(code, null, true); // press
382+
rfb.sendKey(code, null, false); // release
383+
}
384+
}
385+
// Clear the textarea so it stays ready for more input
386+
kbInput.value = '';
387+
});
328388
</script>
329389
</body>
330390

0 commit comments

Comments
 (0)