Let me ask you something: when you work on Windows and need to SSH into a remote server β what do you actually reach for?
PuTTY? Sure, it works. But it was designed in 1999. It shows.
Windows Terminal? Great for local shells. SFTP? You're on your own.
MobaXterm? Heavy, bloated, and the free tier constantly nags you.
I got tired of it. So I built TmarTerminal β a fast, dark, keyboard-first SSH terminal for Windows built with Tauri 2.x, React, xterm.js, and a Rust backend. And today I want to share why I built it, the tech decisions behind it, and what I learned along the way.
π github.com/toggiho/TmarTerminal β Stars and feedback are very welcome!
π€ The Problem with Existing SSH Clients on Windows
Before diving into the code, let me paint the picture.
Most SSH clients on Windows fall into one of two categories:
Old school (PuTTY, WinSCP): Battle-tested, but the UX is from another era. No tabs. No split panes. Configuring even basic things requires clicking through five nested dialog boxes.
Electron-based "modern" alternatives: They look nice, but the memory usage is absurd. A terminal that needs 400MB RAM to connect to a server feels wrong. Electron bundles an entire Chromium instance just to render a text box.
I wanted something in between: native-feeling performance + a modern UI + no electron overhead. That's where Tauri came in.
βοΈ The Stack: Why Tauri + Rust + React?
Tauri 2.x β The Game Changer
Tauri lets you build desktop apps with a web frontend (React/Vue/Svelte) but uses the system's native WebView instead of bundling Chromium. The backend is Rust.
The result? My installer is around 8β12 MB. Compare that to Electron apps that routinely ship 100MB+ bundles.
The Tauri IPC model is also clean: the frontend calls Rust functions via invoke(), and Rust handles all the heavy lifting β SSH connections, process spawning, file I/O.
// Frontend calls Rust like this
const result = await invoke("connect_ssh", {
host: "192.168.1.100",
port: 22,
username: "admin",
authMethod: "password"
});
Rust Backend β SSH That Doesn't Lie to You
The SSH backend is written in Rust using the ssh2 crate. Why Rust?
- Memory safety β SSH sessions deal with credentials and live connections. You don't want dangling pointers or race conditions here.
- Performance β Rust is fast enough that there's zero perceptible latency between keystrokes in the terminal.
- Tauri-native β The backend and the app framework speak the same language natively.
The Rust backend handles:
- SSH connections (password + private key auth)
- PTY allocation and streaming
- SFTP sessions (file listing, upload, download, rename, mkdir, delete)
- Local PowerShell process spawning
- Live round-trip ping to active SSH hosts
React + xterm.js β The Terminal Renderer
For the frontend, I used React with TypeScript. The actual terminal emulation is handled by xterm.js β the same library powering VS Code's integrated terminal. It handles ANSI escape codes, Unicode, colors, scrollback β all the hard stuff.
// Terminal pane setup
const term = new Terminal({
theme: currentTheme,
fontSize: settings.fontSize,
fontFamily: "JetBrains Mono, Cascadia Code, monospace",
cursorBlink: true,
});
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(terminalRef.current);
fitAddon.fit();
Styling is done with Tailwind CSS. The whole UI was designed to feel like a tool you actually want to stare at for 8 hours.
π Features That I'm Most Proud Of
1. Tabs + Split Panes
Each tab can be split into multiple panes β either SSH sessions or local PowerShell. Think tmux, but inside a native Windows app.
You can have your prod SSH session on the left and a local shell on the right. Or two different SSH hosts side by side.
2. Built-in SFTP
No separate app needed. The SFTP panel shows your local filesystem and the remote filesystem side by side. Drag-and-drop style upload/download with directory creation, rename, and delete.
This was one of the more complex Rust pieces β maintaining a separate SFTP channel on the same SSH connection without interfering with the interactive terminal session.
3. ~/.ssh/config Import
If you already have saved hosts in ~/.ssh/config, TmarTerminal can import them directly. No manual re-entry of 20 servers.
4. Live Ping in Status Bar
The status bar shows the current round-trip latency to your active SSH host in real time. A tiny detail, but surprisingly useful for debugging "is my connection acting weird or is the server just slow?"
5. Configurable Themes + Hotkeys
Pick your terminal color theme (dark variants, solarized-style, etc.), font size, and remap hotkeys to whatever you're used to.
ποΈ Architecture Deep Dive
Here's the high-level data flow:
[React UI] ββ [Tauri IPC] ββ [Rust Backend]
β
[ssh2 crate]
β
[Remote SSH Server]
[PTY stream / SFTP]
When you type in the terminal:
- xterm.js captures the keystroke
- React sends it to Rust via
invoke("write_to_pty", { data }) - Rust writes to the SSH channel
- The remote server processes it and sends back bytes
- Rust streams the output back to the frontend via Tauri events
- xterm.js renders the output
The whole round-trip for a keystroke is imperceptible.
π Lessons Learned (aka: what nearly broke me)
PTY sizing is its own adventure
When you resize the terminal window, you need to tell the remote server about it (via pty_resize). Getting this to work reliably β especially when panes are added/removed β required more iteration than I expected.
SSH + SFTP on the same connection
The ssh2 crate handles this, but managing the lifecycle of both a PTY channel and an SFTP subsystem channel simultaneously required careful state management on the Rust side. One session, two channels, many edge cases.
Windows process spawning for local PowerShell
Spawning a PTY-attached PowerShell process on Windows is... not like Unix. The portable-pty crate helped enormously here, handling the ConPTY (Windows Pseudo Console) API abstraction.
π¦ Installation
Three options β grab whichever fits your workflow:
| Package | Description |
|---|---|
TmarTerminal-portable.exe |
Drop-and-run, no install needed |
TmarTerminal_0.1.0_x64-setup.exe |
Standard Windows installer |
TmarTerminal_0.1.0_x64_en-US.msi |
MSI for enterprise/group policy |
Download from the Releases page.
π¨ Build It Yourself
# Clone it
git clone https://github.com/toggiho/TmarTerminal.git
cd TmarTerminal
# Install frontend deps
npm install
# Run in dev mode (hot reload!)
npm run tauri dev
# If Rust can't find MSVC toolchain on your machine:
.\dev.ps1
# Production build
npm run tauri build
Requirements: Node.js, Rust (stable), Windows with MSVC Build Tools.
πΊοΈ What's Next
TmarTerminal is at v0.1.0 β it works, but there's more planned:
- [ ] Jump hosts / SSH tunneling
- [ ] Port forwarding (local + remote)
- [ ] Session logging to file
- [ ] Snippet/command palette
- [ ] Connection groups / folders
- [ ] macOS + Linux support (Tauri supports it β just need to untangle the Windows-specific PTY code)
π Show Some Love
If this project is useful to you, or you just appreciate the tech stack, a β on GitHub genuinely helps more than you might think β it signals to others that this is worth checking out.
β Star TmarTerminal on GitHub
Issues, PRs, and feature requests are all welcome. If you're a Rust or Tauri developer who's done something similar, I'd especially love to hear your thoughts on the architecture.
Built with Tauri 2.x Β· React 18 Β· TypeScript Β· Rust Β· xterm.js Β· Tailwind CSS
License: GPL-3.0
Top comments (0)