diff --git a/EasyWindowsTerminalControl/EasyTerminalControl.cs b/EasyWindowsTerminalControl/EasyTerminalControl.cs index 559336c..15d80d2 100644 --- a/EasyWindowsTerminalControl/EasyTerminalControl.cs +++ b/EasyWindowsTerminalControl/EasyTerminalControl.cs @@ -133,6 +133,11 @@ public string StartupCommandLine { set => SetValue(StartupCommandLineProperty, value); } + public string WorkingDirectory { + get => (string)GetValue(WorkingDirectoryProperty); + set => SetValue(WorkingDirectoryProperty, value); + } + public bool LogConPTYOutput { get => (bool)GetValue(LogConPTYOutputProperty); set => SetValue(LogConPTYOutputProperty, value); @@ -215,7 +220,12 @@ private void StartTerm(int column_width, int row_height) { var cmd = StartupCommandLine;//thread safety for dp var term = ConPTYTerm; var logOutput = LogConPTYOutput; - Task.Run(() => term.Start(cmd, column_width, row_height, logOutput)); + var workingDir = WorkingDirectory; + //If the directory does not exist, set it to null (default behavior). + if (workingDir != null && !System.IO.Directory.Exists(workingDir)) { + workingDir = null; + } + Task.Run(() => term.Start(cmd, column_width, row_height, logOutput, null, workingDir)); }); } private async void Terminal_Loaded(object sender, RoutedEventArgs e) { @@ -247,6 +257,8 @@ private async Task TermInit() { public static readonly DependencyProperty ConPTYTermProperty = DependencyProperty.Register(nameof(ConPTYTerm), typeof(TermPTY), typeof(EasyTerminalControl), new(null, OnTermChanged)); public static readonly DependencyProperty StartupCommandLineProperty = DependencyProperty.Register(nameof(StartupCommandLine), typeof(string), typeof(EasyTerminalControl), new PropertyMetadata("powershell.exe")); + public static readonly DependencyProperty WorkingDirectoryProperty = DependencyProperty.Register(nameof(WorkingDirectory), typeof(string), typeof(EasyTerminalControl), new PropertyMetadata(null)); + public static readonly DependencyProperty LogConPTYOutputProperty = DependencyProperty.Register(nameof(LogConPTYOutput), typeof(bool), typeof(EasyTerminalControl), new PropertyMetadata(false)); public static readonly DependencyProperty Win32InputModeProperty = DependencyProperty.Register(nameof(Win32InputMode), typeof(bool), typeof(EasyTerminalControl), new PropertyMetadata(true)); public static readonly DependencyProperty IsReadOnlyProperty = PropHelper.GenerateWriteOnlyProperty((c) => c.IsReadOnly); diff --git a/EasyWindowsTerminalControl/Internals/ProcessFactory.cs b/EasyWindowsTerminalControl/Internals/ProcessFactory.cs index 4a6cc72..e6af53f 100644 --- a/EasyWindowsTerminalControl/Internals/ProcessFactory.cs +++ b/EasyWindowsTerminalControl/Internals/ProcessFactory.cs @@ -13,7 +13,7 @@ public interface IProcess : IDisposable { void Kill(bool EntireProcessTree = false); } public interface IProcessFactory { - IProcess Start(string command, nuint attributes, PseudoConsole console); + IProcess Start(string command, nuint attributes, PseudoConsole console, string workingDirectory = null); } /// /// Support for starting and configuring processes. @@ -49,9 +49,9 @@ public void Dispose() { /// /// Start and configure a process. The return value represents the process and should be disposed. /// - public static WrappedProcess Start(string command, nuint attributes, PseudoConsole console) { + public static WrappedProcess Start(string command, nuint attributes, PseudoConsole console, string workingDirectory = null) { var startupInfo = ConfigureProcessThread(console.Handle, attributes); - var processInfo = RunProcess(ref startupInfo, command); + var processInfo = RunProcess(ref startupInfo, command, workingDirectory); return new(new Process(startupInfo, processInfo)); } @@ -100,14 +100,14 @@ unsafe private static STARTUPINFOEXW ConfigureProcessThread(PseudoConsole.ConPty return startupInfo; } - unsafe private static PROCESS_INFORMATION RunProcess(ref STARTUPINFOEXW sInfoEx, string commandLine) { + unsafe private static PROCESS_INFORMATION RunProcess(ref STARTUPINFOEXW sInfoEx, string commandLine, string workingDirectory = null) { uint securityAttributeSize = (uint)Marshal.SizeOf(); var pSec = new SECURITY_ATTRIBUTES { nLength = securityAttributeSize }; var tSec = new SECURITY_ATTRIBUTES { nLength = securityAttributeSize }; var info = sInfoEx; Span spanChar = (commandLine + '\0').ToCharArray(); - var success = PInvoke.CreateProcess(null, ref spanChar, pSec, tSec, false, PROCESS_CREATION_FLAGS.EXTENDED_STARTUPINFO_PRESENT, null, null, info.StartupInfo, out var pInfo); + var success = PInvoke.CreateProcess(null, ref spanChar, pSec, tSec, false, PROCESS_CREATION_FLAGS.EXTENDED_STARTUPINFO_PRESENT, null, workingDirectory, info.StartupInfo, out var pInfo); if (!success) throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not create process."); diff --git a/EasyWindowsTerminalControl/TermPTY.cs b/EasyWindowsTerminalControl/TermPTY.cs index 456fbbe..a043a9f 100644 --- a/EasyWindowsTerminalControl/TermPTY.cs +++ b/EasyWindowsTerminalControl/TermPTY.cs @@ -14,7 +14,7 @@ namespace EasyWindowsTerminalControl { /// public class TermPTY : ITerminalConnection { protected class InternalProcessFactory : IProcessFactory { - public IProcess Start(string command, nuint attributes, PseudoConsole console) => ProcessFactory.Start(command, attributes, console); + public IProcess Start(string command, nuint attributes, PseudoConsole console, string workingDirectory = null) => ProcessFactory.Start(command, attributes, console, workingDirectory); } #if WPF private static bool IsDesignMode = System.ComponentModel.DesignerProperties.GetIsInDesignMode(new System.Windows.DependencyObject()); @@ -61,7 +61,8 @@ public static string StripColors(String str) { /// The width (in characters) to start the pseudoconsole with. Defaults to 30. /// Whether to log the output of the console to a file. Defaults to false. /// While not recommended, you can provide your own process factory for more granular control over process creation - public void Start(string command, int consoleWidth = 80, int consoleHeight = 30, bool logOutput = false, IProcessFactory factory = null) { + /// The working directory for the process. Defaults to null (inherits current process working directory). + public void Start(string command, int consoleWidth = 80, int consoleHeight = 30, bool logOutput = false, IProcessFactory factory = null, string workingDirectory = null) { if (Process != null) throw new Exception("Called Start on ConPTY term after already started"); factory ??= new InternalProcessFactory(); @@ -75,7 +76,7 @@ public void Start(string command, int consoleWidth = 80, int consoleHeight = 30, using (var inputPipe = new PseudoConsolePipe()) using (var outputPipe = new PseudoConsolePipe()) using (var pseudoConsole = PseudoConsole.Create(inputPipe.ReadSide, outputPipe.WriteSide, consoleWidth, consoleHeight)) - using (var process = factory.Start(command, PInvoke.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, pseudoConsole)) { + using (var process = factory.Start(command, PInvoke.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, pseudoConsole, workingDirectory)) { Process = process; TheConsole = pseudoConsole; // copy all pseudoconsole output to a FileStream and expose it to the rest of the app diff --git a/TermExample/MainWindow.xaml b/TermExample/MainWindow.xaml index 716fa73..23ca0f6 100644 --- a/TermExample/MainWindow.xaml +++ b/TermExample/MainWindow.xaml @@ -11,7 +11,7 @@ - +