Implemented Smart Save logic for profiles
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB |
@@ -1,3 +1 @@
|
|||||||
# Shackle
|
# Shackle
|
||||||
|
|
||||||

|
|
||||||
331
Shackle/MainForm.Designer.cs
generated
331
Shackle/MainForm.Designer.cs
generated
@@ -14,7 +14,7 @@
|
|||||||
private System.Windows.Forms.Button btnRun;
|
private System.Windows.Forms.Button btnRun;
|
||||||
private System.Windows.Forms.Button btnStop;
|
private System.Windows.Forms.Button btnStop;
|
||||||
private System.Windows.Forms.Button btnOpenCookies;
|
private System.Windows.Forms.Button btnOpenCookies;
|
||||||
private System.Windows.Forms.Button btnEditServiceConfig; // New
|
private System.Windows.Forms.Button btnEditServiceConfig;
|
||||||
private System.Windows.Forms.Button btnEditYaml;
|
private System.Windows.Forms.Button btnEditYaml;
|
||||||
private System.Windows.Forms.Button btnBrowse;
|
private System.Windows.Forms.Button btnBrowse;
|
||||||
private System.Windows.Forms.Button btnClearLog;
|
private System.Windows.Forms.Button btnClearLog;
|
||||||
@@ -22,219 +22,244 @@
|
|||||||
private System.Windows.Forms.PropertyGrid pgProfile;
|
private System.Windows.Forms.PropertyGrid pgProfile;
|
||||||
private System.Windows.Forms.Label lblPath;
|
private System.Windows.Forms.Label lblPath;
|
||||||
|
|
||||||
protected override void Dispose(bool disposing) { if (disposing && (components != null)) components.Dispose(); base.Dispose(disposing); }
|
// FIXED: Correct variable name (was bntnSaveProfile)
|
||||||
|
private System.Windows.Forms.Button btnSaveProfile;
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && (components != null)) components.Dispose();
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||||
txtRootPath = new TextBox();
|
this.txtRootPath = new System.Windows.Forms.TextBox();
|
||||||
txtBinName = new TextBox();
|
this.txtBinName = new System.Windows.Forms.TextBox();
|
||||||
comboProfiles = new ComboBox();
|
this.comboProfiles = new System.Windows.Forms.ComboBox();
|
||||||
btnAddProfile = new Button();
|
this.btnAddProfile = new System.Windows.Forms.Button();
|
||||||
btnRemoveProfile = new Button();
|
this.btnRemoveProfile = new System.Windows.Forms.Button();
|
||||||
comboService = new ComboBox();
|
this.comboService = new System.Windows.Forms.ComboBox();
|
||||||
txtURL = new TextBox();
|
this.txtURL = new System.Windows.Forms.TextBox();
|
||||||
txtCommandPreview = new TextBox();
|
this.txtCommandPreview = new System.Windows.Forms.TextBox();
|
||||||
btnRun = new Button();
|
this.btnRun = new System.Windows.Forms.Button();
|
||||||
btnStop = new Button();
|
this.btnStop = new System.Windows.Forms.Button();
|
||||||
btnOpenCookies = new Button();
|
this.btnOpenCookies = new System.Windows.Forms.Button();
|
||||||
btnEditServiceConfig = new Button();
|
this.btnEditServiceConfig = new System.Windows.Forms.Button();
|
||||||
btnEditYaml = new Button();
|
this.btnEditYaml = new System.Windows.Forms.Button();
|
||||||
btnBrowse = new Button();
|
this.btnBrowse = new System.Windows.Forms.Button();
|
||||||
btnClearLog = new Button();
|
this.btnClearLog = new System.Windows.Forms.Button();
|
||||||
txtLog = new TextBox();
|
this.txtLog = new System.Windows.Forms.TextBox();
|
||||||
pgProfile = new PropertyGrid();
|
this.pgProfile = new System.Windows.Forms.PropertyGrid();
|
||||||
lblPath = new Label();
|
this.lblPath = new System.Windows.Forms.Label();
|
||||||
SuspendLayout();
|
this.btnSaveProfile = new System.Windows.Forms.Button();
|
||||||
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
// txtRootPath
|
// txtRootPath
|
||||||
//
|
//
|
||||||
txtRootPath.Location = new Point(12, 35);
|
this.txtRootPath.Location = new System.Drawing.Point(12, 35);
|
||||||
txtRootPath.Name = "txtRootPath";
|
this.txtRootPath.Name = "txtRootPath";
|
||||||
txtRootPath.Size = new Size(280, 27);
|
this.txtRootPath.Size = new System.Drawing.Size(280, 27);
|
||||||
txtRootPath.TabIndex = 0;
|
this.txtRootPath.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// txtBinName
|
// txtBinName
|
||||||
//
|
//
|
||||||
txtBinName.Location = new Point(340, 35);
|
this.txtBinName.Location = new System.Drawing.Point(340, 35);
|
||||||
txtBinName.Name = "txtBinName";
|
this.txtBinName.Name = "txtBinName";
|
||||||
txtBinName.Size = new Size(60, 27);
|
this.txtBinName.Size = new System.Drawing.Size(60, 27);
|
||||||
txtBinName.TabIndex = 2;
|
this.txtBinName.TabIndex = 2;
|
||||||
//
|
//
|
||||||
// comboProfiles
|
// comboProfiles
|
||||||
//
|
//
|
||||||
comboProfiles.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
this.comboProfiles.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
comboProfiles.Location = new Point(1177, 35);
|
this.comboProfiles.Location = new System.Drawing.Point(1177, 35);
|
||||||
comboProfiles.Name = "comboProfiles";
|
this.comboProfiles.Name = "comboProfiles";
|
||||||
comboProfiles.Size = new Size(240, 28);
|
this.comboProfiles.Size = new System.Drawing.Size(240, 28);
|
||||||
comboProfiles.TabIndex = 4;
|
this.comboProfiles.TabIndex = 4;
|
||||||
//
|
//
|
||||||
// btnAddProfile
|
// btnAddProfile
|
||||||
//
|
//
|
||||||
btnAddProfile.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
this.btnAddProfile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
btnAddProfile.Location = new Point(1422, 34);
|
this.btnAddProfile.Location = new System.Drawing.Point(1422, 34);
|
||||||
btnAddProfile.Name = "btnAddProfile";
|
this.btnAddProfile.Name = "btnAddProfile";
|
||||||
btnAddProfile.Size = new Size(35, 29);
|
this.btnAddProfile.Size = new System.Drawing.Size(35, 29);
|
||||||
btnAddProfile.TabIndex = 5;
|
this.btnAddProfile.TabIndex = 5;
|
||||||
btnAddProfile.Text = "+";
|
this.btnAddProfile.Text = "+";
|
||||||
//
|
//
|
||||||
// btnRemoveProfile
|
// btnRemoveProfile
|
||||||
//
|
//
|
||||||
btnRemoveProfile.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
this.btnRemoveProfile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
btnRemoveProfile.Location = new Point(1462, 34);
|
this.btnRemoveProfile.Location = new System.Drawing.Point(1462, 34);
|
||||||
btnRemoveProfile.Name = "btnRemoveProfile";
|
this.btnRemoveProfile.Name = "btnRemoveProfile";
|
||||||
btnRemoveProfile.Size = new Size(35, 29);
|
this.btnRemoveProfile.Size = new System.Drawing.Size(35, 29);
|
||||||
btnRemoveProfile.TabIndex = 6;
|
this.btnRemoveProfile.TabIndex = 6;
|
||||||
btnRemoveProfile.Text = "-";
|
this.btnRemoveProfile.Text = "-";
|
||||||
//
|
//
|
||||||
// comboService
|
// comboService
|
||||||
//
|
//
|
||||||
comboService.Location = new Point(12, 80);
|
this.comboService.Location = new System.Drawing.Point(12, 80);
|
||||||
comboService.Name = "comboService";
|
this.comboService.Name = "comboService";
|
||||||
comboService.Size = new Size(100, 28);
|
this.comboService.Size = new System.Drawing.Size(100, 28);
|
||||||
comboService.TabIndex = 7;
|
this.comboService.TabIndex = 7;
|
||||||
//
|
//
|
||||||
// txtURL
|
// txtURL
|
||||||
//
|
//
|
||||||
txtURL.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
this.txtURL.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
txtURL.Location = new Point(115, 80);
|
this.txtURL.Location = new System.Drawing.Point(115, 80);
|
||||||
txtURL.Name = "txtURL";
|
this.txtURL.Name = "txtURL";
|
||||||
txtURL.PlaceholderText = "Paste ID / URL here...";
|
this.txtURL.PlaceholderText = "Paste ID / URL here...";
|
||||||
txtURL.Size = new Size(801, 27);
|
this.txtURL.Size = new System.Drawing.Size(801, 27);
|
||||||
txtURL.TabIndex = 8;
|
this.txtURL.TabIndex = 8;
|
||||||
//
|
//
|
||||||
// txtCommandPreview
|
// txtCommandPreview
|
||||||
//
|
//
|
||||||
txtCommandPreview.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
this.txtCommandPreview.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
txtCommandPreview.BackColor = Color.FromArgb(20, 20, 20);
|
this.txtCommandPreview.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(20)))), ((int)(((byte)(20)))), ((int)(((byte)(20)))));
|
||||||
txtCommandPreview.Font = new Font("Consolas", 10F, FontStyle.Bold);
|
this.txtCommandPreview.Font = new System.Drawing.Font("Consolas", 10F, System.Drawing.FontStyle.Bold);
|
||||||
txtCommandPreview.ForeColor = Color.Cyan;
|
this.txtCommandPreview.ForeColor = System.Drawing.Color.Cyan;
|
||||||
txtCommandPreview.Location = new Point(12, 115);
|
this.txtCommandPreview.Location = new System.Drawing.Point(12, 115);
|
||||||
txtCommandPreview.Name = "txtCommandPreview";
|
this.txtCommandPreview.Name = "txtCommandPreview";
|
||||||
txtCommandPreview.Size = new Size(1130, 27);
|
this.txtCommandPreview.Size = new System.Drawing.Size(1130, 27);
|
||||||
txtCommandPreview.TabIndex = 9;
|
this.txtCommandPreview.TabIndex = 9;
|
||||||
//
|
//
|
||||||
// btnRun
|
// btnRun
|
||||||
//
|
//
|
||||||
btnRun.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
this.btnRun.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
btnRun.Location = new Point(12, 150);
|
this.btnRun.Location = new System.Drawing.Point(12, 150);
|
||||||
btnRun.Name = "btnRun";
|
this.btnRun.Name = "btnRun";
|
||||||
btnRun.Size = new Size(1130, 45);
|
this.btnRun.Size = new System.Drawing.Size(1130, 45);
|
||||||
btnRun.TabIndex = 10;
|
this.btnRun.TabIndex = 10;
|
||||||
btnRun.Text = "RUN";
|
this.btnRun.Text = "RUN";
|
||||||
//
|
//
|
||||||
// btnStop
|
// btnStop
|
||||||
//
|
//
|
||||||
btnStop.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
this.btnStop.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
btnStop.BackColor = Color.Maroon;
|
this.btnStop.BackColor = System.Drawing.Color.Maroon;
|
||||||
btnStop.Location = new Point(12, 150);
|
this.btnStop.Location = new System.Drawing.Point(12, 150);
|
||||||
btnStop.Name = "btnStop";
|
this.btnStop.Name = "btnStop";
|
||||||
btnStop.Size = new Size(1130, 45);
|
this.btnStop.Size = new System.Drawing.Size(1130, 45);
|
||||||
btnStop.TabIndex = 11;
|
this.btnStop.TabIndex = 11;
|
||||||
btnStop.Text = "STOP";
|
this.btnStop.Text = "STOP";
|
||||||
btnStop.UseVisualStyleBackColor = false;
|
this.btnStop.UseVisualStyleBackColor = false;
|
||||||
btnStop.Visible = false;
|
this.btnStop.Visible = false;
|
||||||
//
|
//
|
||||||
// btnOpenCookies
|
// btnOpenCookies
|
||||||
//
|
//
|
||||||
btnOpenCookies.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
this.btnOpenCookies.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
btnOpenCookies.Location = new Point(922, 79);
|
this.btnOpenCookies.Location = new System.Drawing.Point(922, 79);
|
||||||
btnOpenCookies.Name = "btnOpenCookies";
|
this.btnOpenCookies.Name = "btnOpenCookies";
|
||||||
btnOpenCookies.Size = new Size(115, 30);
|
this.btnOpenCookies.Size = new System.Drawing.Size(115, 30);
|
||||||
btnOpenCookies.TabIndex = 12;
|
this.btnOpenCookies.TabIndex = 12;
|
||||||
btnOpenCookies.Text = "🍪 Cookies";
|
this.btnOpenCookies.Text = "🍪 Cookies";
|
||||||
//
|
//
|
||||||
// btnEditServiceConfig
|
// btnEditServiceConfig
|
||||||
//
|
//
|
||||||
btnEditServiceConfig.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
this.btnEditServiceConfig.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
btnEditServiceConfig.Location = new Point(1043, 79);
|
this.btnEditServiceConfig.Location = new System.Drawing.Point(1043, 79);
|
||||||
btnEditServiceConfig.Name = "btnEditServiceConfig";
|
this.btnEditServiceConfig.Name = "btnEditServiceConfig";
|
||||||
btnEditServiceConfig.Size = new Size(99, 30);
|
this.btnEditServiceConfig.Size = new System.Drawing.Size(99, 30);
|
||||||
btnEditServiceConfig.TabIndex = 13;
|
this.btnEditServiceConfig.TabIndex = 13;
|
||||||
btnEditServiceConfig.Text = "⚙️ Service";
|
this.btnEditServiceConfig.Text = "⚙️ Service";
|
||||||
//
|
//
|
||||||
// btnEditYaml
|
// btnEditYaml
|
||||||
//
|
//
|
||||||
btnEditYaml.Location = new Point(410, 34);
|
this.btnEditYaml.Location = new System.Drawing.Point(410, 34);
|
||||||
btnEditYaml.Name = "btnEditYaml";
|
this.btnEditYaml.Name = "btnEditYaml";
|
||||||
btnEditYaml.Size = new Size(144, 29);
|
this.btnEditYaml.Size = new System.Drawing.Size(144, 29);
|
||||||
btnEditYaml.TabIndex = 3;
|
this.btnEditYaml.TabIndex = 3;
|
||||||
btnEditYaml.Text = "📝 Main Config";
|
this.btnEditYaml.Text = "📝 Main Config";
|
||||||
//
|
//
|
||||||
// btnBrowse
|
// btnBrowse
|
||||||
//
|
//
|
||||||
btnBrowse.Location = new Point(295, 34);
|
this.btnBrowse.Location = new System.Drawing.Point(295, 34);
|
||||||
btnBrowse.Name = "btnBrowse";
|
this.btnBrowse.Name = "btnBrowse";
|
||||||
btnBrowse.Size = new Size(35, 29);
|
this.btnBrowse.Size = new System.Drawing.Size(35, 29);
|
||||||
btnBrowse.TabIndex = 1;
|
this.btnBrowse.TabIndex = 1;
|
||||||
btnBrowse.Text = "📂";
|
this.btnBrowse.Text = "📂";
|
||||||
//
|
//
|
||||||
// btnClearLog
|
// btnClearLog
|
||||||
//
|
//
|
||||||
btnClearLog.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
this.btnClearLog.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
btnClearLog.Location = new Point(1022, 201);
|
this.btnClearLog.Location = new System.Drawing.Point(1022, 201);
|
||||||
btnClearLog.Name = "btnClearLog";
|
this.btnClearLog.Name = "btnClearLog";
|
||||||
btnClearLog.Size = new Size(120, 36);
|
this.btnClearLog.Size = new System.Drawing.Size(120, 36);
|
||||||
btnClearLog.TabIndex = 14;
|
this.btnClearLog.TabIndex = 14;
|
||||||
btnClearLog.Text = "Clear Log";
|
this.btnClearLog.Text = "Clear Log";
|
||||||
//
|
//
|
||||||
// txtLog
|
// txtLog
|
||||||
//
|
//
|
||||||
txtLog.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
this.txtLog.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
txtLog.BackColor = Color.Black;
|
this.txtLog.BackColor = System.Drawing.Color.Black;
|
||||||
txtLog.Font = new Font("Consolas", 9F);
|
this.txtLog.Font = new System.Drawing.Font("Consolas", 9F);
|
||||||
txtLog.ForeColor = Color.Lime;
|
this.txtLog.ForeColor = System.Drawing.Color.Lime;
|
||||||
txtLog.Location = new Point(12, 243);
|
this.txtLog.Location = new System.Drawing.Point(12, 243);
|
||||||
txtLog.Multiline = true;
|
this.txtLog.Multiline = true;
|
||||||
txtLog.Name = "txtLog";
|
this.txtLog.Name = "txtLog";
|
||||||
txtLog.ScrollBars = ScrollBars.Vertical;
|
this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
|
||||||
txtLog.Size = new Size(1130, 550);
|
this.txtLog.Size = new System.Drawing.Size(1130, 550);
|
||||||
txtLog.TabIndex = 15;
|
this.txtLog.TabIndex = 15;
|
||||||
//
|
//
|
||||||
// pgProfile
|
// pgProfile
|
||||||
//
|
//
|
||||||
pgProfile.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right;
|
this.pgProfile.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
pgProfile.CategoryForeColor = SystemColors.ActiveCaption;
|
this.pgProfile.CategoryForeColor = System.Drawing.SystemColors.ActiveCaption;
|
||||||
pgProfile.Location = new Point(1177, 80);
|
this.pgProfile.Location = new System.Drawing.Point(1177, 80);
|
||||||
pgProfile.Name = "pgProfile";
|
this.pgProfile.Name = "pgProfile";
|
||||||
pgProfile.Size = new Size(320, 713);
|
this.pgProfile.Size = new System.Drawing.Size(320, 713);
|
||||||
pgProfile.TabIndex = 16;
|
this.pgProfile.TabIndex = 16;
|
||||||
//
|
//
|
||||||
// lblPath
|
// lblPath
|
||||||
//
|
//
|
||||||
lblPath.Location = new Point(12, 12);
|
this.lblPath.Location = new System.Drawing.Point(12, 12);
|
||||||
lblPath.Name = "lblPath";
|
this.lblPath.Name = "lblPath";
|
||||||
lblPath.Size = new Size(150, 23);
|
this.lblPath.Size = new System.Drawing.Size(150, 23);
|
||||||
lblPath.TabIndex = 17;
|
this.lblPath.TabIndex = 17;
|
||||||
lblPath.Text = "Root Path & Binary:";
|
this.lblPath.Text = "Root Path & Binary:";
|
||||||
|
//
|
||||||
|
// btnSaveProfile
|
||||||
|
//
|
||||||
|
this.btnSaveProfile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.btnSaveProfile.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||||
|
this.btnSaveProfile.FlatAppearance.BorderColor = System.Drawing.Color.Gray;
|
||||||
|
this.btnSaveProfile.ForeColor = System.Drawing.Color.White;
|
||||||
|
// FIXED: Calculated position to be right next to the Remove (-) button
|
||||||
|
this.btnSaveProfile.Location = new System.Drawing.Point(1502, 34);
|
||||||
|
this.btnSaveProfile.Name = "btnSaveProfile";
|
||||||
|
this.btnSaveProfile.Size = new System.Drawing.Size(35, 29);
|
||||||
|
this.btnSaveProfile.TabIndex = 18;
|
||||||
|
this.btnSaveProfile.Text = "💾";
|
||||||
|
this.btnSaveProfile.UseVisualStyleBackColor = true;
|
||||||
|
this.btnSaveProfile.Click += new System.EventHandler(this.btnSaveProfile_Click);
|
||||||
//
|
//
|
||||||
// MainForm
|
// MainForm
|
||||||
//
|
//
|
||||||
ClientSize = new Size(1519, 808);
|
this.ClientSize = new System.Drawing.Size(1550, 808); // Slightly widened to fit new button
|
||||||
Controls.Add(txtRootPath);
|
this.Controls.Add(this.txtRootPath);
|
||||||
Controls.Add(btnBrowse);
|
this.Controls.Add(this.btnBrowse);
|
||||||
Controls.Add(txtBinName);
|
this.Controls.Add(this.txtBinName);
|
||||||
Controls.Add(btnEditYaml);
|
this.Controls.Add(this.btnEditYaml);
|
||||||
Controls.Add(comboProfiles);
|
this.Controls.Add(this.comboProfiles);
|
||||||
Controls.Add(btnAddProfile);
|
this.Controls.Add(this.btnAddProfile);
|
||||||
Controls.Add(btnRemoveProfile);
|
this.Controls.Add(this.btnRemoveProfile);
|
||||||
Controls.Add(comboService);
|
// FIXED: Added btnSaveProfile to controls list
|
||||||
Controls.Add(txtURL);
|
this.Controls.Add(this.btnSaveProfile);
|
||||||
Controls.Add(txtCommandPreview);
|
this.Controls.Add(this.comboService);
|
||||||
Controls.Add(btnRun);
|
this.Controls.Add(this.txtURL);
|
||||||
Controls.Add(btnStop);
|
this.Controls.Add(this.txtCommandPreview);
|
||||||
Controls.Add(btnOpenCookies);
|
this.Controls.Add(this.btnRun);
|
||||||
Controls.Add(btnEditServiceConfig);
|
this.Controls.Add(this.btnStop);
|
||||||
Controls.Add(btnClearLog);
|
this.Controls.Add(this.btnOpenCookies);
|
||||||
Controls.Add(txtLog);
|
this.Controls.Add(this.btnEditServiceConfig);
|
||||||
Controls.Add(pgProfile);
|
this.Controls.Add(this.btnClearLog);
|
||||||
Controls.Add(lblPath);
|
this.Controls.Add(this.txtLog);
|
||||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
this.Controls.Add(this.pgProfile);
|
||||||
MinimumSize = new Size(960, 680);
|
this.Controls.Add(this.lblPath);
|
||||||
Name = "MainForm";
|
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||||
Text = "Unshackle Master GUI";
|
this.MinimumSize = new System.Drawing.Size(960, 680);
|
||||||
ResumeLayout(false);
|
this.Name = "MainForm";
|
||||||
PerformLayout();
|
this.Text = "Unshackle Master GUI";
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,40 +13,84 @@ namespace UnshackleGUI
|
|||||||
{
|
{
|
||||||
public partial class MainForm : Form
|
public partial class MainForm : Form
|
||||||
{
|
{
|
||||||
|
// =========================================================
|
||||||
|
// 1. FILE PATHS & SETTINGS
|
||||||
|
// =========================================================
|
||||||
private string _configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json");
|
private string _configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json");
|
||||||
private string _paramsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "params.json");
|
private string _paramsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "params.json");
|
||||||
|
private string _profilesDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles");
|
||||||
|
|
||||||
|
// Data Storage
|
||||||
private AppSettings _appSettings = new AppSettings();
|
private AppSettings _appSettings = new AppSettings();
|
||||||
private List<UnshackleParameter> _parameterDefinitions = new List<UnshackleParameter>();
|
private List<UnshackleParameter> _parameterDefinitions = new List<UnshackleParameter>();
|
||||||
private Dictionary<string, object> _propertyValues = new Dictionary<string, object>();
|
private Dictionary<string, object> _propertyValues = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
// State Variables
|
||||||
private Process? _currentProcess;
|
private Process? _currentProcess;
|
||||||
|
private bool _isLoaded = false; // Flag to prevent saving while the app is starting up
|
||||||
|
private string _lastLoadedProfile = ""; // Tracks the original name
|
||||||
|
// =========================================================
|
||||||
|
// 2. CONSTRUCTOR & INITIALIZATION
|
||||||
|
// =========================================================
|
||||||
public MainForm()
|
public MainForm()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
ApplyDarkTheme();
|
ApplyDarkTheme();
|
||||||
LoadParamsAndConfig();
|
|
||||||
|
|
||||||
// EVENT: This updates the command line the moment a value changes
|
// Ensure the Profiles folder exists
|
||||||
|
if (!Directory.Exists(_profilesDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(_profilesDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Set up all button clicks and text changes
|
||||||
|
SetupEventHandlers();
|
||||||
|
|
||||||
|
// Step 2: Load data from disk (Config, Params, Profiles)
|
||||||
|
LoadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupEventHandlers()
|
||||||
|
{
|
||||||
|
// --- Property Grid (The Settings List) ---
|
||||||
|
// Whenever a value changes in the grid, update the command text and save the profile immediately.
|
||||||
pgProfile.PropertyValueChanged += (s, e) =>
|
pgProfile.PropertyValueChanged += (s, e) =>
|
||||||
{
|
{
|
||||||
UpdateCommandPreview();
|
UpdateCommandPreview();
|
||||||
|
SaveCurrentProfile();
|
||||||
if (comboProfiles.SelectedItem != null)
|
|
||||||
{
|
|
||||||
string selected = comboProfiles.SelectedItem.ToString();
|
|
||||||
_appSettings.Profiles[selected] =
|
|
||||||
new Dictionary<string, object>(_propertyValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
SaveConfig();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
comboService.SelectedIndexChanged += (s, e) => UpdateCommandPreview();
|
// --- Service Dropdown (Netflix, Amazon, etc.) ---
|
||||||
txtURL.TextChanged += (s, e) => UpdateCommandPreview();
|
comboService.SelectedIndexChanged += (s, e) =>
|
||||||
txtBinName.TextChanged += (s, e) => UpdateCommandPreview();
|
{
|
||||||
|
// Don't run this logic if the app is still starting up
|
||||||
|
if (!_isLoaded) return;
|
||||||
|
|
||||||
|
if (comboService.SelectedItem != null)
|
||||||
|
{
|
||||||
|
// Update the "Service" value in our data dictionary
|
||||||
|
_propertyValues["Service"] = comboService.SelectedItem.ToString();
|
||||||
|
|
||||||
|
// Update the command preview
|
||||||
|
UpdateCommandPreview();
|
||||||
|
|
||||||
|
// Save this change to the JSON file immediately
|
||||||
|
SaveCurrentProfile();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Text Box Changes ---
|
||||||
|
txtURL.TextChanged += (s, e) => UpdateCommandPreview();
|
||||||
|
|
||||||
|
txtBinName.TextChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
UpdateCommandPreview();
|
||||||
|
SaveGlobalConfig();
|
||||||
|
};
|
||||||
|
|
||||||
|
txtRootPath.TextChanged += (s, e) => SaveGlobalConfig();
|
||||||
|
|
||||||
|
// --- Button Clicks ---
|
||||||
btnRun.Click += btnRun_Click;
|
btnRun.Click += btnRun_Click;
|
||||||
btnStop.Click += btnStop_Click;
|
btnStop.Click += btnStop_Click;
|
||||||
btnBrowse.Click += btnBrowse_Click;
|
btnBrowse.Click += btnBrowse_Click;
|
||||||
@@ -56,130 +100,317 @@ namespace UnshackleGUI
|
|||||||
btnEditYaml.Click += btnEditYaml_Click;
|
btnEditYaml.Click += btnEditYaml_Click;
|
||||||
btnAddProfile.Click += btnAddProfile_Click;
|
btnAddProfile.Click += btnAddProfile_Click;
|
||||||
btnRemoveProfile.Click += btnRemoveProfile_Click;
|
btnRemoveProfile.Click += btnRemoveProfile_Click;
|
||||||
|
|
||||||
|
// This line connects the click to the function below
|
||||||
|
btnSaveProfile.Click += btnSaveProfile_Click;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadParamsAndConfig()
|
// =========================================================
|
||||||
|
// 3. DATA LOADING LOGIC
|
||||||
|
// =========================================================
|
||||||
|
private void LoadData()
|
||||||
{
|
{
|
||||||
// 1. Load Parameters (Rules)
|
_isLoaded = false; // Stop events from firing while we load
|
||||||
|
|
||||||
|
// A. Load Parameters (The definitions for flags like -q, -v, etc.)
|
||||||
if (File.Exists(_paramsPath))
|
if (File.Exists(_paramsPath))
|
||||||
{
|
{
|
||||||
var json = File.ReadAllText(_paramsPath);
|
var json = File.ReadAllText(_paramsPath);
|
||||||
_parameterDefinitions = JsonConvert.DeserializeObject<List<UnshackleParameter>>(json) ?? new List<UnshackleParameter>();
|
_parameterDefinitions = JsonConvert.DeserializeObject<List<UnshackleParameter>>(json) ?? new List<UnshackleParameter>();
|
||||||
|
}
|
||||||
foreach (var p in _parameterDefinitions)
|
else
|
||||||
{
|
{
|
||||||
// Ensure every parameter has a value in the dictionary
|
MessageBox.Show("Error: params.json not found! The grid will be empty.");
|
||||||
if (!_propertyValues.ContainsKey(p.Name))
|
|
||||||
_propertyValues[p.Name] = p.Default ?? "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Load Config (Paths/Profiles)
|
// B. Load Global Config (Root Path, Binary Name)
|
||||||
if (File.Exists(_configPath))
|
if (File.Exists(_configPath))
|
||||||
{
|
{
|
||||||
var json = File.ReadAllText(_configPath);
|
var json = File.ReadAllText(_configPath);
|
||||||
_appSettings = JsonConvert.DeserializeObject<AppSettings>(json) ?? new AppSettings();
|
_appSettings = JsonConvert.DeserializeObject<AppSettings>(json) ?? new AppSettings();
|
||||||
|
|
||||||
txtRootPath.Text = _appSettings.RootPath;
|
txtRootPath.Text = _appSettings.RootPath;
|
||||||
txtBinName.Text = _appSettings.BinaryName;
|
txtBinName.Text = _appSettings.BinaryName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_appSettings.Profiles == null || _appSettings.Profiles.Count == 0)
|
// C. Populate the Services Dropdown (Read folders from disk)
|
||||||
{
|
|
||||||
_appSettings.Profiles = new Dictionary<string, Dictionary<string, object>>();
|
|
||||||
_appSettings.Profiles["Default"] = new Dictionary<string, object>(_propertyValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
comboProfiles.DataSource = null;
|
|
||||||
comboProfiles.DataSource = _appSettings.Profiles.Keys.ToList();
|
|
||||||
|
|
||||||
comboProfiles.SelectedIndexChanged += (s, e) =>
|
|
||||||
{
|
|
||||||
if (comboProfiles.SelectedItem == null) return;
|
|
||||||
|
|
||||||
string selected = comboProfiles.SelectedItem.ToString();
|
|
||||||
|
|
||||||
if (_appSettings.Profiles.TryGetValue(selected, out var values))
|
|
||||||
{
|
|
||||||
_propertyValues = new Dictionary<string, object>(values);
|
|
||||||
|
|
||||||
pgProfile.SelectedObject =
|
|
||||||
new DynamicObject(_propertyValues, _parameterDefinitions);
|
|
||||||
|
|
||||||
UpdateCommandPreview();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// 3. Bind to Grid using the FIXED Descriptor
|
|
||||||
pgProfile.SelectedObject = new DynamicObject(_propertyValues, _parameterDefinitions);
|
|
||||||
|
|
||||||
|
|
||||||
RefreshFolders();
|
RefreshFolders();
|
||||||
|
|
||||||
|
// D. Load Profiles and select the default one
|
||||||
|
RefreshProfileList();
|
||||||
|
|
||||||
|
_isLoaded = true; // Loading done, enable events
|
||||||
|
|
||||||
|
// Force an initial update of the command text
|
||||||
UpdateCommandPreview();
|
UpdateCommandPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCommandPreview()
|
private void RefreshProfileList()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
// 1. Temporarily stop listening to the selection event to prevent glitches
|
||||||
sb.Append($"{txtBinName.Text} run unshackle dl ");
|
comboProfiles.SelectedIndexChanged -= ComboProfiles_SelectedIndexChanged;
|
||||||
|
|
||||||
|
// 2. Find all .json files in the Profiles folder
|
||||||
|
var files = Directory.GetFiles(_profilesDir, "*.json");
|
||||||
|
var profileNames = files.Select(Path.GetFileNameWithoutExtension).ToList();
|
||||||
|
|
||||||
|
// 3. If no profiles exist, create a "Default" one
|
||||||
|
if (profileNames.Count == 0)
|
||||||
|
{
|
||||||
|
CreateDefaultProfile();
|
||||||
|
profileNames.Add("Default");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Update the ComboBox
|
||||||
|
comboProfiles.DataSource = null;
|
||||||
|
comboProfiles.DataSource = profileNames;
|
||||||
|
|
||||||
|
// 5. Select "Default" or the first available profile
|
||||||
|
string targetProfile = profileNames.Contains("Default") ? "Default" : profileNames[0];
|
||||||
|
comboProfiles.SelectedItem = targetProfile;
|
||||||
|
|
||||||
|
// 6. Manually trigger the load for this profile
|
||||||
|
LoadProfile(targetProfile);
|
||||||
|
|
||||||
|
// 7. Start listening to events again
|
||||||
|
comboProfiles.SelectedIndexChanged += ComboProfiles_SelectedIndexChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateDefaultProfile()
|
||||||
|
{
|
||||||
|
_propertyValues = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
// Fill with default values from params.json
|
||||||
foreach (var p in _parameterDefinitions)
|
foreach (var p in _parameterDefinitions)
|
||||||
{
|
{
|
||||||
|
_propertyValues[p.Name] = p.Default ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default service
|
||||||
|
if (comboService.Items.Count > 0)
|
||||||
|
{
|
||||||
|
_propertyValues["Service"] = comboService.Items[0].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveProfileFile("Default");
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 4. PROFILE SWITCHING & LOADING
|
||||||
|
// =========================================================
|
||||||
|
private void ComboProfiles_SelectedIndexChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (comboProfiles.SelectedItem == null) return;
|
||||||
|
|
||||||
|
string selectedProfile = comboProfiles.SelectedItem.ToString();
|
||||||
|
LoadProfile(selectedProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadProfile(string profileName)
|
||||||
|
{
|
||||||
|
|
||||||
|
_lastLoadedProfile = profileName;
|
||||||
|
|
||||||
|
string path = Path.Combine(_profilesDir, $"{profileName}.json");
|
||||||
|
|
||||||
|
if (!File.Exists(path)) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(path);
|
||||||
|
var loadedValues = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
|
||||||
|
|
||||||
|
if (loadedValues != null)
|
||||||
|
{
|
||||||
|
_propertyValues = loadedValues;
|
||||||
|
|
||||||
|
// --- FIX 1: Add missing parameters ---
|
||||||
|
// If we added new options to params.json, old profiles won't have them.
|
||||||
|
// This adds them with default values.
|
||||||
|
foreach (var p in _parameterDefinitions)
|
||||||
|
{
|
||||||
|
if (!_propertyValues.ContainsKey(p.Name))
|
||||||
|
{
|
||||||
|
_propertyValues[p.Name] = p.Default ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- FIX 2: Handle the Service Dropdown ---
|
||||||
|
// Check if this profile has a saved Service (e.g., "AMZN")
|
||||||
|
if (_propertyValues.TryGetValue("Service", out var serviceObj))
|
||||||
|
{
|
||||||
|
string serviceName = serviceObj.ToString();
|
||||||
|
|
||||||
|
// Only try to switch if the service actually exists in the list
|
||||||
|
if (comboService.Items.Contains(serviceName))
|
||||||
|
{
|
||||||
|
// Temporarily disable _isLoaded to change dropdown without triggering a save loop
|
||||||
|
bool wasLoaded = _isLoaded;
|
||||||
|
_isLoaded = false;
|
||||||
|
|
||||||
|
comboService.SelectedItem = serviceName;
|
||||||
|
|
||||||
|
_isLoaded = wasLoaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (comboService.SelectedItem != null)
|
||||||
|
{
|
||||||
|
// If profile has NO service saved, save the current one to it
|
||||||
|
_propertyValues["Service"] = comboService.SelectedItem.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- FIX 3: Bind to PropertyGrid and Refresh ---
|
||||||
|
pgProfile.SelectedObject = new DynamicObject(_propertyValues, _parameterDefinitions);
|
||||||
|
pgProfile.Refresh(); // Crucial to prevent grey box
|
||||||
|
|
||||||
|
UpdateCommandPreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Error loading profile: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 5. SAVING LOGIC
|
||||||
|
// =========================================================
|
||||||
|
private void SaveCurrentProfile()
|
||||||
|
{
|
||||||
|
// Don't save if we are loading or if nothing is selected
|
||||||
|
if (!_isLoaded || comboProfiles.SelectedItem == null) return;
|
||||||
|
|
||||||
|
string name = comboProfiles.SelectedItem.ToString();
|
||||||
|
SaveProfileFile(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveProfileFile(string name)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Ensure the currently selected service is saved into the dictionary
|
||||||
|
if (comboService.SelectedItem != null)
|
||||||
|
{
|
||||||
|
_propertyValues["Service"] = comboService.SelectedItem.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
string path = Path.Combine(_profilesDir, $"{name}.json");
|
||||||
|
File.WriteAllText(path, JsonConvert.SerializeObject(_propertyValues, Formatting.Indented));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendLog($"[Error Saving Profile]: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveGlobalConfig()
|
||||||
|
{
|
||||||
|
if (!_isLoaded) return;
|
||||||
|
|
||||||
|
_appSettings.RootPath = txtRootPath.Text;
|
||||||
|
_appSettings.BinaryName = txtBinName.Text;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(_configPath, JsonConvert.SerializeObject(_appSettings, Formatting.Indented));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore errors while typing (file might be busy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 6. COMMAND GENERATION
|
||||||
|
// =========================================================
|
||||||
|
private void UpdateCommandPreview()
|
||||||
|
{
|
||||||
|
if (!_isLoaded) return;
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// 1. Start with binary and command
|
||||||
|
// e.g., "uv run unshackle dl "
|
||||||
|
sb.Append($"{txtBinName.Text} run unshackle dl ");
|
||||||
|
|
||||||
|
// 2. Loop through all parameters in the grid
|
||||||
|
foreach (var p in _parameterDefinitions)
|
||||||
|
{
|
||||||
|
// If value doesn't exist in our data, skip it
|
||||||
if (!_propertyValues.TryGetValue(p.Name, out object val)) continue;
|
if (!_propertyValues.TryGetValue(p.Name, out object val)) continue;
|
||||||
|
|
||||||
// Handle Booleans
|
// Case A: Boolean Flags (e.g., --no-mux)
|
||||||
if (p.Type == "Bool" && val is bool b && b)
|
if (p.Type == "Bool" && val is bool isTrue && isTrue)
|
||||||
{
|
{
|
||||||
sb.Append($"{p.Flag} ");
|
sb.Append($"{p.Flag} ");
|
||||||
}
|
}
|
||||||
// Handle Text/Selection/Numbers
|
// Case B: Text / Selection / Number
|
||||||
else if (p.Type != "Bool" && val != null)
|
else if (p.Type != "Bool" && val != null)
|
||||||
{
|
{
|
||||||
string sVal = val.ToString();
|
string sVal = val.ToString();
|
||||||
// Skip empty, "any", or "0" (for default bitrates)
|
|
||||||
|
// Validation:
|
||||||
|
// 1. Not empty
|
||||||
|
// 2. Not "any" (Default)
|
||||||
|
// 3. Not "0" (Default for numbers)
|
||||||
if (!string.IsNullOrWhiteSpace(sVal) && sVal != "any" && sVal != "0")
|
if (!string.IsNullOrWhiteSpace(sVal) && sVal != "any" && sVal != "0")
|
||||||
{
|
{
|
||||||
if (sVal.Contains(" ")) sVal = $"\"{sVal}\"";
|
// Quote the value if it has spaces
|
||||||
|
if (sVal.Contains(" "))
|
||||||
|
{
|
||||||
|
sVal = $"\"{sVal}\"";
|
||||||
|
}
|
||||||
|
|
||||||
sb.Append($"{p.Flag} {sVal} ");
|
sb.Append($"{p.Flag} {sVal} ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Append Service and URL
|
||||||
|
// e.g., "AMZN https://..."
|
||||||
sb.Append($"{comboService.Text} {txtURL.Text}");
|
sb.Append($"{comboService.Text} {txtURL.Text}");
|
||||||
|
|
||||||
|
// 4. Update UI
|
||||||
txtCommandPreview.Text = sb.ToString();
|
txtCommandPreview.Text = sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnEditServiceConfig_Click(object? sender, EventArgs e)
|
// =========================================================
|
||||||
{
|
// 7. CLI EXECUTION (The Run Button)
|
||||||
string yamlPath = Path.Combine(txtRootPath.Text, "unshackle", "services", comboService.Text, "config.yaml");
|
// =========================================================
|
||||||
if (File.Exists(yamlPath))
|
|
||||||
Process.Start(new ProcessStartInfo(yamlPath) { UseShellExecute = true });
|
|
||||||
else
|
|
||||||
MessageBox.Show($"No config.yaml found for {comboService.Text}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void btnRun_Click(object? sender, EventArgs e)
|
private async void btnRun_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
string finalCmd = txtCommandPreview.Text;
|
string finalCmd = txtCommandPreview.Text;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(finalCmd)) return;
|
if (string.IsNullOrWhiteSpace(finalCmd)) return;
|
||||||
|
|
||||||
|
// Disable buttons while running
|
||||||
ToggleUI(true);
|
ToggleUI(true);
|
||||||
|
|
||||||
txtLog.AppendText($"> Executing: {finalCmd}{Environment.NewLine}");
|
txtLog.AppendText($"> Executing: {finalCmd}{Environment.NewLine}");
|
||||||
|
|
||||||
try { await Task.Run(() => RunCli(finalCmd)); }
|
try
|
||||||
catch (Exception ex) { AppendLog($"[ERROR]: {ex.Message}"); }
|
{
|
||||||
finally { ToggleUI(false); }
|
// Run in background thread to keep UI responsive
|
||||||
|
await Task.Run(() => RunCli(finalCmd));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendLog($"[ERROR]: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Re-enable buttons
|
||||||
|
ToggleUI(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RunCli(string cmd)
|
private void RunCli(string cmd)
|
||||||
{
|
{
|
||||||
|
// We use CMD.EXE instead of PowerShell to avoid Antivirus false positives
|
||||||
ProcessStartInfo psi = new ProcessStartInfo
|
ProcessStartInfo psi = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = "cmd.exe",
|
FileName = "cmd.exe",
|
||||||
// /c = Run command and then terminate
|
Arguments = $"/c chcp 65001 && {cmd}", // Force UTF-8
|
||||||
// chcp 65001 = Force console to use UTF-8 encoding
|
|
||||||
// && = Run the actual command immediately after setting encoding
|
|
||||||
Arguments = $"/c chcp 65001 && {cmd}",
|
|
||||||
WorkingDirectory = txtRootPath.Text,
|
WorkingDirectory = txtRootPath.Text,
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
@@ -188,20 +419,24 @@ namespace UnshackleGUI
|
|||||||
StandardOutputEncoding = Encoding.UTF8
|
StandardOutputEncoding = Encoding.UTF8
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure Python scripts know to use UTF-8
|
// Set Environment variable for Python
|
||||||
psi.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8";
|
psi.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8";
|
||||||
|
|
||||||
using (_currentProcess = Process.Start(psi))
|
using (_currentProcess = Process.Start(psi))
|
||||||
{
|
{
|
||||||
if (_currentProcess == null) return;
|
if (_currentProcess == null) return;
|
||||||
|
|
||||||
|
// Handle Output
|
||||||
_currentProcess.OutputDataReceived += (s, e) =>
|
_currentProcess.OutputDataReceived += (s, e) =>
|
||||||
{
|
{
|
||||||
// Filter out the noisy "Active code page: 65001" message from CMD
|
// Filter out the "Active code page: 65001" noise
|
||||||
if (!string.IsNullOrEmpty(e.Data) && !e.Data.Contains("Active code page"))
|
if (!string.IsNullOrEmpty(e.Data) && !e.Data.Contains("Active code page"))
|
||||||
|
{
|
||||||
AppendLog(e.Data);
|
AppendLog(e.Data);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle Errors
|
||||||
_currentProcess.ErrorDataReceived += (s, e) => AppendLog(e.Data);
|
_currentProcess.ErrorDataReceived += (s, e) => AppendLog(e.Data);
|
||||||
|
|
||||||
_currentProcess.BeginOutputReadLine();
|
_currentProcess.BeginOutputReadLine();
|
||||||
@@ -210,104 +445,39 @@ namespace UnshackleGUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AppendLog(string? text)
|
// =========================================================
|
||||||
{
|
// 8. PROFILE MANAGEMENT (Add/Remove/Save Buttons)
|
||||||
if (string.IsNullOrEmpty(text)) return;
|
// =========================================================
|
||||||
this.BeginInvoke(new Action(() => { txtLog.AppendText(text + Environment.NewLine); }));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyDarkTheme()
|
|
||||||
{
|
|
||||||
this.BackColor = Color.FromArgb(30, 30, 30);
|
|
||||||
this.ForeColor = Color.White;
|
|
||||||
foreach (Control c in this.Controls) UpdateControlTheme(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateControlTheme(Control c)
|
|
||||||
{
|
|
||||||
c.BackColor = Color.FromArgb(45, 45, 48);
|
|
||||||
c.ForeColor = Color.White;
|
|
||||||
if (c is TextBox tb) tb.BorderStyle = BorderStyle.FixedSingle;
|
|
||||||
if (c is Button btn) { btn.FlatStyle = FlatStyle.Flat; btn.FlatAppearance.BorderColor = Color.Gray; }
|
|
||||||
if (c is PropertyGrid pg)
|
|
||||||
{
|
|
||||||
pg.BackColor = Color.FromArgb(37, 37, 38);
|
|
||||||
pg.ViewBackColor = Color.FromArgb(37, 37, 38);
|
|
||||||
pg.ViewForeColor = Color.White;
|
|
||||||
pg.LineColor = Color.FromArgb(45, 45, 48);
|
|
||||||
}
|
|
||||||
foreach (Control child in c.Controls) UpdateControlTheme(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SaveConfig()
|
|
||||||
{
|
|
||||||
_appSettings.RootPath = txtRootPath.Text;
|
|
||||||
_appSettings.BinaryName = txtBinName.Text;
|
|
||||||
File.WriteAllText(_configPath, JsonConvert.SerializeObject(_appSettings, Formatting.Indented));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RefreshFolders()
|
|
||||||
{
|
|
||||||
comboService.Items.Clear();
|
|
||||||
string path = Path.Combine(txtRootPath.Text, "unshackle", "services");
|
|
||||||
if (Directory.Exists(path))
|
|
||||||
{
|
|
||||||
var dirs = Directory.GetDirectories(path).Select(Path.GetFileName).ToArray();
|
|
||||||
comboService.Items.AddRange(dirs.Cast<object>().ToArray());
|
|
||||||
if (comboService.Items.Count > 0) comboService.SelectedIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void ComboProfiles_SelectedIndexChanged(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (comboProfiles.SelectedItem == null) return;
|
|
||||||
|
|
||||||
string selected = comboProfiles.SelectedItem.ToString();
|
|
||||||
|
|
||||||
if (_appSettings.Profiles.TryGetValue(selected, out var values))
|
|
||||||
{
|
|
||||||
_propertyValues = new Dictionary<string, object>(values);
|
|
||||||
|
|
||||||
pgProfile.SelectedObject =
|
|
||||||
new DynamicObject(_propertyValues, _parameterDefinitions);
|
|
||||||
|
|
||||||
UpdateCommandPreview();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void btnAddProfile_Click(object? sender, EventArgs e)
|
private void btnAddProfile_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
string name = Microsoft.VisualBasic.Interaction.InputBox(
|
string name = Microsoft.VisualBasic.Interaction.InputBox("Enter profile name:", "New Profile", "");
|
||||||
"Enter profile name:",
|
|
||||||
"New Profile",
|
|
||||||
"NewProfile");
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
if (string.IsNullOrWhiteSpace(name)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (_appSettings.Profiles.ContainsKey(name))
|
// Sanitize filename (remove illegal characters like / \ : *)
|
||||||
|
foreach (char c in Path.GetInvalidFileNameChars())
|
||||||
|
{
|
||||||
|
name = name.Replace(c, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if exists
|
||||||
|
if (File.Exists(Path.Combine(_profilesDir, $"{name}.json")))
|
||||||
{
|
{
|
||||||
MessageBox.Show("Profile already exists.");
|
MessageBox.Show("Profile already exists.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone current settings into new profile
|
// Save current settings as the new profile
|
||||||
_appSettings.Profiles[name] =
|
SaveProfileFile(name);
|
||||||
new Dictionary<string, object>(_propertyValues);
|
|
||||||
|
|
||||||
comboProfiles.DataSource = null;
|
// Reload list and select new profile
|
||||||
comboProfiles.DataSource = _appSettings.Profiles.Keys.ToList();
|
RefreshProfileList();
|
||||||
comboProfiles.SelectedItem = name;
|
comboProfiles.SelectedItem = name;
|
||||||
|
|
||||||
SaveConfig();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnRemoveProfile_Click(object? sender, EventArgs e)
|
private void btnRemoveProfile_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (comboProfiles.SelectedItem == null)
|
if (comboProfiles.SelectedItem == null) return;
|
||||||
return;
|
|
||||||
|
|
||||||
string selected = comboProfiles.SelectedItem.ToString();
|
string selected = comboProfiles.SelectedItem.ToString();
|
||||||
|
|
||||||
@@ -317,50 +487,233 @@ namespace UnshackleGUI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_appSettings.Profiles.ContainsKey(selected))
|
string path = Path.Combine(_profilesDir, $"{selected}.json");
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshProfileList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnSaveProfile_Click(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
string newName = comboProfiles.Text.Trim();
|
||||||
|
|
||||||
|
// 1. Validation
|
||||||
|
if (string.IsNullOrWhiteSpace(newName))
|
||||||
|
{
|
||||||
|
MessageBox.Show("Please enter a profile name.");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_appSettings.Profiles.Remove(selected);
|
foreach (char c in Path.GetInvalidFileNameChars()) newName = newName.Replace(c, '_');
|
||||||
|
|
||||||
comboProfiles.DataSource = null;
|
// 2. CHECK: Did the name change?
|
||||||
comboProfiles.DataSource = _appSettings.Profiles.Keys.ToList();
|
if (newName != _lastLoadedProfile && !string.IsNullOrEmpty(_lastLoadedProfile))
|
||||||
|
{
|
||||||
|
// Special Case: Cannot rename Default
|
||||||
|
if (_lastLoadedProfile == "Default")
|
||||||
|
{
|
||||||
|
// Just create new, don't ask to rename Default
|
||||||
|
SaveProfileFile(newName);
|
||||||
|
RefreshProfileList();
|
||||||
|
comboProfiles.SelectedItem = newName;
|
||||||
|
MessageBox.Show($"Created new profile '{newName}' from Default.", "New Profile Created");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
comboProfiles.SelectedIndex = 0;
|
// Ask the user what to do
|
||||||
|
DialogResult choice = MessageBox.Show(
|
||||||
|
$"You changed the name from '{_lastLoadedProfile}' to '{newName}'.\n\n" +
|
||||||
|
"Click YES to RENAME (Delete old).\n" +
|
||||||
|
"Click NO to CREATE COPY (Keep old).",
|
||||||
|
"Rename or Copy?",
|
||||||
|
MessageBoxButtons.YesNoCancel,
|
||||||
|
MessageBoxIcon.Question);
|
||||||
|
|
||||||
SaveConfig();
|
if (choice == DialogResult.Cancel) return;
|
||||||
|
|
||||||
|
if (choice == DialogResult.Yes) // RENAME
|
||||||
|
{
|
||||||
|
// Save New
|
||||||
|
SaveProfileFile(newName);
|
||||||
|
|
||||||
|
// Delete Old
|
||||||
|
string oldPath = Path.Combine(_profilesDir, $"{_lastLoadedProfile}.json");
|
||||||
|
if (File.Exists(oldPath)) File.Delete(oldPath);
|
||||||
|
|
||||||
|
RefreshProfileList();
|
||||||
|
comboProfiles.SelectedItem = newName; // This updates _lastLoadedProfile automatically
|
||||||
|
MessageBox.Show($"Renamed to '{newName}'.");
|
||||||
|
}
|
||||||
|
else // CREATE COPY
|
||||||
|
{
|
||||||
|
SaveProfileFile(newName);
|
||||||
|
RefreshProfileList();
|
||||||
|
comboProfiles.SelectedItem = newName;
|
||||||
|
MessageBox.Show($"Created copy '{newName}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 3. Name didn't change (Normal Save)
|
||||||
|
SaveProfileFile(newName);
|
||||||
|
MessageBox.Show($"Profile '{newName}' saved!", "Saved", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void ToggleUI(bool r) => this.BeginInvoke(new Action(() => { btnRun.Enabled = !r; btnStop.Visible = r; }));
|
// =========================================================
|
||||||
private void btnStop_Click(object? sender, EventArgs e) { _currentProcess?.Kill(true); }
|
// 9. HELPER METHODS & THEME
|
||||||
private void btnClearLog_Click(object? sender, EventArgs e) => txtLog.Clear();
|
// =========================================================
|
||||||
|
|
||||||
|
private void AppendLog(string? text)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text)) return;
|
||||||
|
|
||||||
|
// Ensure we update the UI on the main thread
|
||||||
|
this.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
txtLog.AppendText(text + Environment.NewLine);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleUI(bool isRunning)
|
||||||
|
{
|
||||||
|
this.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
btnRun.Enabled = !isRunning;
|
||||||
|
btnStop.Visible = isRunning;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnStop_Click(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_currentProcess != null && !_currentProcess.HasExited)
|
||||||
|
{
|
||||||
|
_currentProcess.Kill(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnClearLog_Click(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
txtLog.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
private void btnBrowse_Click(object? sender, EventArgs e)
|
private void btnBrowse_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
using (var fbd = new FolderBrowserDialog()) if (fbd.ShowDialog() == DialogResult.OK) { txtRootPath.Text = fbd.SelectedPath; SaveConfig(); RefreshFolders(); }
|
using (var fbd = new FolderBrowserDialog())
|
||||||
|
{
|
||||||
|
if (fbd.ShowDialog() == DialogResult.OK)
|
||||||
|
{
|
||||||
|
txtRootPath.Text = fbd.SelectedPath;
|
||||||
|
SaveGlobalConfig();
|
||||||
|
RefreshFolders();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnOpenCookies_Click(object? sender, EventArgs e)
|
private void btnOpenCookies_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
string p = Path.Combine(txtRootPath.Text, "unshackle", "cookies", comboService.Text);
|
string path = Path.Combine(txtRootPath.Text, "unshackle", "cookies", comboService.Text);
|
||||||
Directory.CreateDirectory(p); Process.Start("explorer.exe", p);
|
Directory.CreateDirectory(path);
|
||||||
|
Process.Start("explorer.exe", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnEditYaml_Click(object? sender, EventArgs e)
|
private void btnEditYaml_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
string yamlPath = Path.Combine(txtRootPath.Text, "unshackle/unshackle.yaml");
|
string yamlPath = Path.Combine(txtRootPath.Text, "unshackle/unshackle.yaml");
|
||||||
if (File.Exists(yamlPath)) Process.Start(new ProcessStartInfo(yamlPath) { UseShellExecute = true });
|
if (File.Exists(yamlPath))
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo(yamlPath) { UseShellExecute = true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnEditServiceConfig_Click(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
string yamlPath = Path.Combine(txtRootPath.Text, "unshackle", "services", comboService.Text, "config.yaml");
|
||||||
|
if (File.Exists(yamlPath))
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo(yamlPath) { UseShellExecute = true });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MessageBox.Show($"No config.yaml found for {comboService.Text}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshFolders()
|
||||||
|
{
|
||||||
|
comboService.Items.Clear();
|
||||||
|
string path = Path.Combine(txtRootPath.Text, "unshackle", "services");
|
||||||
|
|
||||||
|
if (Directory.Exists(path))
|
||||||
|
{
|
||||||
|
var dirs = Directory.GetDirectories(path).Select(Path.GetFileName).ToArray();
|
||||||
|
comboService.Items.AddRange(dirs.Cast<object>().ToArray());
|
||||||
|
|
||||||
|
if (comboService.Items.Count > 0)
|
||||||
|
{
|
||||||
|
comboService.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyDarkTheme()
|
||||||
|
{
|
||||||
|
this.BackColor = Color.FromArgb(30, 30, 30);
|
||||||
|
this.ForeColor = Color.White;
|
||||||
|
|
||||||
|
foreach (Control c in this.Controls)
|
||||||
|
{
|
||||||
|
UpdateControlTheme(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateControlTheme(Control c)
|
||||||
|
{
|
||||||
|
c.BackColor = Color.FromArgb(45, 45, 48);
|
||||||
|
c.ForeColor = Color.White;
|
||||||
|
|
||||||
|
if (c is TextBox tb)
|
||||||
|
{
|
||||||
|
tb.BorderStyle = BorderStyle.FixedSingle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c is Button btn)
|
||||||
|
{
|
||||||
|
btn.FlatStyle = FlatStyle.Flat;
|
||||||
|
btn.FlatAppearance.BorderColor = Color.Gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c is PropertyGrid pg)
|
||||||
|
{
|
||||||
|
pg.BackColor = Color.FromArgb(37, 37, 38);
|
||||||
|
pg.ViewBackColor = Color.FromArgb(37, 37, 38);
|
||||||
|
pg.ViewForeColor = Color.White;
|
||||||
|
pg.LineColor = Color.FromArgb(45, 45, 48);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursive call for panels and group boxes
|
||||||
|
foreach (Control child in c.Controls)
|
||||||
|
{
|
||||||
|
UpdateControlTheme(child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// 10. DATA MODELS
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
// --- DATA MODELS ---
|
|
||||||
public class AppSettings
|
public class AppSettings
|
||||||
{
|
{
|
||||||
public string RootPath { get; set; } = @"C:\DEVINE\unshackle";
|
public string RootPath { get; set; } = @"C:\DEVINE\unshackle";
|
||||||
public string BinaryName { get; set; } = "uv";
|
public string BinaryName { get; set; } = "uv";
|
||||||
|
|
||||||
public Dictionary<string, Dictionary<string, object>> Profiles { get; set; }
|
|
||||||
= new Dictionary<string, Dictionary<string, object>>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UnshackleParameter
|
public class UnshackleParameter
|
||||||
|
|||||||
Reference in New Issue
Block a user