Had to stop production as I ran out of copilot credits
This commit is contained in:
@@ -2,6 +2,15 @@ namespace PainoBar_Helper
|
||||
{
|
||||
public partial class Main : Form
|
||||
{
|
||||
// Static event for Pianobar output - accessible to all forms
|
||||
public static event Action<string>? OnPianobarOutputReceived;
|
||||
|
||||
// Static terminal history buffer for StationForm replay
|
||||
public static List<string> TerminalHistory = new List<string>();
|
||||
|
||||
// Static reference to the main instance for accessing PianobarManager
|
||||
private static Main? _instance;
|
||||
|
||||
private readonly ProfileManager _profileManager;
|
||||
private readonly PianobarManager _pianobarManager;
|
||||
private OutputManager? _outputManager;
|
||||
@@ -22,6 +31,8 @@ namespace PainoBar_Helper
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_instance = this;
|
||||
|
||||
_profileManager = new ProfileManager();
|
||||
_pianobarManager = new PianobarManager();
|
||||
|
||||
@@ -52,6 +63,18 @@ namespace PainoBar_Helper
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a command to Pianobar's standard input stream.
|
||||
/// </summary>
|
||||
/// <param name="text">The command text to send (newline will be added automatically)</param>
|
||||
public static void SendStdin(string text)
|
||||
{
|
||||
if (_instance?._pianobarManager != null)
|
||||
{
|
||||
_instance._pianobarManager.SendCommand(text);
|
||||
}
|
||||
}
|
||||
|
||||
#region Initialization
|
||||
|
||||
private void InitializeMenus()
|
||||
@@ -83,6 +106,7 @@ namespace PainoBar_Helper
|
||||
_pianobarManager.SongChanged += PianobarManager_SongChanged;
|
||||
_pianobarManager.StationChanged += PianobarManager_StationChanged;
|
||||
_pianobarManager.EventOccurred += PianobarManager_EventOccurred;
|
||||
_pianobarManager.OutputReceived += PianobarManager_OutputReceived;
|
||||
|
||||
// Profile events
|
||||
_profileManager.ProfilesChanged += ProfileManager_ProfilesChanged;
|
||||
@@ -240,7 +264,7 @@ namespace PainoBar_Helper
|
||||
return;
|
||||
}
|
||||
|
||||
var stationForm = new StationForm(_pianobarManager);
|
||||
var stationForm = new StationForm();
|
||||
stationForm.ShowDialog(this);
|
||||
}
|
||||
|
||||
@@ -344,6 +368,7 @@ namespace PainoBar_Helper
|
||||
{
|
||||
_outputManager?.Stop();
|
||||
_outputManager = null;
|
||||
TerminalHistory.Clear(); // Clear history on disconnect
|
||||
UpdateConnectionStatus(ConnectionStatus.Disconnected);
|
||||
UpdatePlayPauseButton();
|
||||
});
|
||||
@@ -375,6 +400,16 @@ namespace PainoBar_Helper
|
||||
});
|
||||
}
|
||||
|
||||
private void PianobarManager_OutputReceived(object? sender, string e)
|
||||
{
|
||||
// Add to history and forward to static event for StationForm and other subscribers
|
||||
if (!string.IsNullOrEmpty(e))
|
||||
{
|
||||
TerminalHistory.Add(e);
|
||||
OnPianobarOutputReceived?.Invoke(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void OutputManager_ActiveStateChanged(object? sender, EventArgs e)
|
||||
{
|
||||
Invoke(() =>
|
||||
|
||||
+9
-3
@@ -313,9 +313,15 @@ namespace PainoBar_Helper
|
||||
|
||||
lock (_inputLock)
|
||||
{
|
||||
// Use LF explicitly to avoid CRLF handling issues with redirected stdin.
|
||||
_inputWriter.Write(command);
|
||||
_inputWriter.Write('\n');
|
||||
// Convert to ASCII bytes with Unix LF to avoid StreamWriter text encoding issues
|
||||
// that can cause Pianobar's C-runtime to see extra characters
|
||||
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(command + "\n");
|
||||
|
||||
// Write raw bytes directly to base stream, bypassing StreamWriter
|
||||
_inputWriter.BaseStream.Write(bytes, 0, bytes.Length);
|
||||
|
||||
// Flush both base stream and StreamWriter buffers immediately
|
||||
_inputWriter.BaseStream.Flush();
|
||||
_inputWriter.Flush();
|
||||
}
|
||||
|
||||
|
||||
@@ -177,6 +177,11 @@ namespace PainoBar_Helper
|
||||
sb.AppendLine($"event_command = \"{eventCmdPath}\"");
|
||||
sb.AppendLine();
|
||||
|
||||
// Force unbuffered stdout for immediate C# pipe output
|
||||
sb.AppendLine("# Unbuffer output for real-time streaming");
|
||||
sb.AppendLine("unbuffer_output = 1");
|
||||
sb.AppendLine();
|
||||
|
||||
// Additional settings for better integration
|
||||
sb.AppendLine("# Output formatting");
|
||||
sb.AppendLine("format_nowplaying_song = %t|%a|%l|%r");
|
||||
|
||||
+24
-37
@@ -5,33 +5,38 @@ namespace PainoBar_Helper
|
||||
{
|
||||
public partial class StationForm : Form
|
||||
{
|
||||
private readonly PianobarManager _pianobarManager;
|
||||
private readonly StringBuilder _outputBuffer = new StringBuilder();
|
||||
|
||||
public StationForm(PianobarManager pianobarManager)
|
||||
public StationForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_pianobarManager = pianobarManager;
|
||||
|
||||
// Subscribe to output BEFORE controls are initialized
|
||||
_pianobarManager.OutputReceived += PianobarManager_TerminalOutputReceived;
|
||||
|
||||
// Handle form load to display buffered output
|
||||
// Handle form load and closing for event subscription
|
||||
this.Load += StationForm_Load;
|
||||
this.FormClosing += StationForm_FormClosing;
|
||||
|
||||
InitializeControls();
|
||||
}
|
||||
|
||||
private void StationForm_Load(object? sender, EventArgs e)
|
||||
{
|
||||
// Display any buffered output when form loads
|
||||
if (terminal_box != null && _outputBuffer.Length > 0)
|
||||
// First, populate terminal with existing history
|
||||
if (terminal_box != null && Main.TerminalHistory.Count > 0)
|
||||
{
|
||||
terminal_box.Text = _outputBuffer.ToString();
|
||||
foreach (string line in Main.TerminalHistory)
|
||||
{
|
||||
terminal_box.AppendText(line + "\n");
|
||||
}
|
||||
terminal_box.SelectionStart = terminal_box.Text.Length;
|
||||
terminal_box.ScrollToCaret();
|
||||
}
|
||||
|
||||
// Second, subscribe to live output events
|
||||
Main.OnPianobarOutputReceived += AppendToTerminal;
|
||||
}
|
||||
|
||||
private void StationForm_FormClosing(object? sender, FormClosingEventArgs e)
|
||||
{
|
||||
// Unsubscribe from the static event
|
||||
Main.OnPianobarOutputReceived -= AppendToTerminal;
|
||||
}
|
||||
|
||||
#region Initialization
|
||||
@@ -61,16 +66,11 @@ namespace PainoBar_Helper
|
||||
|
||||
#region Terminal Output
|
||||
|
||||
private void PianobarManager_TerminalOutputReceived(object? sender, string e)
|
||||
private void AppendToTerminal(string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(e))
|
||||
if (string.IsNullOrEmpty(data))
|
||||
return;
|
||||
|
||||
string lineWithNewline = e + "\n";
|
||||
|
||||
// Buffer output even before form is shown
|
||||
_outputBuffer.Append(lineWithNewline);
|
||||
|
||||
// Thread-safe UI update
|
||||
if (terminal_box != null)
|
||||
{
|
||||
@@ -78,12 +78,12 @@ namespace PainoBar_Helper
|
||||
{
|
||||
terminal_box.Invoke(() =>
|
||||
{
|
||||
AppendTerminalText(lineWithNewline);
|
||||
AppendTerminalText(data + "\n");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendTerminalText(lineWithNewline);
|
||||
AppendTerminalText(data + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,11 +125,8 @@ namespace PainoBar_Helper
|
||||
|
||||
try
|
||||
{
|
||||
// Echo the command to the terminal (simulating user input display)
|
||||
AppendTerminalText($"> {command}\n");
|
||||
|
||||
// Send command to Pianobar with newline and flush
|
||||
_pianobarManager.SendCommand(command);
|
||||
// Send command directly to Pianobar stdin (no manual echo with ">")
|
||||
Main.SendStdin(command);
|
||||
|
||||
command_input_box.Clear();
|
||||
}
|
||||
@@ -157,15 +154,5 @@ namespace PainoBar_Helper
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cleanup
|
||||
|
||||
protected override void OnFormClosing(FormClosingEventArgs e)
|
||||
{
|
||||
_pianobarManager.OutputReceived -= PianobarManager_TerminalOutputReceived;
|
||||
base.OnFormClosing(e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user