Files

3.5 KiB

Architecture

Sources/RemminaMac/
  RemminaMacApp.swift            # @main, WindowGroup, loads ProfileStore
  Models/ConnectionProfile.swift # ProtocolType + ConnectionProfile + StoredSSHKey (Codable)
  Models/ProfileValidation.swift # input checks + trimming (pure, tested)
  Stores/ProfileStore.swift      # ObservableObject, JSON persistence, filter/search/groups
  Security/KeychainHelper.swift  # SecItemAdd/Copy/Delete wrapper
  Services/ConnectionServices.swift # SSHService (ssh args, secrets, sshpass, agent) + RDPService
  Services/PTYSession.swift      # real PTY session (posix_openpt), stderr pipe, TerminalManager
  Services/AnsiParser.swift      # ANSI/SGR parser for diagnostics buffer (pure, tested)
  Services/TCPCheck.swift        # TCP pre-check via Network.framework
  Services/HostKeyService.swift  # keyscan/fingerprint/accept/revoke via system ssh tools
  Services/TerminalTheme.swift   # Dracula palette (SwiftUI + NSColor + SwiftTerm)
  Views/ContentView.swift        # NavigationSplitView: sidebar + tabs + status bar
  Views/SessionView.swift        # per-session detail
  Views/SwiftTermView.swift      # full terminal emulator view (SwiftTerm, Dracula)
  Views/ProfileEditorView.swift  # new/edit form + Keychain wiring + validation
  Views/SSHKeysView.swift        # key list, import, ssh-keygen
  Views/HostKeySheet.swift       # fingerprint popup (accept/decline, MITM warning)
  Views/AboutView.swift          # about sheet (contributor, repo link)
Sources/ctty-run/
  ctty-run.c                     # gives the ssh child a controlling terminal
                                 # (double-fork: setsid + slave open + exec)

Data flow

  1. RemminaMacApp creates ProfileStore as @StateObject, injects as environmentObject.
  2. ProfileStore.load() reads profiles.json + sshkeys.json, else sample data.
  3. ContentView filters via searchText/selectedGroup; selection drives detail.
  4. connect(_:) stamps lastConnected, opens a tab. The terminal view runs: TCP pre-check → host-key status (popup if unknown/changed) → PTYSession.start().
  5. PTYSession spawns via ctty-run (controlling terminal): stdin/stdout on the pty slave, stderr on a loss-free pipe. Output feeds SwiftTerm (onData) and the diagnostics buffer (lines, used by the error banner + report).
  6. Keyboard flows from SwiftTerm's delegate straight into the pty master (sendBytes); window resizes propagate via TIOCSWINSZ (setWinsize).
  7. Secrets only via KeychainHelper; JSON holds account references. TerminalManager caches one session per profile and reconfigures on any config change (fingerprint incl. secret + sshpass presence).

Key files

  • Stores/ProfileStore.swift — paths + load()/save()
  • Models/ConnectionProfile.swift — protocol defaults (ports, icons)
  • Services/PTYSession.swift — spawn, lifecycle, TerminalManager
  • Services/HostKeyService.swift — fingerprint flow
  • Views/SwiftTermView.swift — emulator wiring + Dracula theme
  • Views/ContentView.swift — Remmina layout

Verification strategy

Logic is verified with throwaway swiftc harnesses in /tmp (never committed): ANSI parser, PTY roundtrips, real ssh runs, Keychain roundtrips, TCP checks, host-key flow against a local paramiko test server, SwiftTerm core headless (cursor addressing, alt-screen, clear), view-layer stress. See docs/ERRORS.md.

Next steps

  1. Embedded RDP (FreeRDP bridge) instead of Process.
  2. Integrated SFTP browser.
  3. Export/Import buttons + drag & drop for keys.
  4. Tray / favorites / folder badges like Remmina.