From 786ca72a4cac269e52edeef3b6889cfc4db6c202 Mon Sep 17 00:00:00 2001 From: Ionite Date: Fri, 14 Jul 2023 21:00:08 -0400 Subject: [PATCH] Add input sending for launch page console --- .../Assets/sitecustomize.py | 49 +++++++++++++ .../ViewModels/LaunchPageViewModel.cs | 70 ++++++++++++++++++- .../Views/LaunchPageView.axaml | 62 +++++++++++++++- .../Models/Packages/BaseGitPackage.cs | 26 ++++++- StabilityMatrix.Core/Processes/AnsiProcess.cs | 6 ++ StabilityMatrix.Core/Processes/ApcMessage.cs | 12 ++++ StabilityMatrix.Core/Processes/ApcParser.cs | 55 +++++++++++++++ StabilityMatrix.Core/Processes/ApcType.cs | 11 +++ .../Processes/AsyncStreamReader.cs | 69 +++++++++++++++--- .../Processes/ProcessOutput.cs | 27 +++++-- 10 files changed, 369 insertions(+), 18 deletions(-) create mode 100644 StabilityMatrix.Avalonia/Assets/sitecustomize.py create mode 100644 StabilityMatrix.Core/Processes/ApcMessage.cs create mode 100644 StabilityMatrix.Core/Processes/ApcParser.cs create mode 100644 StabilityMatrix.Core/Processes/ApcType.cs diff --git a/StabilityMatrix.Avalonia/Assets/sitecustomize.py b/StabilityMatrix.Avalonia/Assets/sitecustomize.py new file mode 100644 index 00000000..c51abc9d --- /dev/null +++ b/StabilityMatrix.Avalonia/Assets/sitecustomize.py @@ -0,0 +1,49 @@ +""" +Startup site customization for Stability Matrix. + +Currently this installs an audit hook to notify the parent process when input() is called, +so we can prompt the user to enter something. +""" + +import sys + +# Application Program Command escape sequence +# This wraps messages sent to the parent process. +esc_apc = "\x9F" +esc_prefix = "[SM;" +esc_st = "\x9C" + + +def send_apc(msg: str): + """Send an Application Program Command to the parent process.""" + sys.stdout.flush() + sys.stdout.write(esc_apc + esc_prefix + msg + esc_st) + sys.stdout.flush() + + +def send_apc_input(prompt: str): + """Apc message for input() prompt.""" + send_apc('{"type":"input","data":"' + str(prompt) + '"}') + + +def audit(event: str, *args): + """Main audit hook function.""" + # https://docs.python.org/3/library/functions.html#input + # input() raises audit event `builtins.input` with args (prompt: str) *before* reading from stdin. + # `builtins.input/result` raised after reading from stdin. + + if event == "builtins.input": + try: + prompts = args[0] if args else () + prompt = "".join(prompts) + send_apc_input(prompt) + except Exception: + pass + + +# Reconfigure stdout to UTF-8 +# noinspection PyUnresolvedReferences +sys.stdout.reconfigure(encoding="utf-8") + +# Install the audit hook +sys.addaudithook(audit) diff --git a/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs index 19b8aa83..428d380b 100644 --- a/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs @@ -28,6 +28,7 @@ using StabilityMatrix.Core.Models.Packages; using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Python; using StabilityMatrix.Core.Services; +using ThreadState = System.Diagnostics.ThreadState; namespace StabilityMatrix.Avalonia.ViewModels; @@ -65,6 +66,10 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable // private bool clearingPackages; private string webUiUrl = string.Empty; + + // Input info-bars + [ObservableProperty] private bool showManualInputPrompt; + [ObservableProperty] private bool showConfirmInputPrompt; public LaunchPageViewModel(ILogger logger, ISettingsManager settingsManager, IPackageFactory packageFactory, IPyRunner pyRunner, INotificationService notificationService, ServiceManager dialogFactory) @@ -248,12 +253,27 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable private async Task BeginUpdateConsole(CancellationToken ct) { + // This should be run in the UI thread + Dispatcher.UIThread.CheckAccess(); try { while (true) { ct.ThrowIfCancellationRequested(); var output = await consoleUpdateBuffer.ReceiveAsync(ct); + // Check for Apc messages + if (output.ApcMessage is not null) + { + // Handle Apc message, for now just input audit events + var message = output.ApcMessage.Value; + if (message.Type == ApcType.Input) + { + ShowConfirmInputPrompt = true; + } + // Ignore further processing + continue; + } + using var update = ConsoleDocument.RunUpdate(); // Handle remove if (output.ClearLines > 0) @@ -274,6 +294,54 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable } } + // Send user input to running package + public async Task SendInput(string input) + { + if (RunningPackage is BaseGitPackage package) + { + var venv = package.VenvRunner; + var process = venv?.Process; + if (process is not null) + { + await process.StandardInput.WriteLineAsync(input); + } + else + { + logger.LogWarning("Attempted to write input but Process is null"); + } + } + } + + [RelayCommand] + private async Task SendConfirmInput(bool value) + { + // This must be on the UI thread + Dispatcher.UIThread.CheckAccess(); + // Also send input to our own console + if (value) + { + consoleUpdateBuffer.Post(new ProcessOutput { Text = "y\n" }); + await SendInput("y\n"); + } + else + { + consoleUpdateBuffer.Post(new ProcessOutput { Text = "n\n" }); + await SendInput("n\n"); + } + + ShowConfirmInputPrompt = false; + } + + // Handle user input requests + public async Task HandleApcMessage(ApcMessage message) + { + // Handle inputs by prompting + if (message.Type == ApcType.Input) + { + ShowConfirmInputPrompt = true; + } + } + public async Task Stop() { if (RunningPackage is null) return; @@ -335,7 +403,7 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable consoleUpdateBuffer.Post(output); EventManager.Instance.OnScrollToBottomRequested(); } - + private void OnOneClickInstallFinished(object? sender, bool e) { OnLoaded(); diff --git a/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml b/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml index 6b882f10..774e38fa 100644 --- a/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml +++ b/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml @@ -10,15 +10,21 @@ xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core" xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" + xmlns:system="clr-namespace:System;assembly=System.Runtime" d:DataContext="{x:Static mocks:DesignData.LaunchPageViewModel}" d:DesignHeight="450" d:DesignWidth="700" x:CompileBindings="True" x:DataType="vm:LaunchPageViewModel" mc:Ignorable="d"> + + + True + False + -