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 @@
-
+