Živ shell proces sa preusmerenim ulazom i izlazom.
Send(command) upisuje u stdin, kao da je otkucano i pritisnut Enter.Output za svaku liniju sa stdout/stderr. Stiže sa pozadinske niti, pa ide u UiLogSink, ne direktno u TextBox.Kill() ubija shell i sve što je pokrenuo (entireProcessTree: true).| Problem | Rešenje |
|---|---|
čćžšđ izlaze kao smeće u PowerShell-u | Na startu [Console]::OutputEncoding = UTF8 + StandardOutputEncoding = UTF8 |
| BOM na stdin-u pokvari prvu komandu | new UTF8Encoding(false) |
| cmd prompt se lepi za sledeći red izlaza | PROMPT=[$P]$_ → folder u svom redu |
| git pager i boje bez pravog terminala | GIT_PAGER=cat, NO_COLOR=1 |
| PowerShell visi kad nešto traži unos | -NonInteractive → greška umesto čekanja |
ping -t) zaustavlja se dugmetom Ubij, pa Pokreni.pause). U PowerShell-u -NonInteractive to pretvara u grešku.ShellSession.cs
using System.Diagnostics;
using System.Text;
namespace WinFormsApp_test
{
/// <summary>
/// Živ cmd / powershell / pwsh proces sa preusmerenim ulazom i izlazom.
/// GUI → proces: Send() piše u stdin. Proces → GUI: Output event (sa POZADINSKE niti!).
/// </summary>
public sealed class ShellSession : IDisposable
{
private readonly Process _proc;
/// <summary>Svaka linija sa stdout/stderr. Diže se sa pozadinske niti.</summary>
public event Action<string>? Output;
/// <summary>Proces je završen (exit, kill). Diže se sa pozadinske niti.</summary>
public event Action<int>? Exited;
public string ShellName { get; }
public bool IsRunning => !_proc.HasExited;
private ShellSession(string shellName, Process proc)
{
ShellName = shellName;
_proc = proc;
}
public static ShellSession Start(string shellName, string workingDirectory)
{
// PowerShell se prebacuje na UTF-8, da č/ć/ž/š/đ izađu ispravno.
// cmd ne ume da čita UTF-8 sa preusmerenog ulaza (bug u cmd-u), pa on radi u OEM kodnoj
// strani sistema (852/437). Ako je u Windows-u uključen "Beta: UTF-8", cmd naša slova
// u komandama ne prima nikako → za to koristi powershell.
var (file, args, initCommand, encoding) = shellName switch
{
"cmd" => ("cmd.exe", "/Q", (string?)null, OemEncoding()),
"powershell" => ("powershell.exe", "-NoLogo -NoProfile -NonInteractive -Command -",
"[Console]::OutputEncoding=[Text.Encoding]::UTF8; $OutputEncoding=[Text.Encoding]::UTF8",
(Encoding)new UTF8Encoding(false)),
"pwsh" => ("pwsh.exe", "-NoLogo -NoProfile -NonInteractive -Command -",
"[Console]::OutputEncoding=[Text.Encoding]::UTF8",
new UTF8Encoding(false)),
_ => throw new ArgumentException($"Nepoznat shell: {shellName}"),
};
var psi = new ProcessStartInfo(file, args)
{
WorkingDirectory = workingDirectory,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = encoding,
StandardErrorEncoding = encoding,
StandardInputEncoding = encoding, // UTF8Encoding(false) = bez BOM-a, inače prva komanda pukne
};
// git: bez boja i bez pager-a (nema pravog terminala)
psi.Environment["GIT_PAGER"] = "cat";
psi.Environment["NO_COLOR"] = "1";
// cmd prompt ne završava novim redom, pa bi se zalepio za sledeći izlaz → [folder] u svom redu
psi.Environment["PROMPT"] = "[$P]$_";
var proc = new Process { StartInfo = psi, EnableRaisingEvents = true };
var session = new ShellSession(shellName, proc);
proc.OutputDataReceived += (_, e) => { if (e.Data != null) session.Output?.Invoke(e.Data); };
proc.ErrorDataReceived += (_, e) => { if (e.Data != null) session.Output?.Invoke(e.Data); };
proc.Exited += (_, _) => session.Exited?.Invoke(proc.ExitCode);
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
proc.StandardInput.AutoFlush = true;
if (initCommand != null) proc.StandardInput.WriteLine(initCommand);
return session;
}
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern int GetOEMCP();
private static Encoding OemEncoding()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // 852, 437... nisu ugrađeni u .NET
int cp = GetOEMCP();
return cp == 65001 ? new UTF8Encoding(false) : Encoding.GetEncoding(cp);
}
/// <summary>GUI → proces: pošalji komandu kao da je otkucana i pritisnut Enter.</summary>
public void Send(string command)
{
if (!IsRunning) throw new InvalidOperationException("Shell nije pokrenut.");
_proc.StandardInput.WriteLine(command);
}
/// <summary>Ubija shell i sve što je on pokrenuo (npr. zaglavljen ping -t).</summary>
public void Kill()
{
if (IsRunning) _proc.Kill(entireProcessTree: true);
}
public void Dispose()
{
try { Kill(); } catch (InvalidOperationException) { }
_proc.Dispose();
}
}
}_shell = ShellSession.Start("powershell", @"C:\Users\Kimi\source\repos");
_shell.Output += _termLog.Write; // pozadina → red → uiTimer → TextBox
_shell.Send("git status");