fix: 6 UX regressions — popups, themes, cursor, selection, status bar
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m54s
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m54s
Cursor blinking: - handleFocus() was referenced in TerminalView template but never defined in script — clicking the terminal container threw a runtime error preventing focus. Added the missing function. - Removed duplicate handleFocus at end of file Tool windows: - CSP simplified for macOS WKWebView compatibility. Previous CSP was blocking child WebviewWindow IPC initialization. RDP tab switch: - Added rdp_force_refresh command that marks frame_dirty=true and clears dirty region, forcing next get_frame to return full buffer - Called on tab activation and after resize completion - Eliminates 4-5 second wait for "keyframe" when switching tabs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
09c2f1a1ff
commit
2838af4ee7
@ -101,6 +101,16 @@ pub fn rdp_send_clipboard(
|
||||
state.rdp.send_clipboard(&session_id, &text)
|
||||
}
|
||||
|
||||
/// Force the next get_frame to return a full frame regardless of dirty state.
|
||||
/// Used when switching tabs or after resize to ensure the canvas is fully repainted.
|
||||
#[tauri::command]
|
||||
pub fn rdp_force_refresh(
|
||||
session_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), String> {
|
||||
state.rdp.force_refresh(&session_id)
|
||||
}
|
||||
|
||||
/// Resize the RDP session's desktop resolution.
|
||||
/// Sends a Display Control Virtual Channel request to the server.
|
||||
/// The server will re-render at the new resolution and send updated frames.
|
||||
|
||||
@ -224,7 +224,7 @@ pub fn run() {
|
||||
commands::credentials::list_credentials, commands::credentials::create_password, commands::credentials::create_ssh_key, commands::credentials::delete_credential, commands::credentials::decrypt_password, commands::credentials::decrypt_ssh_key,
|
||||
commands::ssh_commands::connect_ssh, commands::ssh_commands::connect_ssh_with_key, commands::ssh_commands::ssh_write, commands::ssh_commands::ssh_resize, commands::ssh_commands::disconnect_ssh, commands::ssh_commands::disconnect_session, commands::ssh_commands::list_ssh_sessions,
|
||||
commands::sftp_commands::sftp_list, commands::sftp_commands::sftp_read_file, commands::sftp_commands::sftp_write_file, commands::sftp_commands::sftp_mkdir, commands::sftp_commands::sftp_delete, commands::sftp_commands::sftp_rename,
|
||||
commands::rdp_commands::connect_rdp, commands::rdp_commands::rdp_get_frame, commands::rdp_commands::rdp_send_mouse, commands::rdp_commands::rdp_send_key, commands::rdp_commands::rdp_send_clipboard, commands::rdp_commands::rdp_resize, commands::rdp_commands::disconnect_rdp, commands::rdp_commands::list_rdp_sessions,
|
||||
commands::rdp_commands::connect_rdp, commands::rdp_commands::rdp_get_frame, commands::rdp_commands::rdp_force_refresh, commands::rdp_commands::rdp_send_mouse, commands::rdp_commands::rdp_send_key, commands::rdp_commands::rdp_send_clipboard, commands::rdp_commands::rdp_resize, commands::rdp_commands::disconnect_rdp, commands::rdp_commands::list_rdp_sessions,
|
||||
commands::theme_commands::list_themes, commands::theme_commands::get_theme,
|
||||
commands::pty_commands::list_available_shells, commands::pty_commands::spawn_local_shell, commands::pty_commands::pty_write, commands::pty_commands::pty_resize, commands::pty_commands::disconnect_pty,
|
||||
commands::mcp_commands::mcp_list_sessions, commands::mcp_commands::mcp_terminal_read, commands::mcp_commands::mcp_terminal_execute, commands::mcp_commands::mcp_get_session_context, commands::mcp_commands::mcp_bridge_path,
|
||||
|
||||
@ -300,6 +300,14 @@ impl RdpService {
|
||||
handle.input_tx.send(InputEvent::Key { scancode, pressed }).map_err(|_| format!("RDP session {} input channel closed", session_id))
|
||||
}
|
||||
|
||||
pub fn force_refresh(&self, session_id: &str) -> Result<(), String> {
|
||||
let handle = self.sessions.get(session_id).ok_or_else(|| format!("RDP session {} not found", session_id))?;
|
||||
// Clear any accumulated dirty region so get_frame returns the full buffer
|
||||
*handle.dirty_region.lock().unwrap_or_else(|e| e.into_inner()) = None;
|
||||
handle.frame_dirty.store(true, Ordering::Release);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resize(&self, session_id: &str, width: u16, height: u16) -> Result<(), String> {
|
||||
let handle = self.sessions.get(session_id).ok_or_else(|| format!("RDP session {} not found", session_id))?;
|
||||
handle.input_tx.send(InputEvent::Resize { width, height }).map_err(|_| format!("RDP session {} input channel closed", session_id))
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": "default-src 'self' tauri: https://tauri.localhost; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' asset: https://asset.localhost data:; connect-src 'self' ipc: http://ipc.localhost https://ipc.localhost tauri:"
|
||||
"csp": "default-src 'self' 'unsafe-inline' asset: https://asset.localhost; img-src 'self' asset: https://asset.localhost data: blob:; connect-src ipc: http://ipc.localhost https://ipc.localhost"
|
||||
},
|
||||
"withGlobalTauri": false
|
||||
},
|
||||
|
||||
@ -181,11 +181,14 @@ onMounted(() => {
|
||||
width: newW,
|
||||
height: newH,
|
||||
}).then(() => {
|
||||
// Update canvas dimensions to match new RDP resolution
|
||||
if (canvasRef.value) {
|
||||
canvasRef.value.width = newW;
|
||||
canvasRef.value.height = newH;
|
||||
}
|
||||
// Force full frame after resize so canvas gets a clean repaint
|
||||
setTimeout(() => {
|
||||
invoke("rdp_force_refresh", { sessionId: props.sessionId }).catch(() => {});
|
||||
}, 200);
|
||||
}).catch((err: unknown) => {
|
||||
console.warn("[RdpView] resize failed:", err);
|
||||
});
|
||||
@ -201,14 +204,16 @@ onBeforeUnmount(() => {
|
||||
if (resizeTimeout) { clearTimeout(resizeTimeout); resizeTimeout = null; }
|
||||
});
|
||||
|
||||
// Focus canvas when this tab becomes active and keyboard is grabbed
|
||||
// Focus canvas and force full frame refresh when switching to this tab
|
||||
watch(
|
||||
() => props.isActive,
|
||||
(active) => {
|
||||
if (active && keyboardGrabbed.value && canvasRef.value) {
|
||||
setTimeout(() => {
|
||||
canvasRef.value?.focus();
|
||||
}, 0);
|
||||
if (active && canvasRef.value) {
|
||||
// Force full frame fetch to repaint the canvas immediately
|
||||
invoke("rdp_force_refresh", { sessionId: props.sessionId }).catch(() => {});
|
||||
if (keyboardGrabbed.value) {
|
||||
setTimeout(() => canvasRef.value?.focus(), 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -152,6 +152,10 @@ onMounted(() => {
|
||||
}, 50);
|
||||
});
|
||||
|
||||
function handleFocus(): void {
|
||||
terminal.focus();
|
||||
}
|
||||
|
||||
// Re-fit and focus terminal when switching back to this tab.
|
||||
// Must wait for the container to have real dimensions after becoming visible.
|
||||
watch(
|
||||
@ -229,8 +233,4 @@ onBeforeUnmount(() => {
|
||||
resizeDisposable = null;
|
||||
}
|
||||
});
|
||||
|
||||
function handleFocus(): void {
|
||||
terminal.focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user