From 6ff52c30a0fa1b8803e12dd4f40bf8b59a70efda Mon Sep 17 00:00:00 2001 From: hetima <33700+hetima@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:36:47 +0900 Subject: [PATCH] (WPF)Add WorkingDirectory property --- EasyWindowsTerminalControl/EasyTerminalControl.cs | 14 +++++++++++++- .../Internals/ProcessFactory.cs | 10 +++++----- EasyWindowsTerminalControl/TermPTY.cs | 7 ++++--- TermExample/MainWindow.xaml | 2 +- TermExample/MainWindow.xaml.cs | 2 ++ 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/EasyWindowsTerminalControl/EasyTerminalControl.cs b/EasyWindowsTerminalControl/EasyTerminalControl.cs index 559336c..e85874d 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; + //fallback to UserProfile + if (workingDir != null && (!System.IO.Directory.Exists(workingDir) || workingDir == "%USERPROFILE%")) { + workingDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + } + 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 @@ - +