From d21fc4baaa1f62f801be5fe15392a4ccec941bf0 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Mon, 14 Sep 2020 21:30:10 +0200 Subject: [PATCH 01/45] Creating project and the Form --- PowerPointToOBSSceneSwitcher.sln | 56 ++++--- .../PresentationObsSceneSwitcher.Designer.cs | 151 ++++++++++++++++++ .../PresentationObsSceneSwitcher.cs | 21 +++ .../PresentationObsSceneSwitcher.csproj | 9 ++ .../PresentationObsSceneSwitcher.resx | 60 +++++++ .../PresentationObsSceneSwitcher/Program.cs | 23 +++ 6 files changed, 295 insertions(+), 25 deletions(-) create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.resx create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs diff --git a/PowerPointToOBSSceneSwitcher.sln b/PowerPointToOBSSceneSwitcher.sln index e2cad97..7682133 100644 --- a/PowerPointToOBSSceneSwitcher.sln +++ b/PowerPointToOBSSceneSwitcher.sln @@ -1,25 +1,31 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30428.66 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerPointToOBSSceneSwitcher", "PowerPointToOBSSceneSwitcher.csproj", "{8A844363-5A92-403B-90CD-BFCB30ED5054}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8A844363-5A92-403B-90CD-BFCB30ED5054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A844363-5A92-403B-90CD-BFCB30ED5054}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A844363-5A92-403B-90CD-BFCB30ED5054}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A844363-5A92-403B-90CD-BFCB30ED5054}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {515C02C0-0CF2-4890-A1FB-4D6A5F115CD6} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30428.66 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerPointToOBSSceneSwitcher", "PowerPointToOBSSceneSwitcher.csproj", "{8A844363-5A92-403B-90CD-BFCB30ED5054}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresentationObsSceneSwitcher", "PresentationObsSceneSwitcher\PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj", "{5B07EF15-11D6-4646-8322-965BD7D4ADEE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8A844363-5A92-403B-90CD-BFCB30ED5054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A844363-5A92-403B-90CD-BFCB30ED5054}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A844363-5A92-403B-90CD-BFCB30ED5054}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A844363-5A92-403B-90CD-BFCB30ED5054}.Release|Any CPU.Build.0 = Release|Any CPU + {5B07EF15-11D6-4646-8322-965BD7D4ADEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B07EF15-11D6-4646-8322-965BD7D4ADEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B07EF15-11D6-4646-8322-965BD7D4ADEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B07EF15-11D6-4646-8322-965BD7D4ADEE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {515C02C0-0CF2-4890-A1FB-4D6A5F115CD6} + EndGlobalSection +EndGlobal diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs new file mode 100644 index 0000000..4fa0b9c --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs @@ -0,0 +1,151 @@ + +namespace PresentationObsSceneSwitcher +{ + partial class PresentationObsSceneSwitcher + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.panel1 = new System.Windows.Forms.Panel(); + this.menuStrip = new System.Windows.Forms.MenuStrip(); + this.menuItemClose = new System.Windows.Forms.ToolStripMenuItem(); + this.panel1.SuspendLayout(); + this.menuStrip.SuspendLayout(); + this.SuspendLayout(); + // + // panel1 + // + this.panel1.BackColor = System.Drawing.Color.WhiteSmoke; + this.panel1.Controls.Add(this.menuStrip); + this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(515, 275); + this.panel1.TabIndex = 0; + // + // menuStrip + // + this.menuStrip.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); + this.menuStrip.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.menuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuItemClose}); + this.menuStrip.Location = new System.Drawing.Point(0, 0); + this.menuStrip.Name = "menuStrip"; + this.menuStrip.Size = new System.Drawing.Size(515, 25); + this.menuStrip.TabIndex = 0; + this.menuStrip.Text = "Menu Strip"; + // + // menuItemClose + // + this.menuItemClose.ForeColor = System.Drawing.Color.WhiteSmoke; + this.menuItemClose.Name = "menuItemClose"; + this.menuItemClose.Size = new System.Drawing.Size(52, 21); + this.menuItemClose.Text = "Close"; + this.menuItemClose.ToolTipText = "Closes the application"; + // + // PresentationObsSceneSwitcher + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(515, 275); + this.Controls.Add(this.panel1); + this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.MainMenuStrip = this.menuStrip; + this.Name = "PresentationObsSceneSwitcher"; + this.Text = "Form1"; + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.menuStrip.ResumeLayout(false); + this.menuStrip.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.MenuStrip menuStrip; + private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator; + private System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.ToolStripMenuItem printToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem printPreviewToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem undoToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem redoToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; + private System.Windows.Forms.ToolStripMenuItem cutToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem copyToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem pasteToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; + private System.Windows.Forms.ToolStripMenuItem selectAllToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem toolsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem customizeToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem optionsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem contentsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem indexToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem searchToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; + private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem newToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator6; + private System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator7; + private System.Windows.Forms.ToolStripMenuItem printToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem printPreviewToolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator8; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem undoToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem redoToolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator9; + private System.Windows.Forms.ToolStripMenuItem cutToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem copyToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem pasteToolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator10; + private System.Windows.Forms.ToolStripMenuItem selectAllToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem toolsToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem customizeToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem optionsToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem contentsToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem indexToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem searchToolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator11; + private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem menuItemClose; + } +} + diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs new file mode 100644 index 0000000..30999ab --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace PresentationObsSceneSwitcher +{ + public partial class PresentationObsSceneSwitcher : Form + { + public PresentationObsSceneSwitcher() + { + InitializeComponent(); + } + + } +} diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj new file mode 100644 index 0000000..7b05c62 --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj @@ -0,0 +1,9 @@ + + + + WinExe + netcoreapp3.1 + true + + + \ No newline at end of file diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.resx b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.resx new file mode 100644 index 0000000..b5ae26c --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs new file mode 100644 index 0000000..2c4b238 --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace PresentationObsSceneSwitcher +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new PresentationObsSceneSwitcher()); + } + } +} From aaa1f889af7041352826390c5dc907d195c5544b Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Mon, 14 Sep 2020 22:13:59 +0200 Subject: [PATCH 02/45] Initial ConfigurationFormLayour --- ...igner.cs => ConfigurationForm.Designer.cs} | 84 +++++++++++++++++-- ...sSceneSwitcher.cs => ConfigurationForm.cs} | 4 +- ...neSwitcher.resx => ConfigurationForm.resx} | 0 .../PresentationObsSceneSwitcher/Program.cs | 2 +- 4 files changed, 79 insertions(+), 11 deletions(-) rename PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher.Designer.cs => ConfigurationForm.Designer.cs} (66%) rename PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher.cs => ConfigurationForm.cs} (71%) rename PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher.resx => ConfigurationForm.resx} (100%) diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs similarity index 66% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs rename to PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs index 4fa0b9c..510f6b9 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs @@ -1,7 +1,7 @@  namespace PresentationObsSceneSwitcher { - partial class PresentationObsSceneSwitcher + partial class ConfigurationForm { /// /// Required designer variable. @@ -32,6 +32,11 @@ private void InitializeComponent() this.panel1 = new System.Windows.Forms.Panel(); this.menuStrip = new System.Windows.Forms.MenuStrip(); this.menuItemClose = new System.Windows.Forms.ToolStripMenuItem(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); this.panel1.SuspendLayout(); this.menuStrip.SuspendLayout(); this.SuspendLayout(); @@ -39,11 +44,16 @@ private void InitializeComponent() // panel1 // this.panel1.BackColor = System.Drawing.Color.WhiteSmoke; - this.panel1.Controls.Add(this.menuStrip); + this.panel1.Controls.Add(this.textBox2); + this.panel1.Controls.Add(this.label3); + this.panel1.Controls.Add(this.textBox1); + this.panel1.Controls.Add(this.label2); + this.panel1.Controls.Add(this.label1); this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; - this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Location = new System.Drawing.Point(0, 25); + this.panel1.Margin = new System.Windows.Forms.Padding(10); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(515, 275); + this.panel1.Size = new System.Drawing.Size(324, 85); this.panel1.TabIndex = 0; // // menuStrip @@ -54,7 +64,7 @@ private void InitializeComponent() this.menuItemClose}); this.menuStrip.Location = new System.Drawing.Point(0, 0); this.menuStrip.Name = "menuStrip"; - this.menuStrip.Size = new System.Drawing.Size(515, 25); + this.menuStrip.Size = new System.Drawing.Size(434, 25); this.menuStrip.TabIndex = 0; this.menuStrip.Text = "Menu Strip"; // @@ -66,21 +76,74 @@ private void InitializeComponent() this.menuItemClose.Text = "Close"; this.menuItemClose.ToolTipText = "Closes the application"; // - // PresentationObsSceneSwitcher + // label1 + // + this.label1.AutoSize = true; + this.label1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); + this.label1.Location = new System.Drawing.Point(9, 10); + this.label1.Margin = new System.Windows.Forms.Padding(0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(152, 17); + this.label1.TabIndex = 0; + this.label1.Text = "OBS Websocket Server:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(9, 40); + this.label2.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(74, 17); + this.label2.TabIndex = 1; + this.label2.Text = "Ip Address:"; + // + // textBox1 + // + this.textBox1.Location = new System.Drawing.Point(88, 37); + this.textBox1.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(82, 25); + this.textBox1.TabIndex = 2; + this.textBox1.Text = "127.0.0.1"; + this.textBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // textBox2 + // + this.textBox2.Location = new System.Drawing.Point(259, 37); + this.textBox2.Margin = new System.Windows.Forms.Padding(5, 10, 0, 0); + this.textBox2.Name = "textBox2"; + this.textBox2.Size = new System.Drawing.Size(48, 25); + this.textBox2.TabIndex = 4; + this.textBox2.Text = "4444"; + this.textBox2.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(180, 40); + this.label3.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(74, 17); + this.label3.TabIndex = 3; + this.label3.Text = "Ip Address:"; + // + // ConfigurationForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(515, 275); + this.ClientSize = new System.Drawing.Size(324, 110); this.Controls.Add(this.panel1); + this.Controls.Add(this.menuStrip); this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.MainMenuStrip = this.menuStrip; - this.Name = "PresentationObsSceneSwitcher"; + this.Name = "ConfigurationForm"; this.Text = "Form1"; this.panel1.ResumeLayout(false); this.panel1.PerformLayout(); this.menuStrip.ResumeLayout(false); this.menuStrip.PerformLayout(); this.ResumeLayout(false); + this.PerformLayout(); } @@ -146,6 +209,11 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem1; private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem1; private System.Windows.Forms.ToolStripMenuItem menuItemClose; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Label label3; } } diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs similarity index 71% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs rename to PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs index 30999ab..ec0062a 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs @@ -10,9 +10,9 @@ namespace PresentationObsSceneSwitcher { - public partial class PresentationObsSceneSwitcher : Form + public partial class ConfigurationForm : Form { - public PresentationObsSceneSwitcher() + public ConfigurationForm() { InitializeComponent(); } diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.resx b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.resx similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.resx rename to PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.resx diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs index 2c4b238..b506521 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs @@ -17,7 +17,7 @@ static void Main() Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new PresentationObsSceneSwitcher()); + Application.Run(new ConfigurationForm()); } } } From ab1954745a53164670cb36fdf013e28fd4fbcdeb Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Tue, 15 Sep 2020 21:21:09 +0200 Subject: [PATCH 03/45] Interface changes --- .../ConfigurationForm.Designer.cs | 143 +++++++++++------- .../ConfigurationForm.cs | 42 +++++ 2 files changed, 129 insertions(+), 56 deletions(-) diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs index 510f6b9..58d34e7 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs @@ -29,14 +29,17 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); this.panel1 = new System.Windows.Forms.Panel(); - this.menuStrip = new System.Windows.Forms.MenuStrip(); - this.menuItemClose = new System.Windows.Forms.ToolStripMenuItem(); - this.label1 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.textBox1 = new System.Windows.Forms.TextBox(); this.textBox2 = new System.Windows.Forms.TextBox(); this.label3 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.menuStrip = new System.Windows.Forms.MenuStrip(); + this.menuItemClose = new System.Windows.Forms.ToolStripMenuItem(); + this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components); + this.button1 = new System.Windows.Forms.Button(); this.panel1.SuspendLayout(); this.menuStrip.SuspendLayout(); this.SuspendLayout(); @@ -44,6 +47,7 @@ private void InitializeComponent() // panel1 // this.panel1.BackColor = System.Drawing.Color.WhiteSmoke; + this.panel1.Controls.Add(this.button1); this.panel1.Controls.Add(this.textBox2); this.panel1.Controls.Add(this.label3); this.panel1.Controls.Add(this.textBox1); @@ -53,39 +57,38 @@ private void InitializeComponent() this.panel1.Location = new System.Drawing.Point(0, 25); this.panel1.Margin = new System.Windows.Forms.Padding(10); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(324, 85); + this.panel1.Size = new System.Drawing.Size(324, 112); this.panel1.TabIndex = 0; // - // menuStrip + // textBox2 // - this.menuStrip.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); - this.menuStrip.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.menuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.menuItemClose}); - this.menuStrip.Location = new System.Drawing.Point(0, 0); - this.menuStrip.Name = "menuStrip"; - this.menuStrip.Size = new System.Drawing.Size(434, 25); - this.menuStrip.TabIndex = 0; - this.menuStrip.Text = "Menu Strip"; + this.textBox2.Location = new System.Drawing.Point(259, 37); + this.textBox2.Margin = new System.Windows.Forms.Padding(5, 10, 0, 0); + this.textBox2.Name = "textBox2"; + this.textBox2.Size = new System.Drawing.Size(48, 25); + this.textBox2.TabIndex = 4; + this.textBox2.Text = "4444"; + this.textBox2.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // - // menuItemClose + // label3 // - this.menuItemClose.ForeColor = System.Drawing.Color.WhiteSmoke; - this.menuItemClose.Name = "menuItemClose"; - this.menuItemClose.Size = new System.Drawing.Size(52, 21); - this.menuItemClose.Text = "Close"; - this.menuItemClose.ToolTipText = "Closes the application"; + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(180, 40); + this.label3.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(74, 17); + this.label3.TabIndex = 3; + this.label3.Text = "Ip Address:"; // - // label1 + // textBox1 // - this.label1.AutoSize = true; - this.label1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); - this.label1.Location = new System.Drawing.Point(9, 10); - this.label1.Margin = new System.Windows.Forms.Padding(0); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(152, 17); - this.label1.TabIndex = 0; - this.label1.Text = "OBS Websocket Server:"; + this.textBox1.Location = new System.Drawing.Point(88, 37); + this.textBox1.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(82, 25); + this.textBox1.TabIndex = 2; + this.textBox1.Text = "127.0.0.1"; + this.textBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // // label2 // @@ -97,47 +100,73 @@ private void InitializeComponent() this.label2.TabIndex = 1; this.label2.Text = "Ip Address:"; // - // textBox1 + // label1 // - this.textBox1.Location = new System.Drawing.Point(88, 37); - this.textBox1.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); - this.textBox1.Name = "textBox1"; - this.textBox1.Size = new System.Drawing.Size(82, 25); - this.textBox1.TabIndex = 2; - this.textBox1.Text = "127.0.0.1"; - this.textBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.label1.AutoSize = true; + this.label1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); + this.label1.Location = new System.Drawing.Point(9, 10); + this.label1.Margin = new System.Windows.Forms.Padding(0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(152, 17); + this.label1.TabIndex = 0; + this.label1.Text = "OBS Websocket Server:"; // - // textBox2 + // menuStrip // - this.textBox2.Location = new System.Drawing.Point(259, 37); - this.textBox2.Margin = new System.Windows.Forms.Padding(5, 10, 0, 0); - this.textBox2.Name = "textBox2"; - this.textBox2.Size = new System.Drawing.Size(48, 25); - this.textBox2.TabIndex = 4; - this.textBox2.Text = "4444"; - this.textBox2.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.menuStrip.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); + this.menuStrip.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.menuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuItemClose}); + this.menuStrip.Location = new System.Drawing.Point(0, 0); + this.menuStrip.Name = "menuStrip"; + this.menuStrip.Size = new System.Drawing.Size(324, 25); + this.menuStrip.TabIndex = 0; + this.menuStrip.Text = "Menu Strip"; // - // label3 + // menuItemClose // - this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(180, 40); - this.label3.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(74, 17); - this.label3.TabIndex = 3; - this.label3.Text = "Ip Address:"; + this.menuItemClose.ForeColor = System.Drawing.Color.WhiteSmoke; + this.menuItemClose.Name = "menuItemClose"; + this.menuItemClose.Size = new System.Drawing.Size(52, 21); + this.menuItemClose.Text = "Close"; + this.menuItemClose.ToolTipText = "Closes the application"; + this.menuItemClose.Click += new System.EventHandler(this.menuItemClose_Click); + // + // notifyIcon + // + this.notifyIcon.BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Info; + this.notifyIcon.BalloonTipText = "Hidden!"; + this.notifyIcon.BalloonTipTitle = "Presentation Obs Scene Switcher"; + this.notifyIcon.Text = "notifyIcon"; + this.notifyIcon.Visible = true; + this.notifyIcon.DoubleClick += new System.EventHandler(this.notifyIcon_DoubleClick); + // + // button1 + // + this.button1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); + this.button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.button1.ForeColor = System.Drawing.Color.WhiteSmoke; + this.button1.Location = new System.Drawing.Point(9, 72); + this.button1.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(298, 30); + this.button1.TabIndex = 5; + this.button1.Text = "Start / Restart"; + this.button1.UseVisualStyleBackColor = false; // // ConfigurationForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(324, 110); + this.ClientSize = new System.Drawing.Size(324, 137); this.Controls.Add(this.panel1); this.Controls.Add(this.menuStrip); this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.MainMenuStrip = this.menuStrip; this.Name = "ConfigurationForm"; this.Text = "Form1"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ConfigurationForm_FormClosing); + this.Resize += new System.EventHandler(this.ConfigurationForm_Resize); this.panel1.ResumeLayout(false); this.panel1.PerformLayout(); this.menuStrip.ResumeLayout(false); @@ -214,6 +243,8 @@ private void InitializeComponent() private System.Windows.Forms.TextBox textBox1; private System.Windows.Forms.TextBox textBox2; private System.Windows.Forms.Label label3; + private System.Windows.Forms.NotifyIcon notifyIcon; + private System.Windows.Forms.Button button1; } } diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs index ec0062a..65f2a7a 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs @@ -12,10 +12,52 @@ namespace PresentationObsSceneSwitcher { public partial class ConfigurationForm : Form { + #region GUI Behavior fields + + private bool manualClosing; + private bool showDisplay; + + #endregion + public ConfigurationForm() { InitializeComponent(); } + #region GUI Behavior events + + protected override void SetVisibleCore(bool value) => base.SetVisibleCore(showDisplay && value); + + private void menuItemClose_Click(object sender, EventArgs e) + { + this.manualClosing = true; + this.notifyIcon.Dispose(); + this.Close(); + } + + private void notifyIcon_DoubleClick(object sender, EventArgs e) + { + this.showDisplay = true; + this.Visible = !this.Visible; + this.WindowState = FormWindowState.Normal; + } + + private void ConfigurationForm_Resize(object sender, EventArgs e) + { + if (this.WindowState == FormWindowState.Minimized) this.Hide(); + } + + private void ConfigurationForm_FormClosing(object sender, FormClosingEventArgs e) + { + if (!manualClosing) + { + e.Cancel = true; + this.Hide(); + } + } + + #endregion + + } } From 5cd327f7d86ea96386885fb30cfc8631d92fc6fc Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Tue, 15 Sep 2020 21:22:37 +0200 Subject: [PATCH 04/45] Ignore new project folder --- PowerPointToOBSSceneSwitcher.csproj | 54 ++++++++++++++++------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/PowerPointToOBSSceneSwitcher.csproj b/PowerPointToOBSSceneSwitcher.csproj index 75b9f85..43492be 100644 --- a/PowerPointToOBSSceneSwitcher.csproj +++ b/PowerPointToOBSSceneSwitcher.csproj @@ -1,24 +1,30 @@ - - - - Exe - netcoreapp3.1 - - - - - {91493440-5a91-11cf-8700-00aa0060263b} - 2 - 12 - primary - 0 - false - True - - - - - - - - + + + + Exe + netcoreapp3.1 + + + + + + + + + + + {91493440-5a91-11cf-8700-00aa0060263b} + 2 + 12 + primary + 0 + false + True + + + + + + + + From 123041019cbdc49c83a33bee8f048fdd84f70ac7 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Tue, 15 Sep 2020 21:50:07 +0200 Subject: [PATCH 05/45] OBS Sspport --- .../Obs/ObsClient.cs | 112 ++++++++++++++++++ .../PresentationObsSceneSwitcher.csproj | 16 +++ 2 files changed, 128 insertions(+) create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Obs/ObsClient.cs diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Obs/ObsClient.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Obs/ObsClient.cs new file mode 100644 index 0000000..5a90796 --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Obs/ObsClient.cs @@ -0,0 +1,112 @@ +using OBS.WebSocket.NET; +using System; +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Threading.Tasks; + +namespace PowerPointToOBSSceneSwitcher.Obs +{ + /// + /// Defines all the settings a ObsWebSocketClient needs + /// + /// Configuring the ObsWebSocketClient with a Settings object allows to use it with DI + public class ObsWebSocketClientSettings + { + private string ipAddress; + + /// + /// Ip address of the obs websocket server + /// + public string IpAddress + { + get => ipAddress; + set => this.ipAddress = IPAddress.TryParse(value, out _) ? value + : throw new ValidationException($"The field {nameof(IpAddress)} should be a valid IP"); + } + + /// + /// Port of the OBS WebSocket server + /// + public int Port { get; set; } + } + + /// + /// Connects to OBS WebSocket server + /// + public class ObsWebSocketClient : IDisposable + { + private readonly ObsWebSocketClientSettings settings; + + private bool disposedValue; + private ObsWebSocket obsWebSocket; + + /// + /// Constructor + /// + /// Settings to connect + public ObsWebSocketClient(ObsWebSocketClientSettings settings) + { + this.settings = settings; + this.obsWebSocket = new ObsWebSocket(); // Always not null + } + + /// + /// Connects to the OBS WebSocket server + /// + /// + public void Connect() + { + if (!obsWebSocket.IsConnected) + obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", ""); + } + + /// + /// Connects to the OBS WebSocket server + /// + /// + public async Task ConnectAsync() + { + // At least this won't block the main thread if using from GUI + if (!obsWebSocket.IsConnected) + await Task.Run(() => obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", "")); + } + + /// + /// Changes OBS current scene + /// + /// The Scene name in OBS + /// Always true + public bool ChangeScene(string scene) + { + obsWebSocket.Api.SetCurrentScene(scene); + return true; + } + + #region Dispose + + ~ObsWebSocketClient() => Dispose(disposing: false); + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + } + + obsWebSocket.Disconnect(); + obsWebSocket = null; + disposedValue = true; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj index 7b05c62..a868abc 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj @@ -6,4 +6,20 @@ true + + + + + + + {91493440-5a91-11cf-8700-00aa0060263b} + 2 + 12 + primary + 0 + false + True + + + \ No newline at end of file From 1135d182e84731fe902da888e9f8f09456971a44 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Tue, 15 Sep 2020 21:50:23 +0200 Subject: [PATCH 06/45] Powerpoint support --- .../IPresentationSubscriber.cs | 18 +++++ .../PowerPointPresentationSuscriber.cs | 71 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/IPresentationSubscriber.cs create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/IPresentationSubscriber.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/IPresentationSubscriber.cs new file mode 100644 index 0000000..7cf38e8 --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/IPresentationSubscriber.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; + +namespace PresentationObsSceneSwitcher +{ + /// + /// Receives the name of the OBS scene. + /// + /// + public delegate Task PresentationSuscription(string scene); + + /// + /// Manages the suscriptions to a Presetation + /// + public interface IPresentationSubscriber + { + bool Subscribe(string appName, PresentationSuscription suscription); + } +} diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs new file mode 100644 index 0000000..aa07acd --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs @@ -0,0 +1,71 @@ +using Microsoft.Office.Interop.PowerPoint; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace PresentationObsSceneSwitcher.PowerPoint +{ + /// + /// Subscribes to a PowerPoint presentation. + /// + public class PowerPointPresentationSubscriber : IPresentationSubscriber + { + /// + /// \( --> Literal + /// ((?[^\)]+)\) --> Avoid backtracking searching until a ) is found. No wildcard in regex please. + /// \) --> Literal + /// : --> Literal + /// \[ + /// (?[^\]]+) --> Avoid backtracking searching until a ] is found. No wildcard in regex please. + /// \] + /// + /// Making it static and RegexOptions.Compiled is the best for Regex performance. + /// + private static readonly Regex extractInfoFromNotesRegex = new Regex(@"\((?[^\)]+)\):\[(?[^\]]+)\]", RegexOptions.Compiled | RegexOptions.CultureInvariant); + + private static Application powerPoint = new Application(); + + private readonly Dictionary suscriptions = new Dictionary(); + + /// + /// Constructor + /// + public PowerPointPresentationSubscriber() + { + powerPoint.SlideShowNextSlide += async (SlideShowWindow Wn) => + { + Console.WriteLine($"Moved to Slide Number {Wn.View.Slide.SlideNumber}"); + + string note = String.Empty; + try + { + // Text starts at Index 2 ¯\_(ツ)_/¯ + note = Wn.View.Slide.NotesPage.Shapes[2].TextFrame.TextRange.Text; + } + catch + { + // No notes + } + + foreach (Match match in extractInfoFromNotesRegex.Matches(note)) + { + string appName = match.Groups["AppName"].Value; + string info = match.Groups["Info"].Value; + + if (suscriptions.TryGetValue(appName, out PresentationSuscription suscription)) + { + await suscription(info).ConfigureAwait(false); + } + } + }; + } + + /// + /// Subscribes an app to powerpoint slide changes + /// + /// The app subscribing + /// The handler of the slide change + /// + public bool Subscribe(string appName, PresentationSuscription suscription) => suscriptions.TryAdd(appName, suscription); + } +} From 1bd8bb9cf62089188335723a6e15c7acad97ae0d Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Tue, 15 Sep 2020 21:51:18 +0200 Subject: [PATCH 07/45] Move project to its parent folder (bad creation) --- PowerPointToOBSSceneSwitcher.sln | 2 +- .../ConfigurationForm.Designer.cs | 4 ++-- .../{PresentationObsSceneSwitcher => }/ConfigurationForm.cs | 0 .../{PresentationObsSceneSwitcher => }/ConfigurationForm.resx | 0 .../IPresentationSubscriber.cs | 0 .../{PresentationObsSceneSwitcher => }/Obs/ObsClient.cs | 0 .../PowerPoint/PowerPointPresentationSuscriber.cs | 0 .../PresentationObsSceneSwitcher.csproj | 0 .../{PresentationObsSceneSwitcher => }/Program.cs | 0 9 files changed, 3 insertions(+), 3 deletions(-) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/ConfigurationForm.Designer.cs (97%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/ConfigurationForm.cs (100%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/ConfigurationForm.resx (100%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/IPresentationSubscriber.cs (100%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/Obs/ObsClient.cs (100%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/PowerPoint/PowerPointPresentationSuscriber.cs (100%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/PresentationObsSceneSwitcher.csproj (100%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/Program.cs (100%) diff --git a/PowerPointToOBSSceneSwitcher.sln b/PowerPointToOBSSceneSwitcher.sln index 7682133..ebde54a 100644 --- a/PowerPointToOBSSceneSwitcher.sln +++ b/PowerPointToOBSSceneSwitcher.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 16.0.30428.66 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerPointToOBSSceneSwitcher", "PowerPointToOBSSceneSwitcher.csproj", "{8A844363-5A92-403B-90CD-BFCB30ED5054}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresentationObsSceneSwitcher", "PresentationObsSceneSwitcher\PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj", "{5B07EF15-11D6-4646-8322-965BD7D4ADEE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresentationObsSceneSwitcher", "PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj", "{5B07EF15-11D6-4646-8322-965BD7D4ADEE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs similarity index 97% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs rename to PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs index 58d34e7..82ff9c1 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs +++ b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs @@ -57,7 +57,7 @@ private void InitializeComponent() this.panel1.Location = new System.Drawing.Point(0, 25); this.panel1.Margin = new System.Windows.Forms.Padding(10); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(324, 112); + this.panel1.Size = new System.Drawing.Size(317, 112); this.panel1.TabIndex = 0; // // textBox2 @@ -158,7 +158,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(324, 137); + this.ClientSize = new System.Drawing.Size(317, 137); this.Controls.Add(this.panel1); this.Controls.Add(this.menuStrip); this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs b/PresentationObsSceneSwitcher/ConfigurationForm.cs similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs rename to PresentationObsSceneSwitcher/ConfigurationForm.cs diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.resx b/PresentationObsSceneSwitcher/ConfigurationForm.resx similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.resx rename to PresentationObsSceneSwitcher/ConfigurationForm.resx diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/IPresentationSubscriber.cs b/PresentationObsSceneSwitcher/IPresentationSubscriber.cs similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/IPresentationSubscriber.cs rename to PresentationObsSceneSwitcher/IPresentationSubscriber.cs diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Obs/ObsClient.cs b/PresentationObsSceneSwitcher/Obs/ObsClient.cs similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Obs/ObsClient.cs rename to PresentationObsSceneSwitcher/Obs/ObsClient.cs diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs b/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs rename to PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj rename to PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs b/PresentationObsSceneSwitcher/Program.cs similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs rename to PresentationObsSceneSwitcher/Program.cs From 30046dd122bfb989192e1e58d16fb1fe78527d4d Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Tue, 15 Sep 2020 23:15:18 +0200 Subject: [PATCH 08/45] Inital changes for saving configuration and load at startup. --- .../ConfigurationForm.Designer.cs | 119 ++-- .../ConfigurationForm.cs | 39 +- .../ConfigurationForm.resx | 575 ++++++++++++++++++ .../JsonSettingsRepository.cs | 30 + .../{ObsClient.cs => ObsWebSocketClient.cs} | 7 +- PresentationObsSceneSwitcher/Program.cs | 30 +- PresentationObsSceneSwitcher/proyector.ico | Bin 0 -> 16958 bytes 7 files changed, 741 insertions(+), 59 deletions(-) create mode 100644 PresentationObsSceneSwitcher/JsonSettingsRepository.cs rename PresentationObsSceneSwitcher/Obs/{ObsClient.cs => ObsWebSocketClient.cs} (91%) create mode 100644 PresentationObsSceneSwitcher/proyector.ico diff --git a/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs index 82ff9c1..a8e30d8 100644 --- a/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs +++ b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs @@ -30,16 +30,19 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConfigurationForm)); this.panel1 = new System.Windows.Forms.Panel(); - this.textBox2 = new System.Windows.Forms.TextBox(); + this.buttonStart = new System.Windows.Forms.Button(); + this.tbPort = new System.Windows.Forms.TextBox(); this.label3 = new System.Windows.Forms.Label(); - this.textBox1 = new System.Windows.Forms.TextBox(); + this.tbIpAddress = new System.Windows.Forms.TextBox(); this.label2 = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label(); this.menuStrip = new System.Windows.Forms.MenuStrip(); this.menuItemClose = new System.Windows.Forms.ToolStripMenuItem(); this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components); - this.button1 = new System.Windows.Forms.Button(); + this.tbPassword = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); this.panel1.SuspendLayout(); this.menuStrip.SuspendLayout(); this.SuspendLayout(); @@ -47,48 +50,64 @@ private void InitializeComponent() // panel1 // this.panel1.BackColor = System.Drawing.Color.WhiteSmoke; - this.panel1.Controls.Add(this.button1); - this.panel1.Controls.Add(this.textBox2); + this.panel1.Controls.Add(this.tbPassword); + this.panel1.Controls.Add(this.label4); + this.panel1.Controls.Add(this.buttonStart); + this.panel1.Controls.Add(this.tbPort); this.panel1.Controls.Add(this.label3); - this.panel1.Controls.Add(this.textBox1); + this.panel1.Controls.Add(this.tbIpAddress); this.panel1.Controls.Add(this.label2); this.panel1.Controls.Add(this.label1); this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; this.panel1.Location = new System.Drawing.Point(0, 25); this.panel1.Margin = new System.Windows.Forms.Padding(10); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(317, 112); + this.panel1.Size = new System.Drawing.Size(329, 150); this.panel1.TabIndex = 0; // - // textBox2 + // buttonStart // - this.textBox2.Location = new System.Drawing.Point(259, 37); - this.textBox2.Margin = new System.Windows.Forms.Padding(5, 10, 0, 0); - this.textBox2.Name = "textBox2"; - this.textBox2.Size = new System.Drawing.Size(48, 25); - this.textBox2.TabIndex = 4; - this.textBox2.Text = "4444"; - this.textBox2.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.buttonStart.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); + this.buttonStart.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonStart.ForeColor = System.Drawing.Color.WhiteSmoke; + this.buttonStart.Location = new System.Drawing.Point(9, 107); + this.buttonStart.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.buttonStart.Name = "buttonStart"; + this.buttonStart.Size = new System.Drawing.Size(308, 30); + this.buttonStart.TabIndex = 5; + this.buttonStart.Text = "Start / Restart"; + this.buttonStart.UseVisualStyleBackColor = false; + this.buttonStart.Click += new System.EventHandler(this.buttonStart_Click); + // + // tbPort + // + this.tbPort.Location = new System.Drawing.Point(269, 37); + this.tbPort.Margin = new System.Windows.Forms.Padding(5, 10, 0, 0); + this.tbPort.Name = "tbPort"; + this.tbPort.Size = new System.Drawing.Size(48, 25); + this.tbPort.TabIndex = 4; + this.tbPort.Text = "4444"; + this.tbPort.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // // label3 // this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(180, 40); + this.label3.Location = new System.Drawing.Point(185, 40); this.label3.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(74, 17); + this.label3.Size = new System.Drawing.Size(79, 17); this.label3.TabIndex = 3; - this.label3.Text = "Ip Address:"; + this.label3.Text = "Ip Address*:"; // - // textBox1 + // tbIpAddress // - this.textBox1.Location = new System.Drawing.Point(88, 37); - this.textBox1.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); - this.textBox1.Name = "textBox1"; - this.textBox1.Size = new System.Drawing.Size(82, 25); - this.textBox1.TabIndex = 2; - this.textBox1.Text = "127.0.0.1"; - this.textBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.tbIpAddress.Location = new System.Drawing.Point(93, 37); + this.tbIpAddress.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); + this.tbIpAddress.Name = "tbIpAddress"; + this.tbIpAddress.Size = new System.Drawing.Size(82, 25); + this.tbIpAddress.TabIndex = 2; + this.tbIpAddress.Text = "127.0.0.1"; + this.tbIpAddress.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // // label2 // @@ -96,9 +115,9 @@ private void InitializeComponent() this.label2.Location = new System.Drawing.Point(9, 40); this.label2.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(74, 17); + this.label2.Size = new System.Drawing.Size(79, 17); this.label2.TabIndex = 1; - this.label2.Text = "Ip Address:"; + this.label2.Text = "Ip Address*:"; // // label1 // @@ -119,7 +138,7 @@ private void InitializeComponent() this.menuItemClose}); this.menuStrip.Location = new System.Drawing.Point(0, 0); this.menuStrip.Name = "menuStrip"; - this.menuStrip.Size = new System.Drawing.Size(324, 25); + this.menuStrip.Size = new System.Drawing.Size(329, 25); this.menuStrip.TabIndex = 0; this.menuStrip.Text = "Menu Strip"; // @@ -137,34 +156,42 @@ private void InitializeComponent() this.notifyIcon.BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Info; this.notifyIcon.BalloonTipText = "Hidden!"; this.notifyIcon.BalloonTipTitle = "Presentation Obs Scene Switcher"; + this.notifyIcon.Icon = ((System.Drawing.Icon)(resources.GetObject("notifyIcon.Icon"))); this.notifyIcon.Text = "notifyIcon"; this.notifyIcon.Visible = true; this.notifyIcon.DoubleClick += new System.EventHandler(this.notifyIcon_DoubleClick); // - // button1 + // tbPassword + // + this.tbPassword.Location = new System.Drawing.Point(93, 72); + this.tbPassword.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); + this.tbPassword.Name = "tbPassword"; + this.tbPassword.Size = new System.Drawing.Size(224, 25); + this.tbPassword.TabIndex = 7; + this.tbPassword.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // label4 // - this.button1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); - this.button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.button1.ForeColor = System.Drawing.Color.WhiteSmoke; - this.button1.Location = new System.Drawing.Point(9, 72); - this.button1.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); - this.button1.Name = "button1"; - this.button1.Size = new System.Drawing.Size(298, 30); - this.button1.TabIndex = 5; - this.button1.Text = "Start / Restart"; - this.button1.UseVisualStyleBackColor = false; + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(9, 75); + this.label4.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(67, 17); + this.label4.TabIndex = 6; + this.label4.Text = "Password:"; // // ConfigurationForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(317, 137); + this.ClientSize = new System.Drawing.Size(329, 175); this.Controls.Add(this.panel1); this.Controls.Add(this.menuStrip); this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MainMenuStrip = this.menuStrip; this.Name = "ConfigurationForm"; - this.Text = "Form1"; + this.Text = "ConfigurationForm"; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ConfigurationForm_FormClosing); this.Resize += new System.EventHandler(this.ConfigurationForm_Resize); this.panel1.ResumeLayout(false); @@ -240,11 +267,13 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem menuItemClose; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; - private System.Windows.Forms.TextBox textBox1; - private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.TextBox tbIpAddress; + private System.Windows.Forms.TextBox tbPort; private System.Windows.Forms.Label label3; private System.Windows.Forms.NotifyIcon notifyIcon; - private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button buttonStart; + private System.Windows.Forms.TextBox tbPassword; + private System.Windows.Forms.Label label4; } } diff --git a/PresentationObsSceneSwitcher/ConfigurationForm.cs b/PresentationObsSceneSwitcher/ConfigurationForm.cs index 65f2a7a..8c4cb8f 100644 --- a/PresentationObsSceneSwitcher/ConfigurationForm.cs +++ b/PresentationObsSceneSwitcher/ConfigurationForm.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using PowerPointToOBSSceneSwitcher.Obs; +using PresentationObsSceneSwitcher.PowerPoint; +using System; using System.Windows.Forms; namespace PresentationObsSceneSwitcher @@ -19,9 +14,14 @@ public partial class ConfigurationForm : Form #endregion - public ConfigurationForm() + private readonly JsonSettingsRepository settingsRepository; + private ObsWebSocketClient connectedClient; + + // TODO: Refactor this Injection + public ConfigurationForm(JsonSettingsRepository settingsRepository, ObsWebSocketClient connectedClient) { InitializeComponent(); + this.settingsRepository = settingsRepository; } #region GUI Behavior events @@ -54,10 +54,31 @@ private void ConfigurationForm_FormClosing(object sender, FormClosingEventArgs e e.Cancel = true; this.Hide(); } + + connectedClient.Dispose(); } + #endregion + private async void buttonStart_Click(object sender, EventArgs e) + { + // TODO: Handle the parse better. + ObsWebSocketClientSettings settings = new ObsWebSocketClientSettings() + { + IpAddress = tbIpAddress.Text, + Port = int.Parse(tbPort.Text), + Password = tbPassword.Text + }; + await settingsRepository.SaveAsync(settings); + + connectedClient?.Dispose(); + connectedClient = new ObsWebSocketClient(settings); + await connectedClient.ConnectAsync(); + IPresentationSubscriber subscriber = new PowerPointPresentationSubscriber(); + + subscriber.Subscribe("OBS", async scene => connectedClient.ChangeScene(scene)); + } } } diff --git a/PresentationObsSceneSwitcher/ConfigurationForm.resx b/PresentationObsSceneSwitcher/ConfigurationForm.resx index b5ae26c..5456d88 100644 --- a/PresentationObsSceneSwitcher/ConfigurationForm.resx +++ b/PresentationObsSceneSwitcher/ConfigurationForm.resx @@ -57,4 +57,579 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAEAAAOkOAADpDgAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEXi8AhF4wA4NeNQaDXjUGg141BoNe + NQaDXjUGg141BoNeNQaDXjUGg141BoNeNQaCXTMEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgl0zBINeNQaDXjUGg141BoNeNQaDXjUGg141BoNe + NQaDXjUGg141BoNeNQaEXjADhF4vAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAg14wA4Ne + MSiDXjRYg140WINeNFiDXjRYg140WINeNFiDXjRYg140WINeNFiDXjRUgl0zNQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIJdMzWDXjRUg140WINe + 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 + Gv8yJhr/MiYa/zMkGis5HBwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAg10yAINd + MgqEXTIShF0zFIRdMxSAWjIVTjcjOzgqHP84Khz/OCoc/zkrHOVCMB9nhF0zFIRdMxSEXTMUhF0zFIRd + MxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRd + MxSEXTMUkGxBFrOXaCPIsH9JyrSEjsq1hNbLtYT5y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/su1 + hOPLtYSliXZVmEw9K+s7LR7/OCoc/zgqHP9ONyM7gFoyFYRdMxSEXTMUhF0yEoNdMgqDXTIAAAAAAAAA + AACCXDEAglwxDYNdMzWDXTNfg10zeYRdM3+EXTN/g10zf3dVMJRaQSb/WkEm/1pBJv9dQyfxbk4srIRd + M3+EXTN/hF0zf4RdM3+EXTN/hF0zf4RdM3+EXTN/hF0zf4RdM3+EXTN/hF0zf4RdM3+EXTN/hF0zf4Rd + M3+EXTN/hF0zf4RdM3+EXTN/i2Y8f6KDVYe8oXKxyLCA6cu1hP7LtYT/y7WE/8u1hP/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8iygvuijGT+dV9A/2BILP9bQif/d1UwlINdM3+EXTN/hF0zf4Nd + M3mDXTNfg10zNIVeMg2FXjIAg18zC4NdMmWDXTPcg10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/iGQ5/519Uf+8o3P/yrSD/8u1hP/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8Wtff+khln/jGg9/4Re + NP+DXTP/g10z/4NdM/+DXTP/g10z/4NdM9yEXTNkgVo1CoNeM0mDXTP0g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/iWU7/6KDVv/Ap3f/y7WE/8u1 + hP/LtYT/y7WE/8y2hf/PuIj/0rqM/9S8j//VvpD/1b2Q/9S9j//Tu43/0LmK/8y2hv/LtYT/y7WE/8u1 + hP/LtYT/ybOC/6yPYf+NaT7/hF40/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z84JbNEiDXTO6g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/iGM4/6OE + V//Cqnr/y7WE/8u1hP/LtYT/zLaF/9C5iv/Xv5L/4MWc/+jMpv/t0Kz/79Ov//DTsP/u0a3/6s6o/+LH + n//ZwJX/0rqM/8y2hf/LtYT/y7WE/8u1hP/LtIP/q45g/4pmO/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXDS5g10z8oNdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/hmA2/5x7T/++pXX/y7WE/8u1hP/LtYT/zbaG/9W8j//hx5//7dCs//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/79Ku/+XKo//Xv5P/zreI/8u1hP/LtYT/y7WE/8mzgv+jhVf/hmE3/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z8XJRLv9yUS7/clEu/3JRLv9yUS7/clEu/3JRLv9yUS7/clEu/3JR + Lv9yUS7/clEu/3JRLv9yUS7/clEu/3JRLv90Ui7/fFgx/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/5FvQ/+2m2z/y7WE/8u1hP/LtYT/zbeH/9e/k//nzKb/79Kv//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/68+r/9zDmP/OuIj/y7WE/8u1 + hP/LtYT/xK19/5RyRv+EXjT/g10z/4NdM/+DXTP/g10z/4NdM/9EMh//RDIf/0QyH/9EMh//RDIf/0Qy + H/9EMh//RDIf/0QyH/9EMh//RDIf/0QyH/9EMh//RDIf/0QyH/9EMh//STUh/2NHKf+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4diOP+lhln/xq9+/8u1hP/LtYT/zLaF/9e/ + k//ozaf/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/t0a3/28KX/823h//LtYT/y7WE/8u1hP+xlWf/iGQ5/4NdM/+DXTP/g10z/4NdM/+DXTP/Py8e/z8v + Hv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/0My + H/9dQyf/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+TcUb/uJ5v/8u1 + hP/LtYT/y7WE/9O8jf/my6P/8NOv//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw/+zPq//WvpD/zLaF/8u1hP/LtYT/yLGB/5Z1SP+EXjT/g10z/4Nd + M/+DXTP/g10z/2hKK/9oSiv/aEor/2hKK/9oSiv/aEor/2hKK/9oSiv/aEor/2hKK/9oSiv/aEor/2hK + K/9oSiv/aEor/2hKK/9sTSz/elcw/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+FXzX/ooNW/8avf//LtYT/y7WE/863h//exZv/7tGt//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/5Mmi/8+4if/LtYT/y7WE/8u1 + hP+vk2T/hmE3/4NdM/+DXTP/g10z/4NdM/99WTH/fVkx/31ZMf99WTH/fVkx/31ZMf99WTH/fVkx/31Z + Mf99WTH/fVkx/31ZMf99WTH/fVkx/31ZMf99WTH/flox/4JcM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/i2c8/66SY//LtYT/y7WE/8u1hP/UvI//58yl//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+7R + rv/WvZH/zLaF/8u1hP/LtYT/wqp7/4plO/+DXTP/g10z/4NdM/+DXTP/WUAm/1lAJv9ZQCb/WUAm/1lA + Jv9ZQCb/WUAm/1lAJv9ZQCb/WUAm/1lAJv9ZQCb/WUAm/1lAJv9ZQCb/WUAm/11DJ/9vUC3/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/5NxRf+4nm7/y7WE/8u1hP/LtYX/28KX/+3Q + rP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsf/w07H/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/4Mad/863h//LtYT/y7WE/8q1g/+TcET/hF40/4NdM/+DXTP/g10z/zIm + Gv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zIm + Gv84Khz/WEAl/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+ZeEz/v6Z2/8u1 + hP/LtYT/zbeH/+DGnf/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//Lavf/1483/9uTQ//Pc + wP/w1LP/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+jNpv/PuIn/y7WE/8u1hP/LtYT/nn5R/4Rf + Nf+DXTP/g10z/4NdM/9VPiX/VT4l/1U+Jf9VPiX/VT4l/1U+Jf9VPiX/VT4l/1U+Jf9VPiX/VT4l/1U+ + Jf9VPiX/VT4l/1U+Jf9VPiX/WUAm/21OLP+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/nXxQ/8Ore//LtYT/y7WE/9C5iv/jyKD/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//HW + tv/469v//vv5///+/P/68eX/8tq8//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/t0a3/0LmK/8u1 + hP/LtYT/y7WE/6aIWv+FXzX/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/55+Uf/FrX3/y7WE/8u1hP/Ruov/5Mmi//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/z3L//+/Xs//////////////38//Tfxf/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/79Ow/9C5iv/LtYT/y7WE/8u1hP+oi17/hWA1/4NdM/+DXTP/g10z/2hLK/9oSyv/aEsr/2hL + K/9oSyv/aEsr/2hLK/9oSyv/aEsr/2hLK/9oSyv/aEsr/2hLK/9oSyv/aEsr/2hLK/9rTSz/eFYw/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+efVH/xKx8/8u1hP/LtYT/0bmK/+TJ + of/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8tm7//rx5v///v3///////348v/z3cL/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw/+7Rrv/QuYr/y7WE/8u1hP/LtYT/p4lc/4VgNf+DXTP/g10z/4Nd + M/8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4u + Hv89Lh7/QjEf/19EKP+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/m3pO/8Go + ef/LtYT/y7WE/864iP/ix5//8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DUsf/14cn/+e7g//rv + 4//25M//8da2//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/rzqn/z7iJ/8u1hP/LtYT/y7WE/6KC + Vv+FXzX/g10z/4NdM/+DXTP/RTIg/0UyIP9FMiD/RTIg/0UyIP9FMiD/RTIg/0UyIP9FMiD/RTIg/0Uy + IP9FMiD/RTIg/0UyIP9FMiD/RTIg/0o2If9jRyn/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/5Z0SP+7oXL/y7WE/8u1hP/MtoX/3sSa/+/Srv/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NSx//LYuv/y2bv/8dSz//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/5Mmh/864 + iP/LtYT/y7WE/8u1hP+Yd0r/hF40/4NdM/+DXTP/g10z/25OLf9uTi3/bk4t/25OLf9uTi3/bk4t/25O + Lf9uTi3/bk4t/25OLf9uTi3/bk4t/25OLf9uTi3/bk4t/25OLf9xUS7/fVgx/4NdM/+DXTP/g10z/4Ff + Nv96ZkT/dmlK/39hOv+DXTP/g10z/4NdM/+Pa0H/spdp/8u1hP/LtYT/y7WE/9i/k//qzqn/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw/9rBlv/Ntob/y7WE/8u1hP/HsID/jWk+/4NdM/+DXTP/g10z/4NdM/93VS//d1Uv/3dV + L/93VS//d1Uv/3dVL/93VS//d1Uv/3dVL/93VS//d1Uv/3dVL/93VS//d1Uv/3dVL/94VS//elcx/4Fb + M/+DXTP/g10z/39hOv9oeGP/RZqi/0Ceqv9Zhn7/e2VC/4NdM/+DXTP/h2I3/6eJXP/Js4L/y7WE/8u1 + hP/QuYr/4sig/+/Sr//w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw/+rOqf/Suoz/y7WE/8u1hP/LtYT/uJ5v/4diN/+DXTP/g10z/4Nd + M/+DXTP/TTgi/004Iv9NOCL/TTgi/004Iv9NOCL/TTgi/004Iv9NOCL/TTgi/004Iv9NOCL/TTgi/004 + Iv9NOCL/TTki/1I8JP9oSyv/g10z/4NdM/90bE7/QZ2o/xrD7f8aw+//L6/J/2R7a/+DXTP/g10z/4Nd + M/+aeU3/v6Z3/8u1hP/LtYT/y7WE/9e/k//qzqn/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+/Srv/cwpj/zbeG/8u1hP/LtYT/y7SE/6CA + VP+FXzX/g10z/4NdM/+DXTP/g10z/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zIm + Gv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv85Khz/WkEn/4NdM/+DXTP/b3FX/zWovv8ZxPD/GcTw/ye3 + 2P9Zhn7/g10z/4NdM/+DXTP/i2c8/62RY//Js4L/y7WE/8u1hP/OuIj/3cSa/+zQq//w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/iyKD/0LmK/8u1 + hP/LtYT/y7WE/72jdP+NaT7/g10z/4NdM/+DXTP/g10z/4NdM/9gRSj/YEUo/2BFKP9gRSj/YEUo/2BF + KP9gRSj/YEUo/2BFKP9gRSj/YEUo/2BFKP9gRSj/YEUo/2BFKP9gRSj/ZEgp/3NSLv+DXTP/g10z/3ln + Rf9OkJL/IL3j/x3A6f86pLb/bXNb/4NdM/+DXTP/g10z/4ReNP+aeUz/vqV1/8u1hP/LtYT/y7WE/9C5 + iv/fxZz/7NCs//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+/S + r//kyaL/0ruN/8y2hf/LtYT/y7WE/8qzgv+gf1L/hWA1/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z+YNd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+CXjb/dGxO/1uEe/9XiIL/anVf/4BgOf+DXTP/g10z/4NdM/+DXTP/iWU6/6eJ + W//FrX3/y7WE/8u1hP/LtYT/0LmK/9zDmP/pzqj/79Kv//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw/+zQrP/gxp3/0ruN/8y2hf/LtYT/y7WE/8u1hP+zmGn/i2Y7/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z+YNdM9eDXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+CXjX/gV82/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+Oaj//r5Jk/8ixgP/LtYT/y7WE/8u1hP/OuIj/1r6R/+DGnf/pzaf/79Ku//DT + sP/w07D/8NOw//DTsP/w07D/686q/+PIoP/ZwJT/z7mJ/8u1hf/LtYT/y7WE/8u1hP+5oHD/k3FF/4Re + NP+DXTP/g10z/4NdM/+DXTP/g10z/4NdMtWCXTN6g10z/oNdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/hF40/5JvRP+yl2j/yLGA/8u1hP/LtYT/y7WE/8u1 + hP/PuIn/1b2P/9rBlv/exJr/4Mad/+DGnf/exZv/28KX/9a+kf/Ruov/zLaF/8u1hP/LtYT/y7WE/8u1 + hP+7onP/mHZK/4VfNf+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/6DXTJ4gV40HYNdM6WDXTP8g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+FXzX/kW9D/66R + Y//FrX3/y7WE/8u1hP/LtYT/y7WE/8u1hP/MtoX/zreH/8+4if/PuIn/zriH/8y2hf/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u0g/+3nG3/mHZK/4VgNv+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/uDXTOkg1wyHYNe + MwGDXjMpglwzcYNdM6iEXTO7hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRd + NL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRd + NL2EXTS9hF00vYVeNb2RbkS9rI9hzMKqeuvKtIP8y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8WuffOzmGnSmXdLv4ZgNr2EXTS9hF00vYRdNL2EXTS9hF0zu4Nd + M6iDXTRwg10yKINdMgEAAAAAgFo1AIFaNQ2EXTQphFwzM4RdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRd + NDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRd + NDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF42NJt7Tzy+pHRQy7SDiMu1hNHLtYT4y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE4cu1hJrDq3paooJWP4hiOTWEXTQ0hF00NIRd + NDSEXTQ0hF00NIRcMzOEXTQpg102DINdNgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1b+AAs22 + hBnMtIU9y7WEaMu1hJXLtYS4y7WE0su1hN7LtYTey7WE1su1hMDLtYSfy7WFc8u1hEnKtoQhyraEBQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADV1VUC5uBWG+jiViDq6lUEAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/4CAAMuzhAvKtYUey7aELsq1hDrLtYQ/y7WFP8u1hDzLtYUyzLWFI8q2 + hg/Ht4cCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObgVxnm4lgj5uJYAwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+BYJefg + V4vn4Fez5+FXWOTkWwoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObgWDLn4Fet5+BXwebe + ViLm3VUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAObhWCLn4Fec5+BX9ufgV9Dn4Vdp6N9XEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAefh + WErn4Fe+5+BX+OfgV+Hl31ge495aAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD//4AB5uZeCeTkWwzm4lgH5+BWPOfgV67n4Ff25+BX3+fgV5Dm4Vcu399gAwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA6uNVFubfVnnn4FfQ5+BX+ufgWNfm4FhR5OBaB9/fYAbr4FwS6+BcAQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6OBWK+fgV5Ln4Feg5t9WPeniWQjm31Yy5+BXmefg + V+3n4Ffu5+BXwufgV3Tm3lci1dVVAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAADj41UQ5+BXWufgV7Xn4Ffo5+BX/OfgV7jm4FhD5OBZCePjVRPn4FeX5+BXw+fe + VS7n3lUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOfhVzTn4Fe25+BX9Ofg + V7nn4VdJ4t5aCOfgWB/n4Fds5+BXz+fgV/jn4Ffn5+BWxOfgVonn4FdF6uJVEv//QAEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA399QCebgVjTn4Fd25+BXuefgV9/n4Ff55+BX6efgV4Tn4Fcs5eBaBefh + VR3n4Fen5+BX8OfgV/no4Vc16OFWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADo4lUL5+BXUefgV8bn4Ff35+BXzefgV2no4lcS6OBXD+jhVzjn4VeG5+BX2+fgV/rn4Ffu5+BX2+fg + V8fn4Vem5uBXhebhWG/n4Vhh5uBXYOfgVmnn31h+5+BXnOfgV8Hn4FfW5+BX6efgV/rn4Ffu5+BXoObg + V0Tn4FgV5+BYAufhV0Ln4Fe85+BX9efgV/bn31dz6OFXDejiVwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA//8AAOffVwvo4FdK5+BXvOfgV/nn4Ffj5+BXl+ffVzLh3VYG5+FXFObh + Vzbn4FZz5+BXvefgV/Dn4Ff+5+BX9ufgV+7n4Ffo5+BX5efgV+Tn4Ffn5+BX7OfgV/Tn4Ff85+BX++ff + V83n31eI5t9XQObgVhzl4FkF599YFujgV37n4FfW5+BX+ufgV+bo31he5t5XEebeVwAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6OFXCefhVzPn4FeZ5+BX8Ofg + V/Ln31fM5uBXfejgWCbu2lwF5+BWD+fgVyDo4Fc35+BXYefgWIro4Fiq599XwOfgV8zn4FfL5+BWxOjh + V7Hn4FeV599WbObgWD/n31cl6N9XFOTdVwTr4FwR5+BXYufgV8Hn4Ffs5+BX/effWL/o31c/6d5YC+ne + WQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADn4lcE6OJXG+fgV2Tn4FfL5+BX+efgV+7n4FfP5+BXj+ffWEHm31kO6t9VAufhWArn4FgT6OBYGeff + Vx7n4Fch5+BXIOfgVh/o4Vcb5+BWFebfVg3k3lkD/PxDA+bfWTHn4Fd55+BXxefgV+fn4Ff75+BX6Ofg + V33n31gk695WBe7dVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAOrqVQHn4FcL5t9WLufgV33n4FfX5+BX++fgV/Ln4Ffk5+BXx+jg + WJfo4Fdl5+FVP+jgVR/o4lEM5uZNAwAAAADf31AK6OJUGufgVzbn4FZZ5uFXiefgV77n4Ffe5+BX7ufg + V/zn4Fbt5+FXmefhVjfm4FYQ5uBWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOXeVQPn4FcO5t9XKufg + WGvn4Fe/5+BX8ufgV/zn4Ff05+BX7OfgV+bn4Ffh5+BX3ufgV9zn4Ffc5+BX3efgV+Dn4Ffk5+BX6ufg + V/Ln4Ff75+BX/OfgV8/n31iD5t9VMubhVxPn41QD5uZNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAOXcWALo31gK5+BXFufgVjfn4FZt599XpefgV9Tn4Ff05+BX/ufgV//n4Ff/5+BX/+fg + V//n4Ff/5+BX+ufgV9/m4Fez5uFYfejgV0Dn4Fcc599YDuTeVAPb20kAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm4VUD5+BWCuffVxDn4FcW5d9VHujh + VjLn4VZE5+BXUejgWFHm4FZJ5eFWOefhVSLn4FYY5uBXEubhWAvo4FYE6t9VAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAANvbSQDp4lQC5+JVBOfgVwbo4FgG5t9WBePhVQPm5k0B//8AAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA//////////////////////////////////////////////////////gAf////gAf8AB////+ + AA/wAH////4AD/AAf////gAP8AB////+AA/4AH////4AH/wA/////wA//gP///8AQH/+A///+AAAf+AA + AAAAAAAHgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAP/////8AAP//// + //D+AD+P////8H///wf////wP//8B////4AP//gB////gAP/4AD///+AAH8AAP///4AAAAAB////wAAA + AAP////gAAAAB/////AAAAAP////+AAIAB/////+AAAAf/////+AAAH///////AAD////////4D///// + //////////////////////////////////////////////////8= + + + + + AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAEAAAOkOAADpDgAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + MxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRd + MxSEXTMUkGxBFrOXaCPIsH9JyrSEjsq1hNbLtYT5y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/su1 + hOPLtYSliXZVmEw9K+s7LR7/OCoc/zgqHP9ONyM7gFoyFYRdMxSEXTMUhF0yEoNdMgqDXTIAAAAAAAAA + AACCXDEAglwxDYNdMzWDXTNfg10zeYRdM3+EXTN/g10zf3dVMJRaQSb/WkEm/1pBJv9dQyfxbk4srIRd + M3+EXTN/hF0zf4RdM3+EXTN/hF0zf4RdM3+EXTN/hF0zf4RdM3+EXTN/hF0zf4RdM3+EXTN/hF0zf4Rd + M3+EXTN/hF0zf4RdM3+EXTN/i2Y8f6KDVYe8oXKxyLCA6cu1hP7LtYT/y7WE/8u1hP/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8iygvuijGT+dV9A/2BILP9bQif/d1UwlINdM3+EXTN/hF0zf4Nd + M3mDXTNfg10zNIVeMg2FXjIAg18zC4NdMmWDXTPcg10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/iGQ5/519Uf+8o3P/yrSD/8u1hP/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8Wtff+khln/jGg9/4Re + NP+DXTP/g10z/4NdM/+DXTP/g10z/4NdM9yEXTNkgVo1CoNeM0mDXTP0g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/iWU7/6KDVv/Ap3f/y7WE/8u1 + hP/LtYT/y7WE/8y2hf/PuIj/0rqM/9S8j//VvpD/1b2Q/9S9j//Tu43/0LmK/8y2hv/LtYT/y7WE/8u1 + hP/LtYT/ybOC/6yPYf+NaT7/hF40/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z84JbNEiDXTO6g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/iGM4/6OE + V//Cqnr/y7WE/8u1hP/LtYT/zLaF/9C5iv/Xv5L/4MWc/+jMpv/t0Kz/79Ov//DTsP/u0a3/6s6o/+LH + n//ZwJX/0rqM/8y2hf/LtYT/y7WE/8u1hP/LtIP/q45g/4pmO/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXDS5g10z8oNdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/hmA2/5x7T/++pXX/y7WE/8u1hP/LtYT/zbaG/9W8j//hx5//7dCs//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/79Ku/+XKo//Xv5P/zreI/8u1hP/LtYT/y7WE/8mzgv+jhVf/hmE3/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z8XJRLv9yUS7/clEu/3JRLv9yUS7/clEu/3JRLv9yUS7/clEu/3JR + Lv9yUS7/clEu/3JRLv9yUS7/clEu/3JRLv90Ui7/fFgx/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/5FvQ/+2m2z/y7WE/8u1hP/LtYT/zbeH/9e/k//nzKb/79Kv//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/68+r/9zDmP/OuIj/y7WE/8u1 + hP/LtYT/xK19/5RyRv+EXjT/g10z/4NdM/+DXTP/g10z/4NdM/9EMh//RDIf/0QyH/9EMh//RDIf/0Qy + H/9EMh//RDIf/0QyH/9EMh//RDIf/0QyH/9EMh//RDIf/0QyH/9EMh//STUh/2NHKf+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4diOP+lhln/xq9+/8u1hP/LtYT/zLaF/9e/ + k//ozaf/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/t0a3/28KX/823h//LtYT/y7WE/8u1hP+xlWf/iGQ5/4NdM/+DXTP/g10z/4NdM/+DXTP/Py8e/z8v + Hv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/0My + H/9dQyf/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+TcUb/uJ5v/8u1 + hP/LtYT/y7WE/9O8jf/my6P/8NOv//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw/+zPq//WvpD/zLaF/8u1hP/LtYT/yLGB/5Z1SP+EXjT/g10z/4Nd + M/+DXTP/g10z/2hKK/9oSiv/aEor/2hKK/9oSiv/aEor/2hKK/9oSiv/aEor/2hKK/9oSiv/aEor/2hK + K/9oSiv/aEor/2hKK/9sTSz/elcw/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+FXzX/ooNW/8avf//LtYT/y7WE/863h//exZv/7tGt//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/5Mmi/8+4if/LtYT/y7WE/8u1 + hP+vk2T/hmE3/4NdM/+DXTP/g10z/4NdM/99WTH/fVkx/31ZMf99WTH/fVkx/31ZMf99WTH/fVkx/31Z + Mf99WTH/fVkx/31ZMf99WTH/fVkx/31ZMf99WTH/flox/4JcM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/i2c8/66SY//LtYT/y7WE/8u1hP/UvI//58yl//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+7R + rv/WvZH/zLaF/8u1hP/LtYT/wqp7/4plO/+DXTP/g10z/4NdM/+DXTP/WUAm/1lAJv9ZQCb/WUAm/1lA + Jv9ZQCb/WUAm/1lAJv9ZQCb/WUAm/1lAJv9ZQCb/WUAm/1lAJv9ZQCb/WUAm/11DJ/9vUC3/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/5NxRf+4nm7/y7WE/8u1hP/LtYX/28KX/+3Q + rP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsf/w07H/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/4Mad/863h//LtYT/y7WE/8q1g/+TcET/hF40/4NdM/+DXTP/g10z/zIm + Gv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zIm + Gv84Khz/WEAl/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+ZeEz/v6Z2/8u1 + hP/LtYT/zbeH/+DGnf/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//Lavf/1483/9uTQ//Pc + wP/w1LP/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+jNpv/PuIn/y7WE/8u1hP/LtYT/nn5R/4Rf + Nf+DXTP/g10z/4NdM/9VPiX/VT4l/1U+Jf9VPiX/VT4l/1U+Jf9VPiX/VT4l/1U+Jf9VPiX/VT4l/1U+ + Jf9VPiX/VT4l/1U+Jf9VPiX/WUAm/21OLP+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/nXxQ/8Ore//LtYT/y7WE/9C5iv/jyKD/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//HW + tv/469v//vv5///+/P/68eX/8tq8//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/t0a3/0LmK/8u1 + hP/LtYT/y7WE/6aIWv+FXzX/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/55+Uf/FrX3/y7WE/8u1hP/Ruov/5Mmi//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/z3L//+/Xs//////////////38//Tfxf/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/79Ow/9C5iv/LtYT/y7WE/8u1hP+oi17/hWA1/4NdM/+DXTP/g10z/2hLK/9oSyv/aEsr/2hL + K/9oSyv/aEsr/2hLK/9oSyv/aEsr/2hLK/9oSyv/aEsr/2hLK/9oSyv/aEsr/2hLK/9rTSz/eFYw/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+efVH/xKx8/8u1hP/LtYT/0bmK/+TJ + of/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8tm7//rx5v///v3///////348v/z3cL/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw/+7Rrv/QuYr/y7WE/8u1hP/LtYT/p4lc/4VgNf+DXTP/g10z/4Nd + M/8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4u + Hv89Lh7/QjEf/19EKP+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/m3pO/8Go + ef/LtYT/y7WE/864iP/ix5//8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DUsf/14cn/+e7g//rv + 4//25M//8da2//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/rzqn/z7iJ/8u1hP/LtYT/y7WE/6KC + Vv+FXzX/g10z/4NdM/+DXTP/RTIg/0UyIP9FMiD/RTIg/0UyIP9FMiD/RTIg/0UyIP9FMiD/RTIg/0Uy + IP9FMiD/RTIg/0UyIP9FMiD/RTIg/0o2If9jRyn/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/5Z0SP+7oXL/y7WE/8u1hP/MtoX/3sSa/+/Srv/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NSx//LYuv/y2bv/8dSz//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/5Mmh/864 + iP/LtYT/y7WE/8u1hP+Yd0r/hF40/4NdM/+DXTP/g10z/25OLf9uTi3/bk4t/25OLf9uTi3/bk4t/25O + Lf9uTi3/bk4t/25OLf9uTi3/bk4t/25OLf9uTi3/bk4t/25OLf9xUS7/fVgx/4NdM/+DXTP/g10z/4Ff + Nv96ZkT/dmlK/39hOv+DXTP/g10z/4NdM/+Pa0H/spdp/8u1hP/LtYT/y7WE/9i/k//qzqn/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw/9rBlv/Ntob/y7WE/8u1hP/HsID/jWk+/4NdM/+DXTP/g10z/4NdM/93VS//d1Uv/3dV + L/93VS//d1Uv/3dVL/93VS//d1Uv/3dVL/93VS//d1Uv/3dVL/93VS//d1Uv/3dVL/94VS//elcx/4Fb + M/+DXTP/g10z/39hOv9oeGP/RZqi/0Ceqv9Zhn7/e2VC/4NdM/+DXTP/h2I3/6eJXP/Js4L/y7WE/8u1 + hP/QuYr/4sig/+/Sr//w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw/+rOqf/Suoz/y7WE/8u1hP/LtYT/uJ5v/4diN/+DXTP/g10z/4Nd + M/+DXTP/TTgi/004Iv9NOCL/TTgi/004Iv9NOCL/TTgi/004Iv9NOCL/TTgi/004Iv9NOCL/TTgi/004 + Iv9NOCL/TTki/1I8JP9oSyv/g10z/4NdM/90bE7/QZ2o/xrD7f8aw+//L6/J/2R7a/+DXTP/g10z/4Nd + M/+aeU3/v6Z3/8u1hP/LtYT/y7WE/9e/k//qzqn/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+/Srv/cwpj/zbeG/8u1hP/LtYT/y7SE/6CA + VP+FXzX/g10z/4NdM/+DXTP/g10z/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zIm + Gv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv85Khz/WkEn/4NdM/+DXTP/b3FX/zWovv8ZxPD/GcTw/ye3 + 2P9Zhn7/g10z/4NdM/+DXTP/i2c8/62RY//Js4L/y7WE/8u1hP/OuIj/3cSa/+zQq//w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/iyKD/0LmK/8u1 + hP/LtYT/y7WE/72jdP+NaT7/g10z/4NdM/+DXTP/g10z/4NdM/9gRSj/YEUo/2BFKP9gRSj/YEUo/2BF + KP9gRSj/YEUo/2BFKP9gRSj/YEUo/2BFKP9gRSj/YEUo/2BFKP9gRSj/ZEgp/3NSLv+DXTP/g10z/3ln + Rf9OkJL/IL3j/x3A6f86pLb/bXNb/4NdM/+DXTP/g10z/4ReNP+aeUz/vqV1/8u1hP/LtYT/y7WE/9C5 + iv/fxZz/7NCs//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+/S + r//kyaL/0ruN/8y2hf/LtYT/y7WE/8qzgv+gf1L/hWA1/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z+YNd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+CXjb/dGxO/1uEe/9XiIL/anVf/4BgOf+DXTP/g10z/4NdM/+DXTP/iWU6/6eJ + W//FrX3/y7WE/8u1hP/LtYT/0LmK/9zDmP/pzqj/79Kv//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw/+zQrP/gxp3/0ruN/8y2hf/LtYT/y7WE/8u1hP+zmGn/i2Y7/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z+YNdM9eDXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+CXjX/gV82/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+Oaj//r5Jk/8ixgP/LtYT/y7WE/8u1hP/OuIj/1r6R/+DGnf/pzaf/79Ku//DT + sP/w07D/8NOw//DTsP/w07D/686q/+PIoP/ZwJT/z7mJ/8u1hf/LtYT/y7WE/8u1hP+5oHD/k3FF/4Re + NP+DXTP/g10z/4NdM/+DXTP/g10z/4NdMtWCXTN6g10z/oNdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/hF40/5JvRP+yl2j/yLGA/8u1hP/LtYT/y7WE/8u1 + hP/PuIn/1b2P/9rBlv/exJr/4Mad/+DGnf/exZv/28KX/9a+kf/Ruov/zLaF/8u1hP/LtYT/y7WE/8u1 + hP+7onP/mHZK/4VfNf+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/6DXTJ4gV40HYNdM6WDXTP8g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+FXzX/kW9D/66R + Y//FrX3/y7WE/8u1hP/LtYT/y7WE/8u1hP/MtoX/zreH/8+4if/PuIn/zriH/8y2hf/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u0g/+3nG3/mHZK/4VgNv+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/uDXTOkg1wyHYNe + MwGDXjMpglwzcYNdM6iEXTO7hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRd + NL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRd + NL2EXTS9hF00vYVeNb2RbkS9rI9hzMKqeuvKtIP8y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8WuffOzmGnSmXdLv4ZgNr2EXTS9hF00vYRdNL2EXTS9hF0zu4Nd + M6iDXTRwg10yKINdMgEAAAAAgFo1AIFaNQ2EXTQphFwzM4RdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRd + NDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRd + NDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF42NJt7Tzy+pHRQy7SDiMu1hNHLtYT4y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE4cu1hJrDq3paooJWP4hiOTWEXTQ0hF00NIRd + NDSEXTQ0hF00NIRcMzOEXTQpg102DINdNgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1b+AAs22 + hBnMtIU9y7WEaMu1hJXLtYS4y7WE0su1hN7LtYTey7WE1su1hMDLtYSfy7WFc8u1hEnKtoQhyraEBQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADV1VUC5uBWG+jiViDq6lUEAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/4CAAMuzhAvKtYUey7aELsq1hDrLtYQ/y7WFP8u1hDzLtYUyzLWFI8q2 + hg/Ht4cCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObgVxnm4lgj5uJYAwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+BYJefg + V4vn4Fez5+FXWOTkWwoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObgWDLn4Fet5+BXwebe + ViLm3VUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAObhWCLn4Fec5+BX9ufgV9Dn4Vdp6N9XEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAefh + WErn4Fe+5+BX+OfgV+Hl31ge495aAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD//4AB5uZeCeTkWwzm4lgH5+BWPOfgV67n4Ff25+BX3+fgV5Dm4Vcu399gAwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA6uNVFubfVnnn4FfQ5+BX+ufgWNfm4FhR5OBaB9/fYAbr4FwS6+BcAQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6OBWK+fgV5Ln4Feg5t9WPeniWQjm31Yy5+BXmefg + V+3n4Ffu5+BXwufgV3Tm3lci1dVVAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAADj41UQ5+BXWufgV7Xn4Ffo5+BX/OfgV7jm4FhD5OBZCePjVRPn4FeX5+BXw+fe + VS7n3lUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOfhVzTn4Fe25+BX9Ofg + V7nn4VdJ4t5aCOfgWB/n4Fds5+BXz+fgV/jn4Ffn5+BWxOfgVonn4FdF6uJVEv//QAEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA399QCebgVjTn4Fd25+BXuefgV9/n4Ff55+BX6efgV4Tn4Fcs5eBaBefh + VR3n4Fen5+BX8OfgV/no4Vc16OFWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADo4lUL5+BXUefgV8bn4Ff35+BXzefgV2no4lcS6OBXD+jhVzjn4VeG5+BX2+fgV/rn4Ffu5+BX2+fg + V8fn4Vem5uBXhebhWG/n4Vhh5uBXYOfgVmnn31h+5+BXnOfgV8Hn4FfW5+BX6efgV/rn4Ffu5+BXoObg + V0Tn4FgV5+BYAufhV0Ln4Fe85+BX9efgV/bn31dz6OFXDejiVwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA//8AAOffVwvo4FdK5+BXvOfgV/nn4Ffj5+BXl+ffVzLh3VYG5+FXFObh + Vzbn4FZz5+BXvefgV/Dn4Ff+5+BX9ufgV+7n4Ffo5+BX5efgV+Tn4Ffn5+BX7OfgV/Tn4Ff85+BX++ff + V83n31eI5t9XQObgVhzl4FkF599YFujgV37n4FfW5+BX+ufgV+bo31he5t5XEebeVwAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6OFXCefhVzPn4FeZ5+BX8Ofg + V/Ln31fM5uBXfejgWCbu2lwF5+BWD+fgVyDo4Fc35+BXYefgWIro4Fiq599XwOfgV8zn4FfL5+BWxOjh + V7Hn4FeV599WbObgWD/n31cl6N9XFOTdVwTr4FwR5+BXYufgV8Hn4Ffs5+BX/effWL/o31c/6d5YC+ne + WQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADn4lcE6OJXG+fgV2Tn4FfL5+BX+efgV+7n4FfP5+BXj+ffWEHm31kO6t9VAufhWArn4FgT6OBYGeff + Vx7n4Fch5+BXIOfgVh/o4Vcb5+BWFebfVg3k3lkD/PxDA+bfWTHn4Fd55+BXxefgV+fn4Ff75+BX6Ofg + V33n31gk695WBe7dVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAOrqVQHn4FcL5t9WLufgV33n4FfX5+BX++fgV/Ln4Ffk5+BXx+jg + WJfo4Fdl5+FVP+jgVR/o4lEM5uZNAwAAAADf31AK6OJUGufgVzbn4FZZ5uFXiefgV77n4Ffe5+BX7ufg + V/zn4Fbt5+FXmefhVjfm4FYQ5uBWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOXeVQPn4FcO5t9XKufg + WGvn4Fe/5+BX8ufgV/zn4Ff05+BX7OfgV+bn4Ffh5+BX3ufgV9zn4Ffc5+BX3efgV+Dn4Ffk5+BX6ufg + V/Ln4Ff75+BX/OfgV8/n31iD5t9VMubhVxPn41QD5uZNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAOXcWALo31gK5+BXFufgVjfn4FZt599XpefgV9Tn4Ff05+BX/ufgV//n4Ff/5+BX/+fg + V//n4Ff/5+BX+ufgV9/m4Fez5uFYfejgV0Dn4Fcc599YDuTeVAPb20kAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm4VUD5+BWCuffVxDn4FcW5d9VHujh + VjLn4VZE5+BXUejgWFHm4FZJ5eFWOefhVSLn4FYY5uBXEubhWAvo4FYE6t9VAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAANvbSQDp4lQC5+JVBOfgVwbo4FgG5t9WBePhVQPm5k0B//8AAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA//////////////////////////////////////////////////////gAf////gAf8AB////+ + AA/wAH////4AD/AAf////gAP8AB////+AA/4AH////4AH/wA/////wA//gP///8AQH/+A///+AAAf+AA + AAAAAAAHgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAP/////8AAP//// + //D+AD+P////8H///wf////wP//8B////4AP//gB////gAP/4AD///+AAH8AAP///4AAAAAB////wAAA + AAP////gAAAAB/////AAAAAP////+AAIAB/////+AAAAf/////+AAAH///////AAD////////4D///// + //////////////////////////////////////////////////8= + + \ No newline at end of file diff --git a/PresentationObsSceneSwitcher/JsonSettingsRepository.cs b/PresentationObsSceneSwitcher/JsonSettingsRepository.cs new file mode 100644 index 0000000..cf045a0 --- /dev/null +++ b/PresentationObsSceneSwitcher/JsonSettingsRepository.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; +using PowerPointToOBSSceneSwitcher.Obs; +using System.IO; +using System.Threading.Tasks; + +namespace PresentationObsSceneSwitcher +{ + public class JsonSettingsRepository + { + public async Task SaveAsync(ObsWebSocketClientSettings settings) + { + string json = JsonConvert.SerializeObject(settings); + await File.WriteAllTextAsync(@".\settings.json", json).ConfigureAwait(false); // Maybe AppData is a better location, but... + } + + public async Task LoadAsync() + { + try + { + string json = await File.ReadAllTextAsync(@".\settings.json").ConfigureAwait(false); + + return JsonConvert.DeserializeObject(json); + } + catch (FileNotFoundException ex) + { + return null; + } + } + } +} diff --git a/PresentationObsSceneSwitcher/Obs/ObsClient.cs b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs similarity index 91% rename from PresentationObsSceneSwitcher/Obs/ObsClient.cs rename to PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs index 5a90796..f6d63ef 100644 --- a/PresentationObsSceneSwitcher/Obs/ObsClient.cs +++ b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs @@ -28,6 +28,11 @@ public string IpAddress /// Port of the OBS WebSocket server /// public int Port { get; set; } + + /// + /// Optional Password of the OBS WebSocket server + /// + public string Password { get; set; } } /// @@ -57,7 +62,7 @@ public ObsWebSocketClient(ObsWebSocketClientSettings settings) public void Connect() { if (!obsWebSocket.IsConnected) - obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", ""); + obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", settings.Password ?? ""); } /// diff --git a/PresentationObsSceneSwitcher/Program.cs b/PresentationObsSceneSwitcher/Program.cs index b506521..d7d017f 100644 --- a/PresentationObsSceneSwitcher/Program.cs +++ b/PresentationObsSceneSwitcher/Program.cs @@ -1,6 +1,6 @@ +using PowerPointToOBSSceneSwitcher.Obs; +using PresentationObsSceneSwitcher.PowerPoint; using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; @@ -12,12 +12,34 @@ static class Program /// The main entry point for the application. /// [STAThread] - static void Main() + static async Task Main() { Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new ConfigurationForm()); + + JsonSettingsRepository settingsRepository = new JsonSettingsRepository(); + ObsWebSocketClient connectedClient = await ConnectWithSavedSettings(settingsRepository).ConfigureAwait(false); + + Application.Run(new ConfigurationForm(settingsRepository, connectedClient)); + } + + private static async Task ConnectWithSavedSettings(JsonSettingsRepository settingsRepository) + { + ObsWebSocketClientSettings savedSettings = await settingsRepository.LoadAsync(); + + if (!(savedSettings is null)) + { + ObsWebSocketClient connectedClient = new ObsWebSocketClient(savedSettings); + await connectedClient.ConnectAsync(); + + IPresentationSubscriber subscriber = new PowerPointPresentationSubscriber(); + subscriber.Subscribe("OBS", async scene => connectedClient.ChangeScene(scene)); + + return connectedClient; + } + + return null; } } } diff --git a/PresentationObsSceneSwitcher/proyector.ico b/PresentationObsSceneSwitcher/proyector.ico new file mode 100644 index 0000000000000000000000000000000000000000..4ec97ec4dc0b71465ccc596609fd2a2f71bbf0bd GIT binary patch literal 16958 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 QsK~`L+QS3=twVqQ4=*L0-2eap literal 0 HcmV?d00001 From 3547a4a01c889cab4cfc5c653854fb799e6e449e Mon Sep 17 00:00:00 2001 From: Stephen Date: Wed, 16 Sep 2020 10:09:04 +0100 Subject: [PATCH 09/45] Fix exception handling code --- Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Program.cs b/Program.cs index 29ef558..08e08da 100644 --- a/Program.cs +++ b/Program.cs @@ -42,7 +42,7 @@ async static void App_SlideShowNextSlide(SlideShowWindow Wn) line = line.Substring(4).Trim(); Console.WriteLine($" Switching to OBS Scene named \"{line}\""); try { OBS.ChangeScene(line); } - catch { Console.WriteLine($" ERROR: {ex.Message.ToString()}"); } + catch (Exception ex) { Console.WriteLine($" ERROR: {ex.Message}"); } break; } } From d47f0d07c4df2036d481b92477ece5f53f5d5ff6 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 20:52:38 +0200 Subject: [PATCH 10/45] Remove dependencies from the Form. Handle the lifecycle of the client and settings in Program. --- .../ConfigurationForm.Designer.cs | 42 +++++++++---------- .../ConfigurationForm.cs | 32 +++++--------- .../Obs/ObsWebSocketClient.cs | 17 ++++---- PresentationObsSceneSwitcher/Program.cs | 29 ++++++------- 4 files changed, 55 insertions(+), 65 deletions(-) diff --git a/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs index a8e30d8..6a82873 100644 --- a/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs +++ b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs @@ -32,6 +32,8 @@ private void InitializeComponent() this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConfigurationForm)); this.panel1 = new System.Windows.Forms.Panel(); + this.tbPassword = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); this.buttonStart = new System.Windows.Forms.Button(); this.tbPort = new System.Windows.Forms.TextBox(); this.label3 = new System.Windows.Forms.Label(); @@ -41,8 +43,6 @@ private void InitializeComponent() this.menuStrip = new System.Windows.Forms.MenuStrip(); this.menuItemClose = new System.Windows.Forms.ToolStripMenuItem(); this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components); - this.tbPassword = new System.Windows.Forms.TextBox(); - this.label4 = new System.Windows.Forms.Label(); this.panel1.SuspendLayout(); this.menuStrip.SuspendLayout(); this.SuspendLayout(); @@ -65,6 +65,25 @@ private void InitializeComponent() this.panel1.Size = new System.Drawing.Size(329, 150); this.panel1.TabIndex = 0; // + // tbPassword + // + this.tbPassword.Location = new System.Drawing.Point(93, 72); + this.tbPassword.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); + this.tbPassword.Name = "tbPassword"; + this.tbPassword.Size = new System.Drawing.Size(224, 25); + this.tbPassword.TabIndex = 7; + this.tbPassword.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(9, 75); + this.label4.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(67, 17); + this.label4.TabIndex = 6; + this.label4.Text = "Password:"; + // // buttonStart // this.buttonStart.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); @@ -161,25 +180,6 @@ private void InitializeComponent() this.notifyIcon.Visible = true; this.notifyIcon.DoubleClick += new System.EventHandler(this.notifyIcon_DoubleClick); // - // tbPassword - // - this.tbPassword.Location = new System.Drawing.Point(93, 72); - this.tbPassword.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); - this.tbPassword.Name = "tbPassword"; - this.tbPassword.Size = new System.Drawing.Size(224, 25); - this.tbPassword.TabIndex = 7; - this.tbPassword.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; - // - // label4 - // - this.label4.AutoSize = true; - this.label4.Location = new System.Drawing.Point(9, 75); - this.label4.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(67, 17); - this.label4.TabIndex = 6; - this.label4.Text = "Password:"; - // // ConfigurationForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); diff --git a/PresentationObsSceneSwitcher/ConfigurationForm.cs b/PresentationObsSceneSwitcher/ConfigurationForm.cs index 8c4cb8f..6e618db 100644 --- a/PresentationObsSceneSwitcher/ConfigurationForm.cs +++ b/PresentationObsSceneSwitcher/ConfigurationForm.cs @@ -1,5 +1,4 @@ using PowerPointToOBSSceneSwitcher.Obs; -using PresentationObsSceneSwitcher.PowerPoint; using System; using System.Windows.Forms; @@ -14,14 +13,15 @@ public partial class ConfigurationForm : Form #endregion - private readonly JsonSettingsRepository settingsRepository; - private ObsWebSocketClient connectedClient; + private readonly ObsWebSocketClientSettings settings; + private ObsWebSocketClient client; // TODO: Refactor this Injection - public ConfigurationForm(JsonSettingsRepository settingsRepository, ObsWebSocketClient connectedClient) + public ConfigurationForm(ObsWebSocketClientSettings settings, ObsWebSocketClient client) { InitializeComponent(); - this.settingsRepository = settingsRepository; + this.settings = settings ?? new ObsWebSocketClientSettings(); + this.client = client; } #region GUI Behavior events @@ -54,8 +54,6 @@ private void ConfigurationForm_FormClosing(object sender, FormClosingEventArgs e e.Cancel = true; this.Hide(); } - - connectedClient.Dispose(); } @@ -63,22 +61,12 @@ private void ConfigurationForm_FormClosing(object sender, FormClosingEventArgs e private async void buttonStart_Click(object sender, EventArgs e) { - // TODO: Handle the parse better. - ObsWebSocketClientSettings settings = new ObsWebSocketClientSettings() - { - IpAddress = tbIpAddress.Text, - Port = int.Parse(tbPort.Text), - Password = tbPassword.Text - }; - await settingsRepository.SaveAsync(settings); - - connectedClient?.Dispose(); - connectedClient = new ObsWebSocketClient(settings); - await connectedClient.ConnectAsync(); - - IPresentationSubscriber subscriber = new PowerPointPresentationSubscriber(); + // TODO: Handle the parse better when bindingsource support come. + settings.IpAddress = tbIpAddress.Text; + settings.Port = int.Parse(tbPort.Text); + settings.Password = tbPassword.Text; - subscriber.Subscribe("OBS", async scene => connectedClient.ChangeScene(scene)); + await client.ConnectAsync(settings); } } } diff --git a/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs index f6d63ef..43eaada 100644 --- a/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs +++ b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs @@ -40,18 +40,17 @@ public string IpAddress /// public class ObsWebSocketClient : IDisposable { - private readonly ObsWebSocketClientSettings settings; - private bool disposedValue; private ObsWebSocket obsWebSocket; + public bool IsConnected => obsWebSocket.IsConnected; + /// /// Constructor /// /// Settings to connect - public ObsWebSocketClient(ObsWebSocketClientSettings settings) + public ObsWebSocketClient() { - this.settings = settings; this.obsWebSocket = new ObsWebSocket(); // Always not null } @@ -59,17 +58,19 @@ public ObsWebSocketClient(ObsWebSocketClientSettings settings) /// Connects to the OBS WebSocket server /// /// - public void Connect() + public void Connect(ObsWebSocketClientSettings settings) { - if (!obsWebSocket.IsConnected) - obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", settings.Password ?? ""); + if (obsWebSocket.IsConnected) + obsWebSocket.Disconnect(); + + obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", settings.Password ?? ""); } /// /// Connects to the OBS WebSocket server /// /// - public async Task ConnectAsync() + public async Task ConnectAsync(ObsWebSocketClientSettings settings) { // At least this won't block the main thread if using from GUI if (!obsWebSocket.IsConnected) diff --git a/PresentationObsSceneSwitcher/Program.cs b/PresentationObsSceneSwitcher/Program.cs index d7d017f..f5079db 100644 --- a/PresentationObsSceneSwitcher/Program.cs +++ b/PresentationObsSceneSwitcher/Program.cs @@ -18,28 +18,29 @@ static async Task Main() Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); + // TODO: Maybe handle this with DI. + /* Read settings */ JsonSettingsRepository settingsRepository = new JsonSettingsRepository(); - ObsWebSocketClient connectedClient = await ConnectWithSavedSettings(settingsRepository).ConfigureAwait(false); + ObsWebSocketClientSettings settings = await settingsRepository.LoadAsync(); - Application.Run(new ConfigurationForm(settingsRepository, connectedClient)); - } - - private static async Task ConnectWithSavedSettings(JsonSettingsRepository settingsRepository) - { - ObsWebSocketClientSettings savedSettings = await settingsRepository.LoadAsync(); - - if (!(savedSettings is null)) + /* Init the OBS client */ + using (ObsWebSocketClient client = new ObsWebSocketClient()) { - ObsWebSocketClient connectedClient = new ObsWebSocketClient(savedSettings); - await connectedClient.ConnectAsync(); + /* Try initial connection if there are saved settings */ + if (!(settings is null)) await client.ConnectAsync(settings); + /* Suscribe the client. */ IPresentationSubscriber subscriber = new PowerPointPresentationSubscriber(); - subscriber.Subscribe("OBS", async scene => connectedClient.ChangeScene(scene)); + subscriber.Subscribe("OBS", async scene => + { + if (client.IsConnected) + client.ChangeScene(scene); + }); - return connectedClient; + Application.Run(new ConfigurationForm(settings, client)); } - return null; + await settingsRepository.SaveAsync(settings); } } } From b75603b6de45cd14bc064c0d93af2a3193e87e11 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 20:59:43 +0200 Subject: [PATCH 11/45] Github Action --- .github/workflows/release-action.yml | 78 ++++++++++++++++++++++++++++ PowerPointToOBSSceneSwitcher.sln | 7 ++- 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release-action.yml diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml new file mode 100644 index 0000000..f0d1b15 --- /dev/null +++ b/.github/workflows/release-action.yml @@ -0,0 +1,78 @@ +name: .NET Core Desktop + +on: + push: + tags: + - 'v*' + +jobs: + + build: + runs-on: windows-latest # For a list of available runner types, refer to + # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on + + env: + Solution_Name: PowerPointToOBSSceneSwitcher # Replace with your solution name, i.e. MyWpfApp.sln. + ProjectDirectory: PresentationObsSceneSwitcher # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package. + ProjectPath: PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj. + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + # Install the .NET Core workload + - name: Install .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.1.101 + + # Restore the application to populate the obj folder with RuntimeIdentifiers + - name: Restore the application + run: dotnet restore $env:ProjectPath + env: + Configuration: ${{ matrix.configuration }} + + # Decode the base 64 encoded pfx and save the Signing_Certificate + - name: Build + run: dotnet build --configuration Release --no-restore $env:ProjectPath + + # Create the app package by building and packaging the Windows Application Packaging project + - name: Publish + run: dotnet publish --configuration Release --no-restore $env:ProjectPath + + # Publish + - name: Upload build artifacts + uses: actions/upload-artifact@v2 + with: + name: Exe + path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish + + # Zip + - name: Zip + run: | + cd ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish + Compress-Archive -DestinationPath PresentationObsSceneSwitcher -Path .\* + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish\PresentationObsSceneSwitcher.zip + asset_name: PresentationObsSceneSwitcher.zip + asset_content_type: application/zip \ No newline at end of file diff --git a/PowerPointToOBSSceneSwitcher.sln b/PowerPointToOBSSceneSwitcher.sln index ebde54a..8d82a89 100644 --- a/PowerPointToOBSSceneSwitcher.sln +++ b/PowerPointToOBSSceneSwitcher.sln @@ -5,7 +5,12 @@ VisualStudioVersion = 16.0.30428.66 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerPointToOBSSceneSwitcher", "PowerPointToOBSSceneSwitcher.csproj", "{8A844363-5A92-403B-90CD-BFCB30ED5054}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresentationObsSceneSwitcher", "PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj", "{5B07EF15-11D6-4646-8322-965BD7D4ADEE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PresentationObsSceneSwitcher", "PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj", "{5B07EF15-11D6-4646-8322-965BD7D4ADEE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{76FD753E-6BFA-477C-BA87-23D96C053DC5}" + ProjectSection(SolutionItems) = preProject + .github\workflows\release-action.yml = .github\workflows\release-action.yml + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From f6b515e49c9029915a6ea1f61e77ea62706ee33c Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 20:59:43 +0200 Subject: [PATCH 12/45] Github Action --- .github/workflows/release-action.yml | 78 ++++++++++++++++++++++++++++ PowerPointToOBSSceneSwitcher.sln | 7 ++- 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release-action.yml diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml new file mode 100644 index 0000000..74234f1 --- /dev/null +++ b/.github/workflows/release-action.yml @@ -0,0 +1,78 @@ +name: Release PresentationObsSceneSwitcher + +on: + push: + tags: + - 'v*' + +jobs: + + build: + runs-on: windows-latest # For a list of available runner types, refer to + # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on + + env: + Solution_Name: PowerPointToOBSSceneSwitcher # Replace with your solution name, i.e. MyWpfApp.sln. + ProjectDirectory: PresentationObsSceneSwitcher # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package. + ProjectPath: PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj. + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + # Install the .NET Core workload + - name: Install .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.1.101 + + # Restore the application to populate the obj folder with RuntimeIdentifiers + - name: Restore the application + run: dotnet restore $env:ProjectPath + env: + Configuration: ${{ matrix.configuration }} + + # Decode the base 64 encoded pfx and save the Signing_Certificate + - name: Build + run: dotnet build --configuration Release --no-restore $env:ProjectPath + + # Create the app package by building and packaging the Windows Application Packaging project + - name: Publish + run: dotnet publish --configuration Release --no-restore $env:ProjectPath + + # Publish + - name: Upload build artifacts + uses: actions/upload-artifact@v2 + with: + name: Exe + path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish + + # Zip + - name: Zip + run: | + cd ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish + Compress-Archive -DestinationPath PresentationObsSceneSwitcher -Path .\* + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish\PresentationObsSceneSwitcher.zip + asset_name: PresentationObsSceneSwitcher.zip + asset_content_type: application/zip \ No newline at end of file diff --git a/PowerPointToOBSSceneSwitcher.sln b/PowerPointToOBSSceneSwitcher.sln index ebde54a..8d82a89 100644 --- a/PowerPointToOBSSceneSwitcher.sln +++ b/PowerPointToOBSSceneSwitcher.sln @@ -5,7 +5,12 @@ VisualStudioVersion = 16.0.30428.66 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerPointToOBSSceneSwitcher", "PowerPointToOBSSceneSwitcher.csproj", "{8A844363-5A92-403B-90CD-BFCB30ED5054}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresentationObsSceneSwitcher", "PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj", "{5B07EF15-11D6-4646-8322-965BD7D4ADEE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PresentationObsSceneSwitcher", "PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj", "{5B07EF15-11D6-4646-8322-965BD7D4ADEE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{76FD753E-6BFA-477C-BA87-23D96C053DC5}" + ProjectSection(SolutionItems) = preProject + .github\workflows\release-action.yml = .github\workflows\release-action.yml + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From b975bf85c22dcf36ed49fb67073cd1cceee804a8 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 21:01:28 +0200 Subject: [PATCH 13/45] Rename error --- .github/workflows/release-action.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index 74234f1..c460b32 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -1,5 +1,4 @@ name: Release PresentationObsSceneSwitcher - on: push: tags: From 9e2bbeb199828ecc26e7de7d022e06102246f1f8 Mon Sep 17 00:00:00 2001 From: Oscar Date: Wed, 16 Sep 2020 21:08:44 +0200 Subject: [PATCH 14/45] Update release-action.yml Use msbuild instead of dotnet core due to COMReference --- .github/workflows/release-action.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index c460b32..8bbcea5 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -27,6 +27,10 @@ jobs: with: dotnet-version: 3.1.101 + # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild + - name: Setup MSBuild.exe + uses: microsoft/setup-msbuild@2008f912f56e61277eefaac6d1888b750582aa16 + # Restore the application to populate the obj folder with RuntimeIdentifiers - name: Restore the application run: dotnet restore $env:ProjectPath @@ -39,7 +43,7 @@ jobs: # Create the app package by building and packaging the Windows Application Packaging project - name: Publish - run: dotnet publish --configuration Release --no-restore $env:ProjectPath + run: msbuild $env:ProjectPath /p:Configuration=Release /p:OutputPath=${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish # Publish - name: Upload build artifacts @@ -74,4 +78,4 @@ jobs: upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps asset_path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish\PresentationObsSceneSwitcher.zip asset_name: PresentationObsSceneSwitcher.zip - asset_content_type: application/zip \ No newline at end of file + asset_content_type: application/zip From a64c961e28cef3677f380c6980a412b42ee28500 Mon Sep 17 00:00:00 2001 From: Oscar Date: Wed, 16 Sep 2020 21:18:09 +0200 Subject: [PATCH 15/45] Update release-action.yml Use another MSBuild. --- .github/workflows/release-action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index 8bbcea5..fd769d2 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -29,7 +29,7 @@ jobs: # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - name: Setup MSBuild.exe - uses: microsoft/setup-msbuild@2008f912f56e61277eefaac6d1888b750582aa16 + uses: warrenbuckley/Setup-MSBuild@v1 # Restore the application to populate the obj folder with RuntimeIdentifiers - name: Restore the application From 7392b134809115dcaceeefb45ac0e6001509fb93 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 21:25:57 +0200 Subject: [PATCH 16/45] Github Action --- .github/workflows/release-action.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index fd769d2..a09ced0 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -36,13 +36,9 @@ jobs: run: dotnet restore $env:ProjectPath env: Configuration: ${{ matrix.configuration }} - - # Decode the base 64 encoded pfx and save the Signing_Certificate - - name: Build - run: dotnet build --configuration Release --no-restore $env:ProjectPath # Create the app package by building and packaging the Windows Application Packaging project - - name: Publish + - name: Build and Publish run: msbuild $env:ProjectPath /p:Configuration=Release /p:OutputPath=${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish # Publish From bc02717468f72098c297537ad8e0f5852dd4fb86 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 21:46:20 +0200 Subject: [PATCH 17/45] Using good old nuget instead of COM for CI... --- .github/workflows/release-action.yml | 14 +++++++------- PowerPointToOBSSceneSwitcher.csproj | 13 +------------ 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index a09ced0..c460b32 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -27,19 +27,19 @@ jobs: with: dotnet-version: 3.1.101 - # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - - name: Setup MSBuild.exe - uses: warrenbuckley/Setup-MSBuild@v1 - # Restore the application to populate the obj folder with RuntimeIdentifiers - name: Restore the application run: dotnet restore $env:ProjectPath env: Configuration: ${{ matrix.configuration }} + + # Decode the base 64 encoded pfx and save the Signing_Certificate + - name: Build + run: dotnet build --configuration Release --no-restore $env:ProjectPath # Create the app package by building and packaging the Windows Application Packaging project - - name: Build and Publish - run: msbuild $env:ProjectPath /p:Configuration=Release /p:OutputPath=${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish + - name: Publish + run: dotnet publish --configuration Release --no-restore $env:ProjectPath # Publish - name: Upload build artifacts @@ -74,4 +74,4 @@ jobs: upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps asset_path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish\PresentationObsSceneSwitcher.zip asset_name: PresentationObsSceneSwitcher.zip - asset_content_type: application/zip + asset_content_type: application/zip \ No newline at end of file diff --git a/PowerPointToOBSSceneSwitcher.csproj b/PowerPointToOBSSceneSwitcher.csproj index 43492be..f49476f 100644 --- a/PowerPointToOBSSceneSwitcher.csproj +++ b/PowerPointToOBSSceneSwitcher.csproj @@ -10,20 +10,9 @@ - - - - {91493440-5a91-11cf-8700-00aa0060263b} - 2 - 12 - primary - 0 - false - True - - + From 21e01ccb6af57cf2a5fd83f89aa6fe9dba1db678 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 21:46:20 +0200 Subject: [PATCH 18/45] Using good old nuget instead of COM for CI... --- .github/workflows/release-action.yml | 14 +++++++------- .../PresentationObsSceneSwitcher.csproj | 13 +------------ 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index a09ced0..c460b32 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -27,19 +27,19 @@ jobs: with: dotnet-version: 3.1.101 - # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - - name: Setup MSBuild.exe - uses: warrenbuckley/Setup-MSBuild@v1 - # Restore the application to populate the obj folder with RuntimeIdentifiers - name: Restore the application run: dotnet restore $env:ProjectPath env: Configuration: ${{ matrix.configuration }} + + # Decode the base 64 encoded pfx and save the Signing_Certificate + - name: Build + run: dotnet build --configuration Release --no-restore $env:ProjectPath # Create the app package by building and packaging the Windows Application Packaging project - - name: Build and Publish - run: msbuild $env:ProjectPath /p:Configuration=Release /p:OutputPath=${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish + - name: Publish + run: dotnet publish --configuration Release --no-restore $env:ProjectPath # Publish - name: Upload build artifacts @@ -74,4 +74,4 @@ jobs: upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps asset_path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish\PresentationObsSceneSwitcher.zip asset_name: PresentationObsSceneSwitcher.zip - asset_content_type: application/zip + asset_content_type: application/zip \ No newline at end of file diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj index a868abc..9db4643 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj @@ -8,18 +8,7 @@ - - - - - {91493440-5a91-11cf-8700-00aa0060263b} - 2 - 12 - primary - 0 - false - True - + \ No newline at end of file From 205882be11616918caf096a52f2c9df6544b9e62 Mon Sep 17 00:00:00 2001 From: Brian Parks Date: Wed, 16 Sep 2020 20:43:52 -0600 Subject: [PATCH 19/45] Add special commands to start and stop recording; allow multiple commands per slide --- OBS.cs | 12 ++++++++++++ Program.cs | 26 ++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/OBS.cs b/OBS.cs index e82d390..464d499 100644 --- a/OBS.cs +++ b/OBS.cs @@ -25,6 +25,18 @@ public bool ChangeScene(string scene) return true; } + public bool StartRecording() + { + _OBS.Api.StartRecording(); + return true; + } + + public bool StopRecording() + { + _OBS.Api.StopRecording(); + return true; + } + protected virtual void Dispose(bool disposing) { if (!_DisposedValue) diff --git a/Program.cs b/Program.cs index 08e08da..800a973 100644 --- a/Program.cs +++ b/Program.cs @@ -40,13 +40,31 @@ async static void App_SlideShowNextSlide(SlideShowWindow Wn) { if (line.StartsWith("OBS:")) { line = line.Substring(4).Trim(); - Console.WriteLine($" Switching to OBS Scene named \"{line}\""); - try { OBS.ChangeScene(line); } - catch (Exception ex) { Console.WriteLine($" ERROR: {ex.Message}"); } - break; + await HandleCommand(line); } } } } + + static async Task HandleCommand(string command) + { + switch (command) + { + case "": + break; + case "**START": + OBS.StartRecording(); + break; + case "**STOP": + OBS.StopRecording(); + break; + + default: + Console.WriteLine($" Switching to OBS Scene named \"{command}\""); + try { OBS.ChangeScene(command); } + catch (Exception ex) { Console.WriteLine($" ERROR: {ex.Message.ToString()}"); } + break; + } + } } } \ No newline at end of file From bc13238581f986896062300194d91f100f56a929 Mon Sep 17 00:00:00 2001 From: Brian Parks Date: Wed, 16 Sep 2020 20:52:39 -0600 Subject: [PATCH 20/45] Avoid crashing if recording is already started/stopped and start/stop is requested --- OBS.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OBS.cs b/OBS.cs index 464d499..9c792da 100644 --- a/OBS.cs +++ b/OBS.cs @@ -27,13 +27,15 @@ public bool ChangeScene(string scene) public bool StartRecording() { - _OBS.Api.StartRecording(); + try { _OBS.Api.StartRecording(); } + catch { /* Recording already started */ } return true; } public bool StopRecording() { - _OBS.Api.StopRecording(); + try { _OBS.Api.StopRecording(); } + catch { /* Recording already started */ } return true; } From 9e0185959d0102c257001933f288ad42a090b482 Mon Sep 17 00:00:00 2001 From: Brian Parks Date: Wed, 16 Sep 2020 20:53:04 -0600 Subject: [PATCH 21/45] Fix comment --- OBS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OBS.cs b/OBS.cs index 9c792da..ee968bc 100644 --- a/OBS.cs +++ b/OBS.cs @@ -35,7 +35,7 @@ public bool StartRecording() public bool StopRecording() { try { _OBS.Api.StopRecording(); } - catch { /* Recording already started */ } + catch { /* Recording already stopped */ } return true; } From 11dc8552fc4586c4ff8e3556d0bafbe0ec49bf22 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Mon, 14 Sep 2020 21:30:10 +0200 Subject: [PATCH 22/45] Creating project and the Form --- PowerPointToOBSSceneSwitcher.sln | 56 ++++--- .../PresentationObsSceneSwitcher.Designer.cs | 151 ++++++++++++++++++ .../PresentationObsSceneSwitcher.cs | 21 +++ .../PresentationObsSceneSwitcher.csproj | 9 ++ .../PresentationObsSceneSwitcher.resx | 60 +++++++ .../PresentationObsSceneSwitcher/Program.cs | 23 +++ 6 files changed, 295 insertions(+), 25 deletions(-) create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.resx create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs diff --git a/PowerPointToOBSSceneSwitcher.sln b/PowerPointToOBSSceneSwitcher.sln index e2cad97..7682133 100644 --- a/PowerPointToOBSSceneSwitcher.sln +++ b/PowerPointToOBSSceneSwitcher.sln @@ -1,25 +1,31 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30428.66 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerPointToOBSSceneSwitcher", "PowerPointToOBSSceneSwitcher.csproj", "{8A844363-5A92-403B-90CD-BFCB30ED5054}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8A844363-5A92-403B-90CD-BFCB30ED5054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A844363-5A92-403B-90CD-BFCB30ED5054}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A844363-5A92-403B-90CD-BFCB30ED5054}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A844363-5A92-403B-90CD-BFCB30ED5054}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {515C02C0-0CF2-4890-A1FB-4D6A5F115CD6} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30428.66 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerPointToOBSSceneSwitcher", "PowerPointToOBSSceneSwitcher.csproj", "{8A844363-5A92-403B-90CD-BFCB30ED5054}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresentationObsSceneSwitcher", "PresentationObsSceneSwitcher\PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj", "{5B07EF15-11D6-4646-8322-965BD7D4ADEE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8A844363-5A92-403B-90CD-BFCB30ED5054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A844363-5A92-403B-90CD-BFCB30ED5054}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A844363-5A92-403B-90CD-BFCB30ED5054}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A844363-5A92-403B-90CD-BFCB30ED5054}.Release|Any CPU.Build.0 = Release|Any CPU + {5B07EF15-11D6-4646-8322-965BD7D4ADEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B07EF15-11D6-4646-8322-965BD7D4ADEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B07EF15-11D6-4646-8322-965BD7D4ADEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B07EF15-11D6-4646-8322-965BD7D4ADEE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {515C02C0-0CF2-4890-A1FB-4D6A5F115CD6} + EndGlobalSection +EndGlobal diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs new file mode 100644 index 0000000..4fa0b9c --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs @@ -0,0 +1,151 @@ + +namespace PresentationObsSceneSwitcher +{ + partial class PresentationObsSceneSwitcher + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.panel1 = new System.Windows.Forms.Panel(); + this.menuStrip = new System.Windows.Forms.MenuStrip(); + this.menuItemClose = new System.Windows.Forms.ToolStripMenuItem(); + this.panel1.SuspendLayout(); + this.menuStrip.SuspendLayout(); + this.SuspendLayout(); + // + // panel1 + // + this.panel1.BackColor = System.Drawing.Color.WhiteSmoke; + this.panel1.Controls.Add(this.menuStrip); + this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(515, 275); + this.panel1.TabIndex = 0; + // + // menuStrip + // + this.menuStrip.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); + this.menuStrip.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.menuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuItemClose}); + this.menuStrip.Location = new System.Drawing.Point(0, 0); + this.menuStrip.Name = "menuStrip"; + this.menuStrip.Size = new System.Drawing.Size(515, 25); + this.menuStrip.TabIndex = 0; + this.menuStrip.Text = "Menu Strip"; + // + // menuItemClose + // + this.menuItemClose.ForeColor = System.Drawing.Color.WhiteSmoke; + this.menuItemClose.Name = "menuItemClose"; + this.menuItemClose.Size = new System.Drawing.Size(52, 21); + this.menuItemClose.Text = "Close"; + this.menuItemClose.ToolTipText = "Closes the application"; + // + // PresentationObsSceneSwitcher + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(515, 275); + this.Controls.Add(this.panel1); + this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.MainMenuStrip = this.menuStrip; + this.Name = "PresentationObsSceneSwitcher"; + this.Text = "Form1"; + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.menuStrip.ResumeLayout(false); + this.menuStrip.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.MenuStrip menuStrip; + private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator; + private System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.ToolStripMenuItem printToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem printPreviewToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem undoToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem redoToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; + private System.Windows.Forms.ToolStripMenuItem cutToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem copyToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem pasteToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; + private System.Windows.Forms.ToolStripMenuItem selectAllToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem toolsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem customizeToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem optionsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem contentsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem indexToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem searchToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; + private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem newToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator6; + private System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator7; + private System.Windows.Forms.ToolStripMenuItem printToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem printPreviewToolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator8; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem undoToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem redoToolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator9; + private System.Windows.Forms.ToolStripMenuItem cutToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem copyToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem pasteToolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator10; + private System.Windows.Forms.ToolStripMenuItem selectAllToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem toolsToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem customizeToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem optionsToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem contentsToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem indexToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem searchToolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator11; + private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem menuItemClose; + } +} + diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs new file mode 100644 index 0000000..30999ab --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace PresentationObsSceneSwitcher +{ + public partial class PresentationObsSceneSwitcher : Form + { + public PresentationObsSceneSwitcher() + { + InitializeComponent(); + } + + } +} diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj new file mode 100644 index 0000000..7b05c62 --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj @@ -0,0 +1,9 @@ + + + + WinExe + netcoreapp3.1 + true + + + \ No newline at end of file diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.resx b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.resx new file mode 100644 index 0000000..b5ae26c --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs new file mode 100644 index 0000000..2c4b238 --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace PresentationObsSceneSwitcher +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new PresentationObsSceneSwitcher()); + } + } +} From 6980d23703ec20fc6fd3ff09ce3bd6abf442adb6 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Mon, 14 Sep 2020 22:13:59 +0200 Subject: [PATCH 23/45] Initial ConfigurationFormLayour --- ...igner.cs => ConfigurationForm.Designer.cs} | 84 +++++++++++++++++-- ...sSceneSwitcher.cs => ConfigurationForm.cs} | 4 +- ...neSwitcher.resx => ConfigurationForm.resx} | 0 .../PresentationObsSceneSwitcher/Program.cs | 2 +- 4 files changed, 79 insertions(+), 11 deletions(-) rename PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher.Designer.cs => ConfigurationForm.Designer.cs} (66%) rename PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher.cs => ConfigurationForm.cs} (71%) rename PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher.resx => ConfigurationForm.resx} (100%) diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs similarity index 66% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs rename to PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs index 4fa0b9c..510f6b9 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.Designer.cs +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs @@ -1,7 +1,7 @@  namespace PresentationObsSceneSwitcher { - partial class PresentationObsSceneSwitcher + partial class ConfigurationForm { /// /// Required designer variable. @@ -32,6 +32,11 @@ private void InitializeComponent() this.panel1 = new System.Windows.Forms.Panel(); this.menuStrip = new System.Windows.Forms.MenuStrip(); this.menuItemClose = new System.Windows.Forms.ToolStripMenuItem(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); this.panel1.SuspendLayout(); this.menuStrip.SuspendLayout(); this.SuspendLayout(); @@ -39,11 +44,16 @@ private void InitializeComponent() // panel1 // this.panel1.BackColor = System.Drawing.Color.WhiteSmoke; - this.panel1.Controls.Add(this.menuStrip); + this.panel1.Controls.Add(this.textBox2); + this.panel1.Controls.Add(this.label3); + this.panel1.Controls.Add(this.textBox1); + this.panel1.Controls.Add(this.label2); + this.panel1.Controls.Add(this.label1); this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; - this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Location = new System.Drawing.Point(0, 25); + this.panel1.Margin = new System.Windows.Forms.Padding(10); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(515, 275); + this.panel1.Size = new System.Drawing.Size(324, 85); this.panel1.TabIndex = 0; // // menuStrip @@ -54,7 +64,7 @@ private void InitializeComponent() this.menuItemClose}); this.menuStrip.Location = new System.Drawing.Point(0, 0); this.menuStrip.Name = "menuStrip"; - this.menuStrip.Size = new System.Drawing.Size(515, 25); + this.menuStrip.Size = new System.Drawing.Size(434, 25); this.menuStrip.TabIndex = 0; this.menuStrip.Text = "Menu Strip"; // @@ -66,21 +76,74 @@ private void InitializeComponent() this.menuItemClose.Text = "Close"; this.menuItemClose.ToolTipText = "Closes the application"; // - // PresentationObsSceneSwitcher + // label1 + // + this.label1.AutoSize = true; + this.label1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); + this.label1.Location = new System.Drawing.Point(9, 10); + this.label1.Margin = new System.Windows.Forms.Padding(0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(152, 17); + this.label1.TabIndex = 0; + this.label1.Text = "OBS Websocket Server:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(9, 40); + this.label2.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(74, 17); + this.label2.TabIndex = 1; + this.label2.Text = "Ip Address:"; + // + // textBox1 + // + this.textBox1.Location = new System.Drawing.Point(88, 37); + this.textBox1.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(82, 25); + this.textBox1.TabIndex = 2; + this.textBox1.Text = "127.0.0.1"; + this.textBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // textBox2 + // + this.textBox2.Location = new System.Drawing.Point(259, 37); + this.textBox2.Margin = new System.Windows.Forms.Padding(5, 10, 0, 0); + this.textBox2.Name = "textBox2"; + this.textBox2.Size = new System.Drawing.Size(48, 25); + this.textBox2.TabIndex = 4; + this.textBox2.Text = "4444"; + this.textBox2.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(180, 40); + this.label3.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(74, 17); + this.label3.TabIndex = 3; + this.label3.Text = "Ip Address:"; + // + // ConfigurationForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(515, 275); + this.ClientSize = new System.Drawing.Size(324, 110); this.Controls.Add(this.panel1); + this.Controls.Add(this.menuStrip); this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.MainMenuStrip = this.menuStrip; - this.Name = "PresentationObsSceneSwitcher"; + this.Name = "ConfigurationForm"; this.Text = "Form1"; this.panel1.ResumeLayout(false); this.panel1.PerformLayout(); this.menuStrip.ResumeLayout(false); this.menuStrip.PerformLayout(); this.ResumeLayout(false); + this.PerformLayout(); } @@ -146,6 +209,11 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem1; private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem1; private System.Windows.Forms.ToolStripMenuItem menuItemClose; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Label label3; } } diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs similarity index 71% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs rename to PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs index 30999ab..ec0062a 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.cs +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs @@ -10,9 +10,9 @@ namespace PresentationObsSceneSwitcher { - public partial class PresentationObsSceneSwitcher : Form + public partial class ConfigurationForm : Form { - public PresentationObsSceneSwitcher() + public ConfigurationForm() { InitializeComponent(); } diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.resx b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.resx similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.resx rename to PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.resx diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs index 2c4b238..b506521 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs @@ -17,7 +17,7 @@ static void Main() Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new PresentationObsSceneSwitcher()); + Application.Run(new ConfigurationForm()); } } } From 9396bf6f9b5f9da5ce394c85fe6db67853f55b5f Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Tue, 15 Sep 2020 21:21:09 +0200 Subject: [PATCH 24/45] Interface changes --- .../ConfigurationForm.Designer.cs | 143 +++++++++++------- .../ConfigurationForm.cs | 42 +++++ 2 files changed, 129 insertions(+), 56 deletions(-) diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs index 510f6b9..58d34e7 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs @@ -29,14 +29,17 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); this.panel1 = new System.Windows.Forms.Panel(); - this.menuStrip = new System.Windows.Forms.MenuStrip(); - this.menuItemClose = new System.Windows.Forms.ToolStripMenuItem(); - this.label1 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.textBox1 = new System.Windows.Forms.TextBox(); this.textBox2 = new System.Windows.Forms.TextBox(); this.label3 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.menuStrip = new System.Windows.Forms.MenuStrip(); + this.menuItemClose = new System.Windows.Forms.ToolStripMenuItem(); + this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components); + this.button1 = new System.Windows.Forms.Button(); this.panel1.SuspendLayout(); this.menuStrip.SuspendLayout(); this.SuspendLayout(); @@ -44,6 +47,7 @@ private void InitializeComponent() // panel1 // this.panel1.BackColor = System.Drawing.Color.WhiteSmoke; + this.panel1.Controls.Add(this.button1); this.panel1.Controls.Add(this.textBox2); this.panel1.Controls.Add(this.label3); this.panel1.Controls.Add(this.textBox1); @@ -53,39 +57,38 @@ private void InitializeComponent() this.panel1.Location = new System.Drawing.Point(0, 25); this.panel1.Margin = new System.Windows.Forms.Padding(10); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(324, 85); + this.panel1.Size = new System.Drawing.Size(324, 112); this.panel1.TabIndex = 0; // - // menuStrip + // textBox2 // - this.menuStrip.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); - this.menuStrip.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.menuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.menuItemClose}); - this.menuStrip.Location = new System.Drawing.Point(0, 0); - this.menuStrip.Name = "menuStrip"; - this.menuStrip.Size = new System.Drawing.Size(434, 25); - this.menuStrip.TabIndex = 0; - this.menuStrip.Text = "Menu Strip"; + this.textBox2.Location = new System.Drawing.Point(259, 37); + this.textBox2.Margin = new System.Windows.Forms.Padding(5, 10, 0, 0); + this.textBox2.Name = "textBox2"; + this.textBox2.Size = new System.Drawing.Size(48, 25); + this.textBox2.TabIndex = 4; + this.textBox2.Text = "4444"; + this.textBox2.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // - // menuItemClose + // label3 // - this.menuItemClose.ForeColor = System.Drawing.Color.WhiteSmoke; - this.menuItemClose.Name = "menuItemClose"; - this.menuItemClose.Size = new System.Drawing.Size(52, 21); - this.menuItemClose.Text = "Close"; - this.menuItemClose.ToolTipText = "Closes the application"; + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(180, 40); + this.label3.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(74, 17); + this.label3.TabIndex = 3; + this.label3.Text = "Ip Address:"; // - // label1 + // textBox1 // - this.label1.AutoSize = true; - this.label1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); - this.label1.Location = new System.Drawing.Point(9, 10); - this.label1.Margin = new System.Windows.Forms.Padding(0); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(152, 17); - this.label1.TabIndex = 0; - this.label1.Text = "OBS Websocket Server:"; + this.textBox1.Location = new System.Drawing.Point(88, 37); + this.textBox1.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(82, 25); + this.textBox1.TabIndex = 2; + this.textBox1.Text = "127.0.0.1"; + this.textBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // // label2 // @@ -97,47 +100,73 @@ private void InitializeComponent() this.label2.TabIndex = 1; this.label2.Text = "Ip Address:"; // - // textBox1 + // label1 // - this.textBox1.Location = new System.Drawing.Point(88, 37); - this.textBox1.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); - this.textBox1.Name = "textBox1"; - this.textBox1.Size = new System.Drawing.Size(82, 25); - this.textBox1.TabIndex = 2; - this.textBox1.Text = "127.0.0.1"; - this.textBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.label1.AutoSize = true; + this.label1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); + this.label1.Location = new System.Drawing.Point(9, 10); + this.label1.Margin = new System.Windows.Forms.Padding(0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(152, 17); + this.label1.TabIndex = 0; + this.label1.Text = "OBS Websocket Server:"; // - // textBox2 + // menuStrip // - this.textBox2.Location = new System.Drawing.Point(259, 37); - this.textBox2.Margin = new System.Windows.Forms.Padding(5, 10, 0, 0); - this.textBox2.Name = "textBox2"; - this.textBox2.Size = new System.Drawing.Size(48, 25); - this.textBox2.TabIndex = 4; - this.textBox2.Text = "4444"; - this.textBox2.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.menuStrip.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); + this.menuStrip.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.menuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuItemClose}); + this.menuStrip.Location = new System.Drawing.Point(0, 0); + this.menuStrip.Name = "menuStrip"; + this.menuStrip.Size = new System.Drawing.Size(324, 25); + this.menuStrip.TabIndex = 0; + this.menuStrip.Text = "Menu Strip"; // - // label3 + // menuItemClose // - this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(180, 40); - this.label3.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(74, 17); - this.label3.TabIndex = 3; - this.label3.Text = "Ip Address:"; + this.menuItemClose.ForeColor = System.Drawing.Color.WhiteSmoke; + this.menuItemClose.Name = "menuItemClose"; + this.menuItemClose.Size = new System.Drawing.Size(52, 21); + this.menuItemClose.Text = "Close"; + this.menuItemClose.ToolTipText = "Closes the application"; + this.menuItemClose.Click += new System.EventHandler(this.menuItemClose_Click); + // + // notifyIcon + // + this.notifyIcon.BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Info; + this.notifyIcon.BalloonTipText = "Hidden!"; + this.notifyIcon.BalloonTipTitle = "Presentation Obs Scene Switcher"; + this.notifyIcon.Text = "notifyIcon"; + this.notifyIcon.Visible = true; + this.notifyIcon.DoubleClick += new System.EventHandler(this.notifyIcon_DoubleClick); + // + // button1 + // + this.button1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); + this.button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.button1.ForeColor = System.Drawing.Color.WhiteSmoke; + this.button1.Location = new System.Drawing.Point(9, 72); + this.button1.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(298, 30); + this.button1.TabIndex = 5; + this.button1.Text = "Start / Restart"; + this.button1.UseVisualStyleBackColor = false; // // ConfigurationForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(324, 110); + this.ClientSize = new System.Drawing.Size(324, 137); this.Controls.Add(this.panel1); this.Controls.Add(this.menuStrip); this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.MainMenuStrip = this.menuStrip; this.Name = "ConfigurationForm"; this.Text = "Form1"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ConfigurationForm_FormClosing); + this.Resize += new System.EventHandler(this.ConfigurationForm_Resize); this.panel1.ResumeLayout(false); this.panel1.PerformLayout(); this.menuStrip.ResumeLayout(false); @@ -214,6 +243,8 @@ private void InitializeComponent() private System.Windows.Forms.TextBox textBox1; private System.Windows.Forms.TextBox textBox2; private System.Windows.Forms.Label label3; + private System.Windows.Forms.NotifyIcon notifyIcon; + private System.Windows.Forms.Button button1; } } diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs index ec0062a..65f2a7a 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs @@ -12,10 +12,52 @@ namespace PresentationObsSceneSwitcher { public partial class ConfigurationForm : Form { + #region GUI Behavior fields + + private bool manualClosing; + private bool showDisplay; + + #endregion + public ConfigurationForm() { InitializeComponent(); } + #region GUI Behavior events + + protected override void SetVisibleCore(bool value) => base.SetVisibleCore(showDisplay && value); + + private void menuItemClose_Click(object sender, EventArgs e) + { + this.manualClosing = true; + this.notifyIcon.Dispose(); + this.Close(); + } + + private void notifyIcon_DoubleClick(object sender, EventArgs e) + { + this.showDisplay = true; + this.Visible = !this.Visible; + this.WindowState = FormWindowState.Normal; + } + + private void ConfigurationForm_Resize(object sender, EventArgs e) + { + if (this.WindowState == FormWindowState.Minimized) this.Hide(); + } + + private void ConfigurationForm_FormClosing(object sender, FormClosingEventArgs e) + { + if (!manualClosing) + { + e.Cancel = true; + this.Hide(); + } + } + + #endregion + + } } From e00cff8416d9a220711d737a9ca8f01f0d3a8f52 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Tue, 15 Sep 2020 21:22:37 +0200 Subject: [PATCH 25/45] Ignore new project folder --- PowerPointToOBSSceneSwitcher.csproj | 54 ++++++++++++++++------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/PowerPointToOBSSceneSwitcher.csproj b/PowerPointToOBSSceneSwitcher.csproj index 75b9f85..43492be 100644 --- a/PowerPointToOBSSceneSwitcher.csproj +++ b/PowerPointToOBSSceneSwitcher.csproj @@ -1,24 +1,30 @@ - - - - Exe - netcoreapp3.1 - - - - - {91493440-5a91-11cf-8700-00aa0060263b} - 2 - 12 - primary - 0 - false - True - - - - - - - - + + + + Exe + netcoreapp3.1 + + + + + + + + + + + {91493440-5a91-11cf-8700-00aa0060263b} + 2 + 12 + primary + 0 + false + True + + + + + + + + From 050da9f493f0a5eb080301620a26f95ccec4d2b6 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Tue, 15 Sep 2020 21:50:07 +0200 Subject: [PATCH 26/45] OBS Sspport --- .../Obs/ObsClient.cs | 112 ++++++++++++++++++ .../PresentationObsSceneSwitcher.csproj | 16 +++ 2 files changed, 128 insertions(+) create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Obs/ObsClient.cs diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Obs/ObsClient.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Obs/ObsClient.cs new file mode 100644 index 0000000..5a90796 --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Obs/ObsClient.cs @@ -0,0 +1,112 @@ +using OBS.WebSocket.NET; +using System; +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Threading.Tasks; + +namespace PowerPointToOBSSceneSwitcher.Obs +{ + /// + /// Defines all the settings a ObsWebSocketClient needs + /// + /// Configuring the ObsWebSocketClient with a Settings object allows to use it with DI + public class ObsWebSocketClientSettings + { + private string ipAddress; + + /// + /// Ip address of the obs websocket server + /// + public string IpAddress + { + get => ipAddress; + set => this.ipAddress = IPAddress.TryParse(value, out _) ? value + : throw new ValidationException($"The field {nameof(IpAddress)} should be a valid IP"); + } + + /// + /// Port of the OBS WebSocket server + /// + public int Port { get; set; } + } + + /// + /// Connects to OBS WebSocket server + /// + public class ObsWebSocketClient : IDisposable + { + private readonly ObsWebSocketClientSettings settings; + + private bool disposedValue; + private ObsWebSocket obsWebSocket; + + /// + /// Constructor + /// + /// Settings to connect + public ObsWebSocketClient(ObsWebSocketClientSettings settings) + { + this.settings = settings; + this.obsWebSocket = new ObsWebSocket(); // Always not null + } + + /// + /// Connects to the OBS WebSocket server + /// + /// + public void Connect() + { + if (!obsWebSocket.IsConnected) + obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", ""); + } + + /// + /// Connects to the OBS WebSocket server + /// + /// + public async Task ConnectAsync() + { + // At least this won't block the main thread if using from GUI + if (!obsWebSocket.IsConnected) + await Task.Run(() => obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", "")); + } + + /// + /// Changes OBS current scene + /// + /// The Scene name in OBS + /// Always true + public bool ChangeScene(string scene) + { + obsWebSocket.Api.SetCurrentScene(scene); + return true; + } + + #region Dispose + + ~ObsWebSocketClient() => Dispose(disposing: false); + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + } + + obsWebSocket.Disconnect(); + obsWebSocket = null; + disposedValue = true; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj index 7b05c62..a868abc 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj @@ -6,4 +6,20 @@ true + + + + + + + {91493440-5a91-11cf-8700-00aa0060263b} + 2 + 12 + primary + 0 + false + True + + + \ No newline at end of file From 83fd3051348ba6daf7ffed2d7acc9dcaa789cd7a Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Tue, 15 Sep 2020 21:50:23 +0200 Subject: [PATCH 27/45] Powerpoint support --- .../IPresentationSubscriber.cs | 18 +++++ .../PowerPointPresentationSuscriber.cs | 71 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/IPresentationSubscriber.cs create mode 100644 PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/IPresentationSubscriber.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/IPresentationSubscriber.cs new file mode 100644 index 0000000..7cf38e8 --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/IPresentationSubscriber.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; + +namespace PresentationObsSceneSwitcher +{ + /// + /// Receives the name of the OBS scene. + /// + /// + public delegate Task PresentationSuscription(string scene); + + /// + /// Manages the suscriptions to a Presetation + /// + public interface IPresentationSubscriber + { + bool Subscribe(string appName, PresentationSuscription suscription); + } +} diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs new file mode 100644 index 0000000..aa07acd --- /dev/null +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs @@ -0,0 +1,71 @@ +using Microsoft.Office.Interop.PowerPoint; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace PresentationObsSceneSwitcher.PowerPoint +{ + /// + /// Subscribes to a PowerPoint presentation. + /// + public class PowerPointPresentationSubscriber : IPresentationSubscriber + { + /// + /// \( --> Literal + /// ((?[^\)]+)\) --> Avoid backtracking searching until a ) is found. No wildcard in regex please. + /// \) --> Literal + /// : --> Literal + /// \[ + /// (?[^\]]+) --> Avoid backtracking searching until a ] is found. No wildcard in regex please. + /// \] + /// + /// Making it static and RegexOptions.Compiled is the best for Regex performance. + /// + private static readonly Regex extractInfoFromNotesRegex = new Regex(@"\((?[^\)]+)\):\[(?[^\]]+)\]", RegexOptions.Compiled | RegexOptions.CultureInvariant); + + private static Application powerPoint = new Application(); + + private readonly Dictionary suscriptions = new Dictionary(); + + /// + /// Constructor + /// + public PowerPointPresentationSubscriber() + { + powerPoint.SlideShowNextSlide += async (SlideShowWindow Wn) => + { + Console.WriteLine($"Moved to Slide Number {Wn.View.Slide.SlideNumber}"); + + string note = String.Empty; + try + { + // Text starts at Index 2 ¯\_(ツ)_/¯ + note = Wn.View.Slide.NotesPage.Shapes[2].TextFrame.TextRange.Text; + } + catch + { + // No notes + } + + foreach (Match match in extractInfoFromNotesRegex.Matches(note)) + { + string appName = match.Groups["AppName"].Value; + string info = match.Groups["Info"].Value; + + if (suscriptions.TryGetValue(appName, out PresentationSuscription suscription)) + { + await suscription(info).ConfigureAwait(false); + } + } + }; + } + + /// + /// Subscribes an app to powerpoint slide changes + /// + /// The app subscribing + /// The handler of the slide change + /// + public bool Subscribe(string appName, PresentationSuscription suscription) => suscriptions.TryAdd(appName, suscription); + } +} From e9f306404be5d68084a0fb1b0bc2b65d68ae5234 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Tue, 15 Sep 2020 21:51:18 +0200 Subject: [PATCH 28/45] Move project to its parent folder (bad creation) --- PowerPointToOBSSceneSwitcher.sln | 2 +- .../ConfigurationForm.Designer.cs | 4 ++-- .../{PresentationObsSceneSwitcher => }/ConfigurationForm.cs | 0 .../{PresentationObsSceneSwitcher => }/ConfigurationForm.resx | 0 .../IPresentationSubscriber.cs | 0 .../{PresentationObsSceneSwitcher => }/Obs/ObsClient.cs | 0 .../PowerPoint/PowerPointPresentationSuscriber.cs | 0 .../PresentationObsSceneSwitcher.csproj | 0 .../{PresentationObsSceneSwitcher => }/Program.cs | 0 9 files changed, 3 insertions(+), 3 deletions(-) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/ConfigurationForm.Designer.cs (97%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/ConfigurationForm.cs (100%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/ConfigurationForm.resx (100%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/IPresentationSubscriber.cs (100%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/Obs/ObsClient.cs (100%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/PowerPoint/PowerPointPresentationSuscriber.cs (100%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/PresentationObsSceneSwitcher.csproj (100%) rename PresentationObsSceneSwitcher/{PresentationObsSceneSwitcher => }/Program.cs (100%) diff --git a/PowerPointToOBSSceneSwitcher.sln b/PowerPointToOBSSceneSwitcher.sln index 7682133..ebde54a 100644 --- a/PowerPointToOBSSceneSwitcher.sln +++ b/PowerPointToOBSSceneSwitcher.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 16.0.30428.66 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerPointToOBSSceneSwitcher", "PowerPointToOBSSceneSwitcher.csproj", "{8A844363-5A92-403B-90CD-BFCB30ED5054}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresentationObsSceneSwitcher", "PresentationObsSceneSwitcher\PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj", "{5B07EF15-11D6-4646-8322-965BD7D4ADEE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresentationObsSceneSwitcher", "PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj", "{5B07EF15-11D6-4646-8322-965BD7D4ADEE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs similarity index 97% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs rename to PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs index 58d34e7..82ff9c1 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs +++ b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs @@ -57,7 +57,7 @@ private void InitializeComponent() this.panel1.Location = new System.Drawing.Point(0, 25); this.panel1.Margin = new System.Windows.Forms.Padding(10); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(324, 112); + this.panel1.Size = new System.Drawing.Size(317, 112); this.panel1.TabIndex = 0; // // textBox2 @@ -158,7 +158,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(324, 137); + this.ClientSize = new System.Drawing.Size(317, 137); this.Controls.Add(this.panel1); this.Controls.Add(this.menuStrip); this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs b/PresentationObsSceneSwitcher/ConfigurationForm.cs similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.cs rename to PresentationObsSceneSwitcher/ConfigurationForm.cs diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.resx b/PresentationObsSceneSwitcher/ConfigurationForm.resx similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/ConfigurationForm.resx rename to PresentationObsSceneSwitcher/ConfigurationForm.resx diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/IPresentationSubscriber.cs b/PresentationObsSceneSwitcher/IPresentationSubscriber.cs similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/IPresentationSubscriber.cs rename to PresentationObsSceneSwitcher/IPresentationSubscriber.cs diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Obs/ObsClient.cs b/PresentationObsSceneSwitcher/Obs/ObsClient.cs similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Obs/ObsClient.cs rename to PresentationObsSceneSwitcher/Obs/ObsClient.cs diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs b/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs rename to PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj rename to PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs b/PresentationObsSceneSwitcher/Program.cs similarity index 100% rename from PresentationObsSceneSwitcher/PresentationObsSceneSwitcher/Program.cs rename to PresentationObsSceneSwitcher/Program.cs From b7c3e53502763859db632fe5bb55f72ba3abe2be Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Tue, 15 Sep 2020 23:15:18 +0200 Subject: [PATCH 29/45] Inital changes for saving configuration and load at startup. --- .../ConfigurationForm.Designer.cs | 119 ++-- .../ConfigurationForm.cs | 39 +- .../ConfigurationForm.resx | 575 ++++++++++++++++++ .../JsonSettingsRepository.cs | 30 + .../{ObsClient.cs => ObsWebSocketClient.cs} | 7 +- PresentationObsSceneSwitcher/Program.cs | 30 +- PresentationObsSceneSwitcher/proyector.ico | Bin 0 -> 16958 bytes 7 files changed, 741 insertions(+), 59 deletions(-) create mode 100644 PresentationObsSceneSwitcher/JsonSettingsRepository.cs rename PresentationObsSceneSwitcher/Obs/{ObsClient.cs => ObsWebSocketClient.cs} (91%) create mode 100644 PresentationObsSceneSwitcher/proyector.ico diff --git a/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs index 82ff9c1..a8e30d8 100644 --- a/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs +++ b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs @@ -30,16 +30,19 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConfigurationForm)); this.panel1 = new System.Windows.Forms.Panel(); - this.textBox2 = new System.Windows.Forms.TextBox(); + this.buttonStart = new System.Windows.Forms.Button(); + this.tbPort = new System.Windows.Forms.TextBox(); this.label3 = new System.Windows.Forms.Label(); - this.textBox1 = new System.Windows.Forms.TextBox(); + this.tbIpAddress = new System.Windows.Forms.TextBox(); this.label2 = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label(); this.menuStrip = new System.Windows.Forms.MenuStrip(); this.menuItemClose = new System.Windows.Forms.ToolStripMenuItem(); this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components); - this.button1 = new System.Windows.Forms.Button(); + this.tbPassword = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); this.panel1.SuspendLayout(); this.menuStrip.SuspendLayout(); this.SuspendLayout(); @@ -47,48 +50,64 @@ private void InitializeComponent() // panel1 // this.panel1.BackColor = System.Drawing.Color.WhiteSmoke; - this.panel1.Controls.Add(this.button1); - this.panel1.Controls.Add(this.textBox2); + this.panel1.Controls.Add(this.tbPassword); + this.panel1.Controls.Add(this.label4); + this.panel1.Controls.Add(this.buttonStart); + this.panel1.Controls.Add(this.tbPort); this.panel1.Controls.Add(this.label3); - this.panel1.Controls.Add(this.textBox1); + this.panel1.Controls.Add(this.tbIpAddress); this.panel1.Controls.Add(this.label2); this.panel1.Controls.Add(this.label1); this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; this.panel1.Location = new System.Drawing.Point(0, 25); this.panel1.Margin = new System.Windows.Forms.Padding(10); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(317, 112); + this.panel1.Size = new System.Drawing.Size(329, 150); this.panel1.TabIndex = 0; // - // textBox2 + // buttonStart // - this.textBox2.Location = new System.Drawing.Point(259, 37); - this.textBox2.Margin = new System.Windows.Forms.Padding(5, 10, 0, 0); - this.textBox2.Name = "textBox2"; - this.textBox2.Size = new System.Drawing.Size(48, 25); - this.textBox2.TabIndex = 4; - this.textBox2.Text = "4444"; - this.textBox2.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.buttonStart.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); + this.buttonStart.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonStart.ForeColor = System.Drawing.Color.WhiteSmoke; + this.buttonStart.Location = new System.Drawing.Point(9, 107); + this.buttonStart.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.buttonStart.Name = "buttonStart"; + this.buttonStart.Size = new System.Drawing.Size(308, 30); + this.buttonStart.TabIndex = 5; + this.buttonStart.Text = "Start / Restart"; + this.buttonStart.UseVisualStyleBackColor = false; + this.buttonStart.Click += new System.EventHandler(this.buttonStart_Click); + // + // tbPort + // + this.tbPort.Location = new System.Drawing.Point(269, 37); + this.tbPort.Margin = new System.Windows.Forms.Padding(5, 10, 0, 0); + this.tbPort.Name = "tbPort"; + this.tbPort.Size = new System.Drawing.Size(48, 25); + this.tbPort.TabIndex = 4; + this.tbPort.Text = "4444"; + this.tbPort.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // // label3 // this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(180, 40); + this.label3.Location = new System.Drawing.Point(185, 40); this.label3.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(74, 17); + this.label3.Size = new System.Drawing.Size(79, 17); this.label3.TabIndex = 3; - this.label3.Text = "Ip Address:"; + this.label3.Text = "Ip Address*:"; // - // textBox1 + // tbIpAddress // - this.textBox1.Location = new System.Drawing.Point(88, 37); - this.textBox1.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); - this.textBox1.Name = "textBox1"; - this.textBox1.Size = new System.Drawing.Size(82, 25); - this.textBox1.TabIndex = 2; - this.textBox1.Text = "127.0.0.1"; - this.textBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.tbIpAddress.Location = new System.Drawing.Point(93, 37); + this.tbIpAddress.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); + this.tbIpAddress.Name = "tbIpAddress"; + this.tbIpAddress.Size = new System.Drawing.Size(82, 25); + this.tbIpAddress.TabIndex = 2; + this.tbIpAddress.Text = "127.0.0.1"; + this.tbIpAddress.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // // label2 // @@ -96,9 +115,9 @@ private void InitializeComponent() this.label2.Location = new System.Drawing.Point(9, 40); this.label2.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(74, 17); + this.label2.Size = new System.Drawing.Size(79, 17); this.label2.TabIndex = 1; - this.label2.Text = "Ip Address:"; + this.label2.Text = "Ip Address*:"; // // label1 // @@ -119,7 +138,7 @@ private void InitializeComponent() this.menuItemClose}); this.menuStrip.Location = new System.Drawing.Point(0, 0); this.menuStrip.Name = "menuStrip"; - this.menuStrip.Size = new System.Drawing.Size(324, 25); + this.menuStrip.Size = new System.Drawing.Size(329, 25); this.menuStrip.TabIndex = 0; this.menuStrip.Text = "Menu Strip"; // @@ -137,34 +156,42 @@ private void InitializeComponent() this.notifyIcon.BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Info; this.notifyIcon.BalloonTipText = "Hidden!"; this.notifyIcon.BalloonTipTitle = "Presentation Obs Scene Switcher"; + this.notifyIcon.Icon = ((System.Drawing.Icon)(resources.GetObject("notifyIcon.Icon"))); this.notifyIcon.Text = "notifyIcon"; this.notifyIcon.Visible = true; this.notifyIcon.DoubleClick += new System.EventHandler(this.notifyIcon_DoubleClick); // - // button1 + // tbPassword + // + this.tbPassword.Location = new System.Drawing.Point(93, 72); + this.tbPassword.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); + this.tbPassword.Name = "tbPassword"; + this.tbPassword.Size = new System.Drawing.Size(224, 25); + this.tbPassword.TabIndex = 7; + this.tbPassword.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // label4 // - this.button1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); - this.button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.button1.ForeColor = System.Drawing.Color.WhiteSmoke; - this.button1.Location = new System.Drawing.Point(9, 72); - this.button1.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); - this.button1.Name = "button1"; - this.button1.Size = new System.Drawing.Size(298, 30); - this.button1.TabIndex = 5; - this.button1.Text = "Start / Restart"; - this.button1.UseVisualStyleBackColor = false; + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(9, 75); + this.label4.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(67, 17); + this.label4.TabIndex = 6; + this.label4.Text = "Password:"; // // ConfigurationForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(317, 137); + this.ClientSize = new System.Drawing.Size(329, 175); this.Controls.Add(this.panel1); this.Controls.Add(this.menuStrip); this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MainMenuStrip = this.menuStrip; this.Name = "ConfigurationForm"; - this.Text = "Form1"; + this.Text = "ConfigurationForm"; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ConfigurationForm_FormClosing); this.Resize += new System.EventHandler(this.ConfigurationForm_Resize); this.panel1.ResumeLayout(false); @@ -240,11 +267,13 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem menuItemClose; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; - private System.Windows.Forms.TextBox textBox1; - private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.TextBox tbIpAddress; + private System.Windows.Forms.TextBox tbPort; private System.Windows.Forms.Label label3; private System.Windows.Forms.NotifyIcon notifyIcon; - private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button buttonStart; + private System.Windows.Forms.TextBox tbPassword; + private System.Windows.Forms.Label label4; } } diff --git a/PresentationObsSceneSwitcher/ConfigurationForm.cs b/PresentationObsSceneSwitcher/ConfigurationForm.cs index 65f2a7a..8c4cb8f 100644 --- a/PresentationObsSceneSwitcher/ConfigurationForm.cs +++ b/PresentationObsSceneSwitcher/ConfigurationForm.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using PowerPointToOBSSceneSwitcher.Obs; +using PresentationObsSceneSwitcher.PowerPoint; +using System; using System.Windows.Forms; namespace PresentationObsSceneSwitcher @@ -19,9 +14,14 @@ public partial class ConfigurationForm : Form #endregion - public ConfigurationForm() + private readonly JsonSettingsRepository settingsRepository; + private ObsWebSocketClient connectedClient; + + // TODO: Refactor this Injection + public ConfigurationForm(JsonSettingsRepository settingsRepository, ObsWebSocketClient connectedClient) { InitializeComponent(); + this.settingsRepository = settingsRepository; } #region GUI Behavior events @@ -54,10 +54,31 @@ private void ConfigurationForm_FormClosing(object sender, FormClosingEventArgs e e.Cancel = true; this.Hide(); } + + connectedClient.Dispose(); } + #endregion + private async void buttonStart_Click(object sender, EventArgs e) + { + // TODO: Handle the parse better. + ObsWebSocketClientSettings settings = new ObsWebSocketClientSettings() + { + IpAddress = tbIpAddress.Text, + Port = int.Parse(tbPort.Text), + Password = tbPassword.Text + }; + await settingsRepository.SaveAsync(settings); + + connectedClient?.Dispose(); + connectedClient = new ObsWebSocketClient(settings); + await connectedClient.ConnectAsync(); + IPresentationSubscriber subscriber = new PowerPointPresentationSubscriber(); + + subscriber.Subscribe("OBS", async scene => connectedClient.ChangeScene(scene)); + } } } diff --git a/PresentationObsSceneSwitcher/ConfigurationForm.resx b/PresentationObsSceneSwitcher/ConfigurationForm.resx index b5ae26c..5456d88 100644 --- a/PresentationObsSceneSwitcher/ConfigurationForm.resx +++ b/PresentationObsSceneSwitcher/ConfigurationForm.resx @@ -57,4 +57,579 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAEAAAOkOAADpDgAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEXi8AhF4wA4NeNQaDXjUGg141BoNe + NQaDXjUGg141BoNeNQaDXjUGg141BoNeNQaCXTMEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgl0zBINeNQaDXjUGg141BoNeNQaDXjUGg141BoNe + NQaDXjUGg141BoNeNQaEXjADhF4vAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAg14wA4Ne + MSiDXjRYg140WINeNFiDXjRYg140WINeNFiDXjRYg140WINeNFiDXjRUgl0zNQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIJdMzWDXjRUg140WINe + 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 + Gv8yJhr/MiYa/zMkGis5HBwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAg10yAINd + MgqEXTIShF0zFIRdMxSAWjIVTjcjOzgqHP84Khz/OCoc/zkrHOVCMB9nhF0zFIRdMxSEXTMUhF0zFIRd + MxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRd + MxSEXTMUkGxBFrOXaCPIsH9JyrSEjsq1hNbLtYT5y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/su1 + hOPLtYSliXZVmEw9K+s7LR7/OCoc/zgqHP9ONyM7gFoyFYRdMxSEXTMUhF0yEoNdMgqDXTIAAAAAAAAA + AACCXDEAglwxDYNdMzWDXTNfg10zeYRdM3+EXTN/g10zf3dVMJRaQSb/WkEm/1pBJv9dQyfxbk4srIRd + M3+EXTN/hF0zf4RdM3+EXTN/hF0zf4RdM3+EXTN/hF0zf4RdM3+EXTN/hF0zf4RdM3+EXTN/hF0zf4Rd + M3+EXTN/hF0zf4RdM3+EXTN/i2Y8f6KDVYe8oXKxyLCA6cu1hP7LtYT/y7WE/8u1hP/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8iygvuijGT+dV9A/2BILP9bQif/d1UwlINdM3+EXTN/hF0zf4Nd + M3mDXTNfg10zNIVeMg2FXjIAg18zC4NdMmWDXTPcg10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/iGQ5/519Uf+8o3P/yrSD/8u1hP/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8Wtff+khln/jGg9/4Re + NP+DXTP/g10z/4NdM/+DXTP/g10z/4NdM9yEXTNkgVo1CoNeM0mDXTP0g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/iWU7/6KDVv/Ap3f/y7WE/8u1 + hP/LtYT/y7WE/8y2hf/PuIj/0rqM/9S8j//VvpD/1b2Q/9S9j//Tu43/0LmK/8y2hv/LtYT/y7WE/8u1 + hP/LtYT/ybOC/6yPYf+NaT7/hF40/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z84JbNEiDXTO6g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/iGM4/6OE + V//Cqnr/y7WE/8u1hP/LtYT/zLaF/9C5iv/Xv5L/4MWc/+jMpv/t0Kz/79Ov//DTsP/u0a3/6s6o/+LH + n//ZwJX/0rqM/8y2hf/LtYT/y7WE/8u1hP/LtIP/q45g/4pmO/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXDS5g10z8oNdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/hmA2/5x7T/++pXX/y7WE/8u1hP/LtYT/zbaG/9W8j//hx5//7dCs//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/79Ku/+XKo//Xv5P/zreI/8u1hP/LtYT/y7WE/8mzgv+jhVf/hmE3/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z8XJRLv9yUS7/clEu/3JRLv9yUS7/clEu/3JRLv9yUS7/clEu/3JR + Lv9yUS7/clEu/3JRLv9yUS7/clEu/3JRLv90Ui7/fFgx/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/5FvQ/+2m2z/y7WE/8u1hP/LtYT/zbeH/9e/k//nzKb/79Kv//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/68+r/9zDmP/OuIj/y7WE/8u1 + hP/LtYT/xK19/5RyRv+EXjT/g10z/4NdM/+DXTP/g10z/4NdM/9EMh//RDIf/0QyH/9EMh//RDIf/0Qy + H/9EMh//RDIf/0QyH/9EMh//RDIf/0QyH/9EMh//RDIf/0QyH/9EMh//STUh/2NHKf+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4diOP+lhln/xq9+/8u1hP/LtYT/zLaF/9e/ + k//ozaf/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/t0a3/28KX/823h//LtYT/y7WE/8u1hP+xlWf/iGQ5/4NdM/+DXTP/g10z/4NdM/+DXTP/Py8e/z8v + Hv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/0My + H/9dQyf/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+TcUb/uJ5v/8u1 + hP/LtYT/y7WE/9O8jf/my6P/8NOv//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw/+zPq//WvpD/zLaF/8u1hP/LtYT/yLGB/5Z1SP+EXjT/g10z/4Nd + M/+DXTP/g10z/2hKK/9oSiv/aEor/2hKK/9oSiv/aEor/2hKK/9oSiv/aEor/2hKK/9oSiv/aEor/2hK + K/9oSiv/aEor/2hKK/9sTSz/elcw/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+FXzX/ooNW/8avf//LtYT/y7WE/863h//exZv/7tGt//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/5Mmi/8+4if/LtYT/y7WE/8u1 + hP+vk2T/hmE3/4NdM/+DXTP/g10z/4NdM/99WTH/fVkx/31ZMf99WTH/fVkx/31ZMf99WTH/fVkx/31Z + Mf99WTH/fVkx/31ZMf99WTH/fVkx/31ZMf99WTH/flox/4JcM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/i2c8/66SY//LtYT/y7WE/8u1hP/UvI//58yl//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+7R + rv/WvZH/zLaF/8u1hP/LtYT/wqp7/4plO/+DXTP/g10z/4NdM/+DXTP/WUAm/1lAJv9ZQCb/WUAm/1lA + Jv9ZQCb/WUAm/1lAJv9ZQCb/WUAm/1lAJv9ZQCb/WUAm/1lAJv9ZQCb/WUAm/11DJ/9vUC3/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/5NxRf+4nm7/y7WE/8u1hP/LtYX/28KX/+3Q + rP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsf/w07H/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/4Mad/863h//LtYT/y7WE/8q1g/+TcET/hF40/4NdM/+DXTP/g10z/zIm + Gv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zIm + Gv84Khz/WEAl/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+ZeEz/v6Z2/8u1 + hP/LtYT/zbeH/+DGnf/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//Lavf/1483/9uTQ//Pc + wP/w1LP/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+jNpv/PuIn/y7WE/8u1hP/LtYT/nn5R/4Rf + Nf+DXTP/g10z/4NdM/9VPiX/VT4l/1U+Jf9VPiX/VT4l/1U+Jf9VPiX/VT4l/1U+Jf9VPiX/VT4l/1U+ + Jf9VPiX/VT4l/1U+Jf9VPiX/WUAm/21OLP+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/nXxQ/8Ore//LtYT/y7WE/9C5iv/jyKD/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//HW + tv/469v//vv5///+/P/68eX/8tq8//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/t0a3/0LmK/8u1 + hP/LtYT/y7WE/6aIWv+FXzX/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/55+Uf/FrX3/y7WE/8u1hP/Ruov/5Mmi//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/z3L//+/Xs//////////////38//Tfxf/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/79Ow/9C5iv/LtYT/y7WE/8u1hP+oi17/hWA1/4NdM/+DXTP/g10z/2hLK/9oSyv/aEsr/2hL + K/9oSyv/aEsr/2hLK/9oSyv/aEsr/2hLK/9oSyv/aEsr/2hLK/9oSyv/aEsr/2hLK/9rTSz/eFYw/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+efVH/xKx8/8u1hP/LtYT/0bmK/+TJ + of/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8tm7//rx5v///v3///////348v/z3cL/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw/+7Rrv/QuYr/y7WE/8u1hP/LtYT/p4lc/4VgNf+DXTP/g10z/4Nd + M/8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4u + Hv89Lh7/QjEf/19EKP+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/m3pO/8Go + ef/LtYT/y7WE/864iP/ix5//8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DUsf/14cn/+e7g//rv + 4//25M//8da2//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/rzqn/z7iJ/8u1hP/LtYT/y7WE/6KC + Vv+FXzX/g10z/4NdM/+DXTP/RTIg/0UyIP9FMiD/RTIg/0UyIP9FMiD/RTIg/0UyIP9FMiD/RTIg/0Uy + IP9FMiD/RTIg/0UyIP9FMiD/RTIg/0o2If9jRyn/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/5Z0SP+7oXL/y7WE/8u1hP/MtoX/3sSa/+/Srv/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NSx//LYuv/y2bv/8dSz//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/5Mmh/864 + iP/LtYT/y7WE/8u1hP+Yd0r/hF40/4NdM/+DXTP/g10z/25OLf9uTi3/bk4t/25OLf9uTi3/bk4t/25O + Lf9uTi3/bk4t/25OLf9uTi3/bk4t/25OLf9uTi3/bk4t/25OLf9xUS7/fVgx/4NdM/+DXTP/g10z/4Ff + Nv96ZkT/dmlK/39hOv+DXTP/g10z/4NdM/+Pa0H/spdp/8u1hP/LtYT/y7WE/9i/k//qzqn/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw/9rBlv/Ntob/y7WE/8u1hP/HsID/jWk+/4NdM/+DXTP/g10z/4NdM/93VS//d1Uv/3dV + L/93VS//d1Uv/3dVL/93VS//d1Uv/3dVL/93VS//d1Uv/3dVL/93VS//d1Uv/3dVL/94VS//elcx/4Fb + M/+DXTP/g10z/39hOv9oeGP/RZqi/0Ceqv9Zhn7/e2VC/4NdM/+DXTP/h2I3/6eJXP/Js4L/y7WE/8u1 + hP/QuYr/4sig/+/Sr//w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw/+rOqf/Suoz/y7WE/8u1hP/LtYT/uJ5v/4diN/+DXTP/g10z/4Nd + M/+DXTP/TTgi/004Iv9NOCL/TTgi/004Iv9NOCL/TTgi/004Iv9NOCL/TTgi/004Iv9NOCL/TTgi/004 + Iv9NOCL/TTki/1I8JP9oSyv/g10z/4NdM/90bE7/QZ2o/xrD7f8aw+//L6/J/2R7a/+DXTP/g10z/4Nd + M/+aeU3/v6Z3/8u1hP/LtYT/y7WE/9e/k//qzqn/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+/Srv/cwpj/zbeG/8u1hP/LtYT/y7SE/6CA + VP+FXzX/g10z/4NdM/+DXTP/g10z/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zIm + Gv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv85Khz/WkEn/4NdM/+DXTP/b3FX/zWovv8ZxPD/GcTw/ye3 + 2P9Zhn7/g10z/4NdM/+DXTP/i2c8/62RY//Js4L/y7WE/8u1hP/OuIj/3cSa/+zQq//w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/iyKD/0LmK/8u1 + hP/LtYT/y7WE/72jdP+NaT7/g10z/4NdM/+DXTP/g10z/4NdM/9gRSj/YEUo/2BFKP9gRSj/YEUo/2BF + KP9gRSj/YEUo/2BFKP9gRSj/YEUo/2BFKP9gRSj/YEUo/2BFKP9gRSj/ZEgp/3NSLv+DXTP/g10z/3ln + Rf9OkJL/IL3j/x3A6f86pLb/bXNb/4NdM/+DXTP/g10z/4ReNP+aeUz/vqV1/8u1hP/LtYT/y7WE/9C5 + iv/fxZz/7NCs//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+/S + r//kyaL/0ruN/8y2hf/LtYT/y7WE/8qzgv+gf1L/hWA1/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z+YNd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+CXjb/dGxO/1uEe/9XiIL/anVf/4BgOf+DXTP/g10z/4NdM/+DXTP/iWU6/6eJ + W//FrX3/y7WE/8u1hP/LtYT/0LmK/9zDmP/pzqj/79Kv//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw/+zQrP/gxp3/0ruN/8y2hf/LtYT/y7WE/8u1hP+zmGn/i2Y7/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z+YNdM9eDXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+CXjX/gV82/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+Oaj//r5Jk/8ixgP/LtYT/y7WE/8u1hP/OuIj/1r6R/+DGnf/pzaf/79Ku//DT + sP/w07D/8NOw//DTsP/w07D/686q/+PIoP/ZwJT/z7mJ/8u1hf/LtYT/y7WE/8u1hP+5oHD/k3FF/4Re + NP+DXTP/g10z/4NdM/+DXTP/g10z/4NdMtWCXTN6g10z/oNdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/hF40/5JvRP+yl2j/yLGA/8u1hP/LtYT/y7WE/8u1 + hP/PuIn/1b2P/9rBlv/exJr/4Mad/+DGnf/exZv/28KX/9a+kf/Ruov/zLaF/8u1hP/LtYT/y7WE/8u1 + hP+7onP/mHZK/4VfNf+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/6DXTJ4gV40HYNdM6WDXTP8g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+FXzX/kW9D/66R + Y//FrX3/y7WE/8u1hP/LtYT/y7WE/8u1hP/MtoX/zreH/8+4if/PuIn/zriH/8y2hf/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u0g/+3nG3/mHZK/4VgNv+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/uDXTOkg1wyHYNe + MwGDXjMpglwzcYNdM6iEXTO7hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRd + NL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRd + NL2EXTS9hF00vYVeNb2RbkS9rI9hzMKqeuvKtIP8y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8WuffOzmGnSmXdLv4ZgNr2EXTS9hF00vYRdNL2EXTS9hF0zu4Nd + M6iDXTRwg10yKINdMgEAAAAAgFo1AIFaNQ2EXTQphFwzM4RdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRd + NDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRd + NDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF42NJt7Tzy+pHRQy7SDiMu1hNHLtYT4y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE4cu1hJrDq3paooJWP4hiOTWEXTQ0hF00NIRd + NDSEXTQ0hF00NIRcMzOEXTQpg102DINdNgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1b+AAs22 + hBnMtIU9y7WEaMu1hJXLtYS4y7WE0su1hN7LtYTey7WE1su1hMDLtYSfy7WFc8u1hEnKtoQhyraEBQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADV1VUC5uBWG+jiViDq6lUEAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/4CAAMuzhAvKtYUey7aELsq1hDrLtYQ/y7WFP8u1hDzLtYUyzLWFI8q2 + hg/Ht4cCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObgVxnm4lgj5uJYAwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+BYJefg + V4vn4Fez5+FXWOTkWwoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObgWDLn4Fet5+BXwebe + ViLm3VUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAObhWCLn4Fec5+BX9ufgV9Dn4Vdp6N9XEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAefh + WErn4Fe+5+BX+OfgV+Hl31ge495aAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD//4AB5uZeCeTkWwzm4lgH5+BWPOfgV67n4Ff25+BX3+fgV5Dm4Vcu399gAwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA6uNVFubfVnnn4FfQ5+BX+ufgWNfm4FhR5OBaB9/fYAbr4FwS6+BcAQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6OBWK+fgV5Ln4Feg5t9WPeniWQjm31Yy5+BXmefg + V+3n4Ffu5+BXwufgV3Tm3lci1dVVAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAADj41UQ5+BXWufgV7Xn4Ffo5+BX/OfgV7jm4FhD5OBZCePjVRPn4FeX5+BXw+fe + VS7n3lUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOfhVzTn4Fe25+BX9Ofg + V7nn4VdJ4t5aCOfgWB/n4Fds5+BXz+fgV/jn4Ffn5+BWxOfgVonn4FdF6uJVEv//QAEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA399QCebgVjTn4Fd25+BXuefgV9/n4Ff55+BX6efgV4Tn4Fcs5eBaBefh + VR3n4Fen5+BX8OfgV/no4Vc16OFWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADo4lUL5+BXUefgV8bn4Ff35+BXzefgV2no4lcS6OBXD+jhVzjn4VeG5+BX2+fgV/rn4Ffu5+BX2+fg + V8fn4Vem5uBXhebhWG/n4Vhh5uBXYOfgVmnn31h+5+BXnOfgV8Hn4FfW5+BX6efgV/rn4Ffu5+BXoObg + V0Tn4FgV5+BYAufhV0Ln4Fe85+BX9efgV/bn31dz6OFXDejiVwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA//8AAOffVwvo4FdK5+BXvOfgV/nn4Ffj5+BXl+ffVzLh3VYG5+FXFObh + Vzbn4FZz5+BXvefgV/Dn4Ff+5+BX9ufgV+7n4Ffo5+BX5efgV+Tn4Ffn5+BX7OfgV/Tn4Ff85+BX++ff + V83n31eI5t9XQObgVhzl4FkF599YFujgV37n4FfW5+BX+ufgV+bo31he5t5XEebeVwAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6OFXCefhVzPn4FeZ5+BX8Ofg + V/Ln31fM5uBXfejgWCbu2lwF5+BWD+fgVyDo4Fc35+BXYefgWIro4Fiq599XwOfgV8zn4FfL5+BWxOjh + V7Hn4FeV599WbObgWD/n31cl6N9XFOTdVwTr4FwR5+BXYufgV8Hn4Ffs5+BX/effWL/o31c/6d5YC+ne + WQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADn4lcE6OJXG+fgV2Tn4FfL5+BX+efgV+7n4FfP5+BXj+ffWEHm31kO6t9VAufhWArn4FgT6OBYGeff + Vx7n4Fch5+BXIOfgVh/o4Vcb5+BWFebfVg3k3lkD/PxDA+bfWTHn4Fd55+BXxefgV+fn4Ff75+BX6Ofg + V33n31gk695WBe7dVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAOrqVQHn4FcL5t9WLufgV33n4FfX5+BX++fgV/Ln4Ffk5+BXx+jg + WJfo4Fdl5+FVP+jgVR/o4lEM5uZNAwAAAADf31AK6OJUGufgVzbn4FZZ5uFXiefgV77n4Ffe5+BX7ufg + V/zn4Fbt5+FXmefhVjfm4FYQ5uBWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOXeVQPn4FcO5t9XKufg + WGvn4Fe/5+BX8ufgV/zn4Ff05+BX7OfgV+bn4Ffh5+BX3ufgV9zn4Ffc5+BX3efgV+Dn4Ffk5+BX6ufg + V/Ln4Ff75+BX/OfgV8/n31iD5t9VMubhVxPn41QD5uZNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAOXcWALo31gK5+BXFufgVjfn4FZt599XpefgV9Tn4Ff05+BX/ufgV//n4Ff/5+BX/+fg + V//n4Ff/5+BX+ufgV9/m4Fez5uFYfejgV0Dn4Fcc599YDuTeVAPb20kAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm4VUD5+BWCuffVxDn4FcW5d9VHujh + VjLn4VZE5+BXUejgWFHm4FZJ5eFWOefhVSLn4FYY5uBXEubhWAvo4FYE6t9VAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAANvbSQDp4lQC5+JVBOfgVwbo4FgG5t9WBePhVQPm5k0B//8AAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA//////////////////////////////////////////////////////gAf////gAf8AB////+ + AA/wAH////4AD/AAf////gAP8AB////+AA/4AH////4AH/wA/////wA//gP///8AQH/+A///+AAAf+AA + AAAAAAAHgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAP/////8AAP//// + //D+AD+P////8H///wf////wP//8B////4AP//gB////gAP/4AD///+AAH8AAP///4AAAAAB////wAAA + AAP////gAAAAB/////AAAAAP////+AAIAB/////+AAAAf/////+AAAH///////AAD////////4D///// + //////////////////////////////////////////////////8= + + + + + AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAEAAAOkOAADpDgAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + MxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRdMxSEXTMUhF0zFIRd + MxSEXTMUkGxBFrOXaCPIsH9JyrSEjsq1hNbLtYT5y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/su1 + hOPLtYSliXZVmEw9K+s7LR7/OCoc/zgqHP9ONyM7gFoyFYRdMxSEXTMUhF0yEoNdMgqDXTIAAAAAAAAA + AACCXDEAglwxDYNdMzWDXTNfg10zeYRdM3+EXTN/g10zf3dVMJRaQSb/WkEm/1pBJv9dQyfxbk4srIRd + M3+EXTN/hF0zf4RdM3+EXTN/hF0zf4RdM3+EXTN/hF0zf4RdM3+EXTN/hF0zf4RdM3+EXTN/hF0zf4Rd + M3+EXTN/hF0zf4RdM3+EXTN/i2Y8f6KDVYe8oXKxyLCA6cu1hP7LtYT/y7WE/8u1hP/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8iygvuijGT+dV9A/2BILP9bQif/d1UwlINdM3+EXTN/hF0zf4Nd + M3mDXTNfg10zNIVeMg2FXjIAg18zC4NdMmWDXTPcg10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/iGQ5/519Uf+8o3P/yrSD/8u1hP/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8Wtff+khln/jGg9/4Re + NP+DXTP/g10z/4NdM/+DXTP/g10z/4NdM9yEXTNkgVo1CoNeM0mDXTP0g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/iWU7/6KDVv/Ap3f/y7WE/8u1 + hP/LtYT/y7WE/8y2hf/PuIj/0rqM/9S8j//VvpD/1b2Q/9S9j//Tu43/0LmK/8y2hv/LtYT/y7WE/8u1 + hP/LtYT/ybOC/6yPYf+NaT7/hF40/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z84JbNEiDXTO6g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/iGM4/6OE + V//Cqnr/y7WE/8u1hP/LtYT/zLaF/9C5iv/Xv5L/4MWc/+jMpv/t0Kz/79Ov//DTsP/u0a3/6s6o/+LH + n//ZwJX/0rqM/8y2hf/LtYT/y7WE/8u1hP/LtIP/q45g/4pmO/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXDS5g10z8oNdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/hmA2/5x7T/++pXX/y7WE/8u1hP/LtYT/zbaG/9W8j//hx5//7dCs//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/79Ku/+XKo//Xv5P/zreI/8u1hP/LtYT/y7WE/8mzgv+jhVf/hmE3/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z8XJRLv9yUS7/clEu/3JRLv9yUS7/clEu/3JRLv9yUS7/clEu/3JR + Lv9yUS7/clEu/3JRLv9yUS7/clEu/3JRLv90Ui7/fFgx/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/5FvQ/+2m2z/y7WE/8u1hP/LtYT/zbeH/9e/k//nzKb/79Kv//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/68+r/9zDmP/OuIj/y7WE/8u1 + hP/LtYT/xK19/5RyRv+EXjT/g10z/4NdM/+DXTP/g10z/4NdM/9EMh//RDIf/0QyH/9EMh//RDIf/0Qy + H/9EMh//RDIf/0QyH/9EMh//RDIf/0QyH/9EMh//RDIf/0QyH/9EMh//STUh/2NHKf+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4diOP+lhln/xq9+/8u1hP/LtYT/zLaF/9e/ + k//ozaf/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/t0a3/28KX/823h//LtYT/y7WE/8u1hP+xlWf/iGQ5/4NdM/+DXTP/g10z/4NdM/+DXTP/Py8e/z8v + Hv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/z8vHv8/Lx7/Py8e/0My + H/9dQyf/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+TcUb/uJ5v/8u1 + hP/LtYT/y7WE/9O8jf/my6P/8NOv//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw/+zPq//WvpD/zLaF/8u1hP/LtYT/yLGB/5Z1SP+EXjT/g10z/4Nd + M/+DXTP/g10z/2hKK/9oSiv/aEor/2hKK/9oSiv/aEor/2hKK/9oSiv/aEor/2hKK/9oSiv/aEor/2hK + K/9oSiv/aEor/2hKK/9sTSz/elcw/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+FXzX/ooNW/8avf//LtYT/y7WE/863h//exZv/7tGt//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/5Mmi/8+4if/LtYT/y7WE/8u1 + hP+vk2T/hmE3/4NdM/+DXTP/g10z/4NdM/99WTH/fVkx/31ZMf99WTH/fVkx/31ZMf99WTH/fVkx/31Z + Mf99WTH/fVkx/31ZMf99WTH/fVkx/31ZMf99WTH/flox/4JcM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/i2c8/66SY//LtYT/y7WE/8u1hP/UvI//58yl//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+7R + rv/WvZH/zLaF/8u1hP/LtYT/wqp7/4plO/+DXTP/g10z/4NdM/+DXTP/WUAm/1lAJv9ZQCb/WUAm/1lA + Jv9ZQCb/WUAm/1lAJv9ZQCb/WUAm/1lAJv9ZQCb/WUAm/1lAJv9ZQCb/WUAm/11DJ/9vUC3/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/5NxRf+4nm7/y7WE/8u1hP/LtYX/28KX/+3Q + rP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsf/w07H/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/4Mad/863h//LtYT/y7WE/8q1g/+TcET/hF40/4NdM/+DXTP/g10z/zIm + Gv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zIm + Gv84Khz/WEAl/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+ZeEz/v6Z2/8u1 + hP/LtYT/zbeH/+DGnf/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//Lavf/1483/9uTQ//Pc + wP/w1LP/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+jNpv/PuIn/y7WE/8u1hP/LtYT/nn5R/4Rf + Nf+DXTP/g10z/4NdM/9VPiX/VT4l/1U+Jf9VPiX/VT4l/1U+Jf9VPiX/VT4l/1U+Jf9VPiX/VT4l/1U+ + Jf9VPiX/VT4l/1U+Jf9VPiX/WUAm/21OLP+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/nXxQ/8Ore//LtYT/y7WE/9C5iv/jyKD/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//HW + tv/469v//vv5///+/P/68eX/8tq8//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/t0a3/0LmK/8u1 + hP/LtYT/y7WE/6aIWv+FXzX/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/55+Uf/FrX3/y7WE/8u1hP/Ruov/5Mmi//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/z3L//+/Xs//////////////38//Tfxf/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/79Ow/9C5iv/LtYT/y7WE/8u1hP+oi17/hWA1/4NdM/+DXTP/g10z/2hLK/9oSyv/aEsr/2hL + K/9oSyv/aEsr/2hLK/9oSyv/aEsr/2hLK/9oSyv/aEsr/2hLK/9oSyv/aEsr/2hLK/9rTSz/eFYw/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+efVH/xKx8/8u1hP/LtYT/0bmK/+TJ + of/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8tm7//rx5v///v3///////348v/z3cL/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw/+7Rrv/QuYr/y7WE/8u1hP/LtYT/p4lc/4VgNf+DXTP/g10z/4Nd + M/8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4uHv8+Lh7/Pi4e/z4u + Hv89Lh7/QjEf/19EKP+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/m3pO/8Go + ef/LtYT/y7WE/864iP/ix5//8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DUsf/14cn/+e7g//rv + 4//25M//8da2//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/rzqn/z7iJ/8u1hP/LtYT/y7WE/6KC + Vv+FXzX/g10z/4NdM/+DXTP/RTIg/0UyIP9FMiD/RTIg/0UyIP9FMiD/RTIg/0UyIP9FMiD/RTIg/0Uy + IP9FMiD/RTIg/0UyIP9FMiD/RTIg/0o2If9jRyn/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/5Z0SP+7oXL/y7WE/8u1hP/MtoX/3sSa/+/Srv/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NSx//LYuv/y2bv/8dSz//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/5Mmh/864 + iP/LtYT/y7WE/8u1hP+Yd0r/hF40/4NdM/+DXTP/g10z/25OLf9uTi3/bk4t/25OLf9uTi3/bk4t/25O + Lf9uTi3/bk4t/25OLf9uTi3/bk4t/25OLf9uTi3/bk4t/25OLf9xUS7/fVgx/4NdM/+DXTP/g10z/4Ff + Nv96ZkT/dmlK/39hOv+DXTP/g10z/4NdM/+Pa0H/spdp/8u1hP/LtYT/y7WE/9i/k//qzqn/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw/9rBlv/Ntob/y7WE/8u1hP/HsID/jWk+/4NdM/+DXTP/g10z/4NdM/93VS//d1Uv/3dV + L/93VS//d1Uv/3dVL/93VS//d1Uv/3dVL/93VS//d1Uv/3dVL/93VS//d1Uv/3dVL/94VS//elcx/4Fb + M/+DXTP/g10z/39hOv9oeGP/RZqi/0Ceqv9Zhn7/e2VC/4NdM/+DXTP/h2I3/6eJXP/Js4L/y7WE/8u1 + hP/QuYr/4sig/+/Sr//w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw/+rOqf/Suoz/y7WE/8u1hP/LtYT/uJ5v/4diN/+DXTP/g10z/4Nd + M/+DXTP/TTgi/004Iv9NOCL/TTgi/004Iv9NOCL/TTgi/004Iv9NOCL/TTgi/004Iv9NOCL/TTgi/004 + Iv9NOCL/TTki/1I8JP9oSyv/g10z/4NdM/90bE7/QZ2o/xrD7f8aw+//L6/J/2R7a/+DXTP/g10z/4Nd + M/+aeU3/v6Z3/8u1hP/LtYT/y7WE/9e/k//qzqn/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+/Srv/cwpj/zbeG/8u1hP/LtYT/y7SE/6CA + VP+FXzX/g10z/4NdM/+DXTP/g10z/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv8yJhr/MiYa/zIm + Gv8yJhr/MiYa/zImGv8yJhr/MiYa/zImGv85Khz/WkEn/4NdM/+DXTP/b3FX/zWovv8ZxPD/GcTw/ye3 + 2P9Zhn7/g10z/4NdM/+DXTP/i2c8/62RY//Js4L/y7WE/8u1hP/OuIj/3cSa/+zQq//w07D/8NOw//DT + sP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/iyKD/0LmK/8u1 + hP/LtYT/y7WE/72jdP+NaT7/g10z/4NdM/+DXTP/g10z/4NdM/9gRSj/YEUo/2BFKP9gRSj/YEUo/2BF + KP9gRSj/YEUo/2BFKP9gRSj/YEUo/2BFKP9gRSj/YEUo/2BFKP9gRSj/ZEgp/3NSLv+DXTP/g10z/3ln + Rf9OkJL/IL3j/x3A6f86pLb/bXNb/4NdM/+DXTP/g10z/4ReNP+aeUz/vqV1/8u1hP/LtYT/y7WE/9C5 + iv/fxZz/7NCs//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DTsP/w07D/8NOw/+/S + r//kyaL/0ruN/8y2hf/LtYT/y7WE/8qzgv+gf1L/hWA1/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z+YNd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+CXjb/dGxO/1uEe/9XiIL/anVf/4BgOf+DXTP/g10z/4NdM/+DXTP/iWU6/6eJ + W//FrX3/y7WE/8u1hP/LtYT/0LmK/9zDmP/pzqj/79Kv//DTsP/w07D/8NOw//DTsP/w07D/8NOw//DT + sP/w07D/8NOw/+zQrP/gxp3/0ruN/8y2hf/LtYT/y7WE/8u1hP+zmGn/i2Y7/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z+YNdM9eDXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+CXjX/gV82/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+Oaj//r5Jk/8ixgP/LtYT/y7WE/8u1hP/OuIj/1r6R/+DGnf/pzaf/79Ku//DT + sP/w07D/8NOw//DTsP/w07D/686q/+PIoP/ZwJT/z7mJ/8u1hf/LtYT/y7WE/8u1hP+5oHD/k3FF/4Re + NP+DXTP/g10z/4NdM/+DXTP/g10z/4NdMtWCXTN6g10z/oNdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/hF40/5JvRP+yl2j/yLGA/8u1hP/LtYT/y7WE/8u1 + hP/PuIn/1b2P/9rBlv/exJr/4Mad/+DGnf/exZv/28KX/9a+kf/Ruov/zLaF/8u1hP/LtYT/y7WE/8u1 + hP+7onP/mHZK/4VfNf+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/6DXTJ4gV40HYNdM6WDXTP8g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4Nd + M/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/+FXzX/kW9D/66R + Y//FrX3/y7WE/8u1hP/LtYT/y7WE/8u1hP/MtoX/zreH/8+4if/PuIn/zriH/8y2hf/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u0g/+3nG3/mHZK/4VgNv+DXTP/g10z/4NdM/+DXTP/g10z/4NdM/uDXTOkg1wyHYNe + MwGDXjMpglwzcYNdM6iEXTO7hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRd + NL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRdNL2EXTS9hF00vYRd + NL2EXTS9hF00vYVeNb2RbkS9rI9hzMKqeuvKtIP8y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8WuffOzmGnSmXdLv4ZgNr2EXTS9hF00vYRdNL2EXTS9hF0zu4Nd + M6iDXTRwg10yKINdMgEAAAAAgFo1AIFaNQ2EXTQphFwzM4RdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRd + NDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRd + NDSEXTQ0hF00NIRdNDSEXTQ0hF00NIRdNDSEXTQ0hF42NJt7Tzy+pHRQy7SDiMu1hNHLtYT4y7WE/8u1 + hP/LtYT/y7WE/8u1hP/LtYT/y7WE/8u1hP/LtYT/y7WE4cu1hJrDq3paooJWP4hiOTWEXTQ0hF00NIRd + NDSEXTQ0hF00NIRcMzOEXTQpg102DINdNgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1b+AAs22 + hBnMtIU9y7WEaMu1hJXLtYS4y7WE0su1hN7LtYTey7WE1su1hMDLtYSfy7WFc8u1hEnKtoQhyraEBQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADV1VUC5uBWG+jiViDq6lUEAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/4CAAMuzhAvKtYUey7aELsq1hDrLtYQ/y7WFP8u1hDzLtYUyzLWFI8q2 + hg/Ht4cCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObgVxnm4lgj5uJYAwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+BYJefg + V4vn4Fez5+FXWOTkWwoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObgWDLn4Fet5+BXwebe + ViLm3VUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAObhWCLn4Fec5+BX9ufgV9Dn4Vdp6N9XEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAefh + WErn4Fe+5+BX+OfgV+Hl31ge495aAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD//4AB5uZeCeTkWwzm4lgH5+BWPOfgV67n4Ff25+BX3+fgV5Dm4Vcu399gAwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA6uNVFubfVnnn4FfQ5+BX+ufgWNfm4FhR5OBaB9/fYAbr4FwS6+BcAQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6OBWK+fgV5Ln4Feg5t9WPeniWQjm31Yy5+BXmefg + V+3n4Ffu5+BXwufgV3Tm3lci1dVVAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAADj41UQ5+BXWufgV7Xn4Ffo5+BX/OfgV7jm4FhD5OBZCePjVRPn4FeX5+BXw+fe + VS7n3lUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOfhVzTn4Fe25+BX9Ofg + V7nn4VdJ4t5aCOfgWB/n4Fds5+BXz+fgV/jn4Ffn5+BWxOfgVonn4FdF6uJVEv//QAEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA399QCebgVjTn4Fd25+BXuefgV9/n4Ff55+BX6efgV4Tn4Fcs5eBaBefh + VR3n4Fen5+BX8OfgV/no4Vc16OFWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADo4lUL5+BXUefgV8bn4Ff35+BXzefgV2no4lcS6OBXD+jhVzjn4VeG5+BX2+fgV/rn4Ffu5+BX2+fg + V8fn4Vem5uBXhebhWG/n4Vhh5uBXYOfgVmnn31h+5+BXnOfgV8Hn4FfW5+BX6efgV/rn4Ffu5+BXoObg + V0Tn4FgV5+BYAufhV0Ln4Fe85+BX9efgV/bn31dz6OFXDejiVwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA//8AAOffVwvo4FdK5+BXvOfgV/nn4Ffj5+BXl+ffVzLh3VYG5+FXFObh + Vzbn4FZz5+BXvefgV/Dn4Ff+5+BX9ufgV+7n4Ffo5+BX5efgV+Tn4Ffn5+BX7OfgV/Tn4Ff85+BX++ff + V83n31eI5t9XQObgVhzl4FkF599YFujgV37n4FfW5+BX+ufgV+bo31he5t5XEebeVwAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6OFXCefhVzPn4FeZ5+BX8Ofg + V/Ln31fM5uBXfejgWCbu2lwF5+BWD+fgVyDo4Fc35+BXYefgWIro4Fiq599XwOfgV8zn4FfL5+BWxOjh + V7Hn4FeV599WbObgWD/n31cl6N9XFOTdVwTr4FwR5+BXYufgV8Hn4Ffs5+BX/effWL/o31c/6d5YC+ne + WQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADn4lcE6OJXG+fgV2Tn4FfL5+BX+efgV+7n4FfP5+BXj+ffWEHm31kO6t9VAufhWArn4FgT6OBYGeff + Vx7n4Fch5+BXIOfgVh/o4Vcb5+BWFebfVg3k3lkD/PxDA+bfWTHn4Fd55+BXxefgV+fn4Ff75+BX6Ofg + V33n31gk695WBe7dVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAOrqVQHn4FcL5t9WLufgV33n4FfX5+BX++fgV/Ln4Ffk5+BXx+jg + WJfo4Fdl5+FVP+jgVR/o4lEM5uZNAwAAAADf31AK6OJUGufgVzbn4FZZ5uFXiefgV77n4Ffe5+BX7ufg + V/zn4Fbt5+FXmefhVjfm4FYQ5uBWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOXeVQPn4FcO5t9XKufg + WGvn4Fe/5+BX8ufgV/zn4Ff05+BX7OfgV+bn4Ffh5+BX3ufgV9zn4Ffc5+BX3efgV+Dn4Ffk5+BX6ufg + V/Ln4Ff75+BX/OfgV8/n31iD5t9VMubhVxPn41QD5uZNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAOXcWALo31gK5+BXFufgVjfn4FZt599XpefgV9Tn4Ff05+BX/ufgV//n4Ff/5+BX/+fg + V//n4Ff/5+BX+ufgV9/m4Fez5uFYfejgV0Dn4Fcc599YDuTeVAPb20kAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm4VUD5+BWCuffVxDn4FcW5d9VHujh + VjLn4VZE5+BXUejgWFHm4FZJ5eFWOefhVSLn4FYY5uBXEubhWAvo4FYE6t9VAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAANvbSQDp4lQC5+JVBOfgVwbo4FgG5t9WBePhVQPm5k0B//8AAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA//////////////////////////////////////////////////////gAf////gAf8AB////+ + AA/wAH////4AD/AAf////gAP8AB////+AA/4AH////4AH/wA/////wA//gP///8AQH/+A///+AAAf+AA + AAAAAAAHgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAP/////8AAP//// + //D+AD+P////8H///wf////wP//8B////4AP//gB////gAP/4AD///+AAH8AAP///4AAAAAB////wAAA + AAP////gAAAAB/////AAAAAP////+AAIAB/////+AAAAf/////+AAAH///////AAD////////4D///// + //////////////////////////////////////////////////8= + + \ No newline at end of file diff --git a/PresentationObsSceneSwitcher/JsonSettingsRepository.cs b/PresentationObsSceneSwitcher/JsonSettingsRepository.cs new file mode 100644 index 0000000..cf045a0 --- /dev/null +++ b/PresentationObsSceneSwitcher/JsonSettingsRepository.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; +using PowerPointToOBSSceneSwitcher.Obs; +using System.IO; +using System.Threading.Tasks; + +namespace PresentationObsSceneSwitcher +{ + public class JsonSettingsRepository + { + public async Task SaveAsync(ObsWebSocketClientSettings settings) + { + string json = JsonConvert.SerializeObject(settings); + await File.WriteAllTextAsync(@".\settings.json", json).ConfigureAwait(false); // Maybe AppData is a better location, but... + } + + public async Task LoadAsync() + { + try + { + string json = await File.ReadAllTextAsync(@".\settings.json").ConfigureAwait(false); + + return JsonConvert.DeserializeObject(json); + } + catch (FileNotFoundException ex) + { + return null; + } + } + } +} diff --git a/PresentationObsSceneSwitcher/Obs/ObsClient.cs b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs similarity index 91% rename from PresentationObsSceneSwitcher/Obs/ObsClient.cs rename to PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs index 5a90796..f6d63ef 100644 --- a/PresentationObsSceneSwitcher/Obs/ObsClient.cs +++ b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs @@ -28,6 +28,11 @@ public string IpAddress /// Port of the OBS WebSocket server /// public int Port { get; set; } + + /// + /// Optional Password of the OBS WebSocket server + /// + public string Password { get; set; } } /// @@ -57,7 +62,7 @@ public ObsWebSocketClient(ObsWebSocketClientSettings settings) public void Connect() { if (!obsWebSocket.IsConnected) - obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", ""); + obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", settings.Password ?? ""); } /// diff --git a/PresentationObsSceneSwitcher/Program.cs b/PresentationObsSceneSwitcher/Program.cs index b506521..d7d017f 100644 --- a/PresentationObsSceneSwitcher/Program.cs +++ b/PresentationObsSceneSwitcher/Program.cs @@ -1,6 +1,6 @@ +using PowerPointToOBSSceneSwitcher.Obs; +using PresentationObsSceneSwitcher.PowerPoint; using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; @@ -12,12 +12,34 @@ static class Program /// The main entry point for the application. /// [STAThread] - static void Main() + static async Task Main() { Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new ConfigurationForm()); + + JsonSettingsRepository settingsRepository = new JsonSettingsRepository(); + ObsWebSocketClient connectedClient = await ConnectWithSavedSettings(settingsRepository).ConfigureAwait(false); + + Application.Run(new ConfigurationForm(settingsRepository, connectedClient)); + } + + private static async Task ConnectWithSavedSettings(JsonSettingsRepository settingsRepository) + { + ObsWebSocketClientSettings savedSettings = await settingsRepository.LoadAsync(); + + if (!(savedSettings is null)) + { + ObsWebSocketClient connectedClient = new ObsWebSocketClient(savedSettings); + await connectedClient.ConnectAsync(); + + IPresentationSubscriber subscriber = new PowerPointPresentationSubscriber(); + subscriber.Subscribe("OBS", async scene => connectedClient.ChangeScene(scene)); + + return connectedClient; + } + + return null; } } } diff --git a/PresentationObsSceneSwitcher/proyector.ico b/PresentationObsSceneSwitcher/proyector.ico new file mode 100644 index 0000000000000000000000000000000000000000..4ec97ec4dc0b71465ccc596609fd2a2f71bbf0bd GIT binary patch literal 16958 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 QsK~`L+QS3=twVqQ4=*L0-2eap literal 0 HcmV?d00001 From aa7e63df68ba9808856dcaf25e1c8bedc0b7811e Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 20:52:38 +0200 Subject: [PATCH 30/45] Remove dependencies from the Form. Handle the lifecycle of the client and settings in Program. --- .../ConfigurationForm.Designer.cs | 42 +++++++++---------- .../ConfigurationForm.cs | 32 +++++--------- .../Obs/ObsWebSocketClient.cs | 17 ++++---- PresentationObsSceneSwitcher/Program.cs | 29 ++++++------- 4 files changed, 55 insertions(+), 65 deletions(-) diff --git a/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs index a8e30d8..6a82873 100644 --- a/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs +++ b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs @@ -32,6 +32,8 @@ private void InitializeComponent() this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConfigurationForm)); this.panel1 = new System.Windows.Forms.Panel(); + this.tbPassword = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); this.buttonStart = new System.Windows.Forms.Button(); this.tbPort = new System.Windows.Forms.TextBox(); this.label3 = new System.Windows.Forms.Label(); @@ -41,8 +43,6 @@ private void InitializeComponent() this.menuStrip = new System.Windows.Forms.MenuStrip(); this.menuItemClose = new System.Windows.Forms.ToolStripMenuItem(); this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components); - this.tbPassword = new System.Windows.Forms.TextBox(); - this.label4 = new System.Windows.Forms.Label(); this.panel1.SuspendLayout(); this.menuStrip.SuspendLayout(); this.SuspendLayout(); @@ -65,6 +65,25 @@ private void InitializeComponent() this.panel1.Size = new System.Drawing.Size(329, 150); this.panel1.TabIndex = 0; // + // tbPassword + // + this.tbPassword.Location = new System.Drawing.Point(93, 72); + this.tbPassword.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); + this.tbPassword.Name = "tbPassword"; + this.tbPassword.Size = new System.Drawing.Size(224, 25); + this.tbPassword.TabIndex = 7; + this.tbPassword.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(9, 75); + this.label4.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(67, 17); + this.label4.TabIndex = 6; + this.label4.Text = "Password:"; + // // buttonStart // this.buttonStart.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85))))); @@ -161,25 +180,6 @@ private void InitializeComponent() this.notifyIcon.Visible = true; this.notifyIcon.DoubleClick += new System.EventHandler(this.notifyIcon_DoubleClick); // - // tbPassword - // - this.tbPassword.Location = new System.Drawing.Point(93, 72); - this.tbPassword.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); - this.tbPassword.Name = "tbPassword"; - this.tbPassword.Size = new System.Drawing.Size(224, 25); - this.tbPassword.TabIndex = 7; - this.tbPassword.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; - // - // label4 - // - this.label4.AutoSize = true; - this.label4.Location = new System.Drawing.Point(9, 75); - this.label4.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(67, 17); - this.label4.TabIndex = 6; - this.label4.Text = "Password:"; - // // ConfigurationForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); diff --git a/PresentationObsSceneSwitcher/ConfigurationForm.cs b/PresentationObsSceneSwitcher/ConfigurationForm.cs index 8c4cb8f..6e618db 100644 --- a/PresentationObsSceneSwitcher/ConfigurationForm.cs +++ b/PresentationObsSceneSwitcher/ConfigurationForm.cs @@ -1,5 +1,4 @@ using PowerPointToOBSSceneSwitcher.Obs; -using PresentationObsSceneSwitcher.PowerPoint; using System; using System.Windows.Forms; @@ -14,14 +13,15 @@ public partial class ConfigurationForm : Form #endregion - private readonly JsonSettingsRepository settingsRepository; - private ObsWebSocketClient connectedClient; + private readonly ObsWebSocketClientSettings settings; + private ObsWebSocketClient client; // TODO: Refactor this Injection - public ConfigurationForm(JsonSettingsRepository settingsRepository, ObsWebSocketClient connectedClient) + public ConfigurationForm(ObsWebSocketClientSettings settings, ObsWebSocketClient client) { InitializeComponent(); - this.settingsRepository = settingsRepository; + this.settings = settings ?? new ObsWebSocketClientSettings(); + this.client = client; } #region GUI Behavior events @@ -54,8 +54,6 @@ private void ConfigurationForm_FormClosing(object sender, FormClosingEventArgs e e.Cancel = true; this.Hide(); } - - connectedClient.Dispose(); } @@ -63,22 +61,12 @@ private void ConfigurationForm_FormClosing(object sender, FormClosingEventArgs e private async void buttonStart_Click(object sender, EventArgs e) { - // TODO: Handle the parse better. - ObsWebSocketClientSettings settings = new ObsWebSocketClientSettings() - { - IpAddress = tbIpAddress.Text, - Port = int.Parse(tbPort.Text), - Password = tbPassword.Text - }; - await settingsRepository.SaveAsync(settings); - - connectedClient?.Dispose(); - connectedClient = new ObsWebSocketClient(settings); - await connectedClient.ConnectAsync(); - - IPresentationSubscriber subscriber = new PowerPointPresentationSubscriber(); + // TODO: Handle the parse better when bindingsource support come. + settings.IpAddress = tbIpAddress.Text; + settings.Port = int.Parse(tbPort.Text); + settings.Password = tbPassword.Text; - subscriber.Subscribe("OBS", async scene => connectedClient.ChangeScene(scene)); + await client.ConnectAsync(settings); } } } diff --git a/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs index f6d63ef..43eaada 100644 --- a/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs +++ b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs @@ -40,18 +40,17 @@ public string IpAddress /// public class ObsWebSocketClient : IDisposable { - private readonly ObsWebSocketClientSettings settings; - private bool disposedValue; private ObsWebSocket obsWebSocket; + public bool IsConnected => obsWebSocket.IsConnected; + /// /// Constructor /// /// Settings to connect - public ObsWebSocketClient(ObsWebSocketClientSettings settings) + public ObsWebSocketClient() { - this.settings = settings; this.obsWebSocket = new ObsWebSocket(); // Always not null } @@ -59,17 +58,19 @@ public ObsWebSocketClient(ObsWebSocketClientSettings settings) /// Connects to the OBS WebSocket server /// /// - public void Connect() + public void Connect(ObsWebSocketClientSettings settings) { - if (!obsWebSocket.IsConnected) - obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", settings.Password ?? ""); + if (obsWebSocket.IsConnected) + obsWebSocket.Disconnect(); + + obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", settings.Password ?? ""); } /// /// Connects to the OBS WebSocket server /// /// - public async Task ConnectAsync() + public async Task ConnectAsync(ObsWebSocketClientSettings settings) { // At least this won't block the main thread if using from GUI if (!obsWebSocket.IsConnected) diff --git a/PresentationObsSceneSwitcher/Program.cs b/PresentationObsSceneSwitcher/Program.cs index d7d017f..f5079db 100644 --- a/PresentationObsSceneSwitcher/Program.cs +++ b/PresentationObsSceneSwitcher/Program.cs @@ -18,28 +18,29 @@ static async Task Main() Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); + // TODO: Maybe handle this with DI. + /* Read settings */ JsonSettingsRepository settingsRepository = new JsonSettingsRepository(); - ObsWebSocketClient connectedClient = await ConnectWithSavedSettings(settingsRepository).ConfigureAwait(false); + ObsWebSocketClientSettings settings = await settingsRepository.LoadAsync(); - Application.Run(new ConfigurationForm(settingsRepository, connectedClient)); - } - - private static async Task ConnectWithSavedSettings(JsonSettingsRepository settingsRepository) - { - ObsWebSocketClientSettings savedSettings = await settingsRepository.LoadAsync(); - - if (!(savedSettings is null)) + /* Init the OBS client */ + using (ObsWebSocketClient client = new ObsWebSocketClient()) { - ObsWebSocketClient connectedClient = new ObsWebSocketClient(savedSettings); - await connectedClient.ConnectAsync(); + /* Try initial connection if there are saved settings */ + if (!(settings is null)) await client.ConnectAsync(settings); + /* Suscribe the client. */ IPresentationSubscriber subscriber = new PowerPointPresentationSubscriber(); - subscriber.Subscribe("OBS", async scene => connectedClient.ChangeScene(scene)); + subscriber.Subscribe("OBS", async scene => + { + if (client.IsConnected) + client.ChangeScene(scene); + }); - return connectedClient; + Application.Run(new ConfigurationForm(settings, client)); } - return null; + await settingsRepository.SaveAsync(settings); } } } From f506ac8841d2b9de2c5c03c4347a856e830f53b3 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 20:59:43 +0200 Subject: [PATCH 31/45] Github Action --- .github/workflows/release-action.yml | 78 ++++++++++++++++++++++++++++ PowerPointToOBSSceneSwitcher.sln | 7 ++- 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release-action.yml diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml new file mode 100644 index 0000000..74234f1 --- /dev/null +++ b/.github/workflows/release-action.yml @@ -0,0 +1,78 @@ +name: Release PresentationObsSceneSwitcher + +on: + push: + tags: + - 'v*' + +jobs: + + build: + runs-on: windows-latest # For a list of available runner types, refer to + # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on + + env: + Solution_Name: PowerPointToOBSSceneSwitcher # Replace with your solution name, i.e. MyWpfApp.sln. + ProjectDirectory: PresentationObsSceneSwitcher # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package. + ProjectPath: PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj. + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + # Install the .NET Core workload + - name: Install .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.1.101 + + # Restore the application to populate the obj folder with RuntimeIdentifiers + - name: Restore the application + run: dotnet restore $env:ProjectPath + env: + Configuration: ${{ matrix.configuration }} + + # Decode the base 64 encoded pfx and save the Signing_Certificate + - name: Build + run: dotnet build --configuration Release --no-restore $env:ProjectPath + + # Create the app package by building and packaging the Windows Application Packaging project + - name: Publish + run: dotnet publish --configuration Release --no-restore $env:ProjectPath + + # Publish + - name: Upload build artifacts + uses: actions/upload-artifact@v2 + with: + name: Exe + path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish + + # Zip + - name: Zip + run: | + cd ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish + Compress-Archive -DestinationPath PresentationObsSceneSwitcher -Path .\* + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish\PresentationObsSceneSwitcher.zip + asset_name: PresentationObsSceneSwitcher.zip + asset_content_type: application/zip \ No newline at end of file diff --git a/PowerPointToOBSSceneSwitcher.sln b/PowerPointToOBSSceneSwitcher.sln index ebde54a..8d82a89 100644 --- a/PowerPointToOBSSceneSwitcher.sln +++ b/PowerPointToOBSSceneSwitcher.sln @@ -5,7 +5,12 @@ VisualStudioVersion = 16.0.30428.66 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerPointToOBSSceneSwitcher", "PowerPointToOBSSceneSwitcher.csproj", "{8A844363-5A92-403B-90CD-BFCB30ED5054}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresentationObsSceneSwitcher", "PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj", "{5B07EF15-11D6-4646-8322-965BD7D4ADEE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PresentationObsSceneSwitcher", "PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj", "{5B07EF15-11D6-4646-8322-965BD7D4ADEE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{76FD753E-6BFA-477C-BA87-23D96C053DC5}" + ProjectSection(SolutionItems) = preProject + .github\workflows\release-action.yml = .github\workflows\release-action.yml + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From b6ecda13cfb94f833687ed333d1021cedcb36b31 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 21:01:28 +0200 Subject: [PATCH 32/45] Rename error --- .github/workflows/release-action.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index 74234f1..c460b32 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -1,5 +1,4 @@ name: Release PresentationObsSceneSwitcher - on: push: tags: From a755f88ebb20a0e100960fea0e25d958e5a68660 Mon Sep 17 00:00:00 2001 From: Oscar Date: Wed, 16 Sep 2020 21:08:44 +0200 Subject: [PATCH 33/45] Update release-action.yml Use msbuild instead of dotnet core due to COMReference --- .github/workflows/release-action.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index c460b32..8bbcea5 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -27,6 +27,10 @@ jobs: with: dotnet-version: 3.1.101 + # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild + - name: Setup MSBuild.exe + uses: microsoft/setup-msbuild@2008f912f56e61277eefaac6d1888b750582aa16 + # Restore the application to populate the obj folder with RuntimeIdentifiers - name: Restore the application run: dotnet restore $env:ProjectPath @@ -39,7 +43,7 @@ jobs: # Create the app package by building and packaging the Windows Application Packaging project - name: Publish - run: dotnet publish --configuration Release --no-restore $env:ProjectPath + run: msbuild $env:ProjectPath /p:Configuration=Release /p:OutputPath=${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish # Publish - name: Upload build artifacts @@ -74,4 +78,4 @@ jobs: upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps asset_path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish\PresentationObsSceneSwitcher.zip asset_name: PresentationObsSceneSwitcher.zip - asset_content_type: application/zip \ No newline at end of file + asset_content_type: application/zip From 772ac38ff69caf7d161e0e37850161242b254af1 Mon Sep 17 00:00:00 2001 From: Oscar Date: Wed, 16 Sep 2020 21:18:09 +0200 Subject: [PATCH 34/45] Update release-action.yml Use another MSBuild. --- .github/workflows/release-action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index 8bbcea5..fd769d2 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -29,7 +29,7 @@ jobs: # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - name: Setup MSBuild.exe - uses: microsoft/setup-msbuild@2008f912f56e61277eefaac6d1888b750582aa16 + uses: warrenbuckley/Setup-MSBuild@v1 # Restore the application to populate the obj folder with RuntimeIdentifiers - name: Restore the application From c6f4a7175750f1d5163d0b47e47676cfa1ab7cfe Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 21:25:57 +0200 Subject: [PATCH 35/45] Github Action --- .github/workflows/release-action.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index fd769d2..a09ced0 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -36,13 +36,9 @@ jobs: run: dotnet restore $env:ProjectPath env: Configuration: ${{ matrix.configuration }} - - # Decode the base 64 encoded pfx and save the Signing_Certificate - - name: Build - run: dotnet build --configuration Release --no-restore $env:ProjectPath # Create the app package by building and packaging the Windows Application Packaging project - - name: Publish + - name: Build and Publish run: msbuild $env:ProjectPath /p:Configuration=Release /p:OutputPath=${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish # Publish From a263ad49072fea79966d1b9e89530b1a3192b707 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 21:46:20 +0200 Subject: [PATCH 36/45] Using good old nuget instead of COM for CI... --- .github/workflows/release-action.yml | 14 +++++++------- .../PresentationObsSceneSwitcher.csproj | 13 +------------ 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index a09ced0..c460b32 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -27,19 +27,19 @@ jobs: with: dotnet-version: 3.1.101 - # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - - name: Setup MSBuild.exe - uses: warrenbuckley/Setup-MSBuild@v1 - # Restore the application to populate the obj folder with RuntimeIdentifiers - name: Restore the application run: dotnet restore $env:ProjectPath env: Configuration: ${{ matrix.configuration }} + + # Decode the base 64 encoded pfx and save the Signing_Certificate + - name: Build + run: dotnet build --configuration Release --no-restore $env:ProjectPath # Create the app package by building and packaging the Windows Application Packaging project - - name: Build and Publish - run: msbuild $env:ProjectPath /p:Configuration=Release /p:OutputPath=${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish + - name: Publish + run: dotnet publish --configuration Release --no-restore $env:ProjectPath # Publish - name: Upload build artifacts @@ -74,4 +74,4 @@ jobs: upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps asset_path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish\PresentationObsSceneSwitcher.zip asset_name: PresentationObsSceneSwitcher.zip - asset_content_type: application/zip + asset_content_type: application/zip \ No newline at end of file diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj index a868abc..9db4643 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj @@ -8,18 +8,7 @@ - - - - - {91493440-5a91-11cf-8700-00aa0060263b} - 2 - 12 - primary - 0 - false - True - + \ No newline at end of file From 3286b24578948906bfbe718e3a11a86cd51f0777 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 16 Sep 2020 21:46:20 +0200 Subject: [PATCH 37/45] Using good old nuget instead of COM for CI... --- PowerPointToOBSSceneSwitcher.csproj | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/PowerPointToOBSSceneSwitcher.csproj b/PowerPointToOBSSceneSwitcher.csproj index 43492be..f49476f 100644 --- a/PowerPointToOBSSceneSwitcher.csproj +++ b/PowerPointToOBSSceneSwitcher.csproj @@ -10,20 +10,9 @@ - - - - {91493440-5a91-11cf-8700-00aa0060263b} - 2 - 12 - primary - 0 - false - True - - + From 12b82332b2826b37f75620e9f17b390242dd07e3 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Sun, 20 Sep 2020 12:10:37 +0200 Subject: [PATCH 38/45] Refactor to Console+Winforms app. --- PowerPointToOBSSceneSwitcher.sln | 6 ++ .../CommandLineApp.cs | 85 +++++++++++++++++++ .../Obs/ObsWebSocketClient.cs | 19 ++++- .../PowerPointPresentationSuscriber.cs | 45 ++++------ .../PresentationObsSceneSwitcher.csproj | 16 +++- PresentationObsSceneSwitcher/Program.cs | 43 ++++------ PresentationObsSceneSwitcher/settings.json | 1 + 7 files changed, 152 insertions(+), 63 deletions(-) create mode 100644 PresentationObsSceneSwitcher/CommandLineApp.cs create mode 100644 PresentationObsSceneSwitcher/settings.json diff --git a/PowerPointToOBSSceneSwitcher.sln b/PowerPointToOBSSceneSwitcher.sln index 8d82a89..1f87192 100644 --- a/PowerPointToOBSSceneSwitcher.sln +++ b/PowerPointToOBSSceneSwitcher.sln @@ -12,6 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\release-action.yml = .github\workflows\release-action.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerPointInterop", "..\PowerPointInterop\PowerPointInterop.csproj", "{6D327EF6-B794-4441-8893-3EFD05DA682F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -26,6 +28,10 @@ Global {5B07EF15-11D6-4646-8322-965BD7D4ADEE}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B07EF15-11D6-4646-8322-965BD7D4ADEE}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B07EF15-11D6-4646-8322-965BD7D4ADEE}.Release|Any CPU.Build.0 = Release|Any CPU + {6D327EF6-B794-4441-8893-3EFD05DA682F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D327EF6-B794-4441-8893-3EFD05DA682F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D327EF6-B794-4441-8893-3EFD05DA682F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D327EF6-B794-4441-8893-3EFD05DA682F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PresentationObsSceneSwitcher/CommandLineApp.cs b/PresentationObsSceneSwitcher/CommandLineApp.cs new file mode 100644 index 0000000..b103db7 --- /dev/null +++ b/PresentationObsSceneSwitcher/CommandLineApp.cs @@ -0,0 +1,85 @@ +using McMaster.Extensions.CommandLineUtils; +using PowerPointToOBSSceneSwitcher.Obs; +using System; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace PresentationObsSceneSwitcher +{ + [Command] + public class CommandLineApp + { + [Option(Description = "Run as console app", ShortName = "cmd")] + public bool NoGui { get; } = false; + + [Option(Description = "IpAddress", ShortName = "ip")] + public string IpAddress { get; } + + [Option(Description = "Port", ShortName = "p")] + public int? Port { get; } + + [Option(Description = "Optional Password", ShortName = "pass")] + public string Password { get; } + + private readonly Func formFactory; + private readonly JsonSettingsRepository settingsRepository; + private readonly ObsWebSocketClient client; + private readonly IPresentationSubscriber subscriber; + + public CommandLineApp(Func formFactory, + JsonSettingsRepository settingsRepository, ObsWebSocketClient client, + IPresentationSubscriber subscriber) + { + this.formFactory = formFactory; + this.settingsRepository = settingsRepository; + this.client = client; + this.subscriber = subscriber; + } + + public async Task OnExecuteAsync() + { + ObsWebSocketClientSettings settings = await ReadSettings().ConfigureAwait(false); + await client.ConnectAsync(settings).ConfigureAwait(false); + + /* Suscribe the client. */ + subscriber.Subscribe("OBS", async scene => + { + if (client.IsConnected) await client.ChangeSceneAsync(scene); + }); + + if (!NoGui) + { + RunGui(settings); + + /* Save if running GUI. */ + await settingsRepository.SaveAsync(settings); + } + } + + private async Task ReadSettings() + { + ObsWebSocketClientSettings settings = (await settingsRepository.LoadAsync()) + ?? new ObsWebSocketClientSettings(); + + if (!string.IsNullOrEmpty(IpAddress)) + settings.IpAddress = IpAddress; + + if (Port.HasValue) + settings.Port = Port.Value; + + if (!string.IsNullOrEmpty(Password)) + settings.Password = Password; + + return settings; + } + + private void RunGui(ObsWebSocketClientSettings settings) + { + Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + Application.Run(formFactory(settings)); + } + } +} diff --git a/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs index 43eaada..72aae16 100644 --- a/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs +++ b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs @@ -12,7 +12,7 @@ namespace PowerPointToOBSSceneSwitcher.Obs /// Configuring the ObsWebSocketClient with a Settings object allows to use it with DI public class ObsWebSocketClientSettings { - private string ipAddress; + private string ipAddress = "127.0.0.1"; /// /// Ip address of the obs websocket server @@ -27,12 +27,12 @@ public string IpAddress /// /// Port of the OBS WebSocket server /// - public int Port { get; set; } + public int Port { get; set; } = 4444; /// /// Optional Password of the OBS WebSocket server /// - public string Password { get; set; } + public string Password { get; set; } = ""; } /// @@ -74,7 +74,7 @@ public async Task ConnectAsync(ObsWebSocketClientSettings settings) { // At least this won't block the main thread if using from GUI if (!obsWebSocket.IsConnected) - await Task.Run(() => obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", "")); + await Task.WhenAny(Task.Run(() => obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", "")), Task.Delay(3000)); } /// @@ -88,6 +88,17 @@ public bool ChangeScene(string scene) return true; } + /// + /// Changes OBS current scene + /// + /// The Scene name in OBS + /// Always true + public async Task ChangeSceneAsync(string scene) + { + await Task.Run(() => obsWebSocket.Api.SetCurrentScene(scene)); + return true; + } + #region Dispose ~ObsWebSocketClient() => Dispose(disposing: false); diff --git a/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs b/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs index aa07acd..f819ba6 100644 --- a/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs +++ b/PresentationObsSceneSwitcher/PowerPoint/PowerPointPresentationSuscriber.cs @@ -1,6 +1,4 @@ -using Microsoft.Office.Interop.PowerPoint; -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Text.RegularExpressions; namespace PresentationObsSceneSwitcher.PowerPoint @@ -23,7 +21,7 @@ public class PowerPointPresentationSubscriber : IPresentationSubscriber /// private static readonly Regex extractInfoFromNotesRegex = new Regex(@"\((?[^\)]+)\):\[(?[^\]]+)\]", RegexOptions.Compiled | RegexOptions.CultureInvariant); - private static Application powerPoint = new Application(); + //private static Application powerPoint = new Application(); private readonly Dictionary suscriptions = new Dictionary(); @@ -32,32 +30,19 @@ public class PowerPointPresentationSubscriber : IPresentationSubscriber /// public PowerPointPresentationSubscriber() { - powerPoint.SlideShowNextSlide += async (SlideShowWindow Wn) => - { - Console.WriteLine($"Moved to Slide Number {Wn.View.Slide.SlideNumber}"); - - string note = String.Empty; - try - { - // Text starts at Index 2 ¯\_(ツ)_/¯ - note = Wn.View.Slide.NotesPage.Shapes[2].TextFrame.TextRange.Text; - } - catch - { - // No notes - } - - foreach (Match match in extractInfoFromNotesRegex.Matches(note)) - { - string appName = match.Groups["AppName"].Value; - string info = match.Groups["Info"].Value; - - if (suscriptions.TryGetValue(appName, out PresentationSuscription suscription)) - { - await suscription(info).ConfigureAwait(false); - } - } - }; + PowerPointInterop.PowerPoint.SubscribeSlideShowNextSlide(async (string note) => + { + foreach (Match match in extractInfoFromNotesRegex.Matches(note)) + { + string appName = match.Groups["AppName"].Value; + string info = match.Groups["Info"].Value; + + if (suscriptions.TryGetValue(appName, out PresentationSuscription suscription)) + { + await suscription(info).ConfigureAwait(false); + } + } + }); } /// diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj index 9db4643..3892504 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj @@ -1,14 +1,26 @@  - WinExe + Exe netcoreapp3.1 true + + + - + + + + + ..\..\PowerPointInterop\bin\Release\netstandard2.0\PowerPointInterop.dll + + + + + \ No newline at end of file diff --git a/PresentationObsSceneSwitcher/Program.cs b/PresentationObsSceneSwitcher/Program.cs index f5079db..342192d 100644 --- a/PresentationObsSceneSwitcher/Program.cs +++ b/PresentationObsSceneSwitcher/Program.cs @@ -1,8 +1,10 @@ +using McMaster.Extensions.CommandLineUtils; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using PowerPointToOBSSceneSwitcher.Obs; using PresentationObsSceneSwitcher.PowerPoint; using System; using System.Threading.Tasks; -using System.Windows.Forms; namespace PresentationObsSceneSwitcher { @@ -11,36 +13,23 @@ static class Program /// /// The main entry point for the application. /// - [STAThread] - static async Task Main() + //[STAThread] + static async Task Main(string[] args) { - Application.SetHighDpiMode(HighDpiMode.SystemAware); - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); + ServiceProvider services = new ServiceCollection().AddLogging(builder => builder.AddConsole().AddDebug()) + .AddScoped() + .AddSingleton() + .AddScoped() + .AddScoped>(ctx => + settings => new ConfigurationForm(settings, ctx.GetRequiredService())) + .BuildServiceProvider(); - // TODO: Maybe handle this with DI. - /* Read settings */ - JsonSettingsRepository settingsRepository = new JsonSettingsRepository(); - ObsWebSocketClientSettings settings = await settingsRepository.LoadAsync(); + CommandLineApplication app = new CommandLineApplication(); + app.Conventions.UseDefaultConventions().UseConstructorInjection(services); - /* Init the OBS client */ - using (ObsWebSocketClient client = new ObsWebSocketClient()) - { - /* Try initial connection if there are saved settings */ - if (!(settings is null)) await client.ConnectAsync(settings); + Console.WriteLine("Hi"); - /* Suscribe the client. */ - IPresentationSubscriber subscriber = new PowerPointPresentationSubscriber(); - subscriber.Subscribe("OBS", async scene => - { - if (client.IsConnected) - client.ChangeScene(scene); - }); - - Application.Run(new ConfigurationForm(settings, client)); - } - - await settingsRepository.SaveAsync(settings); + return await app.ExecuteAsync(args); } } } diff --git a/PresentationObsSceneSwitcher/settings.json b/PresentationObsSceneSwitcher/settings.json new file mode 100644 index 0000000..9756740 --- /dev/null +++ b/PresentationObsSceneSwitcher/settings.json @@ -0,0 +1 @@ +{"IpAddress":"127.0.0.1","Port":4444,"Password":""} \ No newline at end of file From f37cdfc21e828e2b08bdffff76d19d810dbead32 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Sun, 20 Sep 2020 12:20:53 +0200 Subject: [PATCH 39/45] Start/Stop feature. --- .../CommandLineApp.cs | 9 ++++- .../Obs/ObsWebSocketClient.cs | 40 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/PresentationObsSceneSwitcher/CommandLineApp.cs b/PresentationObsSceneSwitcher/CommandLineApp.cs index b103db7..8fb559e 100644 --- a/PresentationObsSceneSwitcher/CommandLineApp.cs +++ b/PresentationObsSceneSwitcher/CommandLineApp.cs @@ -42,9 +42,14 @@ public async Task OnExecuteAsync() await client.ConnectAsync(settings).ConfigureAwait(false); /* Suscribe the client. */ - subscriber.Subscribe("OBS", async scene => + subscriber.Subscribe("OBS", async command => { - if (client.IsConnected) await client.ChangeSceneAsync(scene); + if (command == "**START") + await client.StartRecordingAsync(); + else if (command == "**STOP") + await client.StopRecordingAsync(); + else if (string.IsNullOrEmpty(command)) + await client.ChangeSceneAsync(command); }); if (!NoGui) diff --git a/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs index 72aae16..9669bab 100644 --- a/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs +++ b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs @@ -99,6 +99,46 @@ public async Task ChangeSceneAsync(string scene) return true; } + /// + /// Starts recording OBS + /// + /// Always true + public bool StartRecording() + { + obsWebSocket.Api.StartRecording(); + return true; + } + + /// + /// Starts recording OBS + /// + /// Always true + public async Task StartRecordingAsync() + { + await Task.Run(() => obsWebSocket.Api.StartRecording()); + return true; + } + + /// + /// Starts recording OBS + /// + /// Always true + public bool StopRecording() + { + obsWebSocket.Api.StopRecording(); + return true; + } + + /// + /// Starts recording OBS + /// + /// Always true + public async Task StopRecordingAsync() + { + await Task.Run(() => obsWebSocket.Api.StopRecording()); + return true; + } + #region Dispose ~ObsWebSocketClient() => Dispose(disposing: false); From 5a6867c15d2736e5cdd3ea70a9eae3ef148814cc Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Sun, 20 Sep 2020 12:38:00 +0200 Subject: [PATCH 40/45] Add new project for generating an dll embed with the PowerPoint COMReference --- PowerPointInterop/PowerPoint.cs | 27 +++++++++ PowerPointInterop/PowerPointInterop.csproj | 19 ++++++ PowerPointInterop/README.md | 1 + PowerPointToOBSSceneSwitcher.csproj | 3 + PowerPointToOBSSceneSwitcher.sln | 2 +- .../ConfigurationForm.Designer.cs | 57 ------------------ .../PresentationObsSceneSwitcher.csproj | 26 +++++--- .../lib/Debug/PowerPointInterop.dll | Bin 0 -> 9728 bytes .../lib/Debug/PowerPointInterop.pdb | Bin 0 -> 7780 bytes .../lib/Release/PowerPointInterop.dll | Bin 0 -> 9216 bytes .../lib/Release/PowerPointInterop.pdb | Bin 0 -> 7676 bytes 11 files changed, 68 insertions(+), 67 deletions(-) create mode 100644 PowerPointInterop/PowerPoint.cs create mode 100644 PowerPointInterop/PowerPointInterop.csproj create mode 100644 PowerPointInterop/README.md create mode 100644 PresentationObsSceneSwitcher/lib/Debug/PowerPointInterop.dll create mode 100644 PresentationObsSceneSwitcher/lib/Debug/PowerPointInterop.pdb create mode 100644 PresentationObsSceneSwitcher/lib/Release/PowerPointInterop.dll create mode 100644 PresentationObsSceneSwitcher/lib/Release/PowerPointInterop.pdb diff --git a/PowerPointInterop/PowerPoint.cs b/PowerPointInterop/PowerPoint.cs new file mode 100644 index 0000000..25dd196 --- /dev/null +++ b/PowerPointInterop/PowerPoint.cs @@ -0,0 +1,27 @@ +using Microsoft.Office.Interop.PowerPoint; +using System; +using System.Threading.Tasks; + +namespace PowerPointInterop +{ + /// + /// This projects allows you to compile a .dll with an embed COM Reference. + /// Used to add compilation support using dotnet cli as it doesn't allow you to use COMReference. + /// + public static class PowerPoint + { + private static Application powerPoint = new Application(); + + public static void SubscribeSlideShowNextSlide(Func subscription) + { + powerPoint.SlideShowNextSlide += async (SlideShowWindow Wn) => + { + var command = String.Empty; + try { command = Wn.View.Slide.NotesPage.Shapes[2].TextFrame.TextRange.Text; } + catch { /*no notes*/ } + + await subscription(command); + }; + } + } +} diff --git a/PowerPointInterop/PowerPointInterop.csproj b/PowerPointInterop/PowerPointInterop.csproj new file mode 100644 index 0000000..bb76c8f --- /dev/null +++ b/PowerPointInterop/PowerPointInterop.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0 + + + + + {91493440-5a91-11cf-8700-00aa0060263b} + 2 + 12 + primary + 0 + false + True + + + + diff --git a/PowerPointInterop/README.md b/PowerPointInterop/README.md new file mode 100644 index 0000000..0d0b855 --- /dev/null +++ b/PowerPointInterop/README.md @@ -0,0 +1 @@ +This projects allows you to compile a .dll with an embed COM Reference. Used to add compilation support using dotnet cli as it doesn't allow you to use COMReference. diff --git a/PowerPointToOBSSceneSwitcher.csproj b/PowerPointToOBSSceneSwitcher.csproj index f49476f..3b515f8 100644 --- a/PowerPointToOBSSceneSwitcher.csproj +++ b/PowerPointToOBSSceneSwitcher.csproj @@ -6,8 +6,11 @@ + + + diff --git a/PowerPointToOBSSceneSwitcher.sln b/PowerPointToOBSSceneSwitcher.sln index 1f87192..52f81b9 100644 --- a/PowerPointToOBSSceneSwitcher.sln +++ b/PowerPointToOBSSceneSwitcher.sln @@ -12,7 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\release-action.yml = .github\workflows\release-action.yml EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerPointInterop", "..\PowerPointInterop\PowerPointInterop.csproj", "{6D327EF6-B794-4441-8893-3EFD05DA682F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerPointInterop", "PowerPointInterop\PowerPointInterop.csproj", "{6D327EF6-B794-4441-8893-3EFD05DA682F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs index 6a82873..11f4f41 100644 --- a/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs +++ b/PresentationObsSceneSwitcher/ConfigurationForm.Designer.cs @@ -207,63 +207,6 @@ private void InitializeComponent() private System.Windows.Forms.Panel panel1; private System.Windows.Forms.MenuStrip menuStrip; - private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator; - private System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; - private System.Windows.Forms.ToolStripMenuItem printToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem printPreviewToolStripMenuItem; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; - private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem undoToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem redoToolStripMenuItem; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; - private System.Windows.Forms.ToolStripMenuItem cutToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem copyToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem pasteToolStripMenuItem; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; - private System.Windows.Forms.ToolStripMenuItem selectAllToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem toolsToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem customizeToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem optionsToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem contentsToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem indexToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem searchToolStripMenuItem; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; - private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem newToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem1; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator6; - private System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem1; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator7; - private System.Windows.Forms.ToolStripMenuItem printToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem printPreviewToolStripMenuItem1; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator8; - private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem undoToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem redoToolStripMenuItem1; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator9; - private System.Windows.Forms.ToolStripMenuItem cutToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem copyToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem pasteToolStripMenuItem1; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator10; - private System.Windows.Forms.ToolStripMenuItem selectAllToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem toolsToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem customizeToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem optionsToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem contentsToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem indexToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem searchToolStripMenuItem1; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator11; - private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem1; private System.Windows.Forms.ToolStripMenuItem menuItemClose; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj index 3892504..268c7bf 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj @@ -13,14 +13,22 @@ - - - ..\..\PowerPointInterop\bin\Release\netstandard2.0\PowerPointInterop.dll - - - - - - + + + + + + .\lib\Debug\PowerPointInterop.dll + + + + + + + .\lib\Release\PowerPointInterop.dll + + + + \ No newline at end of file diff --git a/PresentationObsSceneSwitcher/lib/Debug/PowerPointInterop.dll b/PresentationObsSceneSwitcher/lib/Debug/PowerPointInterop.dll new file mode 100644 index 0000000000000000000000000000000000000000..e52e660a46dfc33f7e91a0ab3121c028964db995 GIT binary patch literal 9728 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 literal 0 HcmV?d00001 diff --git a/PresentationObsSceneSwitcher/lib/Debug/PowerPointInterop.pdb b/PresentationObsSceneSwitcher/lib/Debug/PowerPointInterop.pdb new file mode 100644 index 0000000000000000000000000000000000000000..bb97872fb8688ee77eb09fa3a7c0d5c5db62132d GIT binary patch literal 7780 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 literal 0 HcmV?d00001 diff --git a/PresentationObsSceneSwitcher/lib/Release/PowerPointInterop.dll b/PresentationObsSceneSwitcher/lib/Release/PowerPointInterop.dll new file mode 100644 index 0000000000000000000000000000000000000000..36c7c95c630748cc5c2b5c99bf24b8a770c1448e GIT binary patch literal 9216 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 literal 0 HcmV?d00001 From ad39e6d59a1688671a164c4d5c0636b4ef23e222 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Sun, 20 Sep 2020 20:39:13 +0200 Subject: [PATCH 41/45] Creating a console project for optional GUI. --- .config/dotnet-tools.json | 12 +++++++ .github/workflows/release-action.yml | 28 ++++++++-------- PowerPointToOBSSceneSwitcher.sln | 9 +++++- .../Obs/ObsWebSocketClient.cs | 3 +- .../PresentationObsSceneSwitcher.csproj | 2 +- .../CommandLineApp.cs | 32 ++++++++++++++++--- .../ConsoleWindowController.cs | 27 ++++++++++++++++ ...PresentationObsSceneSwitcherConsole.csproj | 22 +++++++++++++ .../Program.cs | 15 +++------ 9 files changed, 118 insertions(+), 32 deletions(-) create mode 100644 .config/dotnet-tools.json rename {PresentationObsSceneSwitcher => PresentationObsSceneSwitcherConsole}/CommandLineApp.cs (71%) create mode 100644 PresentationObsSceneSwitcherConsole/ConsoleWindowController.cs create mode 100644 PresentationObsSceneSwitcherConsole/PresentationObsSceneSwitcherConsole.csproj rename {PresentationObsSceneSwitcher => PresentationObsSceneSwitcherConsole}/Program.cs (74%) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..a7ce38b --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-warp": { + "version": "1.1.0", + "commands": [ + "dotnet-warp" + ] + } + } +} \ No newline at end of file diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index c460b32..186de27 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -1,4 +1,5 @@ name: Release PresentationObsSceneSwitcher + on: push: tags: @@ -11,9 +12,10 @@ jobs: # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on env: - Solution_Name: PowerPointToOBSSceneSwitcher # Replace with your solution name, i.e. MyWpfApp.sln. - ProjectDirectory: PresentationObsSceneSwitcher # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package. - ProjectPath: PresentationObsSceneSwitcher\PresentationObsSceneSwitcher.csproj # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj. + Solution_Name: PresentationObsSceneSwitcherConsole + ProjectDirectory: PresentationObsSceneSwitcherConsole + FileName: PresentationObsSceneSwitcher + ProjectPath: PresentationObsSceneSwitcherConsole\PresentationObsSceneSwitcherConsole.csproj steps: - name: Checkout @@ -27,32 +29,30 @@ jobs: with: dotnet-version: 3.1.101 + # Restore tools + - name: Restore tools + run: dotnet tool restore + # Restore the application to populate the obj folder with RuntimeIdentifiers - name: Restore the application run: dotnet restore $env:ProjectPath - env: - Configuration: ${{ matrix.configuration }} # Decode the base 64 encoded pfx and save the Signing_Certificate - name: Build - run: dotnet build --configuration Release --no-restore $env:ProjectPath - - # Create the app package by building and packaging the Windows Application Packaging project - - name: Publish - run: dotnet publish --configuration Release --no-restore $env:ProjectPath + run: dotnet warp $env:ProjectPath -o ${{ env.ProjectDirectory }}\publish # Publish - name: Upload build artifacts uses: actions/upload-artifact@v2 with: name: Exe - path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish + path: ${{ env.ProjectDirectory }}\publish # Zip - name: Zip run: | - cd ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish - Compress-Archive -DestinationPath PresentationObsSceneSwitcher -Path .\* + cd ${{ env.ProjectDirectory }}\publish + Compress-Archive -DestinationPath ${{ FileName }} -Path .\* - name: Create Release id: create_release @@ -72,6 +72,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps - asset_path: ${{ env.ProjectDirectory }}\bin\Release\netcoreapp3.1\publish\PresentationObsSceneSwitcher.zip + asset_path: ${{ env.ProjectDirectory }}\publish\PresentationObsSceneSwitcher.zip asset_name: PresentationObsSceneSwitcher.zip asset_content_type: application/zip \ No newline at end of file diff --git a/PowerPointToOBSSceneSwitcher.sln b/PowerPointToOBSSceneSwitcher.sln index 52f81b9..472cb50 100644 --- a/PowerPointToOBSSceneSwitcher.sln +++ b/PowerPointToOBSSceneSwitcher.sln @@ -9,10 +9,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PresentationObsSceneSwitche EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{76FD753E-6BFA-477C-BA87-23D96C053DC5}" ProjectSection(SolutionItems) = preProject + .config\dotnet-tools.json = .config\dotnet-tools.json .github\workflows\release-action.yml = .github\workflows\release-action.yml EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerPointInterop", "PowerPointInterop\PowerPointInterop.csproj", "{6D327EF6-B794-4441-8893-3EFD05DA682F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerPointInterop", "PowerPointInterop\PowerPointInterop.csproj", "{6D327EF6-B794-4441-8893-3EFD05DA682F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresentationObsSceneSwitcherConsole", "PresentationObsSceneSwitcherConsole\PresentationObsSceneSwitcherConsole.csproj", "{557DE28E-D0A2-4DFB-82F7-D7A32EB96C0C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -32,6 +35,10 @@ Global {6D327EF6-B794-4441-8893-3EFD05DA682F}.Debug|Any CPU.Build.0 = Debug|Any CPU {6D327EF6-B794-4441-8893-3EFD05DA682F}.Release|Any CPU.ActiveCfg = Release|Any CPU {6D327EF6-B794-4441-8893-3EFD05DA682F}.Release|Any CPU.Build.0 = Release|Any CPU + {557DE28E-D0A2-4DFB-82F7-D7A32EB96C0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {557DE28E-D0A2-4DFB-82F7-D7A32EB96C0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {557DE28E-D0A2-4DFB-82F7-D7A32EB96C0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {557DE28E-D0A2-4DFB-82F7-D7A32EB96C0C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs index 9669bab..059f7a4 100644 --- a/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs +++ b/PresentationObsSceneSwitcher/Obs/ObsWebSocketClient.cs @@ -73,8 +73,7 @@ public void Connect(ObsWebSocketClientSettings settings) public async Task ConnectAsync(ObsWebSocketClientSettings settings) { // At least this won't block the main thread if using from GUI - if (!obsWebSocket.IsConnected) - await Task.WhenAny(Task.Run(() => obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", "")), Task.Delay(3000)); + await Task.WhenAny(Task.Run(() => obsWebSocket.Connect($"ws://{settings.IpAddress}:{settings.Port}", "")), Task.Delay(3000)); } /// diff --git a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj index 268c7bf..f61c57e 100644 --- a/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj +++ b/PresentationObsSceneSwitcher/PresentationObsSceneSwitcher.csproj @@ -1,7 +1,7 @@  - Exe + Library netcoreapp3.1 true diff --git a/PresentationObsSceneSwitcher/CommandLineApp.cs b/PresentationObsSceneSwitcherConsole/CommandLineApp.cs similarity index 71% rename from PresentationObsSceneSwitcher/CommandLineApp.cs rename to PresentationObsSceneSwitcherConsole/CommandLineApp.cs index 8fb559e..a822137 100644 --- a/PresentationObsSceneSwitcher/CommandLineApp.cs +++ b/PresentationObsSceneSwitcherConsole/CommandLineApp.cs @@ -1,12 +1,13 @@ using McMaster.Extensions.CommandLineUtils; +using Microsoft.Extensions.Logging; using PowerPointToOBSSceneSwitcher.Obs; +using PresentationObsSceneSwitcherConsole; using System; using System.Threading.Tasks; using System.Windows.Forms; namespace PresentationObsSceneSwitcher { - [Command] public class CommandLineApp { [Option(Description = "Run as console app", ShortName = "cmd")] @@ -25,25 +26,40 @@ public class CommandLineApp private readonly JsonSettingsRepository settingsRepository; private readonly ObsWebSocketClient client; private readonly IPresentationSubscriber subscriber; + private readonly ILogger logger; public CommandLineApp(Func formFactory, JsonSettingsRepository settingsRepository, ObsWebSocketClient client, - IPresentationSubscriber subscriber) + IPresentationSubscriber subscriber, ILogger logger) { this.formFactory = formFactory; this.settingsRepository = settingsRepository; this.client = client; this.subscriber = subscriber; + this.logger = logger; } public async Task OnExecuteAsync() { ObsWebSocketClientSettings settings = await ReadSettings().ConfigureAwait(false); - await client.ConnectAsync(settings).ConfigureAwait(false); + + logger.LogInformation("Try to connect using Address: {IpAddress}:{Port}", settings.IpAddress, settings.Port); + + try + { + await client.ConnectAsync(settings).ConfigureAwait(false); + logger.LogInformation("Connected"); + } + catch (Exception ex) + { + logger.LogError("Cannot connect: {Message}", ex.Message); + } /* Suscribe the client. */ subscriber.Subscribe("OBS", async command => { + logger.LogTrace("Received command: {Command}", command); + if (command == "**START") await client.StartRecordingAsync(); else if (command == "**STOP") @@ -52,18 +68,26 @@ public async Task OnExecuteAsync() await client.ChangeSceneAsync(command); }); + if (!NoGui) { + logger.LogInformation("Runnig with GUI"); + ConsoleWindowController.Hide(); RunGui(settings); /* Save if running GUI. */ await settingsRepository.SaveAsync(settings); } + else + { + logger.LogInformation("Runnig without GUI"); + Console.ReadKey(); + } } private async Task ReadSettings() { - ObsWebSocketClientSettings settings = (await settingsRepository.LoadAsync()) + ObsWebSocketClientSettings settings = (await settingsRepository.LoadAsync().ConfigureAwait(false)) ?? new ObsWebSocketClientSettings(); if (!string.IsNullOrEmpty(IpAddress)) diff --git a/PresentationObsSceneSwitcherConsole/ConsoleWindowController.cs b/PresentationObsSceneSwitcherConsole/ConsoleWindowController.cs new file mode 100644 index 0000000..85d761d --- /dev/null +++ b/PresentationObsSceneSwitcherConsole/ConsoleWindowController.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; + +namespace PresentationObsSceneSwitcherConsole +{ + public class ConsoleWindowController + { + const int SW_HIDE = 0; + const int SW_SHOW = 5; + + [DllImport("kernel32.dll")] + static extern IntPtr GetConsoleWindow(); + + [DllImport("user32.dll")] + static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + public static void Show() + { + ShowWindow(GetConsoleWindow(), SW_SHOW); + } + + public static void Hide() + { + ShowWindow(GetConsoleWindow(), SW_HIDE); + } + } +} diff --git a/PresentationObsSceneSwitcherConsole/PresentationObsSceneSwitcherConsole.csproj b/PresentationObsSceneSwitcherConsole/PresentationObsSceneSwitcherConsole.csproj new file mode 100644 index 0000000..b3ad9e1 --- /dev/null +++ b/PresentationObsSceneSwitcherConsole/PresentationObsSceneSwitcherConsole.csproj @@ -0,0 +1,22 @@ + + + + Exe + netcoreapp3.1 + true + true + win-x64 + + + + + + + + + + + + + + diff --git a/PresentationObsSceneSwitcher/Program.cs b/PresentationObsSceneSwitcherConsole/Program.cs similarity index 74% rename from PresentationObsSceneSwitcher/Program.cs rename to PresentationObsSceneSwitcherConsole/Program.cs index 342192d..d253093 100644 --- a/PresentationObsSceneSwitcher/Program.cs +++ b/PresentationObsSceneSwitcherConsole/Program.cs @@ -1,20 +1,17 @@ -using McMaster.Extensions.CommandLineUtils; +using McMaster.Extensions.CommandLineUtils; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using PowerPointToOBSSceneSwitcher.Obs; +using PresentationObsSceneSwitcher; using PresentationObsSceneSwitcher.PowerPoint; using System; using System.Threading.Tasks; -namespace PresentationObsSceneSwitcher +namespace PresentationObsSceneSwitcherConsole { - static class Program + internal static class Program { - /// - /// The main entry point for the application. - /// - //[STAThread] - static async Task Main(string[] args) + private static async Task Main(string[] args) { ServiceProvider services = new ServiceCollection().AddLogging(builder => builder.AddConsole().AddDebug()) .AddScoped() @@ -27,8 +24,6 @@ static async Task Main(string[] args) CommandLineApplication app = new CommandLineApplication(); app.Conventions.UseDefaultConventions().UseConstructorInjection(services); - Console.WriteLine("Hi"); - return await app.ExecuteAsync(args); } } From 10e1775b1b31951ccf2537e055a6d780f882daee Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Sun, 20 Sep 2020 20:40:50 +0200 Subject: [PATCH 42/45] Fix spelling for action --- .github/workflows/release-action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index 186de27..5787bdd 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -52,7 +52,7 @@ jobs: - name: Zip run: | cd ${{ env.ProjectDirectory }}\publish - Compress-Archive -DestinationPath ${{ FileName }} -Path .\* + Compress-Archive -DestinationPath ${{ env.FileName }} -Path .\* - name: Create Release id: create_release From 92e7c368fb2a6ce8945425a022a6a748d74034ad Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Sun, 20 Sep 2020 20:47:02 +0200 Subject: [PATCH 43/45] Fix dotnet warp output name. --- .github/workflows/release-action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index 5787bdd..4d7b1f7 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -39,7 +39,7 @@ jobs: # Decode the base 64 encoded pfx and save the Signing_Certificate - name: Build - run: dotnet warp $env:ProjectPath -o ${{ env.ProjectDirectory }}\publish + run: dotnet warp $env:ProjectPath -o ${{ env.ProjectDirectory }}\publish\${{ env.FileName }}.exe # Publish - name: Upload build artifacts From beade18121839c7009f54e06baa526aff98cdb3c Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Sun, 20 Sep 2020 20:52:53 +0200 Subject: [PATCH 44/45] dotnet warp can't create a path... --- .github/workflows/release-action.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml index 4d7b1f7..fcf8f55 100644 --- a/.github/workflows/release-action.yml +++ b/.github/workflows/release-action.yml @@ -39,7 +39,9 @@ jobs: # Decode the base 64 encoded pfx and save the Signing_Certificate - name: Build - run: dotnet warp $env:ProjectPath -o ${{ env.ProjectDirectory }}\publish\${{ env.FileName }}.exe + run: | + mkdir ${{ env.ProjectDirectory }}\publish + dotnet warp $env:ProjectPath -o ${{ env.ProjectDirectory }}\publish\${{ env.FileName }}.exe # Publish - name: Upload build artifacts From 075288987427c027bab13955aa106ef3bdb50572 Mon Sep 17 00:00:00 2001 From: smartcodinghub Date: Wed, 23 Sep 2020 19:42:34 +0200 Subject: [PATCH 45/45] Exclude project directory. --- PowerPointToOBSSceneSwitcher.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/PowerPointToOBSSceneSwitcher.csproj b/PowerPointToOBSSceneSwitcher.csproj index c8356c8..e51fe00 100644 --- a/PowerPointToOBSSceneSwitcher.csproj +++ b/PowerPointToOBSSceneSwitcher.csproj @@ -7,10 +7,13 @@ + + +