using FluentTerminal.Models; using FluentTerminal.Models.Enums; using FluentTerminal.Models.Responses; using System; using System.Text; using System.Threading.Tasks; namespace FluentTerminal.App.Services { public class Terminal { private readonly ITrayProcessCommunicationService _trayProcessCommunicationService; private Func> _selectedTextCallback; private bool _closingFromUi; private bool _exited; private readonly bool _requireShellProcessStart; private string _fallbackTitle; public Terminal(ITrayProcessCommunicationService trayProcessCommunicationService, byte? terminalId = null) { _trayProcessCommunicationService = trayProcessCommunicationService; _trayProcessCommunicationService.TerminalExited += OnTerminalExited; Id = terminalId ?? trayProcessCommunicationService.GetNextTerminalId(); _requireShellProcessStart = !terminalId.HasValue; } private void OnTerminalExited(object sender, TerminalExitStatus status) { if (status.TerminalId != Id) { return; } _exited = true; Exited?.Invoke(this, status.ExitCode); if (_closingFromUi || status.ExitCode <= 0) { Closed?.Invoke(this, System.EventArgs.Empty); } _trayProcessCommunicationService.TerminalExited -= OnTerminalExited; } public event EventHandler Exited; /// /// To be observed by both view and viewmodel /// public event EventHandler Closed; /// /// to be observed by viewmodel /// public event EventHandler KeyboardCommandReceived; /// /// to be observed by view /// public event EventHandler OutputReceived; /// /// to be observed by viewmodel /// public event EventHandler SizeChanged; /// /// to be observed by viewmodel /// public event EventHandler TitleChanged; public byte Id { get; } /// /// To be called by either view or viewmodel /// public Task CloseAsync() { if (_exited) { Closed?.Invoke(this, System.EventArgs.Empty); return Task.CompletedTask; } _closingFromUi = true; _trayProcessCommunicationService.UnsubscribeFromTerminalOutput(Id); return _trayProcessCommunicationService.CloseTerminalAsync(Id); } /// /// to be called by viewmodel /// /// public Task GetSelectedText() { return _selectedTextCallback?.Invoke(); } /// /// to be called by view /// public void ProcessKeyboardCommand(string command) { KeyboardCommandReceived?.Invoke(this, command); } /// /// To be set by view /// public void RegisterSelectedTextCallback(Func> selectedTextCallback) { _selectedTextCallback = selectedTextCallback; } /// /// To be called by view /// public async Task SetSizeAsync(TerminalSize size) { await _trayProcessCommunicationService.ResizeTerminalAsync(Id, size).ConfigureAwait(false); SizeChanged?.Invoke(this, size); } /// /// to be called by view /// public void SetTitle(string title) { if (string.IsNullOrWhiteSpace(title)) { title = _fallbackTitle; } TitleChanged?.Invoke(this, title); } /// /// To be called by view when ready /// public async Task StartShellProcessAsync(ShellProfile shellProfile, TerminalSize size, SessionType sessionType, string termState) { if (!_requireShellProcessStart && !string.IsNullOrEmpty(termState)) { OutputReceived?.Invoke(this, Encoding.UTF8.GetBytes(termState)); } _trayProcessCommunicationService.SubscribeForTerminalOutput(Id, t => OutputReceived?.Invoke(this, t)); if (_requireShellProcessStart) { var response = await _trayProcessCommunicationService .CreateTerminalAsync(Id, size, shellProfile, sessionType).ConfigureAwait(false); if (response.Success) { _fallbackTitle = response.Name; SetTitle(_fallbackTitle); } return response; } else { return await _trayProcessCommunicationService.PauseTerminalOutputAsync(Id, false); } } public Task Write(byte[] data) { return _trayProcessCommunicationService.WriteAsync(Id, data); } /// /// Tells the ViewModel that this view failed to launch and can be closed. /// public void ReportLauchFailed() { Closed?.Invoke(this, System.EventArgs.Empty); } } }