Work done so far
This commit is contained in:
@@ -0,0 +1,182 @@
|
|||||||
|
# Kill Process on Disconnect Feature - Implementation Summary
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This document describes the implementation of the "Kill Process on Disconnect" feature for the Pianobar Helper application.
|
||||||
|
|
||||||
|
## Feature Requirements
|
||||||
|
1. **Kill Process Checkbox (`chkkillpro`)**: A checkbox in the Settings form that allows users to enable/disable the kill-on-disconnect behavior
|
||||||
|
2. **Gating Mechanism**: The checkbox must be disabled (non-interactive) unless the "Enable Profile Credentials and Custom Config Generation" checkbox (`chkOverrideCredentials`) is checked
|
||||||
|
3. **Disconnect Behavior**: When the user clicks the Disconnect button in the main application:
|
||||||
|
- If `chkkillpro` is checked: Pianobar process is immediately killed via its PID
|
||||||
|
- If `chkkillpro` is unchecked: Pianobar process is detached (continues running in background)
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### 1. Data Model (`ProfileSettings`)
|
||||||
|
**File**: `ProfileManager.cs`
|
||||||
|
|
||||||
|
Added `KillProcessOnDisconnect` boolean property to `ProfileSettings`:
|
||||||
|
```csharp
|
||||||
|
public class ProfileSettings
|
||||||
|
{
|
||||||
|
// ... other properties ...
|
||||||
|
public bool KillProcessOnDisconnect { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This property is saved/loaded with each profile in `profiles.json`.
|
||||||
|
|
||||||
|
### 2. Process Management (`PianobarManager`)
|
||||||
|
**File**: `PianobarManager.cs`
|
||||||
|
|
||||||
|
Implemented two disconnect methods:
|
||||||
|
|
||||||
|
#### `Disconnect()` - Detach mode
|
||||||
|
- Cleans up streams and event handlers
|
||||||
|
- Sets `IsConnected = false`
|
||||||
|
- Leaves the Pianobar process running in the background
|
||||||
|
|
||||||
|
#### `DisconnectAndKill()` - Kill mode
|
||||||
|
- Checks if the process exists and is not exited
|
||||||
|
- Calls `Process.Kill()` to terminate immediately
|
||||||
|
- Reports the PID that was killed
|
||||||
|
- Cleans up and sets `IsConnected = false`
|
||||||
|
|
||||||
|
### 3. Settings UI (`SettingsForm`)
|
||||||
|
**File**: `Settings.cs` and `Settings.Designer.cs`
|
||||||
|
|
||||||
|
#### Control Initialization
|
||||||
|
- `chkkillpro` checkbox starts **disabled** (set in Designer)
|
||||||
|
- All credential-related fields start disabled:
|
||||||
|
- `txtConfigFilePath`
|
||||||
|
- `btnBrowseConfigFile`
|
||||||
|
- `txtPianobarUser`
|
||||||
|
- `txtPianobarPass`
|
||||||
|
- `chkkillpro`
|
||||||
|
|
||||||
|
#### Event Handling
|
||||||
|
**`chkOverrideCredentials_CheckedChanged`**:
|
||||||
|
- Wired in Designer (line 352)
|
||||||
|
- Calls `ToggleCredentialFields(checked)` method
|
||||||
|
|
||||||
|
**`ToggleCredentialFields(bool enabled)`**:
|
||||||
|
```csharp
|
||||||
|
private void ToggleCredentialFields(bool enabled)
|
||||||
|
{
|
||||||
|
if (txtConfigFilePath != null)
|
||||||
|
txtConfigFilePath.Enabled = enabled;
|
||||||
|
|
||||||
|
if (btnBrowseConfigFile != null)
|
||||||
|
btnBrowseConfigFile.Enabled = enabled;
|
||||||
|
|
||||||
|
if (txtPianobarUser != null)
|
||||||
|
txtPianobarUser.Enabled = enabled;
|
||||||
|
|
||||||
|
if (txtPianobarPass != null)
|
||||||
|
txtPianobarPass.Enabled = enabled;
|
||||||
|
|
||||||
|
if (chkkillpro != null)
|
||||||
|
chkkillpro.Enabled = enabled; // <-- Critical: Kill checkbox is gated here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Profile Save/Load
|
||||||
|
- `LoadSettingsToControls()` loads `KillProcessOnDisconnect` into `chkkillpro`
|
||||||
|
- `SaveSettingsFromControls()` saves `chkkillpro.Checked` to profile settings
|
||||||
|
- When saving, the profile is set as active via `SetActiveProfile()`
|
||||||
|
|
||||||
|
### 4. Main Application Logic (`Main.cs`)
|
||||||
|
**File**: `Main.cs`
|
||||||
|
|
||||||
|
**`btnDisconnect_Click` handler**:
|
||||||
|
```csharp
|
||||||
|
public void btnDisconnect_Click(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (!_pianobarManager.IsConnected)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Not connected to Pianobar.", "Not Connected",
|
||||||
|
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusLabelRight != null)
|
||||||
|
statusLabelRight.Text = "Disconnecting...";
|
||||||
|
|
||||||
|
// Check if we should kill the process or just detach
|
||||||
|
var activeProfile = _profileManager.GetActiveProfile();
|
||||||
|
bool killProcess = activeProfile?.Settings.KillProcessOnDisconnect ?? false;
|
||||||
|
|
||||||
|
if (killProcess)
|
||||||
|
{
|
||||||
|
_pianobarManager.DisconnectAndKill(); // <-- Terminates process
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_pianobarManager.Disconnect(); // <-- Detaches, leaves running
|
||||||
|
}
|
||||||
|
|
||||||
|
_outputManager?.Stop();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Output Folder Browse Button
|
||||||
|
**File**: `Settings.cs` and `Settings.Designer.cs`
|
||||||
|
|
||||||
|
**`output_folder_browse_button`** (ID as requested):
|
||||||
|
- Wired in Designer to `output_folder_browse_button_Click` (line 237)
|
||||||
|
- Opens `SaveFileDialog` to allow user to select output file location
|
||||||
|
- Default filename: `now_playing.txt`
|
||||||
|
- Selected path is saved to `output_folder_text_box` (maps to `TextFilePath` in settings)
|
||||||
|
|
||||||
|
## User Workflow
|
||||||
|
|
||||||
|
### Enabling Kill-on-Disconnect
|
||||||
|
1. Open **File ? Settings**
|
||||||
|
2. Navigate to **"Pianobar Config & Auth"** tab
|
||||||
|
3. Check **"Enable Profile Credentials and Custom Config Generation"** (`chkOverrideCredentials`)
|
||||||
|
- This enables all credential fields **and** the kill process checkbox
|
||||||
|
4. Check **"Kill process"** (`chkkillpro`)
|
||||||
|
5. Click **Save**
|
||||||
|
- Profile is updated and set as active
|
||||||
|
- Setting persists for future sessions
|
||||||
|
|
||||||
|
### Using the Feature
|
||||||
|
- **Connect**: Click Connect button (Pianobar starts in background, PID shown in status)
|
||||||
|
- **Disconnect with Kill**: If `chkkillpro` was checked:
|
||||||
|
- Pianobar process is immediately terminated by PID
|
||||||
|
- Status shows "Pianobar process killed (PID: XXXX)"
|
||||||
|
- **Disconnect without Kill**: If `chkkillpro` was unchecked:
|
||||||
|
- Pianobar process continues running
|
||||||
|
- Application detaches and closes streams
|
||||||
|
- Status shows "Disconnected from Pianobar (process detached)"
|
||||||
|
|
||||||
|
### Disabling Override
|
||||||
|
- If user unchecks `chkOverrideCredentials`:
|
||||||
|
- Kill process checkbox becomes disabled and cannot be checked
|
||||||
|
- All credential fields are disabled
|
||||||
|
- User cannot enable kill-on-disconnect without also enabling custom config generation
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
? **Build Status**: Project builds successfully without errors
|
||||||
|
? **Designer Wiring**: All event handlers properly wired in Designer files:
|
||||||
|
- `chkOverrideCredentials.CheckedChanged` ? `chkOverrideCredentials_CheckedChanged`
|
||||||
|
- `output_folder_browse_button.Click` ? `output_folder_browse_button_Click`
|
||||||
|
- `chkkillpro` exists and is added to `pianobar_override_tab_page`
|
||||||
|
? **Initial State**: All credential fields and `chkkillpro` start disabled
|
||||||
|
? **Gating Logic**: `ToggleCredentialFields()` enables/disables `chkkillpro` based on override checkbox
|
||||||
|
? **Profile Persistence**: `KillProcessOnDisconnect` saved/loaded with profile
|
||||||
|
? **Disconnect Logic**: Main form branches to kill or detach based on profile setting
|
||||||
|
? **Process Management**: `DisconnectAndKill()` terminates by PID; `Disconnect()` detaches only
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
1. **ProfileManager.cs** - Added `KillProcessOnDisconnect` to data model
|
||||||
|
2. **PianobarManager.cs** - Added `DisconnectAndKill()` method
|
||||||
|
3. **Settings.cs** - Load/save logic and gating via `ToggleCredentialFields()`
|
||||||
|
4. **Settings.Designer.cs** - Set initial `Enabled = false` for credential fields and `chkkillpro`
|
||||||
|
5. **Main.cs** - Disconnect logic branches based on profile flag
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The kill-on-disconnect feature is fully implemented and integrated with the existing profile system. The checkbox is properly gated by the override credentials checkbox, ensuring users cannot accidentally enable the feature without understanding its context within custom config generation. The feature provides clean process termination when desired while preserving the option to keep Pianobar running in the background.
|
||||||
@@ -30,6 +30,26 @@ namespace PainoBar_Helper
|
|||||||
InitializeEvents();
|
InitializeEvents();
|
||||||
UpdateProfilesMenu();
|
UpdateProfilesMenu();
|
||||||
UpdateConnectionStatus(ConnectionStatus.Disconnected);
|
UpdateConnectionStatus(ConnectionStatus.Disconnected);
|
||||||
|
|
||||||
|
// Set initial status
|
||||||
|
if (statusLabelRight != null)
|
||||||
|
{
|
||||||
|
var activeProfile = _profileManager.GetActiveProfile();
|
||||||
|
if (activeProfile != null)
|
||||||
|
{
|
||||||
|
statusLabelRight.Text = $"Ready - Profile: {activeProfile.Name}";
|
||||||
|
}
|
||||||
|
else if (_profileManager.Profiles.Any())
|
||||||
|
{
|
||||||
|
// If no active profile but profiles exist, set the first one as active
|
||||||
|
_profileManager.SetActiveProfile(_profileManager.Profiles.First().Name);
|
||||||
|
statusLabelRight.Text = $"Ready - Profile: {_profileManager.ActiveProfileName}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
statusLabelRight.Text = "Ready - No profile configured";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Initialization
|
#region Initialization
|
||||||
@@ -83,18 +103,58 @@ namespace PainoBar_Helper
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get pianobar.exe path from active profile or use default
|
||||||
|
var activeProfile = _profileManager.GetActiveProfile();
|
||||||
|
|
||||||
|
if (activeProfile == null)
|
||||||
|
{
|
||||||
|
var result = MessageBox.Show(
|
||||||
|
"No profile selected. Would you like to create one now?\n\n" +
|
||||||
|
"Click Yes to open Settings, or No to cancel.",
|
||||||
|
"No Profile",
|
||||||
|
MessageBoxButtons.YesNo,
|
||||||
|
MessageBoxIcon.Question);
|
||||||
|
|
||||||
|
if (result == DialogResult.Yes)
|
||||||
|
{
|
||||||
|
menuSettings_Click(sender, e);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string pianobarPath = activeProfile.Settings.PianobarPath;
|
||||||
|
string? configPath = _profileManager.GetActiveConfigPath();
|
||||||
|
string? eventFilePath = _profileManager.GetEventOutputPath();
|
||||||
|
|
||||||
|
// Debug information
|
||||||
|
string debugInfo = $"Profile: {activeProfile.Name}\n" +
|
||||||
|
$"Pianobar Path: {pianobarPath}\n" +
|
||||||
|
$"Config Path: {configPath ?? "(none)"}\n" +
|
||||||
|
$"Event File: {eventFilePath ?? "(none)"}\n" +
|
||||||
|
$"File Exists: {File.Exists(pianobarPath)}";
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get pianobar.exe path (you may want to add a setting for this)
|
// Update status to show we're attempting connection
|
||||||
string pianobarPath = "pianobar.exe"; // Default, assumes it's in PATH
|
if (statusLabelRight != null)
|
||||||
string? configPath = _profileManager.GetActiveConfigPath();
|
statusLabelRight.Text = $"Starting Pianobar...";
|
||||||
|
|
||||||
_pianobarManager.Connect(pianobarPath, configPath);
|
_pianobarManager.Connect(pianobarPath, configPath, eventFilePath);
|
||||||
|
|
||||||
|
// Success message will be shown by the Connected event handler
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
MessageBox.Show($"Failed to connect to Pianobar:\n{ex.Message}", "Connection Error",
|
// Update status to show connection failed
|
||||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
if (statusLabelRight != null)
|
||||||
|
statusLabelRight.Text = "Failed to start Pianobar";
|
||||||
|
|
||||||
|
MessageBox.Show(
|
||||||
|
$"Failed to start Pianobar:\n\n{ex.Message}\n\n" +
|
||||||
|
$"Debug Information:\n{debugInfo}",
|
||||||
|
"Connection Error",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +168,22 @@ namespace PainoBar_Helper
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pianobarManager.Disconnect();
|
if (statusLabelRight != null)
|
||||||
|
statusLabelRight.Text = "Disconnecting...";
|
||||||
|
|
||||||
|
// Check if we should kill the process or just detach
|
||||||
|
var activeProfile = _profileManager.GetActiveProfile();
|
||||||
|
bool killProcess = activeProfile?.Settings.KillProcessOnDisconnect ?? false;
|
||||||
|
|
||||||
|
if (killProcess)
|
||||||
|
{
|
||||||
|
_pianobarManager.DisconnectAndKill();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_pianobarManager.Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
_outputManager?.Stop();
|
_outputManager?.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +255,17 @@ namespace PainoBar_Helper
|
|||||||
var settingsForm = new SettingsForm(_profileManager, activeProfile?.Settings ?? new ProfileSettings());
|
var settingsForm = new SettingsForm(_profileManager, activeProfile?.Settings ?? new ProfileSettings());
|
||||||
if (settingsForm.ShowDialog(this) == DialogResult.OK)
|
if (settingsForm.ShowDialog(this) == DialogResult.OK)
|
||||||
{
|
{
|
||||||
// Settings were saved, restart output manager if connected
|
// Settings were saved, update the UI
|
||||||
|
UpdateProfilesMenu();
|
||||||
|
|
||||||
|
// Update status to show the new active profile
|
||||||
|
var newActiveProfile = _profileManager.GetActiveProfile();
|
||||||
|
if (newActiveProfile != null && statusLabelRight != null)
|
||||||
|
{
|
||||||
|
statusLabelRight.Text = $"Profile saved: {newActiveProfile.Name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart output manager if connected
|
||||||
if (_pianobarManager.IsConnected)
|
if (_pianobarManager.IsConnected)
|
||||||
{
|
{
|
||||||
RestartOutputManager();
|
RestartOutputManager();
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace PainoBar_Helper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Monitors Pianobar event output file for song changes and metadata updates
|
||||||
|
/// </summary>
|
||||||
|
public class PianobarEventMonitor : IDisposable
|
||||||
|
{
|
||||||
|
private FileSystemWatcher? _watcher;
|
||||||
|
private string? _eventFilePath;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
public event EventHandler<PianobarEventData>? EventReceived;
|
||||||
|
|
||||||
|
public void StartMonitoring(string eventFilePath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(eventFilePath))
|
||||||
|
throw new ArgumentException("Event file path cannot be empty", nameof(eventFilePath));
|
||||||
|
|
||||||
|
StopMonitoring();
|
||||||
|
|
||||||
|
_eventFilePath = eventFilePath;
|
||||||
|
|
||||||
|
// Ensure the directory exists
|
||||||
|
var directory = Path.GetDirectoryName(eventFilePath);
|
||||||
|
if (!string.IsNullOrEmpty(directory))
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
|
// Create initial file if it doesn't exist
|
||||||
|
if (!File.Exists(eventFilePath))
|
||||||
|
File.WriteAllText(eventFilePath, "{}");
|
||||||
|
|
||||||
|
// Set up file watcher
|
||||||
|
_watcher = new FileSystemWatcher
|
||||||
|
{
|
||||||
|
Path = directory!,
|
||||||
|
Filter = Path.GetFileName(eventFilePath),
|
||||||
|
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size
|
||||||
|
};
|
||||||
|
|
||||||
|
_watcher.Changed += OnFileChanged;
|
||||||
|
_watcher.EnableRaisingEvents = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopMonitoring()
|
||||||
|
{
|
||||||
|
if (_watcher != null)
|
||||||
|
{
|
||||||
|
_watcher.EnableRaisingEvents = false;
|
||||||
|
_watcher.Changed -= OnFileChanged;
|
||||||
|
_watcher.Dispose();
|
||||||
|
_watcher = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFileChanged(object sender, FileSystemEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Give the file a moment to finish writing
|
||||||
|
Thread.Sleep(50);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_eventFilePath) || !File.Exists(_eventFilePath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
string json = File.ReadAllText(_eventFilePath);
|
||||||
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var eventData = JsonSerializer.Deserialize<PianobarEventData>(json);
|
||||||
|
if (eventData != null)
|
||||||
|
{
|
||||||
|
EventReceived?.Invoke(this, eventData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore file read errors (file might be locked, etc.)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
StopMonitoring();
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents event data from Pianobar
|
||||||
|
/// </summary>
|
||||||
|
public class PianobarEventData
|
||||||
|
{
|
||||||
|
[JsonPropertyName("event")]
|
||||||
|
public string Event { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("timestamp")]
|
||||||
|
public string Timestamp { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("artist")]
|
||||||
|
public string Artist { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("title")]
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("album")]
|
||||||
|
public string Album { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("coverArt")]
|
||||||
|
public string CoverArt { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("stationName")]
|
||||||
|
public string StationName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("songDuration")]
|
||||||
|
public string SongDuration { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("songPlayed")]
|
||||||
|
public string SongPlayed { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("rating")]
|
||||||
|
public string Rating { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("detailUrl")]
|
||||||
|
public string DetailUrl { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
+242
-9
@@ -13,6 +13,13 @@ namespace PainoBar_Helper
|
|||||||
private StreamWriter? _inputWriter;
|
private StreamWriter? _inputWriter;
|
||||||
private bool _isConnected;
|
private bool _isConnected;
|
||||||
private readonly object _lockObject = new object();
|
private readonly object _lockObject = new object();
|
||||||
|
private PianobarEventMonitor? _eventMonitor;
|
||||||
|
private bool _receivedStdOut;
|
||||||
|
private bool _receivedStdErr;
|
||||||
|
private readonly string _ioLogPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
"PianobarHelper",
|
||||||
|
"pianobar-io.log");
|
||||||
|
|
||||||
public event EventHandler<SongChangedEventArgs>? SongChanged;
|
public event EventHandler<SongChangedEventArgs>? SongChanged;
|
||||||
public event EventHandler<string>? StationChanged;
|
public event EventHandler<string>? StationChanged;
|
||||||
@@ -52,32 +59,68 @@ namespace PainoBar_Helper
|
|||||||
public string? CurrentStation { get; private set; }
|
public string? CurrentStation { get; private set; }
|
||||||
public bool IsPlaying { get; private set; } = true;
|
public bool IsPlaying { get; private set; } = true;
|
||||||
|
|
||||||
public void Connect(string pianobarPath, string? configPath = null)
|
public void Connect(string pianobarPath, string? configPath = null, string? eventFilePath = null)
|
||||||
{
|
{
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
throw new InvalidOperationException("Already connected to Pianobar.");
|
throw new InvalidOperationException("Already connected to Pianobar.");
|
||||||
|
|
||||||
// Check if pianobar is already running
|
_receivedStdOut = false;
|
||||||
|
_receivedStdErr = false;
|
||||||
|
|
||||||
|
// Check if pianobar is already running (check both with and without .exe)
|
||||||
var existingProcesses = Process.GetProcessesByName("pianobar");
|
var existingProcesses = Process.GetProcessesByName("pianobar");
|
||||||
|
if (existingProcesses.Length == 0)
|
||||||
|
{
|
||||||
|
// Also try without extension
|
||||||
|
string processName = Path.GetFileNameWithoutExtension(pianobarPath);
|
||||||
|
existingProcesses = Process.GetProcessesByName(processName);
|
||||||
|
}
|
||||||
|
|
||||||
if (existingProcesses.Length > 0)
|
if (existingProcesses.Length > 0)
|
||||||
{
|
{
|
||||||
foreach (var proc in existingProcesses)
|
foreach (var proc in existingProcesses)
|
||||||
proc.Dispose();
|
proc.Dispose();
|
||||||
throw new InvalidOperationException("Pianobar is already running. Please close existing instances.");
|
throw new InvalidOperationException("Pianobar is already running. Please close existing instances before connecting.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the pianobar path exists
|
||||||
|
string fullPath = pianobarPath;
|
||||||
|
|
||||||
|
// If it's just a filename or not found, try to find it in PATH
|
||||||
|
if (!File.Exists(fullPath))
|
||||||
|
{
|
||||||
|
string? foundPath = FindInPath(pianobarPath);
|
||||||
|
if (!string.IsNullOrEmpty(foundPath))
|
||||||
|
{
|
||||||
|
fullPath = foundPath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException(
|
||||||
|
$"Pianobar executable not found.\n\n" +
|
||||||
|
$"Path checked: {pianobarPath}\n" +
|
||||||
|
$"Full path: {Path.GetFullPath(pianobarPath)}\n\n" +
|
||||||
|
$"Please verify:\n" +
|
||||||
|
$"1. The path in Settings is correct\n" +
|
||||||
|
$"2. Pianobar is installed\n" +
|
||||||
|
$"3. The file exists at the specified location");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var startInfo = new ProcessStartInfo
|
var startInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = pianobarPath,
|
FileName = fullPath,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
RedirectStandardInput = true,
|
RedirectStandardInput = true,
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true, // Run in background without terminal window
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
StandardOutputEncoding = Encoding.UTF8,
|
StandardOutputEncoding = Encoding.UTF8,
|
||||||
StandardErrorEncoding = Encoding.UTF8
|
StandardErrorEncoding = Encoding.UTF8,
|
||||||
|
WorkingDirectory = Path.GetDirectoryName(fullPath) ?? Environment.CurrentDirectory
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(configPath))
|
if (!string.IsNullOrEmpty(configPath))
|
||||||
@@ -91,13 +134,35 @@ namespace PainoBar_Helper
|
|||||||
_process.Exited += Process_Exited;
|
_process.Exited += Process_Exited;
|
||||||
_process.EnableRaisingEvents = true;
|
_process.EnableRaisingEvents = true;
|
||||||
|
|
||||||
_process.Start();
|
bool started = _process.Start();
|
||||||
|
|
||||||
|
if (!started)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Process.Start() returned false. The process could not be started.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the process actually started
|
||||||
|
if (_process.HasExited)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Process started but immediately exited with code: {_process.ExitCode}");
|
||||||
|
}
|
||||||
|
|
||||||
_inputWriter = _process.StandardInput;
|
_inputWriter = _process.StandardInput;
|
||||||
_process.BeginOutputReadLine();
|
_process.BeginOutputReadLine();
|
||||||
_process.BeginErrorReadLine();
|
_process.BeginErrorReadLine();
|
||||||
|
|
||||||
IsConnected = true;
|
IsConnected = true;
|
||||||
RaiseEvent("Connected to Pianobar");
|
|
||||||
|
// Start monitoring event file if provided
|
||||||
|
if (!string.IsNullOrEmpty(eventFilePath))
|
||||||
|
{
|
||||||
|
_eventMonitor = new PianobarEventMonitor();
|
||||||
|
_eventMonitor.EventReceived += OnEventReceived;
|
||||||
|
_eventMonitor.StartMonitoring(eventFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
RaiseEvent($"Pianobar started successfully (PID: {_process.Id})");
|
||||||
|
RaiseEvent($"I/O log: {_ioLogPath}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -106,6 +171,59 @@ namespace PainoBar_Helper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string? FindInPath(string command)
|
||||||
|
{
|
||||||
|
// Check if the command is available in PATH
|
||||||
|
var pathEnv = Environment.GetEnvironmentVariable("PATH");
|
||||||
|
if (string.IsNullOrEmpty(pathEnv))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var paths = pathEnv.Split(Path.PathSeparator);
|
||||||
|
var extensions = new[] { ".exe", ".cmd", ".bat", "" };
|
||||||
|
|
||||||
|
// Remove .exe if already present in command
|
||||||
|
string baseCommand = Path.GetFileNameWithoutExtension(command);
|
||||||
|
string commandExt = Path.GetExtension(command);
|
||||||
|
|
||||||
|
foreach (var path in paths)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(path))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var ext in extensions)
|
||||||
|
{
|
||||||
|
// If command already has extension, use it as-is
|
||||||
|
if (!string.IsNullOrEmpty(commandExt))
|
||||||
|
{
|
||||||
|
var fullPath = Path.Combine(path, command);
|
||||||
|
if (File.Exists(fullPath))
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var fullPath = Path.Combine(path, baseCommand + ext);
|
||||||
|
if (File.Exists(fullPath))
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Skip paths that cause errors
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsCommandInPath(string command)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(FindInPath(command));
|
||||||
|
}
|
||||||
|
|
||||||
public void Disconnect()
|
public void Disconnect()
|
||||||
{
|
{
|
||||||
if (!IsConnected)
|
if (!IsConnected)
|
||||||
@@ -113,7 +231,33 @@ namespace PainoBar_Helper
|
|||||||
|
|
||||||
Cleanup();
|
Cleanup();
|
||||||
IsConnected = false;
|
IsConnected = false;
|
||||||
RaiseEvent("Disconnected from Pianobar");
|
RaiseEvent("Disconnected from Pianobar (process detached)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisconnectAndKill()
|
||||||
|
{
|
||||||
|
if (!IsConnected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_process != null && !_process.HasExited)
|
||||||
|
{
|
||||||
|
int pid = _process.Id;
|
||||||
|
_process.Kill();
|
||||||
|
_process.WaitForExit(5000); // Wait up to 5 seconds for graceful exit
|
||||||
|
RaiseEvent($"Pianobar process killed (PID: {pid})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RaiseEvent($"Error killing process: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Cleanup();
|
||||||
|
IsConnected = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendCommand(char command)
|
public void SendCommand(char command)
|
||||||
@@ -172,6 +316,13 @@ namespace PainoBar_Helper
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
string line = e.Data;
|
string line = e.Data;
|
||||||
|
if (!_receivedStdOut)
|
||||||
|
{
|
||||||
|
_receivedStdOut = true;
|
||||||
|
RaiseEvent("Received output on STDOUT");
|
||||||
|
}
|
||||||
|
|
||||||
|
LogIoLine("STDOUT", line);
|
||||||
OutputReceived?.Invoke(this, line);
|
OutputReceived?.Invoke(this, line);
|
||||||
|
|
||||||
ParseOutput(line);
|
ParseOutput(line);
|
||||||
@@ -180,15 +331,44 @@ namespace PainoBar_Helper
|
|||||||
private void Process_ErrorDataReceived(object? sender, DataReceivedEventArgs e)
|
private void Process_ErrorDataReceived(object? sender, DataReceivedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(e.Data))
|
if (!string.IsNullOrEmpty(e.Data))
|
||||||
|
{
|
||||||
|
if (!_receivedStdErr)
|
||||||
|
{
|
||||||
|
_receivedStdErr = true;
|
||||||
|
RaiseEvent("Received output on STDERR");
|
||||||
|
}
|
||||||
|
|
||||||
|
LogIoLine("STDERR", e.Data);
|
||||||
OutputReceived?.Invoke(this, e.Data);
|
OutputReceived?.Invoke(this, e.Data);
|
||||||
|
ParseOutput(e.Data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Process_Exited(object? sender, EventArgs e)
|
private void Process_Exited(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
if (!_receivedStdOut)
|
||||||
|
RaiseEvent("No STDOUT data received before process exit");
|
||||||
|
|
||||||
IsConnected = false;
|
IsConnected = false;
|
||||||
RaiseEvent("Pianobar process exited");
|
RaiseEvent("Pianobar process exited");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LogIoLine(string source, string line)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var logDir = Path.GetDirectoryName(_ioLogPath);
|
||||||
|
if (!string.IsNullOrEmpty(logDir))
|
||||||
|
Directory.CreateDirectory(logDir);
|
||||||
|
|
||||||
|
File.AppendAllText(_ioLogPath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] [{source}] {line}{Environment.NewLine}");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore logging errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ParseOutput(string line)
|
private void ParseOutput(string line)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -278,6 +458,51 @@ namespace PainoBar_Helper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnEventReceived(object? sender, PianobarEventData eventData)
|
||||||
|
{
|
||||||
|
// Update metadata from event file
|
||||||
|
if (!string.IsNullOrEmpty(eventData.Title))
|
||||||
|
CurrentSong = eventData.Title;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(eventData.Artist))
|
||||||
|
CurrentArtist = eventData.Artist;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(eventData.Album))
|
||||||
|
CurrentAlbum = eventData.Album;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(eventData.CoverArt))
|
||||||
|
CurrentAlbumArtUrl = eventData.CoverArt;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(eventData.StationName))
|
||||||
|
{
|
||||||
|
CurrentStation = eventData.StationName;
|
||||||
|
StationChanged?.Invoke(this, CurrentStation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raise event based on event type
|
||||||
|
if (!string.IsNullOrEmpty(eventData.Event))
|
||||||
|
{
|
||||||
|
switch (eventData.Event.ToLower())
|
||||||
|
{
|
||||||
|
case "songstart":
|
||||||
|
IsPlaying = true;
|
||||||
|
TriggerSongChanged();
|
||||||
|
break;
|
||||||
|
case "songfinish":
|
||||||
|
break;
|
||||||
|
case "songlove":
|
||||||
|
RaiseEvent("Song Loved");
|
||||||
|
break;
|
||||||
|
case "songban":
|
||||||
|
RaiseEvent("Song Banned");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
RaiseEvent($"Event: {eventData.Event}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void RaiseEvent(string eventText)
|
private void RaiseEvent(string eventText)
|
||||||
{
|
{
|
||||||
EventOccurred?.Invoke(this, new PianobarEventArgs { EventText = eventText });
|
EventOccurred?.Invoke(this, new PianobarEventArgs { EventText = eventText });
|
||||||
@@ -287,6 +512,14 @@ namespace PainoBar_Helper
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Stop event monitoring
|
||||||
|
if (_eventMonitor != null)
|
||||||
|
{
|
||||||
|
_eventMonitor.EventReceived -= OnEventReceived;
|
||||||
|
_eventMonitor.Dispose();
|
||||||
|
_eventMonitor = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (_process != null)
|
if (_process != null)
|
||||||
{
|
{
|
||||||
_process.OutputDataReceived -= Process_OutputDataReceived;
|
_process.OutputDataReceived -= Process_OutputDataReceived;
|
||||||
|
|||||||
+81
-1
@@ -45,7 +45,7 @@ namespace PainoBar_Helper
|
|||||||
throw new ArgumentException($"Profile '{profileName}' does not exist.");
|
throw new ArgumentException($"Profile '{profileName}' does not exist.");
|
||||||
|
|
||||||
_activeProfileName = profileName;
|
_activeProfileName = profileName;
|
||||||
SaveProfiles();
|
SaveProfiles(); // Save immediately to persist the active profile
|
||||||
ActiveProfileChanged?.Invoke(this, EventArgs.Empty);
|
ActiveProfileChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +155,10 @@ namespace PainoBar_Helper
|
|||||||
if (!string.IsNullOrEmpty(configDir))
|
if (!string.IsNullOrEmpty(configDir))
|
||||||
Directory.CreateDirectory(configDir);
|
Directory.CreateDirectory(configDir);
|
||||||
|
|
||||||
|
// Generate event command script path
|
||||||
|
var eventCmdPath = Path.Combine(configDir, "eventcmd.cmd");
|
||||||
|
GenerateEventCommandScript(eventCmdPath, profile);
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("# Pianobar configuration file");
|
sb.AppendLine("# Pianobar configuration file");
|
||||||
sb.AppendLine($"# Generated by Pianobar Helper for profile: {profile.Name}");
|
sb.AppendLine($"# Generated by Pianobar Helper for profile: {profile.Name}");
|
||||||
@@ -169,6 +173,16 @@ namespace PainoBar_Helper
|
|||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add event command directive
|
||||||
|
sb.AppendLine($"event_command = \"{eventCmdPath}\"");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Additional settings for better integration
|
||||||
|
sb.AppendLine("# Output formatting");
|
||||||
|
sb.AppendLine("format_nowplaying_song = %t|%a|%l|%r");
|
||||||
|
sb.AppendLine("format_station_list = %i) %n");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
File.WriteAllText(settings.ConfigFilePath, sb.ToString());
|
File.WriteAllText(settings.ConfigFilePath, sb.ToString());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -177,6 +191,53 @@ namespace PainoBar_Helper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GenerateEventCommandScript(string scriptPath, Profile profile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Create the directory for the event output file
|
||||||
|
var appDataPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
"PianobarHelper"
|
||||||
|
);
|
||||||
|
Directory.CreateDirectory(appDataPath);
|
||||||
|
var eventOutputPath = Path.Combine(appDataPath, $"events_{profile.Name}.json");
|
||||||
|
|
||||||
|
// Generate Windows batch script for event handling
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("@echo off");
|
||||||
|
sb.AppendLine("REM Pianobar event command script");
|
||||||
|
sb.AppendLine("REM Generated by Pianobar Helper");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"set \"EVENT_OUTPUT={eventOutputPath}\"");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("REM Create JSON output for the event");
|
||||||
|
sb.AppendLine("(");
|
||||||
|
sb.AppendLine(" echo {");
|
||||||
|
sb.AppendLine(" echo \"event\": \"%1\",");
|
||||||
|
sb.AppendLine(" echo \"timestamp\": \"%date% %time%\",");
|
||||||
|
sb.AppendLine(" echo \"artist\": \"%artist%\",");
|
||||||
|
sb.AppendLine(" echo \"title\": \"%title%\",");
|
||||||
|
sb.AppendLine(" echo \"album\": \"%album%\",");
|
||||||
|
sb.AppendLine(" echo \"coverArt\": \"%coverArt%\",");
|
||||||
|
sb.AppendLine(" echo \"stationName\": \"%stationName%\",");
|
||||||
|
sb.AppendLine(" echo \"songDuration\": \"%songDuration%\",");
|
||||||
|
sb.AppendLine(" echo \"songPlayed\": \"%songPlayed%\",");
|
||||||
|
sb.AppendLine(" echo \"rating\": \"%rating%\",");
|
||||||
|
sb.AppendLine(" echo \"detailUrl\": \"%detailUrl%\"");
|
||||||
|
sb.AppendLine(" echo }");
|
||||||
|
sb.AppendLine(") > \"%EVENT_OUTPUT%\"");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("exit /b 0");
|
||||||
|
|
||||||
|
File.WriteAllText(scriptPath, sb.ToString());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Failed to generate event command script: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string? GetActiveConfigPath()
|
public string? GetActiveConfigPath()
|
||||||
{
|
{
|
||||||
var activeProfile = GetActiveProfile();
|
var activeProfile = GetActiveProfile();
|
||||||
@@ -184,6 +245,19 @@ namespace PainoBar_Helper
|
|||||||
return activeProfile.Settings.ConfigFilePath;
|
return activeProfile.Settings.ConfigFilePath;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string? GetEventOutputPath()
|
||||||
|
{
|
||||||
|
var activeProfile = GetActiveProfile();
|
||||||
|
if (activeProfile == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var appDataPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
"PianobarHelper"
|
||||||
|
);
|
||||||
|
return Path.Combine(appDataPath, $"events_{activeProfile.Name}.json");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Profile
|
public class Profile
|
||||||
@@ -194,6 +268,9 @@ namespace PainoBar_Helper
|
|||||||
|
|
||||||
public class ProfileSettings
|
public class ProfileSettings
|
||||||
{
|
{
|
||||||
|
// Pianobar Path
|
||||||
|
public string PianobarPath { get; set; } = "pianobar.exe";
|
||||||
|
|
||||||
// Output Settings
|
// Output Settings
|
||||||
public bool EnableTextFile { get; set; }
|
public bool EnableTextFile { get; set; }
|
||||||
public string TextFilePath { get; set; } = string.Empty;
|
public string TextFilePath { get; set; } = string.Empty;
|
||||||
@@ -209,6 +286,9 @@ namespace PainoBar_Helper
|
|||||||
public string ConfigFilePath { get; set; } = string.Empty;
|
public string ConfigFilePath { get; set; } = string.Empty;
|
||||||
public string PianobarUser { get; set; } = string.Empty;
|
public string PianobarUser { get; set; } = string.Empty;
|
||||||
public string PianobarPassword { get; set; } = string.Empty;
|
public string PianobarPassword { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// Process Management
|
||||||
|
public bool KillProcessOnDisconnect { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ProfileData
|
internal class ProfileData
|
||||||
|
|||||||
Generated
+234
-188
@@ -29,36 +29,39 @@
|
|||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
profiles_group_box = new GroupBox();
|
profiles_group_box = new GroupBox();
|
||||||
selected_profile_label = new Label();
|
|
||||||
profiles_combo_box_selector = new ComboBox();
|
|
||||||
new_profile_button = new Button();
|
|
||||||
delete_profile_button = new Button();
|
delete_profile_button = new Button();
|
||||||
|
new_profile_button = new Button();
|
||||||
|
profiles_combo_box_selector = new ComboBox();
|
||||||
|
selected_profile_label = new Label();
|
||||||
profile_name_label = new Label();
|
profile_name_label = new Label();
|
||||||
profile_name_text_box = new TextBox();
|
profile_name_text_box = new TextBox();
|
||||||
profile_configuration_group_box = new GroupBox();
|
profile_configuration_group_box = new GroupBox();
|
||||||
pianobar_path_label = new Label();
|
|
||||||
pianobar_path_text_box = new TextBox();
|
|
||||||
browse_button = new Button();
|
|
||||||
profile_config_tab_control = new TabControl();
|
profile_config_tab_control = new TabControl();
|
||||||
outputs_tab_page = new TabPage();
|
outputs_tab_page = new TabPage();
|
||||||
pianobar_override_tab_page = new TabPage();
|
|
||||||
obs_text_output_check_box = new CheckBox();
|
|
||||||
output_label = new Label();
|
|
||||||
output_folder_text_box = new TextBox();
|
|
||||||
output_folder_browse_button = new Button();
|
|
||||||
local_web_server_check_box = new CheckBox();
|
|
||||||
port_label = new Label();
|
|
||||||
textBox1 = new TextBox();
|
|
||||||
chk_toast_notifications = new CheckBox();
|
|
||||||
chkSMTC = new CheckBox();
|
chkSMTC = new CheckBox();
|
||||||
chkOverrideCredentials = new CheckBox();
|
chk_toast_notifications = new CheckBox();
|
||||||
txtConfigFilePathLabel = new Label();
|
textBox1 = new TextBox();
|
||||||
txtConfigFilePath = new TextBox();
|
port_label = new Label();
|
||||||
btnBrowseConfigFile = new Button();
|
local_web_server_check_box = new CheckBox();
|
||||||
UsernameLabel = new Label();
|
output_folder_browse_button = new Button();
|
||||||
txtPianobarUser = new TextBox();
|
output_folder_text_box = new TextBox();
|
||||||
PasswordLabel = new Label();
|
output_label = new Label();
|
||||||
|
obs_text_output_check_box = new CheckBox();
|
||||||
|
pianobar_override_tab_page = new TabPage();
|
||||||
txtPianobarPass = new TextBox();
|
txtPianobarPass = new TextBox();
|
||||||
|
PasswordLabel = new Label();
|
||||||
|
txtPianobarUser = new TextBox();
|
||||||
|
UsernameLabel = new Label();
|
||||||
|
btnBrowseConfigFile = new Button();
|
||||||
|
txtConfigFilePath = new TextBox();
|
||||||
|
txtConfigFilePathLabel = new Label();
|
||||||
|
chkOverrideCredentials = new CheckBox();
|
||||||
|
painobar_exe_browse_button = new Button();
|
||||||
|
pianobar_path_text_box = new TextBox();
|
||||||
|
pianobar_path_label = new Label();
|
||||||
|
save_button = new Button();
|
||||||
|
cancel_button = new Button();
|
||||||
|
chkkillpro = new CheckBox();
|
||||||
profiles_group_box.SuspendLayout();
|
profiles_group_box.SuspendLayout();
|
||||||
profile_configuration_group_box.SuspendLayout();
|
profile_configuration_group_box.SuspendLayout();
|
||||||
profile_config_tab_control.SuspendLayout();
|
profile_config_tab_control.SuspendLayout();
|
||||||
@@ -79,23 +82,15 @@
|
|||||||
profiles_group_box.TabStop = false;
|
profiles_group_box.TabStop = false;
|
||||||
profiles_group_box.Text = "Profile Management";
|
profiles_group_box.Text = "Profile Management";
|
||||||
//
|
//
|
||||||
// selected_profile_label
|
// delete_profile_button
|
||||||
//
|
//
|
||||||
selected_profile_label.AutoSize = true;
|
delete_profile_button.Location = new Point(329, 49);
|
||||||
selected_profile_label.Location = new Point(16, 36);
|
delete_profile_button.Name = "delete_profile_button";
|
||||||
selected_profile_label.Name = "selected_profile_label";
|
delete_profile_button.Size = new Size(75, 23);
|
||||||
selected_profile_label.Size = new Size(102, 17);
|
delete_profile_button.TabIndex = 3;
|
||||||
selected_profile_label.TabIndex = 0;
|
delete_profile_button.Text = "Delete";
|
||||||
selected_profile_label.Text = "Selected profile:";
|
delete_profile_button.UseVisualStyleBackColor = true;
|
||||||
//
|
delete_profile_button.Click += delete_profile_button_Click;
|
||||||
// profiles_combo_box_selector
|
|
||||||
//
|
|
||||||
profiles_combo_box_selector.FormattingEnabled = true;
|
|
||||||
profiles_combo_box_selector.Location = new Point(124, 32);
|
|
||||||
profiles_combo_box_selector.Name = "profiles_combo_box_selector";
|
|
||||||
profiles_combo_box_selector.Size = new Size(199, 25);
|
|
||||||
profiles_combo_box_selector.TabIndex = 1;
|
|
||||||
profiles_combo_box_selector.SelectedIndexChanged += profiles_combo_box_selector_SelectedIndexChanged;
|
|
||||||
//
|
//
|
||||||
// new_profile_button
|
// new_profile_button
|
||||||
//
|
//
|
||||||
@@ -107,15 +102,24 @@
|
|||||||
new_profile_button.UseVisualStyleBackColor = true;
|
new_profile_button.UseVisualStyleBackColor = true;
|
||||||
new_profile_button.Click += new_profile_button_Click;
|
new_profile_button.Click += new_profile_button_Click;
|
||||||
//
|
//
|
||||||
// delete_profile_button
|
// profiles_combo_box_selector
|
||||||
//
|
//
|
||||||
delete_profile_button.Location = new Point(329, 49);
|
profiles_combo_box_selector.FormattingEnabled = true;
|
||||||
delete_profile_button.Name = "delete_profile_button";
|
profiles_combo_box_selector.Location = new Point(124, 32);
|
||||||
delete_profile_button.Size = new Size(75, 23);
|
profiles_combo_box_selector.Name = "profiles_combo_box_selector";
|
||||||
delete_profile_button.TabIndex = 3;
|
profiles_combo_box_selector.Size = new Size(199, 25);
|
||||||
delete_profile_button.Text = "Delete";
|
profiles_combo_box_selector.TabIndex = 1;
|
||||||
delete_profile_button.UseVisualStyleBackColor = true;
|
profiles_combo_box_selector.SelectedIndexChanged += profiles_combo_box_selector_SelectedIndexChanged;
|
||||||
delete_profile_button.Click += delete_profile_button_Click;
|
profiles_combo_box_selector.TextChanged += profiles_combo_box_selector_TextChanged;
|
||||||
|
//
|
||||||
|
// selected_profile_label
|
||||||
|
//
|
||||||
|
selected_profile_label.AutoSize = true;
|
||||||
|
selected_profile_label.Location = new Point(16, 36);
|
||||||
|
selected_profile_label.Name = "selected_profile_label";
|
||||||
|
selected_profile_label.Size = new Size(102, 17);
|
||||||
|
selected_profile_label.TabIndex = 0;
|
||||||
|
selected_profile_label.Text = "Selected profile:";
|
||||||
//
|
//
|
||||||
// profile_name_label
|
// profile_name_label
|
||||||
//
|
//
|
||||||
@@ -136,7 +140,7 @@
|
|||||||
// profile_configuration_group_box
|
// profile_configuration_group_box
|
||||||
//
|
//
|
||||||
profile_configuration_group_box.Controls.Add(profile_config_tab_control);
|
profile_configuration_group_box.Controls.Add(profile_config_tab_control);
|
||||||
profile_configuration_group_box.Controls.Add(browse_button);
|
profile_configuration_group_box.Controls.Add(painobar_exe_browse_button);
|
||||||
profile_configuration_group_box.Controls.Add(pianobar_path_text_box);
|
profile_configuration_group_box.Controls.Add(pianobar_path_text_box);
|
||||||
profile_configuration_group_box.Controls.Add(pianobar_path_label);
|
profile_configuration_group_box.Controls.Add(pianobar_path_label);
|
||||||
profile_configuration_group_box.Controls.Add(profile_name_text_box);
|
profile_configuration_group_box.Controls.Add(profile_name_text_box);
|
||||||
@@ -148,31 +152,6 @@
|
|||||||
profile_configuration_group_box.TabStop = false;
|
profile_configuration_group_box.TabStop = false;
|
||||||
profile_configuration_group_box.Text = "Profile Configuration";
|
profile_configuration_group_box.Text = "Profile Configuration";
|
||||||
//
|
//
|
||||||
// pianobar_path_label
|
|
||||||
//
|
|
||||||
pianobar_path_label.AutoSize = true;
|
|
||||||
pianobar_path_label.Location = new Point(12, 55);
|
|
||||||
pianobar_path_label.Name = "pianobar_path_label";
|
|
||||||
pianobar_path_label.Size = new Size(86, 17);
|
|
||||||
pianobar_path_label.TabIndex = 2;
|
|
||||||
pianobar_path_label.Text = "Pianobar Path:";
|
|
||||||
//
|
|
||||||
// pianobar_path_text_box
|
|
||||||
//
|
|
||||||
pianobar_path_text_box.Location = new Point(104, 53);
|
|
||||||
pianobar_path_text_box.Name = "pianobar_path_text_box";
|
|
||||||
pianobar_path_text_box.Size = new Size(224, 24);
|
|
||||||
pianobar_path_text_box.TabIndex = 3;
|
|
||||||
//
|
|
||||||
// browse_button
|
|
||||||
//
|
|
||||||
browse_button.Location = new Point(334, 53);
|
|
||||||
browse_button.Name = "browse_button";
|
|
||||||
browse_button.Size = new Size(75, 23);
|
|
||||||
browse_button.TabIndex = 4;
|
|
||||||
browse_button.Text = "Browse";
|
|
||||||
browse_button.UseVisualStyleBackColor = true;
|
|
||||||
//
|
|
||||||
// profile_config_tab_control
|
// profile_config_tab_control
|
||||||
//
|
//
|
||||||
profile_config_tab_control.Controls.Add(outputs_tab_page);
|
profile_config_tab_control.Controls.Add(outputs_tab_page);
|
||||||
@@ -202,8 +181,91 @@
|
|||||||
outputs_tab_page.Text = "Outputs";
|
outputs_tab_page.Text = "Outputs";
|
||||||
outputs_tab_page.UseVisualStyleBackColor = true;
|
outputs_tab_page.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
|
// chkSMTC
|
||||||
|
//
|
||||||
|
chkSMTC.AutoSize = true;
|
||||||
|
chkSMTC.Location = new Point(18, 198);
|
||||||
|
chkSMTC.Name = "chkSMTC";
|
||||||
|
chkSMTC.Size = new Size(251, 21);
|
||||||
|
chkSMTC.TabIndex = 8;
|
||||||
|
chkSMTC.Text = "Windows System Media Controls (SMTC)";
|
||||||
|
chkSMTC.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// chk_toast_notifications
|
||||||
|
//
|
||||||
|
chk_toast_notifications.AutoSize = true;
|
||||||
|
chk_toast_notifications.Location = new Point(18, 149);
|
||||||
|
chk_toast_notifications.Name = "chk_toast_notifications";
|
||||||
|
chk_toast_notifications.Size = new Size(223, 21);
|
||||||
|
chk_toast_notifications.TabIndex = 7;
|
||||||
|
chk_toast_notifications.Text = "Show Windows Toast Notifications";
|
||||||
|
chk_toast_notifications.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// textBox1
|
||||||
|
//
|
||||||
|
textBox1.Location = new Point(111, 105);
|
||||||
|
textBox1.Name = "textBox1";
|
||||||
|
textBox1.Size = new Size(169, 24);
|
||||||
|
textBox1.TabIndex = 6;
|
||||||
|
//
|
||||||
|
// port_label
|
||||||
|
//
|
||||||
|
port_label.AutoSize = true;
|
||||||
|
port_label.Location = new Point(18, 110);
|
||||||
|
port_label.Name = "port_label";
|
||||||
|
port_label.Size = new Size(36, 17);
|
||||||
|
port_label.TabIndex = 5;
|
||||||
|
port_label.Text = "Port:";
|
||||||
|
//
|
||||||
|
// local_web_server_check_box
|
||||||
|
//
|
||||||
|
local_web_server_check_box.AutoSize = true;
|
||||||
|
local_web_server_check_box.Location = new Point(18, 77);
|
||||||
|
local_web_server_check_box.Name = "local_web_server_check_box";
|
||||||
|
local_web_server_check_box.Size = new Size(166, 21);
|
||||||
|
local_web_server_check_box.TabIndex = 4;
|
||||||
|
local_web_server_check_box.Text = "Enable Local Web Server";
|
||||||
|
local_web_server_check_box.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// output_folder_browse_button
|
||||||
|
//
|
||||||
|
output_folder_browse_button.Location = new Point(288, 45);
|
||||||
|
output_folder_browse_button.Name = "output_folder_browse_button";
|
||||||
|
output_folder_browse_button.Size = new Size(75, 23);
|
||||||
|
output_folder_browse_button.TabIndex = 3;
|
||||||
|
output_folder_browse_button.Text = "Browse";
|
||||||
|
output_folder_browse_button.UseVisualStyleBackColor = true;
|
||||||
|
output_folder_browse_button.Click += output_folder_browse_button_Click;
|
||||||
|
//
|
||||||
|
// output_folder_text_box
|
||||||
|
//
|
||||||
|
output_folder_text_box.Location = new Point(111, 43);
|
||||||
|
output_folder_text_box.Name = "output_folder_text_box";
|
||||||
|
output_folder_text_box.Size = new Size(169, 24);
|
||||||
|
output_folder_text_box.TabIndex = 2;
|
||||||
|
//
|
||||||
|
// output_label
|
||||||
|
//
|
||||||
|
output_label.AutoSize = true;
|
||||||
|
output_label.Location = new Point(18, 47);
|
||||||
|
output_label.Name = "output_label";
|
||||||
|
output_label.Size = new Size(91, 17);
|
||||||
|
output_label.TabIndex = 1;
|
||||||
|
output_label.Text = "Output Folder:";
|
||||||
|
//
|
||||||
|
// obs_text_output_check_box
|
||||||
|
//
|
||||||
|
obs_text_output_check_box.AutoSize = true;
|
||||||
|
obs_text_output_check_box.Location = new Point(18, 18);
|
||||||
|
obs_text_output_check_box.Name = "obs_text_output_check_box";
|
||||||
|
obs_text_output_check_box.Size = new Size(219, 21);
|
||||||
|
obs_text_output_check_box.TabIndex = 0;
|
||||||
|
obs_text_output_check_box.Text = "File output (For OBS / Streaming)";
|
||||||
|
obs_text_output_check_box.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
// pianobar_override_tab_page
|
// pianobar_override_tab_page
|
||||||
//
|
//
|
||||||
|
pianobar_override_tab_page.Controls.Add(chkkillpro);
|
||||||
pianobar_override_tab_page.Controls.Add(txtPianobarPass);
|
pianobar_override_tab_page.Controls.Add(txtPianobarPass);
|
||||||
pianobar_override_tab_page.Controls.Add(PasswordLabel);
|
pianobar_override_tab_page.Controls.Add(PasswordLabel);
|
||||||
pianobar_override_tab_page.Controls.Add(txtPianobarUser);
|
pianobar_override_tab_page.Controls.Add(txtPianobarUser);
|
||||||
@@ -220,87 +282,68 @@
|
|||||||
pianobar_override_tab_page.Text = "Pianobar Config & Auth";
|
pianobar_override_tab_page.Text = "Pianobar Config & Auth";
|
||||||
pianobar_override_tab_page.UseVisualStyleBackColor = true;
|
pianobar_override_tab_page.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
// obs_text_output_check_box
|
// txtPianobarPass
|
||||||
//
|
//
|
||||||
obs_text_output_check_box.AutoSize = true;
|
txtPianobarPass.Enabled = false;
|
||||||
obs_text_output_check_box.Location = new Point(18, 18);
|
txtPianobarPass.Location = new Point(133, 109);
|
||||||
obs_text_output_check_box.Name = "obs_text_output_check_box";
|
txtPianobarPass.Name = "txtPianobarPass";
|
||||||
obs_text_output_check_box.Size = new Size(219, 21);
|
txtPianobarPass.Size = new Size(152, 24);
|
||||||
obs_text_output_check_box.TabIndex = 0;
|
txtPianobarPass.TabIndex = 7;
|
||||||
obs_text_output_check_box.Text = "File output (For OBS / Streaming)";
|
txtPianobarPass.UseSystemPasswordChar = true;
|
||||||
obs_text_output_check_box.UseVisualStyleBackColor = true;
|
|
||||||
//
|
//
|
||||||
// output_label
|
// PasswordLabel
|
||||||
//
|
//
|
||||||
output_label.AutoSize = true;
|
PasswordLabel.AutoSize = true;
|
||||||
output_label.Location = new Point(18, 47);
|
PasswordLabel.Location = new Point(19, 115);
|
||||||
output_label.Name = "output_label";
|
PasswordLabel.Name = "PasswordLabel";
|
||||||
output_label.Size = new Size(91, 17);
|
PasswordLabel.Size = new Size(62, 17);
|
||||||
output_label.TabIndex = 1;
|
PasswordLabel.TabIndex = 6;
|
||||||
output_label.Text = "Output Folder:";
|
PasswordLabel.Text = "Password:";
|
||||||
//
|
//
|
||||||
// output_folder_text_box
|
// txtPianobarUser
|
||||||
//
|
//
|
||||||
output_folder_text_box.Location = new Point(111, 43);
|
txtPianobarUser.Enabled = false;
|
||||||
output_folder_text_box.Name = "output_folder_text_box";
|
txtPianobarUser.Location = new Point(134, 76);
|
||||||
output_folder_text_box.Size = new Size(169, 24);
|
txtPianobarUser.Name = "txtPianobarUser";
|
||||||
output_folder_text_box.TabIndex = 2;
|
txtPianobarUser.Size = new Size(152, 24);
|
||||||
|
txtPianobarUser.TabIndex = 5;
|
||||||
//
|
//
|
||||||
// output_folder_browse_button
|
// UsernameLabel
|
||||||
//
|
//
|
||||||
output_folder_browse_button.Location = new Point(288, 45);
|
UsernameLabel.AutoSize = true;
|
||||||
output_folder_browse_button.Name = "output_folder_browse_button";
|
UsernameLabel.Location = new Point(19, 79);
|
||||||
output_folder_browse_button.Size = new Size(75, 23);
|
UsernameLabel.Name = "UsernameLabel";
|
||||||
output_folder_browse_button.TabIndex = 3;
|
UsernameLabel.Size = new Size(109, 17);
|
||||||
output_folder_browse_button.Text = "Browse";
|
UsernameLabel.TabIndex = 4;
|
||||||
output_folder_browse_button.UseVisualStyleBackColor = true;
|
UsernameLabel.Text = "Username / Email:";
|
||||||
output_folder_browse_button.Click += output_folder_browse_button_Click;
|
|
||||||
//
|
//
|
||||||
// local_web_server_check_box
|
// btnBrowseConfigFile
|
||||||
//
|
//
|
||||||
local_web_server_check_box.AutoSize = true;
|
btnBrowseConfigFile.Enabled = false;
|
||||||
local_web_server_check_box.Location = new Point(18, 77);
|
btnBrowseConfigFile.Location = new Point(292, 43);
|
||||||
local_web_server_check_box.Name = "local_web_server_check_box";
|
btnBrowseConfigFile.Name = "btnBrowseConfigFile";
|
||||||
local_web_server_check_box.Size = new Size(166, 21);
|
btnBrowseConfigFile.Size = new Size(75, 23);
|
||||||
local_web_server_check_box.TabIndex = 4;
|
btnBrowseConfigFile.TabIndex = 3;
|
||||||
local_web_server_check_box.Text = "Enable Local Web Server";
|
btnBrowseConfigFile.Text = "Browse";
|
||||||
local_web_server_check_box.UseVisualStyleBackColor = true;
|
btnBrowseConfigFile.UseVisualStyleBackColor = true;
|
||||||
|
btnBrowseConfigFile.Click += btnBrowseConfigFile_Click;
|
||||||
//
|
//
|
||||||
// port_label
|
// txtConfigFilePath
|
||||||
//
|
//
|
||||||
port_label.AutoSize = true;
|
txtConfigFilePath.Enabled = false;
|
||||||
port_label.Location = new Point(18, 110);
|
txtConfigFilePath.Location = new Point(121, 42);
|
||||||
port_label.Name = "port_label";
|
txtConfigFilePath.Name = "txtConfigFilePath";
|
||||||
port_label.Size = new Size(36, 17);
|
txtConfigFilePath.Size = new Size(165, 24);
|
||||||
port_label.TabIndex = 5;
|
txtConfigFilePath.TabIndex = 2;
|
||||||
port_label.Text = "Port:";
|
|
||||||
//
|
//
|
||||||
// textBox1
|
// txtConfigFilePathLabel
|
||||||
//
|
//
|
||||||
textBox1.Location = new Point(111, 105);
|
txtConfigFilePathLabel.AutoSize = true;
|
||||||
textBox1.Name = "textBox1";
|
txtConfigFilePathLabel.Location = new Point(14, 44);
|
||||||
textBox1.Size = new Size(169, 24);
|
txtConfigFilePathLabel.Name = "txtConfigFilePathLabel";
|
||||||
textBox1.TabIndex = 6;
|
txtConfigFilePathLabel.Size = new Size(105, 17);
|
||||||
//
|
txtConfigFilePathLabel.TabIndex = 1;
|
||||||
// chk_toast_notifications
|
txtConfigFilePathLabel.Text = "Config Save Path:";
|
||||||
//
|
|
||||||
chk_toast_notifications.AutoSize = true;
|
|
||||||
chk_toast_notifications.Location = new Point(18, 149);
|
|
||||||
chk_toast_notifications.Name = "chk_toast_notifications";
|
|
||||||
chk_toast_notifications.Size = new Size(223, 21);
|
|
||||||
chk_toast_notifications.TabIndex = 7;
|
|
||||||
chk_toast_notifications.Text = "Show Windows Toast Notifications";
|
|
||||||
chk_toast_notifications.UseVisualStyleBackColor = true;
|
|
||||||
//
|
|
||||||
// chkSMTC
|
|
||||||
//
|
|
||||||
chkSMTC.AutoSize = true;
|
|
||||||
chkSMTC.Location = new Point(18, 198);
|
|
||||||
chkSMTC.Name = "chkSMTC";
|
|
||||||
chkSMTC.Size = new Size(251, 21);
|
|
||||||
chkSMTC.TabIndex = 8;
|
|
||||||
chkSMTC.Text = "Windows System Media Controls (SMTC)";
|
|
||||||
chkSMTC.UseVisualStyleBackColor = true;
|
|
||||||
//
|
//
|
||||||
// chkOverrideCredentials
|
// chkOverrideCredentials
|
||||||
//
|
//
|
||||||
@@ -313,70 +356,70 @@
|
|||||||
chkOverrideCredentials.UseVisualStyleBackColor = true;
|
chkOverrideCredentials.UseVisualStyleBackColor = true;
|
||||||
chkOverrideCredentials.CheckedChanged += chkOverrideCredentials_CheckedChanged;
|
chkOverrideCredentials.CheckedChanged += chkOverrideCredentials_CheckedChanged;
|
||||||
//
|
//
|
||||||
// txtConfigFilePathLabel
|
// painobar_exe_browse_button
|
||||||
//
|
//
|
||||||
txtConfigFilePathLabel.AutoSize = true;
|
painobar_exe_browse_button.Location = new Point(334, 53);
|
||||||
txtConfigFilePathLabel.Location = new Point(14, 44);
|
painobar_exe_browse_button.Name = "painobar_exe_browse_button";
|
||||||
txtConfigFilePathLabel.Name = "txtConfigFilePathLabel";
|
painobar_exe_browse_button.Size = new Size(75, 23);
|
||||||
txtConfigFilePathLabel.Size = new Size(105, 17);
|
painobar_exe_browse_button.TabIndex = 4;
|
||||||
txtConfigFilePathLabel.TabIndex = 1;
|
painobar_exe_browse_button.Text = "Browse";
|
||||||
txtConfigFilePathLabel.Text = "Config Save Path:";
|
painobar_exe_browse_button.UseVisualStyleBackColor = true;
|
||||||
|
painobar_exe_browse_button.Click += painobar_exe_browse_button_Click;
|
||||||
//
|
//
|
||||||
// txtConfigFilePath
|
// pianobar_path_text_box
|
||||||
//
|
//
|
||||||
txtConfigFilePath.Location = new Point(121, 42);
|
pianobar_path_text_box.Location = new Point(104, 53);
|
||||||
txtConfigFilePath.Name = "txtConfigFilePath";
|
pianobar_path_text_box.Name = "pianobar_path_text_box";
|
||||||
txtConfigFilePath.Size = new Size(165, 24);
|
pianobar_path_text_box.Size = new Size(224, 24);
|
||||||
txtConfigFilePath.TabIndex = 2;
|
pianobar_path_text_box.TabIndex = 3;
|
||||||
//
|
//
|
||||||
// btnBrowseConfigFile
|
// pianobar_path_label
|
||||||
//
|
//
|
||||||
btnBrowseConfigFile.Location = new Point(292, 43);
|
pianobar_path_label.AutoSize = true;
|
||||||
btnBrowseConfigFile.Name = "btnBrowseConfigFile";
|
pianobar_path_label.Location = new Point(12, 55);
|
||||||
btnBrowseConfigFile.Size = new Size(75, 23);
|
pianobar_path_label.Name = "pianobar_path_label";
|
||||||
btnBrowseConfigFile.TabIndex = 3;
|
pianobar_path_label.Size = new Size(86, 17);
|
||||||
btnBrowseConfigFile.Text = "Browse";
|
pianobar_path_label.TabIndex = 2;
|
||||||
btnBrowseConfigFile.UseVisualStyleBackColor = true;
|
pianobar_path_label.Text = "Pianobar Path:";
|
||||||
btnBrowseConfigFile.Click += btnBrowseConfigFile_Click;
|
|
||||||
//
|
//
|
||||||
// UsernameLabel
|
// save_button
|
||||||
//
|
//
|
||||||
UsernameLabel.AutoSize = true;
|
save_button.Location = new Point(66, 551);
|
||||||
UsernameLabel.Location = new Point(19, 79);
|
save_button.Name = "save_button";
|
||||||
UsernameLabel.Name = "UsernameLabel";
|
save_button.Size = new Size(75, 23);
|
||||||
UsernameLabel.Size = new Size(109, 17);
|
save_button.TabIndex = 2;
|
||||||
UsernameLabel.TabIndex = 4;
|
save_button.Text = "Save";
|
||||||
UsernameLabel.Text = "Username / Email:";
|
save_button.UseVisualStyleBackColor = true;
|
||||||
|
save_button.Click += btnSave_Click;
|
||||||
//
|
//
|
||||||
// txtPianobarUser
|
// cancel_button
|
||||||
//
|
//
|
||||||
txtPianobarUser.Location = new Point(134, 76);
|
cancel_button.Location = new Point(303, 551);
|
||||||
txtPianobarUser.Name = "txtPianobarUser";
|
cancel_button.Name = "cancel_button";
|
||||||
txtPianobarUser.Size = new Size(152, 24);
|
cancel_button.Size = new Size(75, 23);
|
||||||
txtPianobarUser.TabIndex = 5;
|
cancel_button.TabIndex = 3;
|
||||||
|
cancel_button.Text = "Cancel";
|
||||||
|
cancel_button.UseVisualStyleBackColor = true;
|
||||||
|
cancel_button.Click += btnCancel_Click;
|
||||||
//
|
//
|
||||||
// PasswordLabel
|
// chkkillpro
|
||||||
//
|
//
|
||||||
PasswordLabel.AutoSize = true;
|
chkkillpro.AutoSize = true;
|
||||||
PasswordLabel.Location = new Point(19, 115);
|
chkkillpro.Enabled = false;
|
||||||
PasswordLabel.Name = "PasswordLabel";
|
chkkillpro.Location = new Point(262, 150);
|
||||||
PasswordLabel.Size = new Size(62, 17);
|
chkkillpro.Name = "chkkillpro";
|
||||||
PasswordLabel.TabIndex = 6;
|
chkkillpro.Size = new Size(92, 21);
|
||||||
PasswordLabel.Text = "Password:";
|
chkkillpro.TabIndex = 8;
|
||||||
//
|
chkkillpro.Text = "Kill process";
|
||||||
// txtPianobarPass
|
chkkillpro.UseVisualStyleBackColor = true;
|
||||||
//
|
|
||||||
txtPianobarPass.Location = new Point(133, 109);
|
|
||||||
txtPianobarPass.Name = "txtPianobarPass";
|
|
||||||
txtPianobarPass.Size = new Size(152, 24);
|
|
||||||
txtPianobarPass.TabIndex = 7;
|
|
||||||
txtPianobarPass.UseSystemPasswordChar = true;
|
|
||||||
//
|
//
|
||||||
// SettingsForm
|
// SettingsForm
|
||||||
//
|
//
|
||||||
AutoScaleDimensions = new SizeF(7F, 17F);
|
AutoScaleDimensions = new SizeF(7F, 17F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
ClientSize = new Size(454, 586);
|
ClientSize = new Size(454, 586);
|
||||||
|
Controls.Add(cancel_button);
|
||||||
|
Controls.Add(save_button);
|
||||||
Controls.Add(profile_configuration_group_box);
|
Controls.Add(profile_configuration_group_box);
|
||||||
Controls.Add(profiles_group_box);
|
Controls.Add(profiles_group_box);
|
||||||
Name = "SettingsForm";
|
Name = "SettingsForm";
|
||||||
@@ -403,7 +446,7 @@
|
|||||||
private Label profile_name_label;
|
private Label profile_name_label;
|
||||||
private TextBox profile_name_text_box;
|
private TextBox profile_name_text_box;
|
||||||
private GroupBox profile_configuration_group_box;
|
private GroupBox profile_configuration_group_box;
|
||||||
private Button browse_button;
|
private Button painobar_exe_browse_button;
|
||||||
private TextBox pianobar_path_text_box;
|
private TextBox pianobar_path_text_box;
|
||||||
private Label pianobar_path_label;
|
private Label pianobar_path_label;
|
||||||
private TabControl profile_config_tab_control;
|
private TabControl profile_config_tab_control;
|
||||||
@@ -426,5 +469,8 @@
|
|||||||
private Label UsernameLabel;
|
private Label UsernameLabel;
|
||||||
private Button btnBrowseConfigFile;
|
private Button btnBrowseConfigFile;
|
||||||
private TextBox txtPianobarPass;
|
private TextBox txtPianobarPass;
|
||||||
|
private Button save_button;
|
||||||
|
private Button cancel_button;
|
||||||
|
private CheckBox chkkillpro;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+155
-27
@@ -13,6 +13,7 @@ namespace PainoBar_Helper
|
|||||||
_profileManager = profileManager;
|
_profileManager = profileManager;
|
||||||
_currentSettings = new ProfileSettings
|
_currentSettings = new ProfileSettings
|
||||||
{
|
{
|
||||||
|
PianobarPath = currentSettings.PianobarPath,
|
||||||
EnableTextFile = currentSettings.EnableTextFile,
|
EnableTextFile = currentSettings.EnableTextFile,
|
||||||
TextFilePath = currentSettings.TextFilePath,
|
TextFilePath = currentSettings.TextFilePath,
|
||||||
EnableWebServer = currentSettings.EnableWebServer,
|
EnableWebServer = currentSettings.EnableWebServer,
|
||||||
@@ -22,7 +23,8 @@ namespace PainoBar_Helper
|
|||||||
OverrideCredentials = currentSettings.OverrideCredentials,
|
OverrideCredentials = currentSettings.OverrideCredentials,
|
||||||
ConfigFilePath = currentSettings.ConfigFilePath,
|
ConfigFilePath = currentSettings.ConfigFilePath,
|
||||||
PianobarUser = currentSettings.PianobarUser,
|
PianobarUser = currentSettings.PianobarUser,
|
||||||
PianobarPassword = currentSettings.PianobarPassword
|
PianobarPassword = currentSettings.PianobarPassword,
|
||||||
|
KillProcessOnDisconnect = currentSettings.KillProcessOnDisconnect
|
||||||
};
|
};
|
||||||
|
|
||||||
_currentProfileName = profileManager.ActiveProfileName;
|
_currentProfileName = profileManager.ActiveProfileName;
|
||||||
@@ -43,6 +45,8 @@ namespace PainoBar_Helper
|
|||||||
if (profiles_combo_box_selector == null)
|
if (profiles_combo_box_selector == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
string? currentText = profiles_combo_box_selector.Text;
|
||||||
|
|
||||||
profiles_combo_box_selector.Items.Clear();
|
profiles_combo_box_selector.Items.Clear();
|
||||||
foreach (var profile in _profileManager.Profiles)
|
foreach (var profile in _profileManager.Profiles)
|
||||||
{
|
{
|
||||||
@@ -51,10 +55,20 @@ namespace PainoBar_Helper
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(_currentProfileName))
|
if (!string.IsNullOrEmpty(_currentProfileName))
|
||||||
profiles_combo_box_selector.SelectedItem = _currentProfileName;
|
profiles_combo_box_selector.SelectedItem = _currentProfileName;
|
||||||
|
else if (!string.IsNullOrEmpty(currentText))
|
||||||
|
profiles_combo_box_selector.Text = currentText;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadSettingsToControls()
|
private void LoadSettingsToControls()
|
||||||
{
|
{
|
||||||
|
// Update profile name text box with current profile name
|
||||||
|
if (profile_name_text_box != null && !string.IsNullOrEmpty(_currentProfileName))
|
||||||
|
profile_name_text_box.Text = _currentProfileName;
|
||||||
|
|
||||||
|
// Pianobar executable path
|
||||||
|
if (pianobar_path_text_box != null)
|
||||||
|
pianobar_path_text_box.Text = _currentSettings.PianobarPath;
|
||||||
|
|
||||||
// Output Settings (Tab 1)
|
// Output Settings (Tab 1)
|
||||||
if (obs_text_output_check_box != null)
|
if (obs_text_output_check_box != null)
|
||||||
obs_text_output_check_box.Checked = _currentSettings.EnableTextFile;
|
obs_text_output_check_box.Checked = _currentSettings.EnableTextFile;
|
||||||
@@ -89,10 +103,17 @@ namespace PainoBar_Helper
|
|||||||
|
|
||||||
if (txtPianobarPass != null)
|
if (txtPianobarPass != null)
|
||||||
txtPianobarPass.Text = _currentSettings.PianobarPassword;
|
txtPianobarPass.Text = _currentSettings.PianobarPassword;
|
||||||
|
|
||||||
|
if (chkkillpro != null)
|
||||||
|
chkkillpro.Checked = _currentSettings.KillProcessOnDisconnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveSettingsFromControls()
|
private void SaveSettingsFromControls()
|
||||||
{
|
{
|
||||||
|
// Pianobar Path
|
||||||
|
if (pianobar_path_text_box != null)
|
||||||
|
_currentSettings.PianobarPath = pianobar_path_text_box.Text;
|
||||||
|
|
||||||
// Output Settings
|
// Output Settings
|
||||||
if (obs_text_output_check_box != null)
|
if (obs_text_output_check_box != null)
|
||||||
_currentSettings.EnableTextFile = obs_text_output_check_box.Checked;
|
_currentSettings.EnableTextFile = obs_text_output_check_box.Checked;
|
||||||
@@ -124,6 +145,9 @@ namespace PainoBar_Helper
|
|||||||
|
|
||||||
if (txtPianobarPass != null)
|
if (txtPianobarPass != null)
|
||||||
_currentSettings.PianobarPassword = txtPianobarPass.Text;
|
_currentSettings.PianobarPassword = txtPianobarPass.Text;
|
||||||
|
|
||||||
|
if (chkkillpro != null)
|
||||||
|
_currentSettings.KillProcessOnDisconnect = chkkillpro.Checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -133,46 +157,86 @@ namespace PainoBar_Helper
|
|||||||
// Wire this to profiles_combo_box_selector.SelectedIndexChanged in the Designer
|
// Wire this to profiles_combo_box_selector.SelectedIndexChanged in the Designer
|
||||||
public void profiles_combo_box_selector_SelectedIndexChanged(object? sender, EventArgs e)
|
public void profiles_combo_box_selector_SelectedIndexChanged(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is not ComboBox cmb || cmb.SelectedItem is not string profileName)
|
if (sender is not ComboBox cmb)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var profile = _profileManager.Profiles.FirstOrDefault(p => p.Name == profileName);
|
// Update profile name text box when selection changes
|
||||||
if (profile != null)
|
if (profile_name_text_box != null)
|
||||||
|
profile_name_text_box.Text = cmb.Text;
|
||||||
|
|
||||||
|
// If selecting an existing profile, load its settings
|
||||||
|
if (cmb.SelectedItem is string profileName)
|
||||||
{
|
{
|
||||||
_currentProfileName = profileName;
|
var profile = _profileManager.Profiles.FirstOrDefault(p => p.Name == profileName);
|
||||||
_currentSettings = new ProfileSettings
|
if (profile != null)
|
||||||
{
|
{
|
||||||
EnableTextFile = profile.Settings.EnableTextFile,
|
_currentProfileName = profileName;
|
||||||
TextFilePath = profile.Settings.TextFilePath,
|
_currentSettings = new ProfileSettings
|
||||||
EnableWebServer = profile.Settings.EnableWebServer,
|
{
|
||||||
WebServerPort = profile.Settings.WebServerPort,
|
PianobarPath = profile.Settings.PianobarPath,
|
||||||
EnableToastNotifications = profile.Settings.EnableToastNotifications,
|
EnableTextFile = profile.Settings.EnableTextFile,
|
||||||
EnableSMTC = profile.Settings.EnableSMTC,
|
TextFilePath = profile.Settings.TextFilePath,
|
||||||
OverrideCredentials = profile.Settings.OverrideCredentials,
|
EnableWebServer = profile.Settings.EnableWebServer,
|
||||||
ConfigFilePath = profile.Settings.ConfigFilePath,
|
WebServerPort = profile.Settings.WebServerPort,
|
||||||
PianobarUser = profile.Settings.PianobarUser,
|
EnableToastNotifications = profile.Settings.EnableToastNotifications,
|
||||||
PianobarPassword = profile.Settings.PianobarPassword
|
EnableSMTC = profile.Settings.EnableSMTC,
|
||||||
};
|
OverrideCredentials = profile.Settings.OverrideCredentials,
|
||||||
LoadSettingsToControls();
|
ConfigFilePath = profile.Settings.ConfigFilePath,
|
||||||
|
PianobarUser = profile.Settings.PianobarUser,
|
||||||
|
PianobarPassword = profile.Settings.PianobarPassword,
|
||||||
|
KillProcessOnDisconnect = profile.Settings.KillProcessOnDisconnect
|
||||||
|
};
|
||||||
|
LoadSettingsToControls();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wire this to profiles_combo_box_selector.TextChanged in the Designer
|
||||||
|
public void profiles_combo_box_selector_TextChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not ComboBox cmb)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Keep profile name text box in sync with combo box text
|
||||||
|
if (profile_name_text_box != null)
|
||||||
|
profile_name_text_box.Text = cmb.Text;
|
||||||
|
}
|
||||||
|
|
||||||
// Wire this to new_profile_button.Click in the Designer
|
// Wire this to new_profile_button.Click in the Designer
|
||||||
public void new_profile_button_Click(object? sender, EventArgs e)
|
public void new_profile_button_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
string? profileName = PromptForProfileName("New Profile", "Enter profile name:");
|
// Get the profile name from the combo box text
|
||||||
|
string? profileName = profiles_combo_box_selector?.Text?.Trim();
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(profileName))
|
if (string.IsNullOrWhiteSpace(profileName))
|
||||||
|
{
|
||||||
|
MessageBox.Show("Please enter a profile name in the dropdown box.", "No Profile Name",
|
||||||
|
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if profile already exists
|
||||||
|
if (_profileManager.Profiles.Any(p => p.Name.Equals(profileName, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Profile '{profileName}' already exists. Please choose a different name.", "Profile Exists",
|
||||||
|
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var newSettings = new ProfileSettings();
|
// Create new profile with current settings
|
||||||
_profileManager.CreateProfile(profileName, newSettings);
|
SaveSettingsFromControls();
|
||||||
|
_profileManager.CreateProfile(profileName, _currentSettings);
|
||||||
|
_currentProfileName = profileName;
|
||||||
|
|
||||||
LoadProfileList();
|
LoadProfileList();
|
||||||
if (profiles_combo_box_selector != null)
|
if (profiles_combo_box_selector != null)
|
||||||
profiles_combo_box_selector.SelectedItem = profileName;
|
profiles_combo_box_selector.SelectedItem = profileName;
|
||||||
|
|
||||||
|
if (profile_name_text_box != null)
|
||||||
|
profile_name_text_box.Text = profileName;
|
||||||
|
|
||||||
MessageBox.Show($"Profile '{profileName}' created successfully.", "Profile Created",
|
MessageBox.Show($"Profile '{profileName}' created successfully.", "Profile Created",
|
||||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
}
|
}
|
||||||
@@ -186,20 +250,32 @@ namespace PainoBar_Helper
|
|||||||
// Wire this to delete_profile_button.Click in the Designer
|
// Wire this to delete_profile_button.Click in the Designer
|
||||||
public void delete_profile_button_Click(object? sender, EventArgs e)
|
public void delete_profile_button_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_currentProfileName))
|
// Get the profile name from either the combo box or the profile name text box
|
||||||
|
string? profileName = profiles_combo_box_selector?.Text?.Trim();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(profileName))
|
||||||
{
|
{
|
||||||
MessageBox.Show("No profile selected.", "No Selection", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
MessageBox.Show("No profile selected.", "No Selection", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = MessageBox.Show($"Are you sure you want to delete profile '{_currentProfileName}'?",
|
// Check if profile exists
|
||||||
|
if (!_profileManager.Profiles.Any(p => p.Name.Equals(profileName, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Profile '{profileName}' does not exist.", "Profile Not Found", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = MessageBox.Show($"Are you sure you want to delete profile '{profileName}'?",
|
||||||
"Confirm Deletion", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
|
"Confirm Deletion", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
|
||||||
|
|
||||||
if (result == DialogResult.Yes)
|
if (result == DialogResult.Yes)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_profileManager.DeleteProfile(_currentProfileName);
|
_profileManager.DeleteProfile(profileName);
|
||||||
|
|
||||||
|
// Reset to active profile or clear
|
||||||
_currentProfileName = _profileManager.ActiveProfileName;
|
_currentProfileName = _profileManager.ActiveProfileName;
|
||||||
LoadProfileList();
|
LoadProfileList();
|
||||||
|
|
||||||
@@ -209,8 +285,18 @@ namespace PainoBar_Helper
|
|||||||
_currentSettings = activeProfile.Settings;
|
_currentSettings = activeProfile.Settings;
|
||||||
LoadSettingsToControls();
|
LoadSettingsToControls();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No profiles left, clear the form
|
||||||
|
_currentSettings = new ProfileSettings();
|
||||||
|
LoadSettingsToControls();
|
||||||
|
if (profiles_combo_box_selector != null)
|
||||||
|
profiles_combo_box_selector.Text = string.Empty;
|
||||||
|
if (profile_name_text_box != null)
|
||||||
|
profile_name_text_box.Text = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
MessageBox.Show("Profile deleted successfully.", "Profile Deleted",
|
MessageBox.Show($"Profile '{profileName}' deleted successfully.", "Profile Deleted",
|
||||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -225,6 +311,24 @@ namespace PainoBar_Helper
|
|||||||
|
|
||||||
#region File Browsing
|
#region File Browsing
|
||||||
|
|
||||||
|
// Wire this to painobar_exe_browse_button.Click in the Designer
|
||||||
|
public void painobar_exe_browse_button_Click(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
using var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*",
|
||||||
|
Title = "Select Pianobar Executable",
|
||||||
|
FileName = "pianobar.exe",
|
||||||
|
CheckFileExists = true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() == DialogResult.OK)
|
||||||
|
{
|
||||||
|
if (pianobar_path_text_box != null)
|
||||||
|
pianobar_path_text_box.Text = dialog.FileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Wire this to output_folder_browse_button.Click in the Designer
|
// Wire this to output_folder_browse_button.Click in the Designer
|
||||||
public void output_folder_browse_button_Click(object? sender, EventArgs e)
|
public void output_folder_browse_button_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
@@ -283,6 +387,9 @@ namespace PainoBar_Helper
|
|||||||
|
|
||||||
if (txtPianobarPass != null)
|
if (txtPianobarPass != null)
|
||||||
txtPianobarPass.Enabled = enabled;
|
txtPianobarPass.Enabled = enabled;
|
||||||
|
|
||||||
|
if (chkkillpro != null)
|
||||||
|
chkkillpro.Enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -296,11 +403,32 @@ namespace PainoBar_Helper
|
|||||||
{
|
{
|
||||||
SaveSettingsFromControls();
|
SaveSettingsFromControls();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_currentProfileName))
|
// Get the profile name from the text box (or combo box as fallback)
|
||||||
|
string? profileName = profile_name_text_box?.Text?.Trim();
|
||||||
|
if (string.IsNullOrEmpty(profileName))
|
||||||
|
profileName = profiles_combo_box_selector?.Text?.Trim();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(profileName))
|
||||||
{
|
{
|
||||||
_profileManager.UpdateProfile(_currentProfileName, _currentSettings);
|
MessageBox.Show("Please enter a profile name before saving.", "No Profile Name",
|
||||||
|
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update existing profile or create new one
|
||||||
|
if (_profileManager.Profiles.Any(p => p.Name.Equals(profileName, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
_profileManager.UpdateProfile(profileName, _currentSettings);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_profileManager.CreateProfile(profileName, _currentSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set this profile as the active profile
|
||||||
|
_profileManager.SetActiveProfile(profileName);
|
||||||
|
_currentProfileName = profileName;
|
||||||
|
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+16
-16
@@ -29,9 +29,9 @@
|
|||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
station_list_group_box = new GroupBox();
|
station_list_group_box = new GroupBox();
|
||||||
search_label = new Label();
|
|
||||||
textBox1 = new TextBox();
|
|
||||||
lstStations = new ListBox();
|
lstStations = new ListBox();
|
||||||
|
textBox1 = new TextBox();
|
||||||
|
search_label = new Label();
|
||||||
refresh_button = new Button();
|
refresh_button = new Button();
|
||||||
btnSelectStation = new Button();
|
btnSelectStation = new Button();
|
||||||
btnCancelStation = new Button();
|
btnCancelStation = new Button();
|
||||||
@@ -50,14 +50,13 @@
|
|||||||
station_list_group_box.TabStop = false;
|
station_list_group_box.TabStop = false;
|
||||||
station_list_group_box.Text = "Available Stations";
|
station_list_group_box.Text = "Available Stations";
|
||||||
//
|
//
|
||||||
// search_label
|
// lstStations
|
||||||
//
|
//
|
||||||
search_label.AutoSize = true;
|
lstStations.FormattingEnabled = true;
|
||||||
search_label.Location = new Point(26, 32);
|
lstStations.Location = new Point(26, 65);
|
||||||
search_label.Name = "search_label";
|
lstStations.Name = "lstStations";
|
||||||
search_label.Size = new Size(50, 17);
|
lstStations.Size = new Size(332, 174);
|
||||||
search_label.TabIndex = 0;
|
lstStations.TabIndex = 2;
|
||||||
search_label.Text = "Search:";
|
|
||||||
//
|
//
|
||||||
// textBox1
|
// textBox1
|
||||||
//
|
//
|
||||||
@@ -67,13 +66,14 @@
|
|||||||
textBox1.TabIndex = 1;
|
textBox1.TabIndex = 1;
|
||||||
textBox1.TextChanged += textBox1_TextChanged;
|
textBox1.TextChanged += textBox1_TextChanged;
|
||||||
//
|
//
|
||||||
// lstStations
|
// search_label
|
||||||
//
|
//
|
||||||
lstStations.FormattingEnabled = true;
|
search_label.AutoSize = true;
|
||||||
lstStations.Location = new Point(26, 65);
|
search_label.Location = new Point(26, 32);
|
||||||
lstStations.Name = "lstStations";
|
search_label.Name = "search_label";
|
||||||
lstStations.Size = new Size(332, 174);
|
search_label.Size = new Size(50, 17);
|
||||||
lstStations.TabIndex = 2;
|
search_label.TabIndex = 0;
|
||||||
|
search_label.Text = "Search:";
|
||||||
//
|
//
|
||||||
// refresh_button
|
// refresh_button
|
||||||
//
|
//
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
btnSelectStation.Name = "btnSelectStation";
|
btnSelectStation.Name = "btnSelectStation";
|
||||||
btnSelectStation.Size = new Size(117, 23);
|
btnSelectStation.Size = new Size(117, 23);
|
||||||
btnSelectStation.TabIndex = 2;
|
btnSelectStation.TabIndex = 2;
|
||||||
btnSelectStation.Text = "Refresh stations";
|
btnSelectStation.Text = "Select Station";
|
||||||
btnSelectStation.UseVisualStyleBackColor = true;
|
btnSelectStation.UseVisualStyleBackColor = true;
|
||||||
btnSelectStation.Click += btnSelectStation_Click;
|
btnSelectStation.Click += btnSelectStation_Click;
|
||||||
//
|
//
|
||||||
|
|||||||
+33
-7
@@ -6,6 +6,7 @@ namespace PainoBar_Helper
|
|||||||
private List<string> _stations;
|
private List<string> _stations;
|
||||||
private List<string> _filteredStations;
|
private List<string> _filteredStations;
|
||||||
private bool _stationsLoaded = false;
|
private bool _stationsLoaded = false;
|
||||||
|
private bool _stationEntriesStarted = false;
|
||||||
|
|
||||||
public StationForm(PianobarManager pianobarManager)
|
public StationForm(PianobarManager pianobarManager)
|
||||||
{
|
{
|
||||||
@@ -16,7 +17,6 @@ namespace PainoBar_Helper
|
|||||||
_filteredStations = new List<string>();
|
_filteredStations = new List<string>();
|
||||||
|
|
||||||
InitializeControls();
|
InitializeControls();
|
||||||
LoadStations();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Initialization
|
#region Initialization
|
||||||
@@ -32,8 +32,21 @@ namespace PainoBar_Helper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnShown(EventArgs e)
|
||||||
|
{
|
||||||
|
base.OnShown(e);
|
||||||
|
|
||||||
|
if (!_stationsLoaded)
|
||||||
|
LoadStations();
|
||||||
|
}
|
||||||
|
|
||||||
private void LoadStations()
|
private void LoadStations()
|
||||||
{
|
{
|
||||||
|
_stations.Clear();
|
||||||
|
_filteredStations.Clear();
|
||||||
|
_stationsLoaded = false;
|
||||||
|
_stationEntriesStarted = false;
|
||||||
|
|
||||||
// Subscribe to output to capture station list
|
// Subscribe to output to capture station list
|
||||||
_pianobarManager.OutputReceived += PianobarManager_OutputReceived;
|
_pianobarManager.OutputReceived += PianobarManager_OutputReceived;
|
||||||
|
|
||||||
@@ -62,28 +75,41 @@ namespace PainoBar_Helper
|
|||||||
|
|
||||||
private void PianobarManager_OutputReceived(object? sender, string e)
|
private void PianobarManager_OutputReceived(object? sender, string e)
|
||||||
{
|
{
|
||||||
|
var line = StripAnsiCodes(e).Trim();
|
||||||
|
if (string.IsNullOrEmpty(line))
|
||||||
|
return;
|
||||||
|
|
||||||
// Parse station list from Pianobar output
|
// Parse station list from Pianobar output
|
||||||
// Format is typically: "0) Station Name"
|
// Handles formats like: "0) Station", "*0) Station", " 12) Station"
|
||||||
var match = System.Text.RegularExpressions.Regex.Match(e, @"^\s*(\d+)\)\s*(.+)$");
|
var match = System.Text.RegularExpressions.Regex.Match(line, @"^\s*\*?\s*(\d+)\)\s*(.+)$");
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
|
_stationEntriesStarted = true;
|
||||||
string stationName = match.Groups[2].Value.Trim();
|
string stationName = match.Groups[2].Value.Trim();
|
||||||
if (!_stations.Contains(stationName))
|
if (!_stations.Contains(stationName))
|
||||||
{
|
{
|
||||||
_stations.Add(stationName);
|
_stations.Add(stationName);
|
||||||
Invoke(() => UpdateStationList());
|
if (!IsDisposed && IsHandleCreated)
|
||||||
|
BeginInvoke(() => UpdateStationList());
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect end of station list
|
// Detect end of station list only after we started receiving entries
|
||||||
if (e.Contains("Select station:") || e.Contains("[?]"))
|
if (_stationEntriesStarted && line.Contains("Select station:", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_stationsLoaded = true;
|
_stationsLoaded = true;
|
||||||
_pianobarManager.OutputReceived -= PianobarManager_OutputReceived;
|
_pianobarManager.OutputReceived -= PianobarManager_OutputReceived;
|
||||||
Invoke(() => FinalizeStationList());
|
if (!IsDisposed && IsHandleCreated)
|
||||||
|
BeginInvoke(() => FinalizeStationList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string StripAnsiCodes(string value)
|
||||||
|
{
|
||||||
|
return System.Text.RegularExpressions.Regex.Replace(value, @"\x1B\[[0-9;]*[A-Za-z]", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateStationList()
|
private void UpdateStationList()
|
||||||
{
|
{
|
||||||
if (lstStations == null)
|
if (lstStations == null)
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# Testing Guide - Kill Process on Disconnect
|
||||||
|
|
||||||
|
## Quick Test Steps
|
||||||
|
|
||||||
|
### Test 1: Verify Initial State
|
||||||
|
1. **Run the application**
|
||||||
|
2. **Open Settings** (File ? Settings)
|
||||||
|
3. **Navigate to "Pianobar Config & Auth" tab**
|
||||||
|
4. **Verify**:
|
||||||
|
- [ ] "Enable Profile Credentials and Custom Config Generation" checkbox is **unchecked**
|
||||||
|
- [ ] "Kill process" checkbox is **disabled** (grayed out, cannot be clicked)
|
||||||
|
- [ ] Config file path field is **disabled**
|
||||||
|
- [ ] Browse button is **disabled**
|
||||||
|
- [ ] Username field is **disabled**
|
||||||
|
- [ ] Password field is **disabled**
|
||||||
|
|
||||||
|
### Test 2: Enable Override and Kill Process
|
||||||
|
1. **Check** "Enable Profile Credentials and Custom Config Generation"
|
||||||
|
2. **Verify**:
|
||||||
|
- [ ] "Kill process" checkbox becomes **enabled**
|
||||||
|
- [ ] All credential fields become **enabled**
|
||||||
|
3. **Check** "Kill process" checkbox
|
||||||
|
4. **Click Save**
|
||||||
|
5. **Re-open Settings**
|
||||||
|
6. **Verify**:
|
||||||
|
- [ ] "Enable Profile Credentials..." remains **checked**
|
||||||
|
- [ ] "Kill process" checkbox remains **checked**
|
||||||
|
- [ ] Setting persists correctly
|
||||||
|
|
||||||
|
### Test 3: Connect and Kill on Disconnect
|
||||||
|
1. **Ensure a profile with Kill Process enabled is active**
|
||||||
|
2. **Set Pianobar executable path** if not already set
|
||||||
|
3. **Click Connect**
|
||||||
|
4. **Verify**:
|
||||||
|
- [ ] Pianobar starts (status shows "Connected to Pianobar")
|
||||||
|
- [ ] PID is displayed in status bar (e.g., "PID: 12345")
|
||||||
|
5. **Click Disconnect**
|
||||||
|
6. **Verify**:
|
||||||
|
- [ ] Status shows "Pianobar process killed (PID: XXXX)"
|
||||||
|
- [ ] Pianobar process is **terminated** (check Task Manager)
|
||||||
|
|
||||||
|
### Test 4: Connect and Detach on Disconnect
|
||||||
|
1. **Open Settings**
|
||||||
|
2. **Navigate to "Pianobar Config & Auth" tab**
|
||||||
|
3. **Verify** "Enable Profile Credentials..." is **checked**
|
||||||
|
4. **Uncheck** "Kill process" checkbox
|
||||||
|
5. **Click Save**
|
||||||
|
6. **Click Connect**
|
||||||
|
7. **Verify**:
|
||||||
|
- [ ] Pianobar starts successfully
|
||||||
|
8. **Click Disconnect**
|
||||||
|
9. **Verify**:
|
||||||
|
- [ ] Status shows "Disconnected from Pianobar (process detached)"
|
||||||
|
- [ ] Pianobar process is **still running** in Task Manager
|
||||||
|
- [ ] Music continues playing
|
||||||
|
|
||||||
|
### Test 5: Disable Override (Gating)
|
||||||
|
1. **Open Settings**
|
||||||
|
2. **Navigate to "Pianobar Config & Auth" tab**
|
||||||
|
3. **Uncheck** "Enable Profile Credentials and Custom Config Generation"
|
||||||
|
4. **Verify**:
|
||||||
|
- [ ] "Kill process" checkbox becomes **disabled** (grayed out)
|
||||||
|
- [ ] All credential fields become **disabled**
|
||||||
|
- [ ] Cannot check "Kill process"
|
||||||
|
5. **Try to check "Kill process"** (should not be possible)
|
||||||
|
6. **Click Save**
|
||||||
|
7. **Close Settings**
|
||||||
|
|
||||||
|
### Test 6: Output Folder Browse Button
|
||||||
|
1. **Open Settings**
|
||||||
|
2. **Navigate to "Outputs" tab**
|
||||||
|
3. **Click** the **Browse** button next to the output folder text box (ID: `output_folder_browse_button`)
|
||||||
|
4. **Verify**:
|
||||||
|
- [ ] SaveFileDialog opens with title "Select Text File Output Path"
|
||||||
|
- [ ] Default filename is "now_playing.txt"
|
||||||
|
- [ ] Can select a location
|
||||||
|
5. **Select a location** and click OK
|
||||||
|
6. **Verify**:
|
||||||
|
- [ ] Selected path appears in the output folder text box
|
||||||
|
7. **Click Save**
|
||||||
|
8. **Verify**:
|
||||||
|
- [ ] Output path is saved to the profile
|
||||||
|
|
||||||
|
## Edge Case Tests
|
||||||
|
|
||||||
|
### Edge 1: Switch Profiles with Different Settings
|
||||||
|
1. **Create two profiles**:
|
||||||
|
- Profile A: Kill process **enabled**
|
||||||
|
- Profile B: Kill process **disabled**
|
||||||
|
2. **Switch to Profile A** and connect
|
||||||
|
3. **Disconnect** ? Should **kill** the process
|
||||||
|
4. **Switch to Profile B** and connect
|
||||||
|
5. **Disconnect** ? Should **detach** (not kill)
|
||||||
|
|
||||||
|
### Edge 2: Override Disabled Mid-Session
|
||||||
|
1. **Have Kill process enabled**
|
||||||
|
2. **Open Settings** while connected
|
||||||
|
3. **Uncheck** "Enable Profile Credentials..."
|
||||||
|
4. **Verify** "Kill process" becomes **disabled**
|
||||||
|
5. **Click Save**
|
||||||
|
6. **Disconnect**
|
||||||
|
7. **Verify** process is **detached** (not killed), since override was disabled
|
||||||
|
|
||||||
|
### Edge 3: No Active Profile
|
||||||
|
1. **Delete all profiles** or ensure no profile is active
|
||||||
|
2. **Try to connect**
|
||||||
|
3. **Verify**:
|
||||||
|
- [ ] Error message: "No active profile selected..."
|
||||||
|
- [ ] Cannot connect without a profile
|
||||||
|
|
||||||
|
## Expected Behavior Summary
|
||||||
|
|
||||||
|
| Override Enabled? | Kill Process Checked? | Disconnect Behavior |
|
||||||
|
|-------------------|----------------------|---------------------|
|
||||||
|
| ? No | N/A (disabled) | Detach |
|
||||||
|
| ? Yes | ? No | Detach |
|
||||||
|
| ? Yes | ? Yes | **Kill by PID** |
|
||||||
|
|
||||||
|
## Status Bar Messages
|
||||||
|
|
||||||
|
- **Connect starting**: "Starting Pianobar..."
|
||||||
|
- **Connect success**: "Connected to Pianobar (PID: XXXX)"
|
||||||
|
- **Connect failed**: "Failed to start Pianobar"
|
||||||
|
- **Disconnect (kill)**: "Pianobar process killed (PID: XXXX)"
|
||||||
|
- **Disconnect (detach)**: "Disconnected from Pianobar (process detached)"
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: Kill checkbox is always disabled
|
||||||
|
**Solution**: Make sure "Enable Profile Credentials and Custom Config Generation" is **checked**
|
||||||
|
|
||||||
|
### Issue: Setting doesn't persist
|
||||||
|
**Solution**: Make sure you clicked **Save** before closing the Settings dialog
|
||||||
|
|
||||||
|
### Issue: Process not killed on disconnect
|
||||||
|
**Checks**:
|
||||||
|
- Is "Enable Profile Credentials..." checked?
|
||||||
|
- Is "Kill process" checkbox checked?
|
||||||
|
- Did you save the profile?
|
||||||
|
- Is the correct profile active?
|
||||||
|
|
||||||
|
### Issue: Cannot connect
|
||||||
|
**Checks**:
|
||||||
|
- Is a profile selected and active?
|
||||||
|
- Is the Pianobar executable path set correctly?
|
||||||
|
- Does the Pianobar.exe file exist at that path?
|
||||||
|
|
||||||
|
## Files to Monitor
|
||||||
|
|
||||||
|
- `profiles.json` in `%APPDATA%\PianoBarHelper\`
|
||||||
|
- Check that `"KillProcessOnDisconnect": true` is saved for profiles where it's enabled
|
||||||
|
- Task Manager ? Details
|
||||||
|
- Watch for `pianobar.exe` process appearing/disappearing during connect/disconnect
|
||||||
Reference in New Issue
Block a user