Fix mouse double click issue and switch remote desktop issue
This commit is contained in:
@@ -22,6 +22,7 @@ inline std::string GetWebPageHTML() {
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="theme-color" content="#1a1a2e">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="icon" type="image/png" href="data:image/png;base64,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">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
@@ -74,7 +75,7 @@ inline std::string GetWebPageHTML() {
|
||||
box-shadow: 0 0 0 3px rgba(233, 69, 96, 0.2);
|
||||
}
|
||||
.login-form input::placeholder { color: #666; }
|
||||
.login-form button {
|
||||
.login-form > button {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
border: none;
|
||||
@@ -88,12 +89,80 @@ inline std::string GetWebPageHTML() {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.login-form button:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(233, 69, 96, 0.4); }
|
||||
.login-form button:disabled { background: #444; cursor: not-allowed; transform: none; box-shadow: none; }
|
||||
.login-form > button:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(233, 69, 96, 0.4); }
|
||||
.login-form > button:disabled { background: #444; cursor: not-allowed; transform: none; box-shadow: none; }
|
||||
.error-msg { color: #e94560; text-align: center; margin-top: 16px; font-size: 14px; }
|
||||
.conn-status { text-align: center; margin-bottom: 20px; font-size: 13px; color: #666; }
|
||||
.conn-status.connected { color: #4caf50; }
|
||||
.conn-status.disconnected { color: #f44336; }
|
||||
/* Password input with toggle */
|
||||
.password-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.password-wrapper input {
|
||||
margin-bottom: 0;
|
||||
padding-right: 48px;
|
||||
}
|
||||
.password-toggle {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
transition: color 0.2s;
|
||||
opacity: 0.6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.password-toggle:hover { color: #e94560; opacity: 1; }
|
||||
/* Login footer */
|
||||
.login-footer {
|
||||
margin-top: 24px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
.security-notice {
|
||||
background: rgba(255,152,0,0.1);
|
||||
border: 1px solid rgba(255,152,0,0.3);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.security-notice strong {
|
||||
color: #ff9800;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.login-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.login-links a {
|
||||
color: #e94560;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.login-links a:hover { color: #ff6b8a; }
|
||||
)HTML";
|
||||
|
||||
// Part 2: Device page styles
|
||||
@@ -711,9 +780,22 @@ inline std::string GetWebPageHTML() {
|
||||
<h1>SimpleRemoter</h1>
|
||||
<div id="ws-status" class="conn-status disconnected">Connecting...</div>
|
||||
<input type="text" id="username" placeholder="Username" value="admin">
|
||||
<input type="password" id="password" placeholder="Password">
|
||||
<div class="password-wrapper">
|
||||
<input type="password" id="password" placeholder="Password">
|
||||
<button type="button" class="password-toggle" onclick="togglePasswordVisibility()" title="Show/Hide Password">👁</button>
|
||||
</div>
|
||||
<button id="login-btn" onclick="login()" disabled>Login</button>
|
||||
<div id="login-error" class="error-msg"></div>
|
||||
<div class="login-footer">
|
||||
<div class="security-notice">
|
||||
<strong>⚠ Security Notice</strong>
|
||||
SimpleRemoter.com may be flagged as "dangerous" by browsers due to the word "Remote" in its name. This is a false positive. The software is fully open-source and safe to use.
|
||||
</div>
|
||||
<div class="login-links">
|
||||
<a href="https://simpleremoter.com/" target="_blank">🌐 Website</a>
|
||||
<a href="https://git.simpleremoter.com/" target="_blank">📦 Source Code</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="devices-page" class="page">
|
||||
@@ -903,8 +985,16 @@ inline std::string GetWebPageHTML() {
|
||||
startPingInterval();
|
||||
// Auto-restore session if token exists
|
||||
if (token) {
|
||||
showPage('devices-page');
|
||||
getDevices();
|
||||
// Check if we were on screen-page with an active device
|
||||
const screenPage = document.getElementById('screen-page');
|
||||
if (screenPage.classList.contains('active') && currentDevice) {
|
||||
// Reconnect to current device
|
||||
updateScreenStatus('connecting');
|
||||
ws.send(JSON.stringify({ cmd: 'connect', id: String(currentDevice.id), token }));
|
||||
} else {
|
||||
showPage('devices-page');
|
||||
getDevices();
|
||||
}
|
||||
}
|
||||
};
|
||||
ws.onclose = () => { stopPingInterval(); updateWsStatus('disconnected'); scheduleReconnect(); };
|
||||
@@ -976,17 +1066,17 @@ inline std::string GetWebPageHTML() {
|
||||
}
|
||||
break;
|
||||
case 'disconnect_result':
|
||||
// Only navigate if authenticated
|
||||
if (!token) break;
|
||||
showPage('devices-page');
|
||||
getDevices();
|
||||
// disconnect() already handles navigation, this is just server acknowledgment
|
||||
// No action needed - prevents race conditions when switching devices
|
||||
break;
|
||||
case 'resolution_changed':
|
||||
updateScreenStatus('connected');
|
||||
initDecoder(msg.width, msg.height);
|
||||
break;
|
||||
case 'device_offline':
|
||||
// Only handle if this is the device we're currently viewing
|
||||
if (!token) break;
|
||||
if (!currentDevice || String(msg.id) !== String(currentDevice.id)) break;
|
||||
updateScreenStatus('error', 'Device offline');
|
||||
setTimeout(() => { showPage('devices-page'); getDevices(); }, 2000);
|
||||
break;
|
||||
@@ -1269,6 +1359,18 @@ inline std::string GetWebPageHTML() {
|
||||
else el.textContent = msg || 'Error';
|
||||
}
|
||||
|
||||
function togglePasswordVisibility() {
|
||||
const input = document.getElementById('password');
|
||||
const btn = document.querySelector('.password-toggle');
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
btn.innerHTML = '👀'; // Eyes
|
||||
} else {
|
||||
input.type = 'password';
|
||||
btn.innerHTML = '👁'; // Eye
|
||||
}
|
||||
}
|
||||
|
||||
async function login() {
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
@@ -2239,11 +2341,7 @@ inline std::string GetWebPageHTML() {
|
||||
sendMouse('up', pos.x, pos.y, e.button);
|
||||
});
|
||||
|
||||
canvas.addEventListener('dblclick', function(e) {
|
||||
e.preventDefault();
|
||||
const pos = getMousePos(e);
|
||||
sendMouse('dblclick', pos.x, pos.y, e.button);
|
||||
});
|
||||
// Note: dblclick is handled by mousedown-mouseup sequence, no separate handler needed
|
||||
|
||||
canvas.addEventListener('mousemove', function(e) {
|
||||
const now = Date.now();
|
||||
@@ -2366,7 +2464,10 @@ inline std::string GetWebPageHTML() {
|
||||
canvas.style.transformOrigin = '';
|
||||
|
||||
if (decoder) { try { decoder.close(); } catch(e) {} decoder = null; }
|
||||
if (ws && ws.readyState === WebSocket.OPEN && token) ws.send(JSON.stringify({ cmd: 'disconnect', token }));
|
||||
if (ws && ws.readyState === WebSocket.OPEN && token && currentDevice) {
|
||||
ws.send(JSON.stringify({ cmd: 'disconnect', token, id: String(currentDevice.id) }));
|
||||
}
|
||||
currentDevice = null; // Clear current device
|
||||
showPage('devices-page');
|
||||
getDevices();
|
||||
}
|
||||
@@ -2392,22 +2493,49 @@ inline std::string GetWebPageHTML() {
|
||||
});
|
||||
|
||||
// Handle page visibility change (iOS PWA background/foreground)
|
||||
let backgroundDisconnectTimer = null;
|
||||
const BACKGROUND_TIMEOUT_MOBILE = 30000; // 30s for mobile/tablet
|
||||
|
||||
function doBackgroundDisconnect() {
|
||||
cancelReconnect();
|
||||
stopPingInterval();
|
||||
if (ws) {
|
||||
ws.onclose = null;
|
||||
ws.close();
|
||||
ws = null;
|
||||
}
|
||||
updateWsStatus('disconnected');
|
||||
}
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
isPageVisible = !document.hidden;
|
||||
if (document.hidden) {
|
||||
// Page going to background - close connection and cancel reconnect
|
||||
cancelReconnect();
|
||||
stopPingInterval();
|
||||
if (ws) {
|
||||
ws.onclose = null; // Prevent triggering reconnect
|
||||
ws.close();
|
||||
ws = null;
|
||||
// Page going to background
|
||||
const screenPage = document.getElementById('screen-page');
|
||||
const onScreenPage = screenPage && screenPage.classList.contains('active') && currentDevice;
|
||||
|
||||
if (onScreenPage) {
|
||||
// Mobile/tablet: delay disconnect 30s
|
||||
// Desktop: keep connection alive (no timer)
|
||||
if (isTouchDevice) {
|
||||
backgroundDisconnectTimer = setTimeout(doBackgroundDisconnect, BACKGROUND_TIMEOUT_MOBILE);
|
||||
}
|
||||
} else {
|
||||
// Other pages - disconnect immediately
|
||||
doBackgroundDisconnect();
|
||||
}
|
||||
updateWsStatus('disconnected');
|
||||
} else {
|
||||
// Page coming to foreground - reconnect
|
||||
// Page coming to foreground
|
||||
if (backgroundDisconnectTimer) {
|
||||
clearTimeout(backgroundDisconnectTimer);
|
||||
backgroundDisconnectTimer = null;
|
||||
}
|
||||
// Reconnect or send immediate ping
|
||||
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
||||
connectWebSocket();
|
||||
} else if (token) {
|
||||
// Connection still open - send immediate ping to refresh server heartbeat
|
||||
ws.send(JSON.stringify({ cmd: 'ping', token }));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user