Files
Painobar-helper/Win32ConsoleHelper.cs

170 lines
5.8 KiB
C#

using System.Runtime.InteropServices;
namespace PainoBar_Helper
{
/// <summary>
/// Helper class for injecting keyboard input directly into a Win32 console process.
/// Required for Pianobar Windows port which uses _getch() instead of stdin.
/// </summary>
public static class Win32ConsoleHelper
{
#region Win32 API Imports
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FreeConsole();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteConsoleInput(
IntPtr hConsoleInput,
INPUT_RECORD[] lpBuffer,
uint nLength,
out uint lpNumberOfEventsWritten);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(int nStdHandle);
private const int STD_INPUT_HANDLE = -10;
#endregion
#region Structures
[StructLayout(LayoutKind.Explicit)]
private struct INPUT_RECORD
{
[FieldOffset(0)]
public ushort EventType;
[FieldOffset(4)]
public KEY_EVENT_RECORD KeyEvent;
}
[StructLayout(LayoutKind.Sequential)]
private struct KEY_EVENT_RECORD
{
public bool bKeyDown;
public ushort wRepeatCount;
public ushort wVirtualKeyCode;
public ushort wVirtualScanCode;
public char UnicodeChar;
public uint dwControlKeyState;
}
private const ushort KEY_EVENT = 1;
private const ushort VK_RETURN = 0x0D;
#endregion
#region Public Methods
/// <summary>
/// Sends keyboard input directly to a console process using Win32 API.
/// </summary>
/// <param name="processId">The process ID of the target console application</param>
/// <param name="text">The text to send (Enter key will be automatically appended)</param>
/// <returns>True if successful, false otherwise</returns>
public static bool SendConsoleInput(int processId, string text)
{
if (string.IsNullOrEmpty(text))
return false;
try
{
// Attach to the target process's console
if (!AttachConsole((uint)processId))
{
int error = Marshal.GetLastWin32Error();
System.Diagnostics.Debug.WriteLine($"AttachConsole failed with error: {error}");
return false;
}
try
{
// Get the console input handle
IntPtr hStdIn = GetStdHandle(STD_INPUT_HANDLE);
if (hStdIn == IntPtr.Zero || hStdIn == new IntPtr(-1))
{
System.Diagnostics.Debug.WriteLine("GetStdHandle failed");
return false;
}
// Build input records for each character + Enter key
List<INPUT_RECORD> inputRecords = new List<INPUT_RECORD>();
// Add key events for each character in the text
foreach (char c in text)
{
// Key down event
inputRecords.Add(CreateKeyEvent(c, true));
// Key up event
inputRecords.Add(CreateKeyEvent(c, false));
}
// Add Enter key (VK_RETURN) at the end
inputRecords.Add(CreateKeyEvent('\r', true, VK_RETURN));
inputRecords.Add(CreateKeyEvent('\r', false, VK_RETURN));
// Write all input records to the console input buffer
uint written;
bool success = WriteConsoleInput(
hStdIn,
inputRecords.ToArray(),
(uint)inputRecords.Count,
out written);
if (!success)
{
int error = Marshal.GetLastWin32Error();
System.Diagnostics.Debug.WriteLine($"WriteConsoleInput failed with error: {error}");
return false;
}
System.Diagnostics.Debug.WriteLine($"Successfully wrote {written} input events for: {text}");
return true;
}
finally
{
// Always detach from the console
FreeConsole();
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"SendConsoleInput exception: {ex.Message}");
return false;
}
}
#endregion
#region Private Helper Methods
private static INPUT_RECORD CreateKeyEvent(char character, bool keyDown, ushort virtualKeyCode = 0)
{
// If no virtual key code provided, use the character's value
if (virtualKeyCode == 0)
{
virtualKeyCode = (ushort)char.ToUpper(character);
}
return new INPUT_RECORD
{
EventType = KEY_EVENT,
KeyEvent = new KEY_EVENT_RECORD
{
bKeyDown = keyDown,
wRepeatCount = 1,
wVirtualKeyCode = virtualKeyCode,
wVirtualScanCode = 0,
UnicodeChar = character,
dwControlKeyState = 0
}
};
}
#endregion
}
}