Fix mouse double click issue and switch remote desktop issue

This commit is contained in:
yuanyuanxiang
2026-04-23 08:43:02 +02:00
parent 011ec3d509
commit a649c10d0f
4 changed files with 168 additions and 30 deletions

View File

@@ -5263,14 +5263,18 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
SAFE_DELETE(frpc); SAFE_DELETE(frpc);
break; break;
} }
bool find = false;
for (std::vector<DllInfo*>::const_iterator i=m_DllList.begin(); i!=m_DllList.end(); ++i) { for (std::vector<DllInfo*>::const_iterator i=m_DllList.begin(); i!=m_DllList.end(); ++i) {
DllInfo* dll = *i; DllInfo* dll = *i;
if (dll->Name == info->Name) { if (dll->Name == info->Name) {
// TODO 如果是UDP发送大包数据基本上不可能成功 // TODO 如果是UDP发送大包数据基本上不可能成功
ContextObject->Send2Client(dll->Data->Buf(), dll->Data->length()); ContextObject->Send2Client(dll->Data->Buf(), dll->Data->length());
find = true;
break; break;
} }
} }
if (find) break;
auto dll = ReadPluginDll(PluginPath() + "\\" + info->Name, { SHELLCODE, 0, CALLTYPE_DEFAULT, {}, {}, info->Pid, info->Is32Bit }); auto dll = ReadPluginDll(PluginPath() + "\\" + info->Name, { SHELLCODE, 0, CALLTYPE_DEFAULT, {}, {}, info->Pid, info->Is32Bit });
if (dll) { if (dll) {
Buffer* buf = dll->Data; Buffer* buf = dll->Data;

View File

@@ -22,6 +22,7 @@ inline std::string GetWebPageHTML() {
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#1a1a2e"> <meta name="theme-color" content="#1a1a2e">
<link rel="manifest" href="/manifest.json"> <link rel="manifest" href="/manifest.json">
<link rel="icon" type="image/png" href="data:image/png;base64,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">
<style> <style>
* { margin: 0; padding: 0; box-sizing: border-box; } * { margin: 0; padding: 0; box-sizing: border-box; }
body { body {
@@ -74,7 +75,7 @@ inline std::string GetWebPageHTML() {
box-shadow: 0 0 0 3px rgba(233, 69, 96, 0.2); box-shadow: 0 0 0 3px rgba(233, 69, 96, 0.2);
} }
.login-form input::placeholder { color: #666; } .login-form input::placeholder { color: #666; }
.login-form button { .login-form > button {
width: 100%; width: 100%;
padding: 14px; padding: 14px;
border: none; border: none;
@@ -88,12 +89,80 @@ inline std::string GetWebPageHTML() {
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 1px; letter-spacing: 1px;
} }
.login-form button:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(233, 69, 96, 0.4); } .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:disabled { background: #444; cursor: not-allowed; transform: none; box-shadow: none; }
.error-msg { color: #e94560; text-align: center; margin-top: 16px; font-size: 14px; } .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 { text-align: center; margin-bottom: 20px; font-size: 13px; color: #666; }
.conn-status.connected { color: #4caf50; } .conn-status.connected { color: #4caf50; }
.conn-status.disconnected { color: #f44336; } .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"; )HTML";
// Part 2: Device page styles // Part 2: Device page styles
@@ -711,9 +780,22 @@ inline std::string GetWebPageHTML() {
<h1>SimpleRemoter</h1> <h1>SimpleRemoter</h1>
<div id="ws-status" class="conn-status disconnected">Connecting...</div> <div id="ws-status" class="conn-status disconnected">Connecting...</div>
<input type="text" id="username" placeholder="Username" value="admin"> <input type="text" id="username" placeholder="Username" value="admin">
<div class="password-wrapper">
<input type="password" id="password" placeholder="Password"> <input type="password" id="password" placeholder="Password">
<button type="button" class="password-toggle" onclick="togglePasswordVisibility()" title="Show/Hide Password">&#x1F441;</button>
</div>
<button id="login-btn" onclick="login()" disabled>Login</button> <button id="login-btn" onclick="login()" disabled>Login</button>
<div id="login-error" class="error-msg"></div> <div id="login-error" class="error-msg"></div>
<div class="login-footer">
<div class="security-notice">
<strong>&#x26A0; 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">&#x1F310; Website</a>
<a href="https://git.simpleremoter.com/" target="_blank">&#x1F4E6; Source Code</a>
</div>
</div>
</div> </div>
</div> </div>
<div id="devices-page" class="page"> <div id="devices-page" class="page">
@@ -903,9 +985,17 @@ inline std::string GetWebPageHTML() {
startPingInterval(); startPingInterval();
// Auto-restore session if token exists // Auto-restore session if token exists
if (token) { if (token) {
// 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'); showPage('devices-page');
getDevices(); getDevices();
} }
}
}; };
ws.onclose = () => { stopPingInterval(); updateWsStatus('disconnected'); scheduleReconnect(); }; ws.onclose = () => { stopPingInterval(); updateWsStatus('disconnected'); scheduleReconnect(); };
ws.onerror = (e) => console.error('WS error:', e); ws.onerror = (e) => console.error('WS error:', e);
@@ -976,17 +1066,17 @@ inline std::string GetWebPageHTML() {
} }
break; break;
case 'disconnect_result': case 'disconnect_result':
// Only navigate if authenticated // disconnect() already handles navigation, this is just server acknowledgment
if (!token) break; // No action needed - prevents race conditions when switching devices
showPage('devices-page');
getDevices();
break; break;
case 'resolution_changed': case 'resolution_changed':
updateScreenStatus('connected'); updateScreenStatus('connected');
initDecoder(msg.width, msg.height); initDecoder(msg.width, msg.height);
break; break;
case 'device_offline': case 'device_offline':
// Only handle if this is the device we're currently viewing
if (!token) break; if (!token) break;
if (!currentDevice || String(msg.id) !== String(currentDevice.id)) break;
updateScreenStatus('error', 'Device offline'); updateScreenStatus('error', 'Device offline');
setTimeout(() => { showPage('devices-page'); getDevices(); }, 2000); setTimeout(() => { showPage('devices-page'); getDevices(); }, 2000);
break; break;
@@ -1269,6 +1359,18 @@ inline std::string GetWebPageHTML() {
else el.textContent = msg || 'Error'; 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 = '&#x1F440;'; // Eyes
} else {
input.type = 'password';
btn.innerHTML = '&#x1F441;'; // Eye
}
}
async function login() { async function login() {
const username = document.getElementById('username').value; const username = document.getElementById('username').value;
const password = document.getElementById('password').value; const password = document.getElementById('password').value;
@@ -2239,11 +2341,7 @@ inline std::string GetWebPageHTML() {
sendMouse('up', pos.x, pos.y, e.button); sendMouse('up', pos.x, pos.y, e.button);
}); });
canvas.addEventListener('dblclick', function(e) { // Note: dblclick is handled by mousedown-mouseup sequence, no separate handler needed
e.preventDefault();
const pos = getMousePos(e);
sendMouse('dblclick', pos.x, pos.y, e.button);
});
canvas.addEventListener('mousemove', function(e) { canvas.addEventListener('mousemove', function(e) {
const now = Date.now(); const now = Date.now();
@@ -2366,7 +2464,10 @@ inline std::string GetWebPageHTML() {
canvas.style.transformOrigin = ''; canvas.style.transformOrigin = '';
if (decoder) { try { decoder.close(); } catch(e) {} decoder = null; } 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'); showPage('devices-page');
getDevices(); getDevices();
} }
@@ -2392,22 +2493,49 @@ inline std::string GetWebPageHTML() {
}); });
// Handle page visibility change (iOS PWA background/foreground) // Handle page visibility change (iOS PWA background/foreground)
document.addEventListener('visibilitychange', () => { let backgroundDisconnectTimer = null;
isPageVisible = !document.hidden; const BACKGROUND_TIMEOUT_MOBILE = 30000; // 30s for mobile/tablet
if (document.hidden) {
// Page going to background - close connection and cancel reconnect function doBackgroundDisconnect() {
cancelReconnect(); cancelReconnect();
stopPingInterval(); stopPingInterval();
if (ws) { if (ws) {
ws.onclose = null; // Prevent triggering reconnect ws.onclose = null;
ws.close(); ws.close();
ws = null; ws = null;
} }
updateWsStatus('disconnected'); updateWsStatus('disconnected');
}
document.addEventListener('visibilitychange', () => {
isPageVisible = !document.hidden;
if (document.hidden) {
// 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 { } else {
// Page coming to foreground - reconnect // Other pages - disconnect immediately
doBackgroundDisconnect();
}
} else {
// Page coming to foreground
if (backgroundDisconnectTimer) {
clearTimeout(backgroundDisconnectTimer);
backgroundDisconnectTimer = null;
}
// Reconnect or send immediate ping
if (!ws || ws.readyState !== WebSocket.OPEN) { if (!ws || ws.readyState !== WebSocket.OPEN) {
connectWebSocket(); connectWebSocket();
} else if (token) {
// Connection still open - send immediate ping to refresh server heartbeat
ws.send(JSON.stringify({ cmd: 'ping', token }));
} }
} }
}); });

View File

@@ -318,7 +318,9 @@ void CWebService::ServerThread(int port) {
uint64_t device_id = id_str.empty() ? 0 : strtoull(id_str.c_str(), nullptr, 10); uint64_t device_id = id_str.empty() ? 0 : strtoull(id_str.c_str(), nullptr, 10);
HandleConnect(ws_ptr, token, device_id); HandleConnect(ws_ptr, token, device_id);
} else if (cmd == "disconnect") { } else if (cmd == "disconnect") {
HandleDisconnect(ws_ptr, token); std::string disc_id_str = root.get("id", "").asString();
uint64_t disc_device_id = disc_id_str.empty() ? 0 : strtoull(disc_id_str.c_str(), nullptr, 10);
HandleDisconnect(ws_ptr, token, disc_device_id);
} else if (cmd == "ping") { } else if (cmd == "ping") {
HandlePing(ws_ptr, token); HandlePing(ws_ptr, token);
} else if (cmd == "mouse") { } else if (cmd == "mouse") {
@@ -572,7 +574,7 @@ void CWebService::HandleConnect(void* ws_ptr, const std::string& token, uint64_t
} }
} }
void CWebService::HandleDisconnect(void* ws_ptr, const std::string& token) { void CWebService::HandleDisconnect(void* ws_ptr, const std::string& token, uint64_t requested_device_id) {
std::string username, role; std::string username, role;
if (!ValidateToken(token, username, role)) { if (!ValidateToken(token, username, role)) {
SendText(ws_ptr, BuildJsonResponse("disconnect_result", false, "Invalid token")); SendText(ws_ptr, BuildJsonResponse("disconnect_result", false, "Invalid token"));
@@ -585,10 +587,14 @@ void CWebService::HandleDisconnect(void* ws_ptr, const std::string& token) {
std::lock_guard<std::mutex> lock(m_ClientsMutex); std::lock_guard<std::mutex> lock(m_ClientsMutex);
auto it = m_Clients.find(ws_ptr); auto it = m_Clients.find(ws_ptr);
if (it != m_Clients.end()) { if (it != m_Clients.end()) {
// Only disconnect if no specific device requested, or if it matches current device
// This prevents race condition when quickly switching devices
if (requested_device_id == 0 || it->second.watch_device_id == requested_device_id) {
device_id = it->second.watch_device_id; device_id = it->second.watch_device_id;
it->second.watch_device_id = 0; it->second.watch_device_id = 0;
} }
} }
}
// Close remote desktop if this was the last viewer // Close remote desktop if this was the last viewer
if (device_id > 0) { if (device_id > 0) {

View File

@@ -113,7 +113,7 @@ private:
void HandleLogin(void* ws_ptr, const std::string& msg, const std::string& client_ip); void HandleLogin(void* ws_ptr, const std::string& msg, const std::string& client_ip);
void HandleGetDevices(void* ws_ptr, const std::string& token); void HandleGetDevices(void* ws_ptr, const std::string& token);
void HandleConnect(void* ws_ptr, const std::string& token, uint64_t device_id); void HandleConnect(void* ws_ptr, const std::string& token, uint64_t device_id);
void HandleDisconnect(void* ws_ptr, const std::string& token); void HandleDisconnect(void* ws_ptr, const std::string& token, uint64_t requested_device_id = 0);
void HandlePing(void* ws_ptr, const std::string& token); void HandlePing(void* ws_ptr, const std::string& token);
void HandleMouse(void* ws_ptr, const std::string& msg); void HandleMouse(void* ws_ptr, const std::string& msg);
void HandleKey(void* ws_ptr, const std::string& msg); void HandleKey(void* ws_ptr, const std::string& msg);