Improve web-side remote desktop control user experience
This commit is contained in:
@@ -640,8 +640,8 @@ make
|
|||||||
| **Telegram** | [@doge_grandfather](https://t.me/doge_grandfather) |
|
| **Telegram** | [@doge_grandfather](https://t.me/doge_grandfather) |
|
||||||
| **Email** | [yuanyuanxiang163@gmail.com](mailto:yuanyuanxiang163@gmail.com) |
|
| **Email** | [yuanyuanxiang163@gmail.com](mailto:yuanyuanxiang163@gmail.com) |
|
||||||
| **LinkedIn** | [wishyuanqi](https://www.linkedin.com/in/wishyuanqi) |
|
| **LinkedIn** | [wishyuanqi](https://www.linkedin.com/in/wishyuanqi) |
|
||||||
| **Issues** | [问题反馈](https://github.com/yuanyuanxiang/SimpleRemoter/issues) |
|
| **Issues** | [问题反馈](https://t.me/SimpleRemoter) |
|
||||||
| **PR** | [贡献代码](https://github.com/yuanyuanxiang/SimpleRemoter/pulls) |
|
| **PR** | [贡献代码](https://git.simpleremoter.com/) |
|
||||||
|
|
||||||
### 赞助支持
|
### 赞助支持
|
||||||
|
|
||||||
|
|||||||
@@ -625,8 +625,8 @@ For complete update history, see: [history.md](./history.md)
|
|||||||
| **Telegram** | [@doge_grandfather](https://t.me/doge_grandfather) |
|
| **Telegram** | [@doge_grandfather](https://t.me/doge_grandfather) |
|
||||||
| **Email** | [yuanyuanxiang163@gmail.com](mailto:yuanyuanxiang163@gmail.com) |
|
| **Email** | [yuanyuanxiang163@gmail.com](mailto:yuanyuanxiang163@gmail.com) |
|
||||||
| **LinkedIn** | [wishyuanqi](https://www.linkedin.com/in/wishyuanqi) |
|
| **LinkedIn** | [wishyuanqi](https://www.linkedin.com/in/wishyuanqi) |
|
||||||
| **Issues** | [Report Issues](https://github.com/yuanyuanxiang/SimpleRemoter/issues) |
|
| **Issues** | [Report Issues](https://t.me/SimpleRemoter) |
|
||||||
| **PR** | [Contribute](https://github.com/yuanyuanxiang/SimpleRemoter/pulls) |
|
| **PR** | [Contribute](https://git.simpleremoter.com/) |
|
||||||
|
|
||||||
### Sponsorship
|
### Sponsorship
|
||||||
|
|
||||||
|
|||||||
@@ -624,8 +624,8 @@ make
|
|||||||
| **Telegram** | [@doge_grandfather](https://t.me/doge_grandfather) |
|
| **Telegram** | [@doge_grandfather](https://t.me/doge_grandfather) |
|
||||||
| **Email** | [yuanyuanxiang163@gmail.com](mailto:yuanyuanxiang163@gmail.com) |
|
| **Email** | [yuanyuanxiang163@gmail.com](mailto:yuanyuanxiang163@gmail.com) |
|
||||||
| **LinkedIn** | [wishyuanqi](https://www.linkedin.com/in/wishyuanqi) |
|
| **LinkedIn** | [wishyuanqi](https://www.linkedin.com/in/wishyuanqi) |
|
||||||
| **Issues** | [問題回報](https://github.com/yuanyuanxiang/SimpleRemoter/issues) |
|
| **Issues** | [問題回報](https://t.me/SimpleRemoter) |
|
||||||
| **PR** | [貢獻程式碼](https://github.com/yuanyuanxiang/SimpleRemoter/pulls) |
|
| **PR** | [貢獻程式碼](https://git.simpleremoter.com/) |
|
||||||
|
|
||||||
### 贊助支持
|
### 贊助支持
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 18
|
# Visual Studio Version 18
|
||||||
VisualStudioVersion = 18.6.11709.129 insiders
|
VisualStudioVersion = 18.6.11709.129
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Yama", "server\2015Remote\2015Remote_vs2015.vcxproj", "{D58E96CD-C41F-4DD1-9502-EF1CB7AC65E5}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Yama", "server\2015Remote\2015Remote_vs2015.vcxproj", "{D58E96CD-C41F-4DD1-9502-EF1CB7AC65E5}"
|
||||||
EndProject
|
EndProject
|
||||||
@@ -11,12 +11,13 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestRun", "client\TestRun_v
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ServerDll", "client\ClientDll_vs2015.vcxproj", "{BEBAF888-532D-40D3-A8DD-DDAAF69F49AA}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ServerDll", "client\ClientDll_vs2015.vcxproj", "{BEBAF888-532D-40D3-A8DD-DDAAF69F49AA}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{70702583-26EE-47E0-9847-4D58F9449F4C}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Architectures", "Architectures", "{70702583-26EE-47E0-9847-4D58F9449F4C}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
Dependencies.md = Dependencies.md
|
Dependencies.md = Dependencies.md
|
||||||
history.md = history.md
|
docs\MultiLayerLicense.md = docs\MultiLayerLicense.md
|
||||||
ReadMe.md = ReadMe.md
|
ReadMe.md = ReadMe.md
|
||||||
ReadMe_EN.md = ReadMe_EN.md
|
ReadMe_EN.md = ReadMe_EN.md
|
||||||
|
ReadMe_TW.md = ReadMe_TW.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TinyRun", "client\TinyRun.vcxproj", "{E3F3A477-05BA-431D-B002-28EF8BFA6E86}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TinyRun", "client\TinyRun.vcxproj", "{E3F3A477-05BA-431D-B002-28EF8BFA6E86}"
|
||||||
@@ -268,10 +268,10 @@
|
|||||||
#define BRAND_URL_FEEDBACK "https://t.me/SimpleRemoter"
|
#define BRAND_URL_FEEDBACK "https://t.me/SimpleRemoter"
|
||||||
|
|
||||||
// 帮助文档链接(帮助菜单 → 什么是这个)
|
// 帮助文档链接(帮助菜单 → 什么是这个)
|
||||||
#define BRAND_URL_WIKI "https://github.com/yuanyuanxiang/SimpleRemoter/wiki"
|
#define BRAND_URL_WIKI "https://git.simpleremoter.com/"
|
||||||
|
|
||||||
// 请求授权链接(工具菜单 → 请求授权)
|
// 请求授权链接(工具菜单 → 请求授权)
|
||||||
#define BRAND_URL_REQUEST_AUTH "https://github.com/yuanyuanxiang/SimpleRemoter/wiki#请求授权"
|
#define BRAND_URL_REQUEST_AUTH "https://simpleremoter.com/"
|
||||||
|
|
||||||
// 获取插件
|
// 获取插件
|
||||||
#define BRAND_URL_GET_PLUGIN "This feature has not been implemented!\nPlease contact: 962914132@qq.com"
|
#define BRAND_URL_GET_PLUGIN "This feature has not been implemented!\nPlease contact: 962914132@qq.com"
|
||||||
|
|||||||
@@ -649,6 +649,7 @@ inline std::string GetWebPageHTML() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
<span id="screen-status" class="screen-status connecting">Connecting...</span>
|
<span id="screen-status" class="screen-status connecting">Connecting...</span>
|
||||||
|
<button class="toolbar-btn-bar" id="btn-rdp-reset-bar" onclick="sendRdpReset()" title="RDP Reset">↻</button>
|
||||||
<button class="toolbar-btn-bar" id="btn-mouse-bar" onclick="toggleControl()" title="Mouse Control">🖱</button>
|
<button class="toolbar-btn-bar" id="btn-mouse-bar" onclick="toggleControl()" title="Mouse Control">🖱</button>
|
||||||
<button class="toolbar-btn-bar" id="btn-keyboard-bar" onclick="toggleKeyboard()" title="Keyboard" disabled>⌨</button>
|
<button class="toolbar-btn-bar" id="btn-keyboard-bar" onclick="toggleKeyboard()" title="Keyboard" disabled>⌨</button>
|
||||||
<button class="fullscreen-btn" onclick="toggleFullscreen()" title="Fullscreen (F11)">⛶</button>
|
<button class="fullscreen-btn" onclick="toggleFullscreen()" title="Fullscreen (F11)">⛶</button>
|
||||||
@@ -747,14 +748,35 @@ inline std::string GetWebPageHTML() {
|
|||||||
return viewMode === 'grid' ? 12 : 50;
|
return viewMode === 'grid' ? 12 : 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Page visibility state for background detection (iOS PWA)
|
||||||
|
let isPageVisible = !document.hidden;
|
||||||
|
let reconnectTimer = null;
|
||||||
|
|
||||||
|
function scheduleReconnect() {
|
||||||
|
if (reconnectTimer) return; // Already scheduled
|
||||||
|
if (!isPageVisible) return; // Don't reconnect in background
|
||||||
|
reconnectTimer = setTimeout(() => {
|
||||||
|
reconnectTimer = null;
|
||||||
|
if (isPageVisible) connectWebSocket();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelReconnect() {
|
||||||
|
if (reconnectTimer) {
|
||||||
|
clearTimeout(reconnectTimer);
|
||||||
|
reconnectTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function connectWebSocket() {
|
function connectWebSocket() {
|
||||||
|
if (!isPageVisible) return; // Don't connect in background
|
||||||
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
updateWsStatus('connecting');
|
updateWsStatus('connecting');
|
||||||
try {
|
try {
|
||||||
ws = new WebSocket(protocol + '//' + location.host + '/ws');
|
ws = new WebSocket(protocol + '//' + location.host + '/ws');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
updateWsStatus('disconnected');
|
updateWsStatus('disconnected');
|
||||||
setTimeout(connectWebSocket, 3000);
|
scheduleReconnect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ws.binaryType = 'arraybuffer';
|
ws.binaryType = 'arraybuffer';
|
||||||
@@ -767,7 +789,7 @@ inline std::string GetWebPageHTML() {
|
|||||||
getDevices();
|
getDevices();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ws.onclose = () => { stopPingInterval(); updateWsStatus('disconnected'); setTimeout(connectWebSocket, 3000); };
|
ws.onclose = () => { stopPingInterval(); updateWsStatus('disconnected'); scheduleReconnect(); };
|
||||||
ws.onerror = (e) => console.error('WS error:', e);
|
ws.onerror = (e) => console.error('WS error:', e);
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
if (typeof event.data === 'string') handleSignaling(JSON.parse(event.data));
|
if (typeof event.data === 'string') handleSignaling(JSON.parse(event.data));
|
||||||
@@ -1631,6 +1653,10 @@ inline std::string GetWebPageHTML() {
|
|||||||
showZoomIndicator();
|
showZoomIndicator();
|
||||||
}
|
}
|
||||||
applyZoomTransform();
|
applyZoomTransform();
|
||||||
|
// Update cursor overlay to follow canvas transform
|
||||||
|
if (cursorState.initialized) {
|
||||||
|
updateCursorOverlay(cursorState.x, cursorState.y);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1715,10 +1741,14 @@ inline std::string GetWebPageHTML() {
|
|||||||
if (zoomState.isPinching) {
|
if (zoomState.isPinching) {
|
||||||
if (e.touches.length < 2) {
|
if (e.touches.length < 2) {
|
||||||
zoomState.isPinching = false;
|
zoomState.isPinching = false;
|
||||||
// Update pan center for smooth transition to pan mode
|
// Update pan center and lastX/lastY for smooth transition
|
||||||
if (e.touches.length === 1) {
|
if (e.touches.length === 1) {
|
||||||
zoomState.pinchCenterX = e.touches[0].clientX;
|
const touch = e.touches[0];
|
||||||
zoomState.pinchCenterY = e.touches[0].clientY;
|
zoomState.pinchCenterX = touch.clientX;
|
||||||
|
zoomState.pinchCenterY = touch.clientY;
|
||||||
|
// Reset lastX/lastY to prevent delta jump on next move
|
||||||
|
touchState.lastX = touch.clientX;
|
||||||
|
touchState.lastY = touch.clientY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
touchState.touchCount = e.touches.length;
|
touchState.touchCount = e.touches.length;
|
||||||
@@ -1930,6 +1960,27 @@ inline std::string GetWebPageHTML() {
|
|||||||
if (e.key === 'Enter' && document.querySelector('.page.active').id === 'login-page') login();
|
if (e.key === 'Enter' && document.querySelector('.page.active').id === 'login-page') login();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle page visibility change (iOS PWA background/foreground)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
updateWsStatus('disconnected');
|
||||||
|
} else {
|
||||||
|
// Page coming to foreground - reconnect
|
||||||
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
||||||
|
connectWebSocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
const compat = checkWebCodecs();
|
const compat = checkWebCodecs();
|
||||||
if (!compat.supported) {
|
if (!compat.supported) {
|
||||||
|
|||||||
Reference in New Issue
Block a user