RDP performance overhaul:
- Switched from polling (rAF loop calling rdp_get_frame every tick)
to event-driven rendering (backend emits rdp:frame:{id} when
frame buffer updates, frontend fetches on demand)
- Eliminates thousands of empty IPC round-trips per second when
the screen is idle
- Backend passes AppHandle into run_active_session for event emission
- Frontend uses listen() instead of requestAnimationFrame polling
MCP terminal fix:
- terminal_type and terminal_execute now send \r (carriage return)
instead of \n (newline) — PTY terminals expect CR to submit
- Fixes commands not auto-sending, requiring manual Enter press
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Clipboard paste (rdp_send_clipboard): simulates typing each character
via scancode key press/release events. Full ASCII coverage including
all symbols, numbers, and shifted characters. Handles 32-char
generated passwords without manual typing.
2. Keyboard grab defaults to ON so RDP sessions accept keyboard input
immediately without requiring the user to click the toolbar toggle.
3. Frame dirty flag: GraphicsUpdate sets an AtomicBool, get_frame only
encodes + returns base64 when dirty (returns empty string otherwise).
Eliminates ~8MB/frame base64 encoding on unchanged frames at 30fps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rust RDP service: ironrdp client with full connection handshake
(TCP -> TLS -> CredSSP -> NLA), pixel buffer frame delivery,
mouse/keyboard input via scancode mapping, graceful disconnect.
Runs in dedicated thread with own tokio runtime to avoid Send
lifetime issues with ironrdp trait objects.
Vue frontend: RdpView canvas renderer with 30fps polling,
mouse/keyboard capture, RdpToolbar with Ctrl+Alt+Del and
clipboard. SessionContainer handles both SSH and RDP tabs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>