using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Iciclecreek.Terminal; namespace ClaudeDo.Ui.Services; /// /// Thin wrapper around — the library owns the /// Porta.Pty spawn, keyboard input, rendering, resize, and focus end to end (see /// spikes/ConPtyTerminal/MainWindow.axaml.cs, which proved this renders correctly and /// stays responsive). We only: /// 1) apply onto the current process environment /// before launching — Porta.Pty inherits the calling process's environment and there is no /// per-launch env seam on / — /// 2) relay the control's own event and /// method. /// public sealed class PtyTerminalSession : IDisposable { private TerminalControl? _control; private bool _disposed; public bool IsRunning { get; private set; } public int? ExitCode { get; private set; } /// Raised when the child process exits, on the UI thread, with its exit code. public event EventHandler? ProcessExited; /// /// Applies 's env vars to the current process, then drives /// to launch it via . /// public async Task StartAsync(TerminalLaunchDescriptor descriptor, TerminalControl control, CancellationToken ct = default) { if (_control is not null) throw new InvalidOperationException("Session already started."); _control = control; control.ProcessExited += OnControlProcessExited; foreach (var (key, value) in descriptor.Env) Environment.SetEnvironmentVariable(key, value); control.Process = descriptor.Exe; control.Args = new List(descriptor.Args); control.StartingDirectory = descriptor.Cwd; await control.LaunchProcess(); IsRunning = true; } private void OnControlProcessExited(object? sender, ProcessExitedEventArgs e) { IsRunning = false; ExitCode = e.ExitCode; ProcessExited?.Invoke(this, e.ExitCode); } public void Kill() { try { _control?.Kill(); } catch (Exception) { /* already exited */ } } public void Dispose() { if (_disposed) return; _disposed = true; if (_control is not null) _control.ProcessExited -= OnControlProcessExited; } }