Improve keyboard input user-experience and support Shift
This commit is contained in:
@@ -449,6 +449,15 @@ inline std::string GetWebPageHTML() {
|
||||
height: 100dvh !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
/* Portrait fullscreen: align canvas to top */
|
||||
@media (orientation: portrait) {
|
||||
#screen-page:fullscreen .canvas-container,
|
||||
#screen-page:-webkit-full-screen .canvas-container,
|
||||
#screen-page.pseudo-fullscreen .canvas-container {
|
||||
align-items: flex-start !important;
|
||||
padding-top: 56px !important;
|
||||
}
|
||||
}
|
||||
#screen-page.pseudo-fullscreen #screen-canvas {
|
||||
max-width: 100vw !important; max-height: 100vh !important;
|
||||
max-height: 100dvh !important;
|
||||
@@ -554,6 +563,65 @@ inline std::string GetWebPageHTML() {
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 999;
|
||||
}
|
||||
/* Utility class for hiding elements */
|
||||
.ui-hidden { display: none !important; }
|
||||
/* Quick control buttons - always visible on touch devices */
|
||||
.quick-controls {
|
||||
position: absolute;
|
||||
display: none;
|
||||
gap: 8px;
|
||||
z-index: 100;
|
||||
padding: 8px;
|
||||
}
|
||||
.quick-controls.visible { display: flex; }
|
||||
.quick-controls .qc-btn {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: rgba(0,0,0,0.6);
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.quick-controls .qc-btn:hover { background: rgba(0,0,0,0.8); }
|
||||
.quick-controls .qc-btn:active { transform: scale(0.9); }
|
||||
.quick-controls .qc-btn.active { background: rgba(52,199,89,0.9); }
|
||||
.quick-controls .qc-btn:disabled { opacity: 0.4; }
|
||||
/* Portrait: horizontal at top center */
|
||||
@media (orientation: portrait) {
|
||||
.quick-controls {
|
||||
top: 4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
flex-direction: row;
|
||||
padding: 4px;
|
||||
gap: 6px;
|
||||
}
|
||||
.quick-controls .qc-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 18px;
|
||||
}
|
||||
/* Portrait: align canvas to top, leave space for controls */
|
||||
.canvas-container {
|
||||
align-items: flex-start;
|
||||
padding-top: 56px;
|
||||
}
|
||||
}
|
||||
/* Landscape: vertical at right center */
|
||||
@media (orientation: landscape) {
|
||||
.quick-controls {
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
transform: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.cursor-overlay {
|
||||
position: fixed;
|
||||
width: 24px;
|
||||
@@ -567,6 +635,37 @@ inline std::string GetWebPageHTML() {
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
.cursor-overlay.active { display: block; }
|
||||
/* Input shortcut bar - below canvas, portrait mode only */
|
||||
.input-shortcuts {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: none;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 6px 4px;
|
||||
z-index: 101;
|
||||
}
|
||||
.input-shortcuts.visible { display: flex; }
|
||||
.input-shortcuts .shortcut-btn {
|
||||
min-width: 36px;
|
||||
height: 36px;
|
||||
padding: 0 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(128,128,128,0.5);
|
||||
background: rgba(128,128,128,0.4);
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.1s;
|
||||
text-shadow: 0 0 2px #000, 0 0 4px #000, 1px 1px 1px #000;
|
||||
}
|
||||
.input-shortcuts .shortcut-btn:hover { background: rgba(128,128,128,0.5); }
|
||||
.input-shortcuts .shortcut-btn:active { transform: scale(0.95); background: rgba(128,128,128,0.6); }
|
||||
/* Mobile responsive */
|
||||
@media (max-width: 768px) {
|
||||
.page { padding: 10px; }
|
||||
@@ -655,7 +754,26 @@ inline std::string GetWebPageHTML() {
|
||||
<button class="fullscreen-btn" onclick="toggleFullscreen()" title="Fullscreen (F11)">⛶</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="canvas-container"><canvas id="screen-canvas"></canvas><div class="cursor-overlay" id="cursor-overlay"></div></div>
|
||||
<div class="canvas-container">
|
||||
<canvas id="screen-canvas"></canvas>
|
||||
<div class="cursor-overlay" id="cursor-overlay"></div>
|
||||
<div class="quick-controls" id="quick-controls">
|
||||
<button class="qc-btn" id="qc-rdp" onclick="sendRdpReset()" title="RDP Reset" tabindex="-1">↻</button>
|
||||
<button class="qc-btn" id="qc-keyboard" onclick="toggleKeyboard()" title="Keyboard" tabindex="-1">⌨</button>
|
||||
<button class="qc-btn" id="qc-mouse" onclick="toggleControl()" title="Mouse" tabindex="-1">🖱</button>
|
||||
<button class="qc-btn" id="qc-disconnect" onclick="disconnect()" title="Disconnect" tabindex="-1">✕</button>
|
||||
</div>
|
||||
<div class="input-shortcuts" id="input-shortcuts">
|
||||
<button class="shortcut-btn" data-key="49" tabindex="-1">1</button>
|
||||
<button class="shortcut-btn" data-key="50" tabindex="-1">2</button>
|
||||
<button class="shortcut-btn" data-key="51" tabindex="-1">3</button>
|
||||
<button class="shortcut-btn" data-key="52" tabindex="-1">4</button>
|
||||
<button class="shortcut-btn" data-key="53" tabindex="-1">5</button>
|
||||
<button class="shortcut-btn" data-key="190" tabindex="-1">.</button>
|
||||
<button class="shortcut-btn" data-key="188" tabindex="-1">,</button>
|
||||
<button class="shortcut-btn" data-key="191" data-shift="1" tabindex="-1">?</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="touch-indicator" id="touch-indicator"></div>
|
||||
<button class="toolbar-toggle" id="toolbar-toggle" onclick="toggleFloatingToolbar()">•••</button>
|
||||
<div class="floating-toolbar" id="floating-toolbar">
|
||||
@@ -907,6 +1025,9 @@ inline std::string GetWebPageHTML() {
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// Update input shortcuts position after canvas resize
|
||||
requestAnimationFrame(updateInputShortcutsPosition);
|
||||
|
||||
// Set up vertical flip transform once (BMP is bottom-up)
|
||||
ctx.setTransform(1, 0, 0, -1, 0, height);
|
||||
if (decoder) { try { decoder.close(); } catch(e) {} }
|
||||
@@ -1198,16 +1319,17 @@ inline std::string GetWebPageHTML() {
|
||||
|
||||
function toggleFullscreen() {
|
||||
const el = document.getElementById('screen-page');
|
||||
const isFullscreen = document.fullscreenElement || document.webkitFullscreenElement;
|
||||
const isFs = document.fullscreenElement || document.webkitFullscreenElement;
|
||||
const isPseudo = el.classList.contains('pseudo-fullscreen');
|
||||
|
||||
// Try native fullscreen first
|
||||
if (!isPseudo && (el.requestFullscreen || el.webkitRequestFullscreen)) {
|
||||
if (!isFullscreen) {
|
||||
if (!isFs) {
|
||||
(el.requestFullscreen || el.webkitRequestFullscreen).call(el).catch(() => {
|
||||
// Native fullscreen failed (iOS), use pseudo-fullscreen
|
||||
el.classList.add('pseudo-fullscreen');
|
||||
window.scrollTo(0, 1);
|
||||
updateUIForOrientation();
|
||||
});
|
||||
} else {
|
||||
if (document.exitFullscreen) document.exitFullscreen();
|
||||
@@ -1217,6 +1339,7 @@ inline std::string GetWebPageHTML() {
|
||||
// Toggle pseudo-fullscreen (iOS fallback)
|
||||
el.classList.toggle('pseudo-fullscreen');
|
||||
if (el.classList.contains('pseudo-fullscreen')) window.scrollTo(0, 1);
|
||||
updateUIForOrientation();
|
||||
}
|
||||
}
|
||||
)HTML";
|
||||
@@ -1235,6 +1358,7 @@ inline std::string GetWebPageHTML() {
|
||||
const now = Date.now();
|
||||
if (now - lastTapTime < 300 && e.changedTouches.length === 1) {
|
||||
el.classList.remove('pseudo-fullscreen');
|
||||
updateUIForOrientation();
|
||||
e.preventDefault();
|
||||
}
|
||||
lastTapTime = now;
|
||||
@@ -1254,14 +1378,25 @@ inline std::string GetWebPageHTML() {
|
||||
let toolbarVisible = false;
|
||||
let toolbarHideTimer = null;
|
||||
|
||||
function isFullscreen() {
|
||||
return !!(document.fullscreenElement || document.webkitFullscreenElement ||
|
||||
document.getElementById('screen-page')?.classList.contains('pseudo-fullscreen'));
|
||||
}
|
||||
|
||||
function toggleFloatingToolbar() {
|
||||
const toolbar = document.getElementById('floating-toolbar');
|
||||
toolbarVisible = !toolbarVisible;
|
||||
toolbar.classList.toggle('visible', toolbarVisible);
|
||||
|
||||
// Auto-hide after 4 seconds
|
||||
if (toolbarHideTimer) clearTimeout(toolbarHideTimer);
|
||||
if (toolbarVisible) {
|
||||
// Clear any existing timer
|
||||
if (toolbarHideTimer) {
|
||||
clearTimeout(toolbarHideTimer);
|
||||
toolbarHideTimer = null;
|
||||
}
|
||||
|
||||
// Only auto-hide when NOT in fullscreen (desktop behavior)
|
||||
// In fullscreen (touch device), user must click again to close
|
||||
if (toolbarVisible && !isFullscreen()) {
|
||||
toolbarHideTimer = setTimeout(() => {
|
||||
toolbarVisible = false;
|
||||
toolbar.classList.remove('visible');
|
||||
@@ -1269,6 +1404,88 @@ inline std::string GetWebPageHTML() {
|
||||
}
|
||||
}
|
||||
|
||||
function isLandscape() {
|
||||
return window.innerWidth > window.innerHeight;
|
||||
}
|
||||
|
||||
// Cached DOM elements (initialized in window.onload)
|
||||
let uiElements = null;
|
||||
|
||||
function initUIElements() {
|
||||
uiElements = {
|
||||
quickControls: document.getElementById('quick-controls'),
|
||||
toolbarToggle: document.getElementById('toolbar-toggle'),
|
||||
floatingToolbar: document.getElementById('floating-toolbar'),
|
||||
btnRdpResetBar: document.getElementById('btn-rdp-reset-bar'),
|
||||
btnMouseBar: document.getElementById('btn-mouse-bar'),
|
||||
btnKeyboardBar: document.getElementById('btn-keyboard-bar'),
|
||||
inputShortcuts: document.getElementById('input-shortcuts')
|
||||
};
|
||||
}
|
||||
|
||||
function updateUIForOrientation() {
|
||||
if (!isTouchDevice || !uiElements) return;
|
||||
|
||||
// Clear any pending toolbar hide timer when orientation/fullscreen changes
|
||||
if (toolbarHideTimer) {
|
||||
clearTimeout(toolbarHideTimer);
|
||||
toolbarHideTimer = null;
|
||||
}
|
||||
|
||||
const { quickControls, toolbarToggle, floatingToolbar,
|
||||
btnRdpResetBar, btnMouseBar, btnKeyboardBar, inputShortcuts } = uiElements;
|
||||
|
||||
if (isLandscape()) {
|
||||
// Landscape mode
|
||||
quickControls.classList.remove('visible');
|
||||
inputShortcuts.classList.remove('visible');
|
||||
|
||||
if (isFullscreen()) {
|
||||
// Landscape fullscreen: show three-dot menu
|
||||
toolbarToggle.classList.remove('ui-hidden');
|
||||
btnRdpResetBar.classList.add('ui-hidden');
|
||||
btnMouseBar.classList.add('ui-hidden');
|
||||
btnKeyboardBar.classList.add('ui-hidden');
|
||||
} else {
|
||||
// Landscape non-fullscreen: show top toolbar buttons
|
||||
toolbarToggle.classList.add('ui-hidden');
|
||||
floatingToolbar.classList.remove('visible');
|
||||
toolbarVisible = false;
|
||||
btnRdpResetBar.classList.remove('ui-hidden');
|
||||
btnMouseBar.classList.remove('ui-hidden');
|
||||
btnKeyboardBar.classList.remove('ui-hidden');
|
||||
}
|
||||
} else {
|
||||
// Portrait mode: show quick controls
|
||||
quickControls.classList.add('visible');
|
||||
// Input shortcuts only visible when keyboard is open
|
||||
const keyboardOpen = document.activeElement === mobileKeyboard;
|
||||
inputShortcuts.classList.toggle('visible', keyboardOpen);
|
||||
toolbarToggle.classList.add('ui-hidden');
|
||||
floatingToolbar.classList.remove('visible');
|
||||
toolbarVisible = false;
|
||||
btnRdpResetBar.classList.add('ui-hidden');
|
||||
btnMouseBar.classList.add('ui-hidden');
|
||||
btnKeyboardBar.classList.add('ui-hidden');
|
||||
}
|
||||
// Position input shortcuts below canvas
|
||||
updateInputShortcutsPosition();
|
||||
}
|
||||
|
||||
// Position input shortcuts bar below the canvas
|
||||
function updateInputShortcutsPosition() {
|
||||
if (!isTouchDevice || !uiElements || !uiElements.inputShortcuts) return;
|
||||
if (isLandscape()) return; // Only for portrait mode
|
||||
|
||||
const canvas = document.getElementById('screen-canvas');
|
||||
const shortcuts = uiElements.inputShortcuts;
|
||||
if (!canvas || canvas.offsetHeight === 0) return;
|
||||
|
||||
// Position shortcuts 8px below canvas
|
||||
const canvasBottom = canvas.offsetTop + canvas.offsetHeight;
|
||||
shortcuts.style.top = (canvasBottom + 8) + 'px';
|
||||
}
|
||||
|
||||
function sendRdpReset() {
|
||||
if (ws && ws.readyState === WebSocket.OPEN && token) {
|
||||
ws.send(JSON.stringify({ cmd: 'rdp_reset', token }));
|
||||
@@ -1290,14 +1507,20 @@ inline std::string GetWebPageHTML() {
|
||||
controlEnabled = !controlEnabled;
|
||||
// Update floating toolbar buttons
|
||||
const btnMouse = document.getElementById('btn-mouse');
|
||||
const btnKeyboard = document.getElementById('btn-keyboard');
|
||||
btnMouse.classList.toggle('active', controlEnabled);
|
||||
btnKeyboard.disabled = !controlEnabled;
|
||||
// Update top toolbar buttons (sync state)
|
||||
const btnMouseBar = document.getElementById('btn-mouse-bar');
|
||||
const btnKeyboardBar = document.getElementById('btn-keyboard-bar');
|
||||
btnMouseBar.classList.toggle('active', controlEnabled);
|
||||
btnKeyboardBar.disabled = !controlEnabled;
|
||||
// Update quick controls buttons
|
||||
const qcMouse = document.getElementById('qc-mouse');
|
||||
if (qcMouse) qcMouse.classList.toggle('active', controlEnabled);
|
||||
// Desktop only: keyboard requires mouse control enabled
|
||||
if (!isTouchDevice) {
|
||||
const btnKeyboard = document.getElementById('btn-keyboard');
|
||||
const btnKeyboardBar = document.getElementById('btn-keyboard-bar');
|
||||
if (btnKeyboard) btnKeyboard.disabled = !controlEnabled;
|
||||
if (btnKeyboardBar) btnKeyboardBar.disabled = !controlEnabled;
|
||||
}
|
||||
// Cursor handling
|
||||
const canvas = document.getElementById('screen-canvas');
|
||||
const cursorOverlay = document.getElementById('cursor-overlay');
|
||||
@@ -1314,15 +1537,14 @@ inline std::string GetWebPageHTML() {
|
||||
const canvas = document.getElementById('screen-canvas');
|
||||
const cursorOverlay = document.getElementById('cursor-overlay');
|
||||
|
||||
// Get canvas base rect (without transform, use container)
|
||||
// Get canvas base position (without transform, use container + offset)
|
||||
// offsetLeft/offsetTop are layout positions, unaffected by CSS transform
|
||||
const container = document.querySelector('.canvas-container');
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
|
||||
// Calculate canvas position within container (centered)
|
||||
const canvasDisplayWidth = canvas.offsetWidth;
|
||||
const canvasDisplayHeight = canvas.offsetHeight;
|
||||
const canvasLeft = containerRect.left + (containerRect.width - canvasDisplayWidth) / 2;
|
||||
const canvasTop = containerRect.top + (containerRect.height - canvasDisplayHeight) / 2;
|
||||
const canvasLeft = containerRect.left + canvas.offsetLeft;
|
||||
const canvasTop = containerRect.top + canvas.offsetTop;
|
||||
|
||||
// Convert canvas coords to position on unzoomed canvas
|
||||
// Map [0, width-1] to [0, 1] for proper edge-to-edge display
|
||||
@@ -1354,13 +1576,13 @@ inline std::string GetWebPageHTML() {
|
||||
// Used to recalculate cursor position after zoom/pan
|
||||
function screenToCanvas(screenX, screenY) {
|
||||
const canvas = document.getElementById('screen-canvas');
|
||||
// Get canvas base position (without transform, use container + offset)
|
||||
const container = document.querySelector('.canvas-container');
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
|
||||
const canvasDisplayWidth = canvas.offsetWidth;
|
||||
const canvasDisplayHeight = canvas.offsetHeight;
|
||||
const canvasLeft = containerRect.left + (containerRect.width - canvasDisplayWidth) / 2;
|
||||
const canvasTop = containerRect.top + (containerRect.height - canvasDisplayHeight) / 2;
|
||||
const canvasLeft = containerRect.left + canvas.offsetLeft;
|
||||
const canvasTop = containerRect.top + canvas.offsetTop;
|
||||
|
||||
// Reverse the transform chain
|
||||
const scaledX = screenX - canvasLeft;
|
||||
@@ -1937,10 +2159,49 @@ inline std::string GetWebPageHTML() {
|
||||
touchState.touchCount = e.touches.length;
|
||||
}, { passive: false });
|
||||
|
||||
function toggleKeyboard() {
|
||||
mobileKeyboard.focus();
|
||||
let lastKeyboardBtnTouch = 0; // Timestamp of last keyboard button touch
|
||||
let wasKeyboardFocused = false; // Capture focus state at touchstart (before blur)
|
||||
|
||||
function updateKeyboardButtons(active) {
|
||||
// Update all keyboard buttons across different toolbars
|
||||
const qcKeyboard = document.getElementById('qc-keyboard');
|
||||
const btnKeyboard = document.getElementById('btn-keyboard');
|
||||
const btnKeyboardBar = document.getElementById('btn-keyboard-bar');
|
||||
if (qcKeyboard) qcKeyboard.classList.toggle('active', active);
|
||||
if (btnKeyboard) btnKeyboard.classList.toggle('active', active);
|
||||
if (btnKeyboardBar) btnKeyboardBar.classList.toggle('active', active);
|
||||
// Show/hide input shortcuts based on keyboard state (portrait only)
|
||||
const shortcuts = document.getElementById('input-shortcuts');
|
||||
if (shortcuts && !isLandscape()) {
|
||||
shortcuts.classList.toggle('visible', active);
|
||||
if (active) updateInputShortcutsPosition();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleKeyboard() {
|
||||
// Use focus state captured at touchstart (more reliable than button's active class)
|
||||
const isOpen = wasKeyboardFocused;
|
||||
wasKeyboardFocused = false; // Reset
|
||||
|
||||
if (isOpen) {
|
||||
mobileKeyboard.blur();
|
||||
updateKeyboardButtons(false);
|
||||
} else {
|
||||
mobileKeyboard.focus();
|
||||
updateKeyboardButtons(true);
|
||||
}
|
||||
}
|
||||
|
||||
mobileKeyboard.addEventListener('focus', function() {
|
||||
updateKeyboardButtons(true);
|
||||
});
|
||||
|
||||
mobileKeyboard.addEventListener('blur', function() {
|
||||
// Skip if keyboard button was touched within last 300ms
|
||||
if (Date.now() - lastKeyboardBtnTouch < 300) return;
|
||||
updateKeyboardButtons(false);
|
||||
});
|
||||
|
||||
function sendRightClick() {
|
||||
// Use cursor position (canvas coordinates), not touch position (screen coordinates)
|
||||
const x = Math.round(cursorState.x);
|
||||
@@ -2029,9 +2290,32 @@ inline std::string GetWebPageHTML() {
|
||||
mobileKeyboard.addEventListener('input', function(e) {
|
||||
const char = e.data;
|
||||
if (char) {
|
||||
const keyCode = char.toUpperCase().charCodeAt(0);
|
||||
// Check if character needs Shift key
|
||||
const isUpperCase = char >= 'A' && char <= 'Z';
|
||||
const shiftSymbols = '~!@#$%^&*()_+{}|:"<>?';
|
||||
const needsShift = isUpperCase || shiftSymbols.includes(char);
|
||||
|
||||
// Map symbols to their base keys
|
||||
const symbolMap = {
|
||||
'~': 192, '!': 49, '@': 50, '#': 51, '$': 52, '%': 53,
|
||||
'^': 54, '&': 55, '*': 56, '(': 57, ')': 48, '_': 189,
|
||||
'+': 187, '{': 219, '}': 221, '|': 220, ':': 186,
|
||||
'"': 222, '<': 188, '>': 190, '?': 191
|
||||
};
|
||||
|
||||
let keyCode;
|
||||
if (symbolMap[char]) {
|
||||
keyCode = symbolMap[char];
|
||||
} else {
|
||||
keyCode = char.toUpperCase().charCodeAt(0);
|
||||
}
|
||||
|
||||
// Send Shift down if needed
|
||||
if (needsShift) sendKey(16, true); // VK_SHIFT = 16
|
||||
sendKey(keyCode, true);
|
||||
sendKey(keyCode, false);
|
||||
// Send Shift up if needed
|
||||
if (needsShift) sendKey(16, false);
|
||||
}
|
||||
mobileKeyboard.value = '';
|
||||
});
|
||||
@@ -2052,16 +2336,24 @@ inline std::string GetWebPageHTML() {
|
||||
function disconnect() {
|
||||
// Reset control mode
|
||||
controlEnabled = false;
|
||||
// Reset keyboard state (blur event will update button state)
|
||||
mobileKeyboard.blur();
|
||||
// Reset floating toolbar buttons
|
||||
const btnMouse = document.getElementById('btn-mouse');
|
||||
const btnKeyboard = document.getElementById('btn-keyboard');
|
||||
if (btnMouse) btnMouse.classList.remove('active');
|
||||
if (btnKeyboard) btnKeyboard.disabled = true;
|
||||
// Reset top toolbar buttons
|
||||
const btnMouseBar = document.getElementById('btn-mouse-bar');
|
||||
const btnKeyboardBar = document.getElementById('btn-keyboard-bar');
|
||||
if (btnMouseBar) btnMouseBar.classList.remove('active');
|
||||
if (btnKeyboardBar) btnKeyboardBar.disabled = true;
|
||||
// Desktop only: disable keyboard button
|
||||
if (!isTouchDevice) {
|
||||
const btnKeyboard = document.getElementById('btn-keyboard');
|
||||
const btnKeyboardBar = document.getElementById('btn-keyboard-bar');
|
||||
if (btnKeyboard) btnKeyboard.disabled = true;
|
||||
if (btnKeyboardBar) btnKeyboardBar.disabled = true;
|
||||
}
|
||||
// Reset quick control buttons
|
||||
const qcMouse = document.getElementById('qc-mouse');
|
||||
if (qcMouse) qcMouse.classList.remove('active');
|
||||
document.getElementById('screen-canvas').style.cursor = 'default';
|
||||
document.getElementById('cursor-overlay').classList.remove('active');
|
||||
|
||||
@@ -2121,11 +2413,89 @@ inline std::string GetWebPageHTML() {
|
||||
});
|
||||
|
||||
window.onload = function() {
|
||||
// Initialize cached DOM elements
|
||||
initUIElements();
|
||||
|
||||
const compat = checkWebCodecs();
|
||||
if (!compat.supported) {
|
||||
document.body.insertAdjacentHTML('afterbegin',
|
||||
'<div class="compat-warning">Warning: Your browser may not support H264 decoding.</div>');
|
||||
}
|
||||
// On touch devices: setup UI based on orientation and fullscreen state
|
||||
if (isTouchDevice) {
|
||||
// Initial UI setup
|
||||
updateUIForOrientation();
|
||||
|
||||
// Touch devices: keyboard is independent from mouse, enable keyboard buttons
|
||||
const btnKeyboard = document.getElementById('btn-keyboard');
|
||||
const btnKeyboardBar = document.getElementById('btn-keyboard-bar');
|
||||
if (btnKeyboard) btnKeyboard.disabled = false;
|
||||
if (btnKeyboardBar) btnKeyboardBar.disabled = false;
|
||||
|
||||
// Listen for orientation changes (with debounce)
|
||||
let resizeTimer = null;
|
||||
window.addEventListener('resize', function() {
|
||||
if (resizeTimer) clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(updateUIForOrientation, 100);
|
||||
});
|
||||
|
||||
// Listen for fullscreen changes
|
||||
document.addEventListener('fullscreenchange', updateUIForOrientation);
|
||||
document.addEventListener('webkitfullscreenchange', updateUIForOrientation);
|
||||
|
||||
// Input shortcut buttons event handlers
|
||||
// Shortcuts only visible when keyboard is open, so no extra check needed
|
||||
function sendShortcutKey(keyCode, isDown) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN && token) {
|
||||
ws.send(JSON.stringify({ cmd: 'key', token, keyCode, down: isDown, alt: false }));
|
||||
}
|
||||
}
|
||||
const shortcutBtns = document.querySelectorAll('.shortcut-btn');
|
||||
shortcutBtns.forEach(function(btn) {
|
||||
btn.addEventListener('touchstart', function(e) {
|
||||
e.preventDefault();
|
||||
const keyCode = parseInt(btn.dataset.key);
|
||||
const needShift = btn.dataset.shift === '1';
|
||||
if (needShift) sendShortcutKey(16, true); // Shift down
|
||||
sendShortcutKey(keyCode, true);
|
||||
sendShortcutKey(keyCode, false);
|
||||
if (needShift) sendShortcutKey(16, false); // Shift up
|
||||
});
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
// Only handle click for non-touch (mouse)
|
||||
if (!('ontouchstart' in window)) {
|
||||
const keyCode = parseInt(btn.dataset.key);
|
||||
const needShift = btn.dataset.shift === '1';
|
||||
if (needShift) sendShortcutKey(16, true);
|
||||
sendShortcutKey(keyCode, true);
|
||||
sendShortcutKey(keyCode, false);
|
||||
if (needShift) sendShortcutKey(16, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Desktop: hide keyboard buttons (physical keyboard available)
|
||||
const btnKeyboard = document.getElementById('btn-keyboard');
|
||||
const btnKeyboardBar = document.getElementById('btn-keyboard-bar');
|
||||
if (btnKeyboard) btnKeyboard.classList.add('ui-hidden');
|
||||
if (btnKeyboardBar) btnKeyboardBar.classList.add('ui-hidden');
|
||||
}
|
||||
// Keyboard buttons: capture focus state and prevent blur from updating button state
|
||||
function bindKeyboardBtnEvents(btn) {
|
||||
if (!btn) return;
|
||||
btn.addEventListener('touchstart', function() {
|
||||
lastKeyboardBtnTouch = Date.now();
|
||||
wasKeyboardFocused = (document.activeElement === mobileKeyboard);
|
||||
}, { passive: true });
|
||||
btn.addEventListener('mousedown', function() {
|
||||
lastKeyboardBtnTouch = Date.now();
|
||||
wasKeyboardFocused = (document.activeElement === mobileKeyboard);
|
||||
});
|
||||
}
|
||||
bindKeyboardBtnEvents(document.getElementById('qc-keyboard'));
|
||||
bindKeyboardBtnEvents(document.getElementById('btn-keyboard'));
|
||||
bindKeyboardBtnEvents(document.getElementById('btn-keyboard-bar'));
|
||||
// Restore token from sessionStorage
|
||||
token = sessionStorage.getItem('token');
|
||||
connectWebSocket();
|
||||
|
||||
Reference in New Issue
Block a user