using System; using System.Text; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Threading; using LANCommander.SDK.PowerShell; using Microsoft.Extensions.Logging; namespace LANCommander.Launcher.Views.Components; public partial class ScriptConsoleControl : UserControl { public static readonly StyledProperty IsInputEnabledProperty = AvaloniaProperty.Register(nameof(IsInputEnabled), false); public bool IsInputEnabled { get => GetValue(IsInputEnabledProperty); set { SetValue(IsInputEnabledProperty, value); InputBorder.IsVisible = value; } } private readonly StringBuilder _outputBuffer = new(); private IScriptDebugContext? _debugContext; private TaskCompletionSource? _exitTcs; public ScriptConsoleControl() { InitializeComponent(); CommandInput.KeyDown += OnCommandInputKeyDown; ExecuteButton.Click += OnExecuteButtonClick; } /// /// Called when debug session starts /// public void OnDebugStart(IScriptDebugContext context) { _debugContext = context; _exitTcs = new TaskCompletionSource(); Dispatcher.UIThread.Post(() => { _outputBuffer.Clear(); OutputTextBlock.Text = string.Empty; IsInputEnabled = false; }); } /// /// Called when output is received from the script /// public void OnOutput(LogLevel level, string message) { Dispatcher.UIThread.Post(() => { _outputBuffer.AppendLine(message); OutputTextBlock.Text = _outputBuffer.ToString(); // Auto-scroll to bottom OutputScrollViewer.Offset = new Vector(0, OutputScrollViewer.Extent.Height); }); } /// /// Called when script execution completes and enters debug break mode /// public async Task OnDebugBreakAsync(IScriptDebugContext context) { _debugContext = context; _exitTcs = new TaskCompletionSource(); await Dispatcher.UIThread.InvokeAsync(() => { IsInputEnabled = true; CommandInput.Focus(); AppendOutput("\n--------- DEBUG MODE ---------"); AppendOutput("Script execution complete. You can now run commands."); AppendOutput("Type 'exit' to close this window.\n"); }); // Wait for user to type 'exit' await _exitTcs.Task; } /// /// Called when debug session ends /// public void OnDebugEnd(IScriptDebugContext context) { Dispatcher.UIThread.Post(() => { IsInputEnabled = false; AppendOutput("\n--------- SESSION ENDED ---------"); }); } private void AppendOutput(string text) { _outputBuffer.AppendLine(text); OutputTextBlock.Text = _outputBuffer.ToString(); OutputScrollViewer.Offset = new Vector(0, OutputScrollViewer.Extent.Height); } private async void OnCommandInputKeyDown(object? sender, KeyEventArgs e) { if (e.Key == Key.Enter) { await ExecuteCommandAsync(); e.Handled = true; } else if (e.Key == Key.Escape) { CommandInput.Text = string.Empty; e.Handled = true; } } private async void OnExecuteButtonClick(object? sender, global::Avalonia.Interactivity.RoutedEventArgs e) { await ExecuteCommandAsync(); } private async Task ExecuteCommandAsync() { var command = CommandInput.Text?.Trim(); if (string.IsNullOrEmpty(command)) return; CommandInput.Text = string.Empty; // Echo the command AppendOutput($"PS> {command}"); if (command.Equals("exit", StringComparison.OrdinalIgnoreCase)) { _exitTcs?.TrySetResult(true); return; } if (_debugContext == null) { AppendOutput("Error: No active debug context"); return; } try { // If command starts with $, wrap it in Write-Host to display the value if (command.StartsWith('$')) command = "Write-Host " + command; await _debugContext.ExecuteAsync(command); } catch (Exception ex) { AppendOutput($"Error: {ex.Message}"); } } /// /// Clears the console output /// public void Clear() { Dispatcher.UIThread.Post(() => { _outputBuffer.Clear(); OutputTextBlock.Text = string.Empty; }); } }