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
|
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
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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