<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: TogiHo</title>
    <description>The latest articles on DEV Community by TogiHo (@togiho).</description>
    <link>https://dev.to/togiho</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3961608%2F6ee73e0b-07e6-47fa-bc13-2cb5c8135386.png</url>
      <title>DEV Community: TogiHo</title>
      <link>https://dev.to/togiho</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/togiho"/>
    <language>en</language>
    <item>
      <title>🖥️ I Built a Modern SSH Terminal for Windows Using Rust + Tauri + React — Here's Why (and How)</title>
      <dc:creator>TogiHo</dc:creator>
      <pubDate>Sun, 31 May 2026 19:38:49 +0000</pubDate>
      <link>https://dev.to/togiho/i-built-a-modern-ssh-terminal-for-windows-using-rust-tauri-react-heres-why-and-how-1852</link>
      <guid>https://dev.to/togiho/i-built-a-modern-ssh-terminal-for-windows-using-rust-tauri-react-heres-why-and-how-1852</guid>
      <description>&lt;p&gt;Let me ask you something: when you work on Windows and need to SSH into a remote server — what do you actually reach for?&lt;/p&gt;

&lt;p&gt;PuTTY? Sure, it works. But it was designed in 1999. It shows.&lt;br&gt;
Windows Terminal? Great for local shells. SFTP? You're on your own.&lt;br&gt;
MobaXterm? Heavy, bloated, and the free tier constantly nags you.&lt;/p&gt;

&lt;p&gt;I got tired of it. So I built &lt;strong&gt;TmarTerminal&lt;/strong&gt; — 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.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/toggiho/TmarTerminal" rel="noopener noreferrer"&gt;github.com/toggiho/TmarTerminal&lt;/a&gt;&lt;/strong&gt; — Stars and feedback are very welcome!&lt;/p&gt;


&lt;h2&gt;
  
  
  🤔 The Problem with Existing SSH Clients on Windows
&lt;/h2&gt;

&lt;p&gt;Before diving into the code, let me paint the picture.&lt;/p&gt;

&lt;p&gt;Most SSH clients on Windows fall into one of two categories:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Old school (PuTTY, WinSCP):&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Electron-based "modern" alternatives:&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;I wanted something in between: &lt;strong&gt;native-feeling performance + a modern UI + no electron overhead&lt;/strong&gt;. That's where Tauri came in.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚙️ The Stack: Why Tauri + Rust + React?
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Tauri 2.x — The Game Changer
&lt;/h3&gt;

&lt;p&gt;Tauri lets you build desktop apps with a web frontend (React/Vue/Svelte) but uses the &lt;strong&gt;system's native WebView&lt;/strong&gt; instead of bundling Chromium. The backend is Rust.&lt;/p&gt;

&lt;p&gt;The result? My installer is around &lt;strong&gt;8–12 MB&lt;/strong&gt;. Compare that to Electron apps that routinely ship 100MB+ bundles.&lt;/p&gt;

&lt;p&gt;The Tauri IPC model is also clean: the frontend calls Rust functions via &lt;code&gt;invoke()&lt;/code&gt;, and Rust handles all the heavy lifting — SSH connections, process spawning, file I/O.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Frontend calls Rust like this&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connect_ssh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;192.168.1.100&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rust Backend — SSH That Doesn't Lie to You
&lt;/h3&gt;

&lt;p&gt;The SSH backend is written in Rust using the &lt;code&gt;ssh2&lt;/code&gt; crate. Why Rust?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Memory safety&lt;/strong&gt; — SSH sessions deal with credentials and live connections. You don't want dangling pointers or race conditions here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; — Rust is fast enough that there's zero perceptible latency between keystrokes in the terminal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tauri-native&lt;/strong&gt; — The backend and the app framework speak the same language natively.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Rust backend handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSH connections (password + private key auth)&lt;/li&gt;
&lt;li&gt;PTY allocation and streaming&lt;/li&gt;
&lt;li&gt;SFTP sessions (file listing, upload, download, rename, mkdir, delete)&lt;/li&gt;
&lt;li&gt;Local PowerShell process spawning&lt;/li&gt;
&lt;li&gt;Live round-trip ping to active SSH hosts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  React + xterm.js — The Terminal Renderer
&lt;/h3&gt;

&lt;p&gt;For the frontend, I used React with TypeScript. The actual terminal emulation is handled by &lt;strong&gt;xterm.js&lt;/strong&gt; — the same library powering VS Code's integrated terminal. It handles ANSI escape codes, Unicode, colors, scrollback — all the hard stuff.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Terminal pane setup&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;term&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Terminal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fontFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;JetBrains Mono, Cascadia Code, monospace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cursorBlink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fitAddon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FitAddon&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadAddon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fitAddon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;terminalRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;fitAddon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Features That I'm Most Proud Of
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Tabs + Split Panes
&lt;/h3&gt;

&lt;p&gt;Each tab can be split into multiple panes — either SSH sessions or local PowerShell. Think tmux, but inside a native Windows app.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Built-in SFTP
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. ~/.ssh/config Import
&lt;/h3&gt;

&lt;p&gt;If you already have saved hosts in &lt;code&gt;~/.ssh/config&lt;/code&gt;, TmarTerminal can import them directly. No manual re-entry of 20 servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Live Ping in Status Bar
&lt;/h3&gt;

&lt;p&gt;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?"&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Configurable Themes + Hotkeys
&lt;/h3&gt;

&lt;p&gt;Pick your terminal color theme (dark variants, solarized-style, etc.), font size, and remap hotkeys to whatever you're used to.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ Architecture Deep Dive
&lt;/h2&gt;

&lt;p&gt;Here's the high-level data flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[React UI] ←→ [Tauri IPC] ←→ [Rust Backend]
                                    ↓
                              [ssh2 crate]
                                    ↓
                         [Remote SSH Server]
                         [PTY stream / SFTP]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you type in the terminal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;xterm.js captures the keystroke&lt;/li&gt;
&lt;li&gt;React sends it to Rust via &lt;code&gt;invoke("write_to_pty", { data })&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Rust writes to the SSH channel&lt;/li&gt;
&lt;li&gt;The remote server processes it and sends back bytes&lt;/li&gt;
&lt;li&gt;Rust streams the output back to the frontend via Tauri events&lt;/li&gt;
&lt;li&gt;xterm.js renders the output&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole round-trip for a keystroke is imperceptible.&lt;/p&gt;




&lt;h2&gt;
  
  
  😅 Lessons Learned (aka: what nearly broke me)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  PTY sizing is its own adventure
&lt;/h3&gt;

&lt;p&gt;When you resize the terminal window, you need to tell the remote server about it (via &lt;code&gt;pty_resize&lt;/code&gt;). Getting this to work reliably — especially when panes are added/removed — required more iteration than I expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSH + SFTP on the same connection
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;ssh2&lt;/code&gt; 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Windows process spawning for local PowerShell
&lt;/h3&gt;

&lt;p&gt;Spawning a PTY-attached PowerShell process on Windows is... not like Unix. The &lt;code&gt;portable-pty&lt;/code&gt; crate helped enormously here, handling the ConPTY (Windows Pseudo Console) API abstraction.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Installation
&lt;/h2&gt;

&lt;p&gt;Three options — grab whichever fits your workflow:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TmarTerminal-portable.exe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Drop-and-run, no install needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TmarTerminal_0.1.0_x64-setup.exe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Standard Windows installer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TmarTerminal_0.1.0_x64_en-US.msi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MSI for enterprise/group policy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Download from the &lt;strong&gt;&lt;a href="https://github.com/toggiho/TmarTerminal/releases" rel="noopener noreferrer"&gt;Releases page&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔨 Build It Yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone it&lt;/span&gt;
git clone https://github.com/toggiho/TmarTerminal.git
&lt;span class="nb"&gt;cd &lt;/span&gt;TmarTerminal

&lt;span class="c"&gt;# Install frontend deps&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Run in dev mode (hot reload!)&lt;/span&gt;
npm run tauri dev

&lt;span class="c"&gt;# If Rust can't find MSVC toolchain on your machine:&lt;/span&gt;
.&lt;span class="se"&gt;\d&lt;/span&gt;ev.ps1

&lt;span class="c"&gt;# Production build&lt;/span&gt;
npm run tauri build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requirements: Node.js, Rust (stable), Windows with MSVC Build Tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  🗺️ What's Next
&lt;/h2&gt;

&lt;p&gt;TmarTerminal is at v0.1.0 — it works, but there's more planned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Jump hosts / SSH tunneling&lt;/li&gt;
&lt;li&gt;[ ] Port forwarding (local + remote)&lt;/li&gt;
&lt;li&gt;[ ] Session logging to file&lt;/li&gt;
&lt;li&gt;[ ] Snippet/command palette&lt;/li&gt;
&lt;li&gt;[ ] Connection groups / folders&lt;/li&gt;
&lt;li&gt;[ ] macOS + Linux support (Tauri supports it — just need to untangle the Windows-specific PTY code)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🌟 Show Some Love
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/toggiho/TmarTerminal" rel="noopener noreferrer"&gt;→ Star TmarTerminal on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Tauri 2.x · React 18 · TypeScript · Rust · xterm.js · Tailwind CSS&lt;/em&gt;&lt;br&gt;
&lt;em&gt;License: GPL-3.0&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>tauri</category>
      <category>terminal</category>
      <category>ssh</category>
    </item>
  </channel>
</rss>
