Had to stop production as I ran out of copilot credits

This commit is contained in:
minster586
2026-08-15 04:11:52 -04:00
parent 65421eb379
commit 5838d4dcdd
5 changed files with 243 additions and 41 deletions
+36 -1
View File
@@ -2,6 +2,15 @@ namespace PainoBar_Helper
{ {
public partial class Main : Form 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 ProfileManager _profileManager;
private readonly PianobarManager _pianobarManager; private readonly PianobarManager _pianobarManager;
private OutputManager? _outputManager; private OutputManager? _outputManager;
@@ -22,6 +31,8 @@ namespace PainoBar_Helper
{ {
InitializeComponent(); InitializeComponent();
_instance = this;
_profileManager = new ProfileManager(); _profileManager = new ProfileManager();
_pianobarManager = new PianobarManager(); _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 #region Initialization
private void InitializeMenus() private void InitializeMenus()
@@ -83,6 +106,7 @@ namespace PainoBar_Helper
_pianobarManager.SongChanged += PianobarManager_SongChanged; _pianobarManager.SongChanged += PianobarManager_SongChanged;
_pianobarManager.StationChanged += PianobarManager_StationChanged; _pianobarManager.StationChanged += PianobarManager_StationChanged;
_pianobarManager.EventOccurred += PianobarManager_EventOccurred; _pianobarManager.EventOccurred += PianobarManager_EventOccurred;
_pianobarManager.OutputReceived += PianobarManager_OutputReceived;
// Profile events // Profile events
_profileManager.ProfilesChanged += ProfileManager_ProfilesChanged; _profileManager.ProfilesChanged += ProfileManager_ProfilesChanged;
@@ -240,7 +264,7 @@ namespace PainoBar_Helper
return; return;
} }
var stationForm = new StationForm(_pianobarManager); var stationForm = new StationForm();
stationForm.ShowDialog(this); stationForm.ShowDialog(this);
} }
@@ -344,6 +368,7 @@ namespace PainoBar_Helper
{ {
_outputManager?.Stop(); _outputManager?.Stop();
_outputManager = null; _outputManager = null;
TerminalHistory.Clear(); // Clear history on disconnect
UpdateConnectionStatus(ConnectionStatus.Disconnected); UpdateConnectionStatus(ConnectionStatus.Disconnected);
UpdatePlayPauseButton(); 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) private void OutputManager_ActiveStateChanged(object? sender, EventArgs e)
{ {
Invoke(() => Invoke(() =>
+9 -3
View File
@@ -313,9 +313,15 @@ namespace PainoBar_Helper
lock (_inputLock) lock (_inputLock)
{ {
// Use LF explicitly to avoid CRLF handling issues with redirected stdin. // Convert to ASCII bytes with Unix LF to avoid StreamWriter text encoding issues
_inputWriter.Write(command); // that can cause Pianobar's C-runtime to see extra characters
_inputWriter.Write('\n'); 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(); _inputWriter.Flush();
} }
+5
View File
@@ -177,6 +177,11 @@ namespace PainoBar_Helper
sb.AppendLine($"event_command = \"{eventCmdPath}\""); sb.AppendLine($"event_command = \"{eventCmdPath}\"");
sb.AppendLine(); 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 // Additional settings for better integration
sb.AppendLine("# Output formatting"); sb.AppendLine("# Output formatting");
sb.AppendLine("format_nowplaying_song = %t|%a|%l|%r"); sb.AppendLine("format_nowplaying_song = %t|%a|%l|%r");
+24 -37
View File
@@ -5,33 +5,38 @@ namespace PainoBar_Helper
{ {
public partial class StationForm : Form public partial class StationForm : Form
{ {
private readonly PianobarManager _pianobarManager; public StationForm()
private readonly StringBuilder _outputBuffer = new StringBuilder();
public StationForm(PianobarManager pianobarManager)
{ {
InitializeComponent(); InitializeComponent();
_pianobarManager = pianobarManager; // Handle form load and closing for event subscription
// Subscribe to output BEFORE controls are initialized
_pianobarManager.OutputReceived += PianobarManager_TerminalOutputReceived;
// Handle form load to display buffered output
this.Load += StationForm_Load; this.Load += StationForm_Load;
this.FormClosing += StationForm_FormClosing;
InitializeControls(); InitializeControls();
} }
private void StationForm_Load(object? sender, EventArgs e) private void StationForm_Load(object? sender, EventArgs e)
{ {
// Display any buffered output when form loads // First, populate terminal with existing history
if (terminal_box != null && _outputBuffer.Length > 0) 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.SelectionStart = terminal_box.Text.Length;
terminal_box.ScrollToCaret(); 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 #region Initialization
@@ -61,16 +66,11 @@ namespace PainoBar_Helper
#region Terminal Output #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; return;
string lineWithNewline = e + "\n";
// Buffer output even before form is shown
_outputBuffer.Append(lineWithNewline);
// Thread-safe UI update // Thread-safe UI update
if (terminal_box != null) if (terminal_box != null)
{ {
@@ -78,12 +78,12 @@ namespace PainoBar_Helper
{ {
terminal_box.Invoke(() => terminal_box.Invoke(() =>
{ {
AppendTerminalText(lineWithNewline); AppendTerminalText(data + "\n");
}); });
} }
else else
{ {
AppendTerminalText(lineWithNewline); AppendTerminalText(data + "\n");
} }
} }
} }
@@ -125,11 +125,8 @@ namespace PainoBar_Helper
try try
{ {
// Echo the command to the terminal (simulating user input display) // Send command directly to Pianobar stdin (no manual echo with ">")
AppendTerminalText($"> {command}\n"); Main.SendStdin(command);
// Send command to Pianobar with newline and flush
_pianobarManager.SendCommand(command);
command_input_box.Clear(); command_input_box.Clear();
} }
@@ -157,15 +154,5 @@ namespace PainoBar_Helper
} }
#endregion #endregion
#region Cleanup
protected override void OnFormClosing(FormClosingEventArgs e)
{
_pianobarManager.OutputReceived -= PianobarManager_TerminalOutputReceived;
base.OnFormClosing(e);
}
#endregion
} }
} }
+169
View File
@@ -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
}
}